[Apps] Fix - app-synced object should be searchable (#19206)

## Summary

- **Make app-synced objects searchable**: `isSearchable` was hardcoded
to `false` and the `searchVector` field was missing the `GENERATED
ALWAYS AS (...)` expression, causing all records to have a `NULL` search
vector and be excluded from search results. Fixed by defaulting
`isSearchable` to `true` (configurable via the object manifest),
computing the `asExpression` from the label identifier field, and
allowing the update-field-action-handler to handle the `null` → defined
`asExpression` transition.
- **Make `isSearchable` updatable on an object**: The property had
`toCompare: false` in the entity properties configuration, so updates
via the API were silently ignored and never persisted. Fixed by setting
`toCompare: true`.
This commit is contained in:
Marie
2026-04-02 19:14:37 +02:00
committed by GitHub
parent 43baf7b91c
commit 2d6c8be7df
34 changed files with 639 additions and 51 deletions
@@ -2,15 +2,15 @@ import {
FieldMetadataType,
compositeTypeDefinitions,
} from 'twenty-shared/types';
import type { SearchableFieldType } from 'twenty-shared/utils';
import {
computeColumnName,
computeCompositeColumnName,
} from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { escapeIdentifier } from 'src/engine/workspace-manager/workspace-migration/utils/remove-sql-injection.util';
import { type SearchableFieldType } from 'src/engine/workspace-manager/utils/is-searchable-field.util';
import { isSearchableSubfield } from 'src/engine/workspace-manager/utils/is-searchable-subfield.util';
import { escapeIdentifier } from 'src/engine/workspace-manager/workspace-migration/utils/remove-sql-injection.util';
export type FieldTypeAndNameMetadata = {
name: string;
@@ -126,6 +126,9 @@ const getColumnExpression = (
case FieldMetadataType.PHONES:
return `COALESCE(${quotedColumnName}, '')`;
case FieldMetadataType.UUID:
return `COALESCE(${quotedColumnName}::text, '')`;
default:
return `COALESCE(public.unaccent_immutable(${quotedColumnName}), '')`;
}
@@ -1,19 +0,0 @@
import { FieldMetadataType } from 'twenty-shared/types';
const SEARCHABLE_FIELD_TYPES = [
FieldMetadataType.TEXT,
FieldMetadataType.FULL_NAME,
FieldMetadataType.EMAILS,
FieldMetadataType.ADDRESS,
FieldMetadataType.LINKS,
FieldMetadataType.PHONES,
FieldMetadataType.RICH_TEXT,
FieldMetadataType.UUID,
] as const;
export type SearchableFieldType = (typeof SEARCHABLE_FIELD_TYPES)[number];
export const isSearchableFieldType = (
type: FieldMetadataType,
): type is SearchableFieldType => {
return SEARCHABLE_FIELD_TYPES.includes(type as SearchableFieldType);
};