Wire up search field metadata (#21964)

## Part 1 - Exact scope of the current PR (#21964)

close https://github.com/twentyhq/core-team-issues/issues/2586

This PR introduces `searchFieldMetadata` as a first-class flat metadata
entity and migrates the existing search surface onto it, with **no
change to which records are searchable** (ISO with `main`).

In scope (what the PR does):
- New flat entity `searchFieldMetadata` (universalIdentifier,
applicationId, **`position`**, maps, conversions), registered in the
central flat-entity constants and the migration build orchestrator.
- `searchVector.asExpression` is **derived server-side** from
`searchFieldMetadata` rows (validated by `isSafeTsVectorExpression`);
never trusted from client input.
- **Derivation order is deterministic, driven by each row's `position`**
([compute-search-vector-as-expression-from-search-field-metadatas.util.ts](packages/twenty-server/src/engine/metadata-modules/flat-search-field-metadata/utils/compute-search-vector-as-expression-from-search-field-metadatas.util.ts)),
replacing the previous non-deterministic `(createdAt, id)` sort. That
sort collapsed to random UUIDs for standard fields (same `createdAt`),
so any rename/relabel rewrote the `STORED` generated column to a
logically-identical-but-textually-different expression and produced a
permanent per-workspace diff vs the standard definition. Ordering now
equals provisioning order; ties break on `universalIdentifier`.
- Provisioning at object creation mirrors the existing surface exactly
**and seeds `position`**:
- custom objects -> the `name` field only, at `position: 0`
([build-default-search-field-metadatas-for-custom-object.util.ts](packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-search-field-metadatas-for-custom-object.util.ts))
- standard objects -> their curated `SEARCH_FIELDS_FOR_*` sets,
`position` = the curated index
- Backfill (instance + workspace commands in `2-16`) provisions rows for
existing workspaces with the same surface **and the same positions**
(standard from the curated standard maps, custom `name` = `0`), scoped
to the workspace's own custom application
([build-search-field-metadata-backfill-operations.util.ts](packages/twenty-server/src/database/commands/upgrade-version-command/2-16/utils/build-search-field-metadata-backfill-operations.util.ts)).
The `position` column is added in the same `2-16` fast instance command
as `universalIdentifier`/`applicationId`.
- Field rename of an already-indexed field recomputes `asExpression`
(positions preserved, so order is stable)
([recompute-search-vector-on-field-rename.util.ts](packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-search-vector-on-field-rename.util.ts)).
- Field delete drops the matching row(s) and recomputes; remaining rows
keep their relative order (no renumber)
([from-delete-field-input-to-flat-field-metadatas-to-delete.util.ts](packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-delete-field-input-to-flat-field-metadatas-to-delete.util.ts)).
- Object relabel is **additive** and ISO/regression-fix only: it indexes
the new label identifier **appended last (`position = max(existing) +
1`)** without dropping `name`
([recompute-search-vector-on-label-identifier-update.util.ts](packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/recompute-search-vector-on-label-identifier-update.util.ts)).
This is a deliberate, temporary bridge.

Explicitly OUT of scope (deferred):
- No API to edit `searchFieldMetadata` (no user-facing search-field
configuration, including `position` — it is internal and only written by
provisioning/backfill/recompute).
- No auto-indexing of arbitrary searchable fields. Creating a custom
TEXT/EMAILS/etc. field does NOT add it to search (the
`computeSearchFieldMetadataCreationForFields` behavior was removed in
`e6820ad`).
- No field-type-transition handling (field type is immutable - not in
`FLAT_FIELD_METADATA_EDITABLE_PROPERTIES`, so that path was dead code).
- No `position` validation (uniqueness/range) and no multi-vector /
per-field `weight` config — deferred to the configurable-search
follow-up (#1428).

Net: `searchFieldMetadata` becomes the source of truth for the *same*
surface as `main`. The only intentional divergences from `main` are
"relabel preserves `name`" (additive) and the deterministic
`position`-ordered `asExpression` (a correctness/perf fix that is
byte-identical to provisioning order, so it does not change the
searchable surface).

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Paul Rastoin
2026-06-23 16:27:13 +02:00
committed by GitHub
parent 99e7c2cde0
commit e9d5d71cd3
102 changed files with 3850 additions and 366 deletions
@@ -2,6 +2,7 @@ import { type AllMetadataName } from 'twenty-shared/metadata';
export const TWENTY_STANDARD_ALL_METADATA_NAME = [
'index',
'searchFieldMetadata',
'objectMetadata',
'fieldMetadata',
'viewField',
@@ -106,6 +106,7 @@ export const createStandardObjectFlatMetadata = <
targetTableName: 'DEPRECATED',
fieldIds: [],
indexMetadataIds: [],
searchFieldMetadataIds: [],
objectPermissionIds: [],
fieldPermissionIds: [],
viewIds: [],
@@ -119,6 +120,7 @@ export const createStandardObjectFlatMetadata = <
fieldPermissionUniversalIdentifiers: [],
viewUniversalIdentifiers: [],
indexMetadataUniversalIdentifiers: [],
searchFieldMetadataUniversalIdentifiers: [],
labelIdentifierFieldMetadataUniversalIdentifier,
imageIdentifierFieldMetadataUniversalIdentifier,
};
@@ -0,0 +1,64 @@
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
import { type AllStandardObjectName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-name.type';
import { buildCompanyStandardFlatSearchFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/compute-company-standard-flat-search-field-metadata.util';
import { buildDashboardStandardFlatSearchFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/compute-dashboard-standard-flat-search-field-metadata.util';
import { buildMessageListStandardFlatSearchFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/compute-message-list-standard-flat-search-field-metadata.util';
import { buildNoteStandardFlatSearchFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/compute-note-standard-flat-search-field-metadata.util';
import { buildOpportunityStandardFlatSearchFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/compute-opportunity-standard-flat-search-field-metadata.util';
import { buildPersonStandardFlatSearchFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/compute-person-standard-flat-search-field-metadata.util';
import { buildTaskStandardFlatSearchFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/compute-task-standard-flat-search-field-metadata.util';
import { buildWorkflowStandardFlatSearchFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/compute-workflow-standard-flat-search-field-metadata.util';
import { buildWorkspaceMemberStandardFlatSearchFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/compute-workspace-member-standard-flat-search-field-metadata.util';
import { type CreateStandardSearchFieldArgs } from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/create-standard-search-field-flat-metadata.util';
type StandardSearchFieldBuilder<P extends AllStandardObjectName> = (
args: Omit<CreateStandardSearchFieldArgs<P>, 'context'>,
) => FlatSearchFieldMetadata[];
// Only isSearchable objects get rows; each object's field set mirrors its searchVector
// asExpression, built from the same SEARCH_FIELDS_FOR_* constant.
const STANDARD_FLAT_SEARCH_FIELD_METADATA_BUILDERS_BY_OBJECT_NAME = {
company: buildCompanyStandardFlatSearchFieldMetadatas,
dashboard: buildDashboardStandardFlatSearchFieldMetadatas,
messageList: buildMessageListStandardFlatSearchFieldMetadatas,
note: buildNoteStandardFlatSearchFieldMetadatas,
opportunity: buildOpportunityStandardFlatSearchFieldMetadatas,
person: buildPersonStandardFlatSearchFieldMetadatas,
task: buildTaskStandardFlatSearchFieldMetadatas,
workflow: buildWorkflowStandardFlatSearchFieldMetadatas,
workspaceMember: buildWorkspaceMemberStandardFlatSearchFieldMetadatas,
} satisfies {
[P in AllStandardObjectName]?: StandardSearchFieldBuilder<P>;
};
export const buildStandardFlatSearchFieldMetadataMaps = (
args: Omit<CreateStandardSearchFieldArgs, 'context' | 'objectName'>,
): FlatEntityMaps<FlatSearchFieldMetadata> => {
const allSearchFieldMetadatas: FlatSearchFieldMetadata[] = (
Object.keys(
STANDARD_FLAT_SEARCH_FIELD_METADATA_BUILDERS_BY_OBJECT_NAME,
) as (keyof typeof STANDARD_FLAT_SEARCH_FIELD_METADATA_BUILDERS_BY_OBJECT_NAME)[]
).flatMap((objectName) => {
const builder: StandardSearchFieldBuilder<typeof objectName> =
STANDARD_FLAT_SEARCH_FIELD_METADATA_BUILDERS_BY_OBJECT_NAME[objectName];
return builder({
...args,
objectName,
});
});
let flatSearchFieldMetadataMaps = createEmptyFlatEntityMaps();
for (const searchFieldMetadata of allSearchFieldMetadatas) {
flatSearchFieldMetadataMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: searchFieldMetadata,
flatEntityMaps: flatSearchFieldMetadataMaps,
});
}
return flatSearchFieldMetadataMaps;
};
@@ -0,0 +1,34 @@
import { SEARCH_FIELDS_FOR_COMPANY } from 'src/modules/company/standard-objects/company.workspace-entity';
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type';
import {
type CreateStandardSearchFieldArgs,
createStandardSearchFieldFlatMetadata,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/create-standard-search-field-flat-metadata.util';
export const buildCompanyStandardFlatSearchFieldMetadatas = ({
now,
objectName,
workspaceId,
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
}: Omit<
CreateStandardSearchFieldArgs<'company'>,
'context'
>): FlatSearchFieldMetadata[] =>
SEARCH_FIELDS_FOR_COMPANY.map((searchField, position) =>
createStandardSearchFieldFlatMetadata({
objectName,
workspaceId,
context: {
fieldName: searchField.name as AllStandardObjectFieldName<'company'>,
position,
},
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
now,
}),
);
@@ -0,0 +1,34 @@
import { SEARCH_FIELDS_FOR_DASHBOARD } from 'src/modules/dashboard/standard-objects/dashboard.workspace-entity';
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type';
import {
type CreateStandardSearchFieldArgs,
createStandardSearchFieldFlatMetadata,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/create-standard-search-field-flat-metadata.util';
export const buildDashboardStandardFlatSearchFieldMetadatas = ({
now,
objectName,
workspaceId,
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
}: Omit<
CreateStandardSearchFieldArgs<'dashboard'>,
'context'
>): FlatSearchFieldMetadata[] =>
SEARCH_FIELDS_FOR_DASHBOARD.map((searchField, position) =>
createStandardSearchFieldFlatMetadata({
objectName,
workspaceId,
context: {
fieldName: searchField.name as AllStandardObjectFieldName<'dashboard'>,
position,
},
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
now,
}),
);
@@ -0,0 +1,35 @@
import { SEARCH_FIELDS_FOR_MESSAGE_LIST } from 'src/modules/emailing/standard-objects/message-list.workspace-entity';
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type';
import {
type CreateStandardSearchFieldArgs,
createStandardSearchFieldFlatMetadata,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/create-standard-search-field-flat-metadata.util';
export const buildMessageListStandardFlatSearchFieldMetadatas = ({
now,
objectName,
workspaceId,
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
}: Omit<
CreateStandardSearchFieldArgs<'messageList'>,
'context'
>): FlatSearchFieldMetadata[] =>
SEARCH_FIELDS_FOR_MESSAGE_LIST.map((searchField, position) =>
createStandardSearchFieldFlatMetadata({
objectName,
workspaceId,
context: {
fieldName:
searchField.name as AllStandardObjectFieldName<'messageList'>,
position,
},
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
now,
}),
);
@@ -0,0 +1,34 @@
import { SEARCH_FIELDS_FOR_NOTES } from 'src/modules/note/standard-objects/note.workspace-entity';
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type';
import {
type CreateStandardSearchFieldArgs,
createStandardSearchFieldFlatMetadata,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/create-standard-search-field-flat-metadata.util';
export const buildNoteStandardFlatSearchFieldMetadatas = ({
now,
objectName,
workspaceId,
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
}: Omit<
CreateStandardSearchFieldArgs<'note'>,
'context'
>): FlatSearchFieldMetadata[] =>
SEARCH_FIELDS_FOR_NOTES.map((searchField, position) =>
createStandardSearchFieldFlatMetadata({
objectName,
workspaceId,
context: {
fieldName: searchField.name as AllStandardObjectFieldName<'note'>,
position,
},
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
now,
}),
);
@@ -0,0 +1,35 @@
import { SEARCH_FIELDS_FOR_OPPORTUNITY } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity';
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type';
import {
type CreateStandardSearchFieldArgs,
createStandardSearchFieldFlatMetadata,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/create-standard-search-field-flat-metadata.util';
export const buildOpportunityStandardFlatSearchFieldMetadatas = ({
now,
objectName,
workspaceId,
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
}: Omit<
CreateStandardSearchFieldArgs<'opportunity'>,
'context'
>): FlatSearchFieldMetadata[] =>
SEARCH_FIELDS_FOR_OPPORTUNITY.map((searchField, position) =>
createStandardSearchFieldFlatMetadata({
objectName,
workspaceId,
context: {
fieldName:
searchField.name as AllStandardObjectFieldName<'opportunity'>,
position,
},
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
now,
}),
);
@@ -0,0 +1,34 @@
import { SEARCH_FIELDS_FOR_PERSON } from 'src/modules/person/standard-objects/person.workspace-entity';
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type';
import {
type CreateStandardSearchFieldArgs,
createStandardSearchFieldFlatMetadata,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/create-standard-search-field-flat-metadata.util';
export const buildPersonStandardFlatSearchFieldMetadatas = ({
now,
objectName,
workspaceId,
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
}: Omit<
CreateStandardSearchFieldArgs<'person'>,
'context'
>): FlatSearchFieldMetadata[] =>
SEARCH_FIELDS_FOR_PERSON.map((searchField, position) =>
createStandardSearchFieldFlatMetadata({
objectName,
workspaceId,
context: {
fieldName: searchField.name as AllStandardObjectFieldName<'person'>,
position,
},
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
now,
}),
);
@@ -0,0 +1,34 @@
import { SEARCH_FIELDS_FOR_TASKS } from 'src/modules/task/standard-objects/task.workspace-entity';
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type';
import {
type CreateStandardSearchFieldArgs,
createStandardSearchFieldFlatMetadata,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/create-standard-search-field-flat-metadata.util';
export const buildTaskStandardFlatSearchFieldMetadatas = ({
now,
objectName,
workspaceId,
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
}: Omit<
CreateStandardSearchFieldArgs<'task'>,
'context'
>): FlatSearchFieldMetadata[] =>
SEARCH_FIELDS_FOR_TASKS.map((searchField, position) =>
createStandardSearchFieldFlatMetadata({
objectName,
workspaceId,
context: {
fieldName: searchField.name as AllStandardObjectFieldName<'task'>,
position,
},
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
now,
}),
);
@@ -0,0 +1,34 @@
import { SEARCH_FIELDS_FOR_WORKFLOWS } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type';
import {
type CreateStandardSearchFieldArgs,
createStandardSearchFieldFlatMetadata,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/create-standard-search-field-flat-metadata.util';
export const buildWorkflowStandardFlatSearchFieldMetadatas = ({
now,
objectName,
workspaceId,
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
}: Omit<
CreateStandardSearchFieldArgs<'workflow'>,
'context'
>): FlatSearchFieldMetadata[] =>
SEARCH_FIELDS_FOR_WORKFLOWS.map((searchField, position) =>
createStandardSearchFieldFlatMetadata({
objectName,
workspaceId,
context: {
fieldName: searchField.name as AllStandardObjectFieldName<'workflow'>,
position,
},
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
now,
}),
);
@@ -0,0 +1,35 @@
import { SEARCH_FIELDS_FOR_WORKSPACE_MEMBER } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type';
import {
type CreateStandardSearchFieldArgs,
createStandardSearchFieldFlatMetadata,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/create-standard-search-field-flat-metadata.util';
export const buildWorkspaceMemberStandardFlatSearchFieldMetadatas = ({
now,
objectName,
workspaceId,
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
}: Omit<
CreateStandardSearchFieldArgs<'workspaceMember'>,
'context'
>): FlatSearchFieldMetadata[] =>
SEARCH_FIELDS_FOR_WORKSPACE_MEMBER.map((searchField, position) =>
createStandardSearchFieldFlatMetadata({
objectName,
workspaceId,
context: {
fieldName:
searchField.name as AllStandardObjectFieldName<'workspaceMember'>,
position,
},
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
now,
}),
);
@@ -0,0 +1,74 @@
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { v4 } from 'uuid';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type';
import { type AllStandardObjectName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-name.type';
import { type StandardBuilderArgs } from 'src/engine/workspace-manager/twenty-standard-application/types/metadata-standard-buillder-args.type';
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type';
export type CreateStandardSearchFieldOptions<O extends AllStandardObjectName> =
{
fieldName: AllStandardObjectFieldName<O>;
position: number;
};
export type CreateStandardSearchFieldArgs<
O extends AllStandardObjectName = AllStandardObjectName,
> = StandardBuilderArgs<'searchFieldMetadata'> & {
objectName: O;
context: CreateStandardSearchFieldOptions<O>;
};
export const createStandardSearchFieldFlatMetadata = <
O extends AllStandardObjectName,
>({
workspaceId,
objectName,
context: { fieldName, position },
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps: { flatFieldMetadataMaps, flatObjectMetadataMaps },
twentyStandardApplicationId,
now,
}: CreateStandardSearchFieldArgs<O>): FlatSearchFieldMetadata => {
const objectFields = STANDARD_OBJECTS[objectName].fields;
const objectMetadataId =
standardObjectMetadataRelatedEntityIds[objectName].id;
const fieldMetadataId =
standardObjectMetadataRelatedEntityIds[objectName].fields[fieldName].id;
const objectMetadataUniversalIdentifier =
STANDARD_OBJECTS[objectName].universalIdentifier;
const flatObjectMetadata =
findFlatEntityByUniversalIdentifierOrThrow<UniversalFlatObjectMetadata>({
universalIdentifier: objectMetadataUniversalIdentifier,
flatEntityMaps: flatObjectMetadataMaps,
});
const fieldMetadataUniversalIdentifier =
objectFields[fieldName as keyof typeof objectFields].universalIdentifier;
const flatFieldMetadata =
findFlatEntityByUniversalIdentifierOrThrow<UniversalFlatFieldMetadata>({
universalIdentifier: fieldMetadataUniversalIdentifier,
flatEntityMaps: flatFieldMetadataMaps,
});
return {
id: v4(),
universalIdentifier: v4(),
applicationId: twentyStandardApplicationId,
applicationUniversalIdentifier:
flatObjectMetadata.applicationUniversalIdentifier,
objectMetadataId,
objectMetadataUniversalIdentifier: flatObjectMetadata.universalIdentifier,
fieldMetadataId,
fieldMetadataUniversalIdentifier: flatFieldMetadata.universalIdentifier,
position,
workspaceId,
createdAt: now,
updatedAt: now,
};
};
@@ -17,6 +17,7 @@ import { buildStandardFlatPageLayoutWidgetMetadataMaps } from 'src/engine/worksp
import { buildStandardFlatPageLayoutMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout/build-standard-flat-page-layout-metadata-maps.util';
import { buildStandardFlatPermissionFlagMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/permission-flag/build-standard-flat-permission-flag-metadata-maps.util';
import { buildStandardFlatRoleMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/role-metadata/build-standard-flat-role-metadata-maps.util';
import { buildStandardFlatSearchFieldMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/search-field-metadata/build-standard-flat-search-field-metadata-maps.util';
import { buildStandardFlatSkillMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/build-standard-flat-skill-metadata-maps.util';
import { buildStandardFlatViewFieldMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field/build-standard-flat-view-field-metadata-maps.util';
import { buildStandardFlatViewFieldGroupMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field-group/build-standard-flat-view-field-group-metadata-maps.util';
@@ -74,6 +75,17 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({
twentyStandardApplicationId,
});
const flatSearchFieldMetadataMaps = buildStandardFlatSearchFieldMetadataMaps({
dependencyFlatEntityMaps: {
flatFieldMetadataMaps,
flatObjectMetadataMaps,
},
now,
standardObjectMetadataRelatedEntityIds,
workspaceId,
twentyStandardApplicationId,
});
const flatViewMaps = buildStandardFlatViewMetadataMaps({
dependencyFlatEntityMaps: {
flatFieldMetadataMaps,
@@ -217,6 +229,7 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({
flatViewGroupMaps,
flatViewMaps,
flatIndexMaps,
flatSearchFieldMetadataMaps,
flatFieldMetadataMaps,
flatObjectMetadataMaps,
flatNavigationMenuItemMaps,
@@ -304,7 +304,8 @@ export const enrichCreateWorkspaceMigrationActionsWithIds = ({
case 'fieldPermission':
case 'webhook':
case 'applicationVariable':
case 'connectionProvider': {
case 'connectionProvider':
case 'searchFieldMetadata': {
if (!isDefined(idByUniversalIdentifier)) {
return action;
}
@@ -44,6 +44,7 @@ import { WorkspaceMigrationRoleTargetActionsBuilderService } from 'src/engine/wo
import { WorkspaceMigrationRoleActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role/workspace-migration-role-actions-builder.service';
import { WorkspaceMigrationRowLevelPermissionPredicateGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/row-level-permission-predicate-group/workspace-migration-row-level-permission-predicate-group-actions-builder.service';
import { WorkspaceMigrationRowLevelPermissionPredicateActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/row-level-permission-predicate/workspace-migration-row-level-permission-predicate-actions-builder.service';
import { WorkspaceMigrationSearchFieldMetadataActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/search-field-metadata/workspace-migration-search-field-metadata-actions.builder.service';
import { WorkspaceMigrationSkillActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/skill/workspace-migration-skill-actions-builder.service';
import { WorkspaceMigrationViewFieldGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field-group/workspace-migration-view-field-group-actions-builder.service';
import { WorkspaceMigrationViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field/workspace-migration-view-field-actions-builder.service';
@@ -156,6 +157,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
workspaceMigrationWebhookActionsBuilderService: WorkspaceMigrationWebhookActionsBuilderService,
workspaceMigrationApplicationVariableActionsBuilderService: WorkspaceMigrationApplicationVariableActionsBuilderService,
workspaceMigrationConnectionProviderActionsBuilderService: WorkspaceMigrationConnectionProviderActionsBuilderService,
workspaceMigrationSearchFieldMetadataActionsBuilderService: WorkspaceMigrationSearchFieldMetadataActionsBuilderService,
) {
// The order of this array defines the execution order of the per-entity
// builders. Each builder may mutate `optimisticAllFlatEntityMaps`, so
@@ -204,6 +206,10 @@ export class WorkspaceMigrationBuildOrchestratorService {
ALL_METADATA_NAME.viewSort,
workspaceMigrationViewSortActionsBuilderService,
),
createEntityActionsBuilderTask(
ALL_METADATA_NAME.searchFieldMetadata,
workspaceMigrationSearchFieldMetadataActionsBuilderService,
),
createEntityActionsBuilderTask(
ALL_METADATA_NAME.logicFunction,
workspaceMigrationLogicFunctionActionsBuilderService,
@@ -26,6 +26,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should matc
"objectPermissionUniversalIdentifiers",
"fieldPermissionUniversalIdentifiers",
"viewUniversalIdentifiers",
"searchFieldMetadataUniversalIdentifiers",
],
"objectPermission": [],
"pageLayout": [
@@ -53,6 +54,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should matc
"childRowLevelPermissionPredicateGroupUniversalIdentifiers",
"rowLevelPermissionPredicateUniversalIdentifiers",
],
"searchFieldMetadata": [],
"skill": [],
"view": [
"viewFieldUniversalIdentifiers",
@@ -49,6 +49,7 @@ export const ALL_JSONB_PROPERTIES_WITH_SERIALIZED_RELATION_BY_METADATA_NAME = {
frontComponent: {},
applicationVariable: {},
connectionProvider: {},
searchFieldMetadata: {},
} as const satisfies {
[P in AllMetadataName]: Partial<{
// TODO prastoin: improve strict typing to recursively serach for nested SerializedRelation
@@ -0,0 +1,7 @@
import { type SearchFieldMetadataEntity } from 'src/engine/metadata-modules/search-field-metadata/search-field-metadata.entity';
import { type UniversalFlatEntityFrom } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
export type UniversalFlatSearchFieldMetadata = UniversalFlatEntityFrom<
SearchFieldMetadataEntity,
'searchFieldMetadata'
>;
@@ -6,6 +6,7 @@ export const computeOrderedMigrationActions = (
): AllUniversalWorkspaceMigrationAction[] => {
return [
// Object and fields and indexes
...aggregatedOrchestratorActionsReport.searchFieldMetadata.delete,
...aggregatedOrchestratorActionsReport.index.delete,
...aggregatedOrchestratorActionsReport.fieldMetadata.delete,
...aggregatedOrchestratorActionsReport.objectMetadata.delete,
@@ -15,6 +16,8 @@ export const computeOrderedMigrationActions = (
...aggregatedOrchestratorActionsReport.fieldMetadata.update,
...aggregatedOrchestratorActionsReport.index.create,
...aggregatedOrchestratorActionsReport.index.update.flat(),
...aggregatedOrchestratorActionsReport.searchFieldMetadata.create,
...aggregatedOrchestratorActionsReport.searchFieldMetadata.update,
///
// Views
@@ -0,0 +1,24 @@
import { type BaseFlatCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-create-workspace-migration-action.type';
import { type BaseFlatUpdateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-update-workspace-migration-action.type';
import { type BaseFlatDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-delete-workspace-migration-action.type';
import { type BaseUniversalCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-create-workspace-migration-action.type';
import { type BaseUniversalUpdateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-update-workspace-migration-action.type';
import { type BaseUniversalDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-delete-workspace-migration-action.type';
export type FlatCreateSearchFieldMetadataAction =
BaseFlatCreateWorkspaceMigrationAction<'searchFieldMetadata'>;
export type FlatUpdateSearchFieldMetadataAction =
BaseFlatUpdateWorkspaceMigrationAction<'searchFieldMetadata'>;
export type FlatDeleteSearchFieldMetadataAction =
BaseFlatDeleteWorkspaceMigrationAction<'searchFieldMetadata'>;
export type UniversalCreateSearchFieldMetadataAction =
BaseUniversalCreateWorkspaceMigrationAction<'searchFieldMetadata'>;
export type UniversalUpdateSearchFieldMetadataAction =
BaseUniversalUpdateWorkspaceMigrationAction<'searchFieldMetadata'>;
export type UniversalDeleteSearchFieldMetadataAction =
BaseUniversalDeleteWorkspaceMigrationAction<'searchFieldMetadata'>;
@@ -0,0 +1,122 @@
import { Injectable } from '@nestjs/common';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { UniversalUpdateSearchFieldMetadataAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/search-field-metadata/types/workspace-migration-search-field-metadata-action.type';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type';
import { UniversalFlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-result.type';
import { WorkspaceEntityMigrationBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service';
import { FlatSearchFieldMetadataValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-search-field-metadata-validator.service';
@Injectable()
export class WorkspaceMigrationSearchFieldMetadataActionsBuilderService extends WorkspaceEntityMigrationBuilderService<
typeof ALL_METADATA_NAME.searchFieldMetadata
> {
constructor(
private readonly flatSearchFieldMetadataValidatorService: FlatSearchFieldMetadataValidatorService,
) {
super(ALL_METADATA_NAME.searchFieldMetadata);
}
protected validateFlatEntityCreation(
args: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.searchFieldMetadata
>,
): UniversalFlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.searchFieldMetadata,
'create'
> {
const validationResult =
this.flatSearchFieldMetadataValidatorService.validateFlatSearchFieldMetadataCreation(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityToValidate: flatSearchFieldMetadataToValidate } = args;
return {
status: 'success',
action: {
type: 'create',
metadataName: 'searchFieldMetadata',
flatEntity: flatSearchFieldMetadataToValidate,
},
};
}
protected validateFlatEntityDeletion(
args: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.searchFieldMetadata
>,
): UniversalFlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.searchFieldMetadata,
'delete'
> {
const validationResult =
this.flatSearchFieldMetadataValidatorService.validateFlatSearchFieldMetadataDeletion(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityToValidate: flatSearchFieldMetadataToValidate } = args;
return {
status: 'success',
action: {
type: 'delete',
metadataName: 'searchFieldMetadata',
universalIdentifier:
flatSearchFieldMetadataToValidate.universalIdentifier,
},
};
}
protected validateFlatEntityUpdate(
args: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.searchFieldMetadata
>,
): UniversalFlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.searchFieldMetadata,
'update'
> {
const validationResult =
this.flatSearchFieldMetadataValidatorService.validateFlatSearchFieldMetadataUpdate(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { universalIdentifier, flatEntityUpdate } = args;
const updateSearchFieldMetadataAction: UniversalUpdateSearchFieldMetadataAction =
{
type: 'update',
metadataName: 'searchFieldMetadata',
universalIdentifier,
update: flatEntityUpdate,
};
return {
status: 'success',
action: updateSearchFieldMetadataAction,
};
}
}
@@ -0,0 +1,187 @@
import { Injectable } from '@nestjs/common';
import { msg, t } from '@lingui/core/macro';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
import { findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps-or-throw.util';
import { SearchFieldMetadataExceptionCode } from 'src/engine/metadata-modules/search-field-metadata/exceptions/search-field-metadata.exception';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util';
import { type FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type';
@Injectable()
export class FlatSearchFieldMetadataValidatorService {
constructor() {}
validateFlatSearchFieldMetadataCreation({
flatEntityToValidate: flatSearchFieldMetadataToValidate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatSearchFieldMetadataMaps: optimisticFlatSearchFieldMetadataMaps,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
},
}: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.searchFieldMetadata
>): FailedFlatEntityValidation<'searchFieldMetadata', 'create'> {
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier:
flatSearchFieldMetadataToValidate.universalIdentifier,
objectMetadataUniversalIdentifier:
flatSearchFieldMetadataToValidate.objectMetadataUniversalIdentifier,
},
metadataName: 'searchFieldMetadata',
type: 'create',
});
const existingFlatSearchFieldMetadata = findFlatEntityByUniversalIdentifier(
{
universalIdentifier:
flatSearchFieldMetadataToValidate.universalIdentifier,
flatEntityMaps: optimisticFlatSearchFieldMetadataMaps,
},
);
if (isDefined(existingFlatSearchFieldMetadata)) {
const searchFieldMetadataId =
flatSearchFieldMetadataToValidate.universalIdentifier;
validationResult.errors.push({
code: SearchFieldMetadataExceptionCode.INVALID_SEARCH_FIELD_METADATA_DATA,
message: t`Search field metadata with id ${searchFieldMetadataId} already exists`,
userFriendlyMessage: msg`Search field metadata already exists`,
});
}
const flatFieldMetadata = findFlatEntityByUniversalIdentifier({
universalIdentifier:
flatSearchFieldMetadataToValidate.fieldMetadataUniversalIdentifier,
flatEntityMaps: flatFieldMetadataMaps,
});
if (!isDefined(flatFieldMetadata)) {
validationResult.errors.push({
code: SearchFieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
message: t`Field metadata not found`,
userFriendlyMessage: msg`Field metadata not found`,
});
}
const flatObjectMetadata = findFlatEntityByUniversalIdentifier({
universalIdentifier:
flatSearchFieldMetadataToValidate.objectMetadataUniversalIdentifier,
flatEntityMaps: flatObjectMetadataMaps,
});
if (!isDefined(flatObjectMetadata)) {
validationResult.errors.push({
code: SearchFieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
message: t`Object metadata not found`,
userFriendlyMessage: msg`Object metadata not found`,
});
return validationResult;
}
const otherFlatSearchFieldMetadatas =
findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow({
universalIdentifiers:
flatObjectMetadata.searchFieldMetadataUniversalIdentifiers,
flatEntityMaps: optimisticFlatSearchFieldMetadataMaps,
});
const equivalentExistingFlatSearchFieldMetadataExists =
otherFlatSearchFieldMetadatas.some(
(flatSearchFieldMetadata) =>
flatSearchFieldMetadata.universalIdentifier !==
flatSearchFieldMetadataToValidate.universalIdentifier &&
flatSearchFieldMetadata.fieldMetadataUniversalIdentifier ===
flatSearchFieldMetadataToValidate.fieldMetadataUniversalIdentifier,
);
if (equivalentExistingFlatSearchFieldMetadataExists) {
validationResult.errors.push({
code: SearchFieldMetadataExceptionCode.INVALID_SEARCH_FIELD_METADATA_DATA,
message: t`Search field metadata with same fieldMetadataId and objectMetadataId already exists`,
userFriendlyMessage: msg`Search field metadata already exists`,
});
}
return validationResult;
}
public validateFlatSearchFieldMetadataUpdate({
universalIdentifier,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatSearchFieldMetadataMaps: optimisticFlatSearchFieldMetadataMaps,
},
}: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.searchFieldMetadata
>): FailedFlatEntityValidation<'searchFieldMetadata', 'update'> {
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier,
},
metadataName: 'searchFieldMetadata',
type: 'update',
});
const existingFlatSearchFieldMetadata = findFlatEntityByUniversalIdentifier(
{
universalIdentifier,
flatEntityMaps: optimisticFlatSearchFieldMetadataMaps,
},
);
if (!isDefined(existingFlatSearchFieldMetadata)) {
validationResult.errors.push({
code: SearchFieldMetadataExceptionCode.SEARCH_FIELD_METADATA_NOT_FOUND,
message: t`Search field metadata to update not found`,
userFriendlyMessage: msg`Search field metadata to update not found`,
});
return validationResult;
}
return validationResult;
}
public validateFlatSearchFieldMetadataDeletion({
flatEntityToValidate: { universalIdentifier },
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatSearchFieldMetadataMaps: optimisticFlatSearchFieldMetadataMaps,
},
}: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.searchFieldMetadata
>): FailedFlatEntityValidation<'searchFieldMetadata', 'delete'> {
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier,
},
metadataName: 'searchFieldMetadata',
type: 'delete',
});
const existingFlatSearchFieldMetadata = findFlatEntityByUniversalIdentifier(
{
universalIdentifier,
flatEntityMaps: optimisticFlatSearchFieldMetadataMaps,
},
);
if (!isDefined(existingFlatSearchFieldMetadata)) {
validationResult.errors.push({
code: SearchFieldMetadataExceptionCode.SEARCH_FIELD_METADATA_NOT_FOUND,
message: t`Search field metadata to delete not found`,
userFriendlyMessage: msg`Search field metadata to delete not found`,
});
return validationResult;
}
return validationResult;
}
}
@@ -33,6 +33,7 @@ import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace
import { FlatApplicationVariableValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-application-variable-validator.service';
import { FlatWebhookValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-webhook-validator.service';
import { FlatConnectionProviderValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-connection-provider-validator.service';
import { FlatSearchFieldMetadataValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-search-field-metadata-validator.service';
@Module({
imports: [FeatureFlagModule],
@@ -69,6 +70,7 @@ import { FlatConnectionProviderValidatorService } from 'src/engine/workspace-man
FlatWebhookValidatorService,
FlatApplicationVariableValidatorService,
FlatConnectionProviderValidatorService,
FlatSearchFieldMetadataValidatorService,
],
exports: [
FlatViewValidatorService,
@@ -102,6 +104,7 @@ import { FlatConnectionProviderValidatorService } from 'src/engine/workspace-man
FlatWebhookValidatorService,
FlatApplicationVariableValidatorService,
FlatConnectionProviderValidatorService,
FlatSearchFieldMetadataValidatorService,
],
})
export class WorkspaceMigrationBuilderValidatorsModule {}
@@ -32,6 +32,7 @@ import { WorkspaceMigrationViewActionsBuilderService } from 'src/engine/workspac
import { WorkspaceMigrationApplicationVariableActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/application-variable/workspace-migration-application-variable-actions-builder.service';
import { WorkspaceMigrationWebhookActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/webhook/workspace-migration-webhook-actions-builder.service';
import { WorkspaceMigrationConnectionProviderActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/connection-provider/workspace-migration-connection-provider-actions-builder.service';
import { WorkspaceMigrationSearchFieldMetadataActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/search-field-metadata/workspace-migration-search-field-metadata-actions.builder.service';
import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/workspace-migration-builder-validators.module';
@Module({
@@ -68,6 +69,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationWebhookActionsBuilderService,
WorkspaceMigrationApplicationVariableActionsBuilderService,
WorkspaceMigrationConnectionProviderActionsBuilderService,
WorkspaceMigrationSearchFieldMetadataActionsBuilderService,
],
exports: [
WorkspaceMigrationViewActionsBuilderService,
@@ -101,6 +103,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationWebhookActionsBuilderService,
WorkspaceMigrationApplicationVariableActionsBuilderService,
WorkspaceMigrationConnectionProviderActionsBuilderService,
WorkspaceMigrationSearchFieldMetadataActionsBuilderService,
],
})
export class WorkspaceMigrationBuilderModule {}
@@ -117,6 +117,7 @@ export const fromUniversalFlatObjectMetadataToFlatObjectMetadata = ({
fieldIds: [],
viewIds: [],
indexMetadataIds: [],
searchFieldMetadataIds: [],
objectPermissionIds: [],
fieldPermissionIds: [],
...emptyUniversalForeignKeyAggregators,
@@ -0,0 +1,65 @@
import { Injectable } from '@nestjs/common';
import { v4 } from 'uuid';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import {
FlatCreateSearchFieldMetadataAction,
UniversalCreateSearchFieldMetadataAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/search-field-metadata/types/workspace-migration-search-field-metadata-action.type';
import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util';
import {
WorkspaceMigrationActionRunnerArgs,
WorkspaceMigrationActionRunnerContext,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
@Injectable()
export class CreateSearchFieldMetadataActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create',
'searchFieldMetadata',
) {
override async transpileUniversalActionToFlatAction({
action,
allFlatEntityMaps,
flatApplication,
workspaceId,
}: WorkspaceMigrationActionRunnerArgs<UniversalCreateSearchFieldMetadataAction>): Promise<FlatCreateSearchFieldMetadataAction> {
const { objectMetadataId, fieldMetadataId } =
resolveUniversalRelationIdentifiersToIds({
flatEntityMaps: allFlatEntityMaps,
metadataName: action.metadataName,
universalForeignKeyValues: action.flatEntity,
});
return {
...action,
flatEntity: {
...action.flatEntity,
objectMetadataId,
fieldMetadataId,
id: action.id ?? v4(),
applicationId: flatApplication.id,
workspaceId,
},
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerContext<FlatCreateSearchFieldMetadataAction>,
): Promise<void> {
const { flatAction, queryRunner } = context;
const { flatEntity } = flatAction;
await this.insertFlatEntitiesInRepository({
queryRunner,
flatEntities: [flatEntity],
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerContext<FlatCreateSearchFieldMetadataAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,51 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import { SearchFieldMetadataEntity } from 'src/engine/metadata-modules/search-field-metadata/search-field-metadata.entity';
import {
FlatDeleteSearchFieldMetadataAction,
UniversalDeleteSearchFieldMetadataAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/search-field-metadata/types/workspace-migration-search-field-metadata-action.type';
import {
WorkspaceMigrationActionRunnerArgs,
WorkspaceMigrationActionRunnerContext,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
@Injectable()
export class DeleteSearchFieldMetadataActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'delete',
'searchFieldMetadata',
) {
constructor() {
super();
}
override async transpileUniversalActionToFlatAction(
context: WorkspaceMigrationActionRunnerArgs<UniversalDeleteSearchFieldMetadataAction>,
): Promise<FlatDeleteSearchFieldMetadataAction> {
return this.transpileUniversalDeleteActionToFlatDeleteAction(context);
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerContext<FlatDeleteSearchFieldMetadataAction>,
): Promise<void> {
const { flatAction, queryRunner, workspaceId } = context;
const searchFieldMetadataRepository =
queryRunner.manager.getRepository<SearchFieldMetadataEntity>(
SearchFieldMetadataEntity,
);
await searchFieldMetadataRepository.delete({
id: flatAction.entityId,
workspaceId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerContext<FlatDeleteSearchFieldMetadataAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,72 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import { SearchFieldMetadataEntity } from 'src/engine/metadata-modules/search-field-metadata/search-field-metadata.entity';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
import { resolveUniversalUpdateRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-update-relation-identifiers-to-ids.util';
import {
FlatUpdateSearchFieldMetadataAction,
UniversalUpdateSearchFieldMetadataAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/search-field-metadata/types/workspace-migration-search-field-metadata-action.type';
import {
WorkspaceMigrationActionRunnerArgs,
WorkspaceMigrationActionRunnerContext,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
@Injectable()
export class UpdateSearchFieldMetadataActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'update',
'searchFieldMetadata',
) {
constructor() {
super();
}
override async transpileUniversalActionToFlatAction(
context: WorkspaceMigrationActionRunnerArgs<UniversalUpdateSearchFieldMetadataAction>,
): Promise<FlatUpdateSearchFieldMetadataAction> {
const { action, allFlatEntityMaps } = context;
const flatSearchFieldMetadata = findFlatEntityByUniversalIdentifierOrThrow({
flatEntityMaps: allFlatEntityMaps.flatSearchFieldMetadataMaps,
universalIdentifier: action.universalIdentifier,
});
const update = resolveUniversalUpdateRelationIdentifiersToIds({
metadataName: 'searchFieldMetadata',
universalUpdate: action.update,
allFlatEntityMaps,
});
return {
type: 'update',
metadataName: 'searchFieldMetadata',
entityId: flatSearchFieldMetadata.id,
update,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerContext<FlatUpdateSearchFieldMetadataAction>,
): Promise<void> {
const { flatAction, queryRunner, workspaceId } = context;
const { entityId, update } = flatAction;
const searchFieldMetadataRepository =
queryRunner.manager.getRepository<SearchFieldMetadataEntity>(
SearchFieldMetadataEntity,
);
await searchFieldMetadataRepository.update(
{ id: entityId, workspaceId },
update,
);
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerContext<FlatUpdateSearchFieldMetadataAction>,
): Promise<void> {
return;
}
}
@@ -94,6 +94,9 @@ import { UpdateWebhookActionHandlerService } from 'src/engine/workspace-manager/
import { CreateConnectionProviderActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/connection-provider/services/create-connection-provider-action-handler.service';
import { DeleteConnectionProviderActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/connection-provider/services/delete-connection-provider-action-handler.service';
import { UpdateConnectionProviderActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/connection-provider/services/update-connection-provider-action-handler.service';
import { CreateSearchFieldMetadataActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/search-field-metadata/services/create-search-field-metadata-action-handler.service';
import { DeleteSearchFieldMetadataActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/search-field-metadata/services/delete-search-field-metadata-action-handler.service';
import { UpdateSearchFieldMetadataActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/search-field-metadata/services/update-search-field-metadata-action-handler.service';
@Module({
imports: [
@@ -221,6 +224,10 @@ import { UpdateConnectionProviderActionHandlerService } from 'src/engine/workspa
CreateConnectionProviderActionHandlerService,
UpdateConnectionProviderActionHandlerService,
DeleteConnectionProviderActionHandlerService,
CreateSearchFieldMetadataActionHandlerService,
UpdateSearchFieldMetadataActionHandlerService,
DeleteSearchFieldMetadataActionHandlerService,
],
})
export class WorkspaceSchemaMigrationRunnerActionHandlersModule {}
@@ -31,4 +31,5 @@ export const METADATA_EVENTS_TO_EMIT = {
webhook: false,
applicationVariable: false,
connectionProvider: true,
searchFieldMetadata: true,
} as const satisfies { [P in AllMetadataName]: boolean };
@@ -98,7 +98,8 @@ const deriveAllMetadataEventsFromCreateAction = (
case 'fieldPermission':
case 'viewSort':
case 'webhook':
case 'connectionProvider': {
case 'connectionProvider':
case 'searchFieldMetadata': {
return [
{
type: 'created',
@@ -62,7 +62,8 @@ const deriveAllMetadataEventsFromDeleteAction = ({
case 'fieldPermission':
case 'viewSort':
case 'webhook':
case 'connectionProvider': {
case 'connectionProvider':
case 'searchFieldMetadata': {
const flatEntityToDelete = findFlatEntityByIdInFlatEntityMapsOrThrow<
MetadataFlatEntity<typeof flatAction.metadataName>
>({
@@ -140,7 +140,8 @@ const deriveAllMetadataEventsFromUpdateAction = ({
case 'fieldPermission':
case 'viewSort':
case 'webhook':
case 'connectionProvider': {
case 'connectionProvider':
case 'searchFieldMetadata': {
const flatEntityMapsKey = getMetadataFlatEntityMapsKey(
flatAction.metadataName,
);
@@ -74,7 +74,8 @@ export const optimisticallyApplyCreateActionOnAllFlatEntityMaps = ({
case 'fieldPermission':
case 'webhook':
case 'applicationVariable':
case 'connectionProvider': {
case 'connectionProvider':
case 'searchFieldMetadata': {
addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow({
flatEntity: flatAction.flatEntity,
flatEntityAndRelatedMapsToMutate: allFlatEntityMaps,
@@ -46,7 +46,8 @@ export const optimisticallyApplyDeleteActionOnAllFlatEntityMaps = ({
case 'fieldPermission':
case 'webhook':
case 'applicationVariable':
case 'connectionProvider': {
case 'connectionProvider':
case 'searchFieldMetadata': {
const flatEntityToDelete = findFlatEntityByIdInFlatEntityMapsOrThrow<
MetadataFlatEntity<typeof flatAction.metadataName>
>({
@@ -67,7 +67,8 @@ export const optimisticallyApplyUpdateActionOnAllFlatEntityMaps = ({
case 'fieldPermission':
case 'webhook':
case 'applicationVariable':
case 'connectionProvider': {
case 'connectionProvider':
case 'searchFieldMetadata': {
const flatEntityMapsKey = getMetadataFlatEntityMapsKey(
flatAction.metadataName,
);