ViewGroup and ViewFilters side effect in v2 (#15096)

# Introduction

### Summary
Implements side effect handling for `ViewGroup` and `ViewFilters` when
field metadata is updated in the v2 architecture. This ensures that
view-related records are properly maintained when enum field options are
modified, deleted, or created.

### Side effects
- **Side Effect System**: Added side effect handling for field metadata
updates that manages related view groups and view filters
- **Enum Field Updates**: When enum field options are modified, the
system now:
- **View Groups**: Creates new groups for added options, updates
existing groups for modified options, and deletes groups for removed
options
- **View Filters**: Updates filter values to reflect option changes and
removes filters that reference deleted options

### Enum runner fix
Update now works for both atomic enum and array enum ( multi select for
instance )

### Compute flat entity maps from to
Standardized this method usage across v2 services
Next step is to require dependencies dynamically

## Conclusion
closes https://github.com/twentyhq/core-team-issues/issues/1649
This commit is contained in:
Paul Rastoin
2025-10-17 17:09:26 +02:00
committed by GitHub
parent 4f1623ff76
commit 3462a2e288
58 changed files with 3108 additions and 1075 deletions
@@ -213,6 +213,8 @@ export class WorkspaceSchemaEnumManagerService {
oldColumnName,
newColumnName: columnName,
oldToNewEnumOptionMap,
columnDefinition,
oldEnumTypeName: oldEnumName,
});
}
@@ -315,49 +317,116 @@ export class WorkspaceSchemaEnumManagerService {
oldColumnName,
newColumnName,
oldToNewEnumOptionMap,
columnDefinition,
oldEnumTypeName,
}: {
queryRunner: QueryRunner;
schemaName: string;
tableName: string;
oldColumnName: string;
newColumnName: string;
oldEnumTypeName: string;
oldToNewEnumOptionMap: Record<string, string>;
columnDefinition: WorkspaceSchemaColumnDefinition;
}): Promise<void> {
const safeSchemaName = removeSqlDDLInjection(schemaName);
const safeTableName = removeSqlDDLInjection(tableName);
const safeOldColumnName = removeSqlDDLInjection(oldColumnName);
const safeNewColumnName = removeSqlDDLInjection(newColumnName);
const newEnumTypeName = computePostgresEnumName({
tableName,
columnName: newColumnName,
});
if (Object.keys(oldToNewEnumOptionMap).length === 0) {
return;
}
const safeSchemaName = removeSqlDDLInjection(schemaName);
const safeTableName = removeSqlDDLInjection(tableName);
const safeOldColumnName = removeSqlDDLInjection(oldColumnName);
const safeNewColumnName = removeSqlDDLInjection(newColumnName);
const caseStatements = Object.entries(oldToNewEnumOptionMap)
.map(
([oldEnumOption, newEnumOption]) =>
`WHEN '${removeSqlDDLInjection(oldEnumOption)}' THEN '${removeSqlDDLInjection(newEnumOption)}'::"${safeSchemaName}"."${newEnumTypeName}"`,
)
.join(' ');
const mappedValuesCondition = Object.keys(oldToNewEnumOptionMap)
.map((oldValue) => `'${removeSqlDDLInjection(oldValue)}'`)
.join(', ');
// Update rows with mapped enum values
const updateMappedSql = `
UPDATE "${safeSchemaName}"."${safeTableName}"
SET "${safeNewColumnName}" =
CASE "${safeOldColumnName}"::text
${caseStatements}
END
WHERE "${safeOldColumnName}" IS NOT NULL
AND "${safeOldColumnName}"::text IN (${mappedValuesCondition})`;
const sqlQuery = columnDefinition.isArray
? this.updateArrayEnum({
safeSchemaName,
safeTableName,
safeOldColumnName,
safeNewColumnName,
newEnumTypeName,
oldEnumTypeName,
caseStatements,
mappedValuesCondition,
})
: this.updateAtomicEnum({
safeSchemaName,
safeTableName,
safeOldColumnName,
safeNewColumnName,
caseStatements,
mappedValuesCondition,
});
await queryRunner.query(updateMappedSql);
await queryRunner.query(sqlQuery);
}
private updateArrayEnum({
safeNewColumnName,
safeOldColumnName,
safeSchemaName,
safeTableName,
newEnumTypeName,
oldEnumTypeName,
caseStatements,
mappedValuesCondition,
}: {
safeSchemaName: string;
safeTableName: string;
safeOldColumnName: string;
safeNewColumnName: string;
newEnumTypeName: string;
oldEnumTypeName: string;
caseStatements: string;
mappedValuesCondition: string;
}) {
return `
UPDATE "${safeSchemaName}"."${safeTableName}"
SET "${safeNewColumnName}" = (
SELECT array_agg(
CASE unnest_value::text
${caseStatements}
ELSE unnest_value::text::"${safeSchemaName}"."${newEnumTypeName}"
END
)
FROM unnest("${safeOldColumnName}") AS unnest_value
)
WHERE "${safeOldColumnName}" IS NOT NULL
AND "${safeOldColumnName}" && ARRAY[${mappedValuesCondition}]::"${safeSchemaName}"."${oldEnumTypeName}"[]`;
}
private updateAtomicEnum({
safeNewColumnName,
safeOldColumnName,
safeSchemaName,
safeTableName,
caseStatements,
mappedValuesCondition,
}: {
caseStatements: string;
mappedValuesCondition: string;
safeSchemaName: string;
safeTableName: string;
safeOldColumnName: string;
safeNewColumnName: string;
}) {
return `
UPDATE "${safeSchemaName}"."${safeTableName}"
SET "${safeNewColumnName}" =
CASE "${safeOldColumnName}"::text
${caseStatements}
END
WHERE "${safeOldColumnName}" IS NOT NULL
AND "${safeOldColumnName}"::text IN (${mappedValuesCondition})`;
}
}