Skip to content

Commit 3c44f43

Browse files
authored
960 show "nullable" checkbox for highlighted attributes in schema diagram view (#962)
* add nullable checkbox to schema diagram * update metaschema builder to allow nullable types even if multiple types is deactivated * update and improve tests * update test name * apply formatting changes --------- Co-authored-by: Logende <[email protected]>
1 parent 6109b69 commit 3c44f43

17 files changed

Lines changed: 843 additions & 25 deletions

meta_configurator/src/components/panels/schema-diagram/SchemaObjectAttribute.vue

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import Button from 'primevue/button';
1616
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome';
1717
import {useSettings} from '@/settings/useSettings';
18+
import {doesSchemaAllowNull} from '@/schema/schemaReadingUtils';
1819
1920
const settings = useSettings();
2021
@@ -42,13 +43,22 @@ const emit = defineEmits<{
4243
attributeData: SchemaObjectAttributeData,
4344
required: boolean
4445
): void;
46+
(
47+
e: 'update_attribute_nullable',
48+
attributeData: SchemaObjectAttributeData,
49+
nullable: boolean
50+
): void;
4551
(e: 'delete_element', objectData: SchemaElementData): void;
4652
}>();
4753
4854
function updateRequired() {
4955
emit('update_attribute_required', props.data, props.data.required);
5056
}
5157
58+
function updateNullable(event: Event) {
59+
emit('update_attribute_nullable', props.data, (event.target as HTMLInputElement).checked);
60+
}
61+
5262
const attrName = ref(props.data.name);
5363
const selectedType: Ref<AttributeTypeChoice | undefined> = ref(
5464
determineTypeChoiceBySchema(props.typeChoices, props.data.schema)
@@ -83,6 +93,10 @@ function isHighlighted() {
8393
return props.selectedData && props.selectedData == props.data;
8494
}
8595
96+
function isNullable() {
97+
return doesSchemaAllowNull(props.data.schema);
98+
}
99+
86100
function getHandleId() {
87101
return `source-${props.data.name}`;
88102
}
@@ -122,9 +136,11 @@ function getHandleTop() {
122136
<div class="vue-flow-attribute-inline">
123137
<input
124138
type="checkbox"
125-
class="vue-flow-required-checkbox"
139+
class="vue-flow-attribute-checkbox"
126140
v-model="props.data.required"
127141
@change="updateRequired"
142+
aria-label="Required property"
143+
title="Required: controls whether this property must be present."
128144
@mousedown.stop
129145
@click.stop
130146
@dblclick.stop
@@ -141,6 +157,19 @@ function getHandleTop() {
141157
@dblclick.stop
142158
placeholder="Select Type" />
143159

160+
<input
161+
v-if="settings.schemaDiagram.showNullableCheckbox"
162+
type="checkbox"
163+
class="vue-flow-attribute-checkbox"
164+
:checked="isNullable()"
165+
@change="updateNullable"
166+
aria-label="Nullable property"
167+
title="Nullable: allows this property to be null in addition to its current type."
168+
@mousedown.stop
169+
@click.stop
170+
@dblclick.stop
171+
@keydown.stop />
172+
144173
<Button
145174
class="vue-flow-attribute-button vue-flow-attribute-input-dimensions"
146175
size="small"
@@ -164,7 +193,7 @@ function getHandleTop() {
164193
</template>
165194

166195
<style>
167-
.vue-flow-required-checkbox {
196+
.vue-flow-attribute-checkbox {
168197
margin: 0 4px;
169198
transform: scale(0.8);
170199
}

meta_configurator/src/components/panels/schema-diagram/SchemaObjectNode.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ const emit = defineEmits<{
5252
attributeData: SchemaObjectAttributeData,
5353
required: boolean
5454
): void;
55+
(
56+
e: 'update_attribute_nullable',
57+
attributeData: SchemaObjectAttributeData,
58+
nullable: boolean
59+
): void;
5560
}>();
5661
5762
const objectName = ref(props.data.name || '');
@@ -62,6 +67,10 @@ function updateAttributeRequired(attributeData: SchemaObjectAttributeData, requi
6267
emit('update_attribute_required', attributeData, required);
6368
}
6469
70+
function updateAttributeNullable(attributeData: SchemaObjectAttributeData, nullable: boolean) {
71+
emit('update_attribute_nullable', attributeData, nullable);
72+
}
73+
6574
function isObjectEditable() {
6675
return (isHighlighted() || isAttributeHighlighted()) && settings.value.schemaDiagram.editMode;
6776
}
@@ -222,6 +231,7 @@ function isAttributeHighlighted() {
222231
@update_attribute_name="updateAttributeName"
223232
@update_attribute_type="updateAttributeType"
224233
@update_attribute_required="updateAttributeRequired"
234+
@update_attribute_nullable="updateAttributeNullable"
225235
@delete_element="deleteElement" />
226236

227237
<div v-if="isObjectEditable()">

meta_configurator/src/components/panels/schema-diagram/VueFlowPanel.vue

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import {
5353
copySelectedSchemaToClipboard,
5454
pasteSchemaFromClipboard,
5555
} from '@/components/panels/schema-diagram/schemaClipboardUtils';
56+
import {doesSchemaAllowNull, setSchemaNullable} from '@/schema/schemaReadingUtils';
5657
5758
const emit = defineEmits<{
5859
(e: 'update_current_path', path: Path): void;
@@ -374,9 +375,18 @@ function updateAttributeType(
374375
attributeData: SchemaObjectAttributeData,
375376
newType: AttributeTypeChoice
376377
) {
377-
//attributeData.typeDescription = newType.label;
378378
const attributeSchema = structuredClone(schemaData.dataAt(attributeData.absolutePath));
379+
const shouldRemainNullable = doesSchemaAllowNull(attributeSchema, schemaData.data.value);
379380
applyNewType(attributeSchema, newType.schema);
381+
if (shouldRemainNullable) {
382+
setSchemaNullable(attributeSchema, true, schemaData.data.value);
383+
}
384+
schemaData.setDataAt(attributeData.absolutePath, attributeSchema);
385+
}
386+
387+
function updateAttributeNullable(attributeData: SchemaObjectAttributeData, nullable: boolean) {
388+
const attributeSchema = structuredClone(schemaData.dataAt(attributeData.absolutePath));
389+
setSchemaNullable(attributeSchema, nullable, schemaData.data.value);
380390
schemaData.setDataAt(attributeData.absolutePath, attributeSchema);
381391
}
382392
@@ -486,6 +496,7 @@ function updateExternalReferenceValue(
486496
@update_attribute_name="updateAttributeName"
487497
@update_attribute_type="updateAttributeType"
488498
@update_attribute_required="updateAttributeRequired"
499+
@update_attribute_nullable="updateAttributeNullable"
489500
@delete_element="deleteElement"
490501
@add_attribute="addAttribute"
491502
@extract_inlined_element="extractInlinedElement"
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {shallowMount} from '@vue/test-utils';
2+
import {describe, expect, it, vi} from 'vitest';
3+
import {ref} from 'vue';
4+
import SchemaObjectAttribute from '../SchemaObjectAttribute.vue';
5+
import {SchemaObjectAttributeData} from '@/schema/graph-representation/schemaGraphTypes';
6+
7+
vi.mock('@/settings/useSettings', () => ({
8+
useSettings() {
9+
return ref({
10+
schemaDiagram: {
11+
editMode: true,
12+
showNullableCheckbox: true,
13+
},
14+
});
15+
},
16+
}));
17+
18+
describe('SchemaObjectAttribute', () => {
19+
function createAttributeData() {
20+
return new SchemaObjectAttributeData(
21+
'nickname',
22+
'string',
23+
'properties',
24+
['properties', 'nickname'],
25+
false,
26+
true,
27+
0,
28+
{
29+
type: 'string',
30+
}
31+
);
32+
}
33+
34+
it('shows the nullable checkbox only for the highlighted attribute when enabled in settings', () => {
35+
const data = createAttributeData();
36+
const wrapper = shallowMount(SchemaObjectAttribute, {
37+
props: {
38+
data,
39+
selectedData: data,
40+
typeChoices: [{label: 'string', schema: {type: 'string'}}],
41+
},
42+
global: {
43+
directives: {
44+
tooltip: vi.fn(),
45+
},
46+
},
47+
});
48+
49+
const checkboxes = wrapper.findAll('input[type="checkbox"]');
50+
expect(checkboxes).toHaveLength(2);
51+
expect(checkboxes[1]!.attributes('aria-label')).toBe('Nullable property');
52+
});
53+
54+
it('emits nullable updates when the nullable checkbox changes', async () => {
55+
const data = createAttributeData();
56+
const wrapper = shallowMount(SchemaObjectAttribute, {
57+
props: {
58+
data,
59+
selectedData: data,
60+
typeChoices: [{label: 'string', schema: {type: 'string'}}],
61+
},
62+
global: {
63+
directives: {
64+
tooltip: vi.fn(),
65+
},
66+
},
67+
});
68+
69+
const nullableCheckbox = wrapper.findAll('input[type="checkbox"]')[1]!;
70+
await nullableCheckbox.setValue(true);
71+
72+
expect(wrapper.emitted('update_attribute_nullable')).toEqual([[data, true]]);
73+
});
74+
});

0 commit comments

Comments
 (0)