Steps to test

1. Run metadata migrations
2. Run sync-metadata on your workspace
3. Enable the following feature flags: 
IS_SEARCH_ENABLED
IS_QUERY_RUNNER_TWENTY_ORM_ENABLED
IS_WORKSPACE_MIGRATED_FOR_SEARCH
4. Type Cmd + K and search anything
This commit is contained in:
Marie
2024-10-03 17:18:49 +02:00
committed by GitHub
parent 4c250dd811
commit 5f9435c718
71 changed files with 1517 additions and 209 deletions

View File

@ -1,8 +1,11 @@
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants';
import {
ActorMetadata,
FieldActorSource,
} from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { DEFAULT_LABEL_IDENTIFIER_FIELD_NAME } from 'src/engine/metadata-modules/object-metadata/object-metadata.constants';
import {
RelationMetadataType,
RelationOnDeleteAction,
@ -10,10 +13,12 @@ import {
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
import { WorkspaceCustomEntity } from 'src/engine/twenty-orm/decorators/workspace-custom-entity.decorator';
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
import { WorkspaceIndex } from 'src/engine/twenty-orm/decorators/workspace-index.decorator';
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
import { CUSTOM_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util';
import { ActivityTargetWorkspaceEntity } from 'src/modules/activity/standard-objects/activity-target.workspace-entity';
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
@ -136,4 +141,22 @@ export class CustomWorkspaceEntity extends BaseWorkspaceEntity {
@WorkspaceIsNullable()
@WorkspaceIsSystem()
timelineActivities: TimelineActivityWorkspaceEntity[];
@WorkspaceField({
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.searchVector,
type: FieldMetadataType.TS_VECTOR,
label: SEARCH_VECTOR_FIELD.label,
description: SEARCH_VECTOR_FIELD.description,
generatedType: 'STORED',
asExpression: getTsVectorColumnExpressionFromFields([
{
name: DEFAULT_LABEL_IDENTIFIER_FIELD_NAME,
type: FieldMetadataType.TEXT,
},
]),
})
@WorkspaceIsNullable()
@WorkspaceIsSystem()
@WorkspaceIndex({ indexType: IndexType.GIN })
[SEARCH_VECTOR_FIELD.name]: any;
}

View File

@ -20,6 +20,8 @@ export interface WorkspaceFieldOptions<
options?: FieldMetadataOptions<T>;
settings?: FieldMetadataSettings<T>;
isActive?: boolean;
generatedType?: 'STORED' | 'VIRTUAL';
asExpression?: string;
}
export function WorkspaceField<T extends FieldMetadataType>(
@ -76,6 +78,8 @@ export function WorkspaceField<T extends FieldMetadataType>(
gate,
isDeprecated,
isActive: options.isActive,
asExpression: options.asExpression,
generatedType: options.generatedType,
});
};
}

View File

