diff --git a/.changeset/dull-kings-marry.md b/.changeset/dull-kings-marry.md new file mode 100644 index 0000000000..27c50e2d36 --- /dev/null +++ b/.changeset/dull-kings-marry.md @@ -0,0 +1,5 @@ +--- +'@directus/api': patch +--- + +Implemented sort number for fields in collections on creation to ensure order is always retained diff --git a/api/src/services/collections.ts b/api/src/services/collections.ts index 94cfac34b7..e98ffc2252 100644 --- a/api/src/services/collections.ts +++ b/api/src/services/collections.ts @@ -4,7 +4,7 @@ import type { Accountability, FieldMeta, RawField, SchemaOverview } from '@direc import { addFieldFlag } from '@directus/utils'; import type Keyv from 'keyv'; import type { Knex } from 'knex'; -import { chunk, omit } from 'lodash-es'; +import { chunk, groupBy, merge, omit } from 'lodash-es'; import { clearSystemCache, getCache } from '../cache.js'; import { ALIAS_TYPES } from '../constants.js'; import type { Helpers } from '../database/helpers/index.js'; @@ -145,7 +145,30 @@ export class CollectionsService { const fieldPayloads = payload.fields!.filter((field) => field.meta).map((field) => field.meta) as FieldMeta[]; - await fieldItemsService.createMany(fieldPayloads, { + // Sort new fields that does not have any group defined, in ascending order. + // Lodash merge is used so that the "sort" can be overridden if defined. + let sortedFieldPayloads = fieldPayloads + .filter((field) => field?.group === undefined || field?.group === null) + .map((field, index) => merge({ sort: index + 1 }, field)); + + // Sort remaining new fields with group defined, if any, in ascending order. + // sortedFieldPayloads will be less than fieldPayloads if it filtered out any fields with group defined. + if (sortedFieldPayloads.length < fieldPayloads.length) { + const fieldsWithGroups = groupBy( + fieldPayloads.filter((field) => field?.group), + (field) => field?.group + ); + + // The sort order is restarted from 1 for fields in each group and appended to sortedFieldPayloads. + // Lodash merge is used so that the "sort" can be overridden if defined. + for (const [_group, fields] of Object.entries(fieldsWithGroups)) { + sortedFieldPayloads = sortedFieldPayloads.concat( + fields.map((field, index) => merge({ sort: index + 1 }, field)) + ); + } + } + + await fieldItemsService.createMany(sortedFieldPayloads, { bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params), bypassLimits: true, diff --git a/api/src/services/fields.ts b/api/src/services/fields.ts index 0e29176415..3cb0e48832 100644 --- a/api/src/services/fields.ts +++ b/api/src/services/fields.ts @@ -5,7 +5,7 @@ import type { Accountability, Field, FieldMeta, RawField, SchemaOverview, Type } import { addFieldFlag, toArray } from '@directus/utils'; import type Keyv from 'keyv'; import type { Knex } from 'knex'; -import { isEqual, isNil } from 'lodash-es'; +import { isEqual, isNil, merge } from 'lodash-es'; import { clearSystemCache, getCache } from '../cache.js'; import { ALIAS_TYPES } from '../constants.js'; import { translateDatabaseError } from '../database/errors/translate.js'; @@ -308,9 +308,17 @@ export class FieldsService { } if (hookAdjustedField.meta) { + const existingSortRecord: Record<'max', number | null> | undefined = await trx + .from('directus_fields') + .where(hookAdjustedField.meta?.group ? { collection, group: hookAdjustedField.meta.group } : { collection }) + .max('sort', { as: 'max' }) + .first(); + + const newSortValue: number = existingSortRecord?.max ? existingSortRecord.max + 1 : 1; + await itemsService.createOne( { - ...hookAdjustedField.meta, + ...merge({ sort: newSortValue }, hookAdjustedField.meta), collection: collection, field: hookAdjustedField.field, },