Fix readonly and required on groups (#19962)

Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>
This commit is contained in:
Nitwel
2023-10-11 10:55:05 +02:00
committed by GitHub
parent 6d8fff112c
commit e6fe0663fb
6 changed files with 241 additions and 18 deletions

View File

@@ -0,0 +1,5 @@
---
'@directus/app': patch
---
Ensured the "Readonly" and "Required" options work on groups

View File

@@ -12,6 +12,7 @@ import { useI18n } from 'vue-i18n';
import FormField from './form-field.vue';
import type { FormField as TFormField } from './types';
import ValidationErrors from './validation-errors.vue';
import { pushGroupOptionsDown } from '@/utils/push-group-options-down';
type FieldValues = {
[field: string]: any;
@@ -151,13 +152,23 @@ function useForm() {
const { formFields } = useFormFields(fields);
const fieldsMap: ComputedRef<Record<string, TFormField | undefined>> = computed(() => {
const fieldsWithConditions = computed(() => {
const valuesWithDefaults = Object.assign({}, defaultValues.value, values.value);
return formFields.value.reduce((result: Record<string, Field>, field: Field) => {
let fields = formFields.value.reduce((result, field) => {
const newField = applyConditions(valuesWithDefaults, setPrimaryKeyReadonly(field));
if (newField.field) result[newField.field] = newField;
if (newField.field) result.push(newField);
return result;
}, {} as Record<string, Field>);
}, [] as Field[]);
fields = pushGroupOptionsDown(fields);
return fields;
});
const fieldsMap: ComputedRef<Record<string, TFormField | undefined>> = computed(() => {
return Object.fromEntries(fieldsWithConditions.value.map((field) => [field.field, field]));
});
const fieldsInGroup = computed(() =>
@@ -171,17 +182,7 @@ function useForm() {
});
const fieldsForGroup = computed(() => {
const valuesWithDefaults = Object.assign({}, defaultValues.value, values.value);
return fieldNames.value.map((name: string) => {
const fields = getFieldsForGroup(fieldsMap.value[name]?.meta?.field || null);
return fields.reduce((result: Field[], field: Field) => {
const newField = applyConditions(valuesWithDefaults, setPrimaryKeyReadonly(field));
if (newField.field) result.push(newField);
return result;
}, [] as Field[]);
});
return fieldNames.value.map((name: string) => getFieldsForGroup(fieldsMap.value[name]?.meta?.field || null));
});
return { fieldNames, fieldsMap, isDisabled, getFieldsForGroup, fieldsForGroup };
@@ -199,7 +200,7 @@ function useForm() {
}
function getFieldsForGroup(group: null | string, passed: string[] = []): Field[] {
const fieldsInGroup: Field[] = fields.value.filter((field) => {
const fieldsInGroup = fieldsWithConditions.value.filter((field) => {
const meta = fieldsMap.value?.[field.field]?.meta;
return meta?.group === group || (group === null && isNil(meta));
});

View File

@@ -16,6 +16,7 @@ import { AxiosResponse } from 'axios';
import { mergeWith } from 'lodash';
import { ComputedRef, Ref, computed, isRef, ref, unref, watch } from 'vue';
import { usePermissions } from './use-permissions';
import { pushGroupOptionsDown } from '@/utils/push-group-options-down';
type UsableItem<T extends Record<string, any>> = {
edits: Ref<Record<string, any>>;
@@ -129,7 +130,9 @@ export function useItem<T extends Record<string, any>>(
}
);
const errors = validateItem(payloadToValidate, fieldsWithPermissions.value, isNew.value);
const fields = pushGroupOptionsDown(fieldsWithPermissions.value);
const errors = validateItem(payloadToValidate, fields, isNew.value);
if (errors.length > 0) {
validationErrors.value = errors;

View File

@@ -199,7 +199,10 @@ async function onGroupSortChange(fields: Field[]) {
<template #header>
<div class="header full">
<v-icon class="drag-handle" name="drag_indicator" @click.stop />
<span class="name">{{ field.field }}</span>
<span class="name">
{{ field.field }}
<v-icon v-if="field.meta?.required === true" name="star" class="required" sup filled />
</span>
<v-icon v-if="hidden" v-tooltip="t('hidden_field')" name="visibility_off" class="hidden-icon" small />
<field-select-menu
:field="field"

View File

@@ -0,0 +1,169 @@
import { test, expect } from 'vitest';
import { pushGroupOptionsDown } from './push-group-options-down.js';
import { Field } from '@directus/types';
const fields: Field[] = [
{
field: 'group1',
type: 'alias',
collection: 'test',
meta: {
required: true,
readonly: true,
special: ['group'],
} as any,
schema: null,
name: 'Group 1',
},
{
field: 'field_in_group1',
type: 'boolean',
collection: 'test',
meta: {
required: false,
group: 'group1',
} as any,
schema: null,
name: 'Field in group 1',
},
];
test('Test pushGroupOptionsDown not mutating', () => {
expect(pushGroupOptionsDown(fields)).not.toBe(fields);
});
test('Test pushGroupOptionsDown', () => {
expect(pushGroupOptionsDown(fields)).toEqual([
{
field: 'group1',
type: 'alias',
collection: 'test',
meta: {
required: false,
readonly: false,
special: ['group'],
},
schema: null,
name: 'Group 1',
},
{
field: 'field_in_group1',
type: 'boolean',
collection: 'test',
meta: {
required: true,
readonly: true,
group: 'group1',
},
schema: null,
name: 'Field in group 1',
},
]);
});
const fieldsNested: Field[] = [
{
field: 'group1',
type: 'alias',
collection: 'test',
meta: {
required: true,
readonly: false,
special: ['group'],
} as any,
schema: null,
name: 'Group 1',
},
{
field: 'field_in_group1',
type: 'boolean',
collection: 'test',
meta: {
required: false,
readonly: false,
group: 'group1',
} as any,
schema: null,
name: 'Field in group 1',
},
{
field: 'group1_1',
type: 'alias',
collection: 'test',
meta: {
group: 'group1',
required: false,
readonly: true,
special: ['group'],
} as any,
schema: null,
name: 'Group 1 1',
},
{
field: 'field_in_group1_1',
type: 'boolean',
collection: 'test',
meta: {
required: false,
readonly: false,
group: 'group1_1',
} as any,
schema: null,
name: 'Field in group 1 1',
},
];
test('Test pushGroupOptionsDown with nested groups', () => {
expect(pushGroupOptionsDown(fieldsNested)).toEqual([
{
field: 'group1',
type: 'alias',
collection: 'test',
meta: {
required: false,
readonly: false,
special: ['group'],
} as any,
schema: null,
name: 'Group 1',
},
{
field: 'field_in_group1',
type: 'boolean',
collection: 'test',
meta: {
required: true,
readonly: false,
group: 'group1',
} as any,
schema: null,
name: 'Field in group 1',
},
{
field: 'group1_1',
type: 'alias',
collection: 'test',
meta: {
group: 'group1',
required: false,
readonly: false,
special: ['group'],
} as any,
schema: null,
name: 'Group 1 1',
},
{
field: 'field_in_group1_1',
type: 'boolean',
collection: 'test',
meta: {
required: true,
readonly: true,
group: 'group1_1',
} as any,
schema: null,
name: 'Field in group 1 1',
},
]);
});

View File

@@ -0,0 +1,42 @@
import { Field, FieldMeta } from '@directus/types';
import { cloneDeep } from 'lodash';
export function pushGroupOptionsDown(fields: Field[]) {
fields = cloneDeep(fields);
const updatedGroups: string[] = [];
const fieldsQueue: Field[] = [...fields];
while (fieldsQueue.length > 0) {
const field = fieldsQueue.shift();
if (!field) break;
const parent = field?.meta?.group;
const isGroup = field.meta?.special?.includes('group');
if (!isGroup) continue;
if (parent && !updatedGroups.includes(parent)) {
fieldsQueue.push(field);
continue;
}
for (const childField of fields) {
if (childField.meta?.group !== field.field) continue;
childField.meta.required = field.meta?.required || childField.meta.required;
childField.meta.readonly = field.meta?.readonly || childField.meta.readonly;
}
if (!field.meta) {
field.meta = {} as FieldMeta;
}
field.meta.required = false;
field.meta.readonly = false;
updatedGroups.push(field.field);
}
return fields;
}