@ -1,18 +1,36 @@
import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { generateDeterministicIndexName } from 'src/engine/metadata-modules/index-metadata/utils/generate-deterministic-index-name';
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
import { getColumnsForIndex } from 'src/engine/twenty-orm/utils/get-default-columns-for-index.util';
import { convertClassNameToObjectMetadataName } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util';
import { isDefined } from 'src/utils/is-defined';
import { TypedReflect } from 'src/utils/typed-reflect';
export function WorkspaceIndex(): PropertyDecorator;
export function WorkspaceIndex(columns: string[]): ClassDecorator;
export type WorkspaceIndexMetadata = {
columns?: string[];
indexType?: IndexType;
};
export function WorkspaceIndex(
columns?: string[],
metadata?: WorkspaceIndexMetadata,
): PropertyDecorator;
export function WorkspaceIndex(
metadata: WorkspaceIndexMetadata,
): ClassDecorator;
export function WorkspaceIndex(
metadata?: WorkspaceIndexMetadata,
): PropertyDecorator | ClassDecorator {
return (target: any, propertyKey: string | symbol) => {
if (propertyKey === undefined && columns === undefined) {
if (propertyKey === undefined && metadata === undefined) {
throw new Error('Class level WorkspaceIndex should be used with columns');
}
if (propertyKey !== undefined && metadata?.columns !== undefined) {
throw new Error(
'Property level WorkspaceIndex should not be used with columns',
);
}
const gate = TypedReflect.getMetadata(
'workspace:gate-metadata-args',
target,
@ -20,29 +38,46 @@ export function WorkspaceIndex(
);
// TODO: handle composite field metadata types
if (isDefined(metadata?.columns)) {
const columns = metadata.columns;
if (columns.length > 0) {
metadataArgsStorage.addIndexes({
name: `IDX_${generateDeterministicIndexName([
convertClassNameToObjectMetadataName(target.name),
...columns,
])}`,
columns,
target: target,
gate,
...(isDefined(metadata?.indexType)
? { type: metadata.indexType }
: {}),
});
return;
}
}
if (isDefined(propertyKey)) {
const additionalDefaultColumnsForIndex = getColumnsForIndex(
metadata?.indexType,
);
const columns = [
propertyKey.toString(),
...additionalDefaultColumnsForIndex,
];
if (Array.isArray(columns) && columns.length > 0) {
metadataArgsStorage.addIndexes({
name: `IDX_${generateDeterministicIndexName([
convertClassNameToObjectMetadataName(target.name),
convertClassNameToObjectMetadataName(target.constructor.name),
...columns,
])}`,
columns,
target: target,
target: target.constructor,
...(isDefined(metadata?.indexType) ? { type: metadata.indexType } : {}),
gate,
});
return;
}
metadataArgsStorage.addIndexes({
name: `IDX_${generateDeterministicIndexName([
convertClassNameToObjectMetadataName(target.constructor.name),
...[propertyKey.toString(), 'deletedAt'],
])}`,
columns: [propertyKey.toString(), 'deletedAt'],
target: target.constructor,
gate,
});
};
}

View File

@ -89,4 +89,14 @@ export interface WorkspaceFieldMetadataArgs {
* Is active field.
*/
readonly isActive?: boolean;
/**
* Is active field.
*/
readonly generatedType?: 'STORED' | 'VIRTUAL';
/**
* Is active field.
*/
readonly asExpression?: string;
}

View File

@ -1,5 +1,7 @@
import { Gate } from 'src/engine/twenty-orm/interfaces/gate.interface';
import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
export interface WorkspaceIndexMetadataArgs {
/**
* Class to which index is applied.
@ -17,6 +19,11 @@ export interface WorkspaceIndexMetadataArgs {
*/
columns: string[];
/*
* Index type. Defaults to Btree.
*/
type?: IndexType;
/**
* Field gate.
*/

View File

@ -664,7 +664,7 @@ export class WorkspaceRepository<
return formatData(data, objectMetadata) as T;
}
private async formatResult<T>(
async formatResult<T>(
data: T,
objectMetadata?: ObjectMetadataMapItem,
): Promise<T> {

View File

@ -0,0 +1,22 @@
import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { getColumnsForIndex } from 'src/engine/twenty-orm/utils/get-default-columns-for-index.util';
describe('getColumnsForIndex', () => {
it('should return ["deletedAt"] when indexType is undefined', () => {
const result = getColumnsForIndex();
expect(result).toEqual(['deletedAt']);
});
it('should return an empty array when indexType is IndexType.GIN', () => {
const result = getColumnsForIndex(IndexType.GIN);
expect(result).toEqual([]);
});
it('should return ["deletedAt"] when indexType is IndexType.BTREE', () => {
const result = getColumnsForIndex(IndexType.BTREE);
expect(result).toEqual(['deletedAt']);
});
});

View File

@ -0,0 +1,10 @@
import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
export const getColumnsForIndex = (indexType?: IndexType) => {
switch (indexType) {
case IndexType.GIN:
return [];
default:
return ['deletedAt'];
}
};