fix: set isActive=true in morph migration & add system fields toggle (#17736)
## Summary
Three fixes in this PR:
### 1. Migration commands now set `isActive = true` and use generic
'Target' label
When converting relation fields to `MORPH_RELATION` type, the migration
commands now:
- Set `isActive = true` to prevent inactive fields from being selected
as the representative morph field
- Update all field labels to generic **'Target'** instead of keeping
individual labels like 'Company', 'Person'
This ensures the UI shows a coherent label ('Target') alongside 'X
Objects' for the type.
**Files changed:**
- `1-17-migrate-note-target-to-morph-relations.command.ts`
- `1-17-migrate-task-target-to-morph-relations.command.ts`
### 2. Added system fields/relations toggles in Settings
The fields and relations tables in Settings > Data Model were filtering
out system fields with no way to view them. Added a "System fields" /
"System relations" toggle (visible in advanced mode) to allow viewing
these fields.
**Files changed:**
- `SettingsObjectFieldTable.tsx`
- `SettingsObjectRelationsTable.tsx`
This matches the existing behavior on the Objects table which already
has a "System objects" toggle.
This commit is contained in:
+24
-3
@@ -9,12 +9,19 @@ import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
|
||||
import { type TableMetadata } from '@/ui/layout/table/types/TableMetadata';
|
||||
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
|
||||
import styled from '@emotion/styled';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { IconArchive, IconFilter, IconSearch } from 'twenty-ui/display';
|
||||
import {
|
||||
IconArchive,
|
||||
IconFilter,
|
||||
IconSearch,
|
||||
IconSettings,
|
||||
} from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { MenuItemToggle } from 'twenty-ui/navigation';
|
||||
import { normalizeSearchText } from '~/utils/normalizeSearchText';
|
||||
@@ -73,17 +80,20 @@ export const SettingsObjectRelationsTable = ({
|
||||
const { t } = useLingui();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [showInactive, setShowInactive] = useState(true);
|
||||
const [showSystemRelations, setShowSystemRelations] = useState(false);
|
||||
|
||||
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
|
||||
|
||||
const tableMetadata = SETTINGS_OBJECT_RELATION_TABLE_METADATA;
|
||||
|
||||
const relationFields = useMemo(() => {
|
||||
return objectMetadataItem.fields.filter(
|
||||
(field) =>
|
||||
!field.isSystem &&
|
||||
(showSystemRelations || !field.isSystem) &&
|
||||
(field.type === FieldMetadataType.RELATION ||
|
||||
field.type === FieldMetadataType.MORPH_RELATION),
|
||||
);
|
||||
}, [objectMetadataItem.fields]);
|
||||
}, [objectMetadataItem.fields, showSystemRelations]);
|
||||
|
||||
const sortedRelationFields = useSortedArray(relationFields, tableMetadata);
|
||||
|
||||
@@ -136,6 +146,17 @@ export const SettingsObjectRelationsTable = ({
|
||||
text={t`Inactive`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
{isAdvancedModeEnabled && (
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconSettings}
|
||||
onToggleChange={() =>
|
||||
setShowSystemRelations(!showSystemRelations)
|
||||
}
|
||||
toggled={showSystemRelations}
|
||||
text={t`System relations`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
|
||||
@@ -13,13 +13,19 @@ import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
|
||||
import { type TableMetadata } from '@/ui/layout/table/types/TableMetadata';
|
||||
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
|
||||
import styled from '@emotion/styled';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { IconArchive, IconFilter, IconSearch } from 'twenty-ui/display';
|
||||
import {
|
||||
IconArchive,
|
||||
IconFilter,
|
||||
IconSearch,
|
||||
IconSettings,
|
||||
} from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { MenuItemToggle } from 'twenty-ui/navigation';
|
||||
import { useMapFieldMetadataItemToSettingsObjectDetailTableItem } from '~/pages/settings/data-model/hooks/useMapFieldMetadataItemToSettingsObjectDetailTableItem';
|
||||
@@ -81,6 +87,9 @@ export const SettingsObjectFieldTable = ({
|
||||
const { t } = useLingui();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [showInactive, setShowInactive] = useState(true);
|
||||
const [showSystemFields, setShowSystemFields] = useState(false);
|
||||
|
||||
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
|
||||
|
||||
const tableMetadata = SETTINGS_OBJECT_FIELD_TABLE_METADATA;
|
||||
|
||||
@@ -98,17 +107,19 @@ export const SettingsObjectFieldTable = ({
|
||||
}, [objectMetadataItem, setSettingsObjectFields]);
|
||||
|
||||
const allObjectSettingsDetailItems = useMemo(() => {
|
||||
const nonSystemFields = settingsObjectFields?.filter(
|
||||
(fieldMetadataItem) => !fieldMetadataItem.isSystem,
|
||||
);
|
||||
const filteredBySystem = showSystemFields
|
||||
? settingsObjectFields
|
||||
: settingsObjectFields?.filter(
|
||||
(fieldMetadataItem) => !fieldMetadataItem.isSystem,
|
||||
);
|
||||
|
||||
const fieldsToDisplay = excludeRelations
|
||||
? nonSystemFields?.filter(
|
||||
? filteredBySystem?.filter(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.type !== FieldMetadataType.RELATION &&
|
||||
fieldMetadataItem.type !== FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
: nonSystemFields;
|
||||
: filteredBySystem;
|
||||
|
||||
return (
|
||||
fieldsToDisplay?.map(
|
||||
@@ -119,6 +130,7 @@ export const SettingsObjectFieldTable = ({
|
||||
settingsObjectFields,
|
||||
mapFieldMetadataItemToSettingsObjectDetailTableItem,
|
||||
excludeRelations,
|
||||
showSystemFields,
|
||||
]);
|
||||
|
||||
const sortedAllObjectSettingsDetailItems = useSortedArray(
|
||||
@@ -174,6 +186,17 @@ export const SettingsObjectFieldTable = ({
|
||||
text={t`Inactive`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
{isAdvancedModeEnabled && (
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconSettings}
|
||||
onToggleChange={() =>
|
||||
setShowSystemFields(!showSystemFields)
|
||||
}
|
||||
toggled={showSystemFields}
|
||||
text={t`System fields`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
|
||||
+7
-3
@@ -18,8 +18,8 @@ import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/service
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { getMetadataRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-related-metadata-names.util';
|
||||
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
|
||||
@@ -152,6 +152,7 @@ export class MigrateNoteTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
|
||||
const fieldMigrations = noteTargetRelationFields.map((field) => {
|
||||
const newFieldName = `target${capitalize(field.name)}`;
|
||||
const newFieldLabel = 'Target';
|
||||
const relationSettings: RelationFieldMetadataSettings = field.settings;
|
||||
const oldJoinColumnName =
|
||||
relationSettings?.joinColumnName ??
|
||||
@@ -163,6 +164,7 @@ export class MigrateNoteTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
return {
|
||||
field,
|
||||
newFieldName,
|
||||
newFieldLabel,
|
||||
oldJoinColumnName,
|
||||
newJoinColumnName,
|
||||
};
|
||||
@@ -204,6 +206,7 @@ export class MigrateNoteTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
for (const {
|
||||
field: fieldToMigrate,
|
||||
newFieldName,
|
||||
newFieldLabel,
|
||||
newJoinColumnName,
|
||||
} of fieldMigrations) {
|
||||
const settings = {
|
||||
@@ -214,7 +217,7 @@ export class MigrateNoteTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
try {
|
||||
const result = await queryRunner.query(
|
||||
`UPDATE core."fieldMetadata"
|
||||
SET name = $1, type = $5, "morphId" = $3, settings = $4
|
||||
SET name = $1, type = $5, "morphId" = $3, settings = $4, "isActive" = true, label = $6
|
||||
WHERE id = $2`,
|
||||
[
|
||||
newFieldName,
|
||||
@@ -222,6 +225,7 @@ export class MigrateNoteTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
morphId,
|
||||
settings,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
newFieldLabel,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -229,7 +233,7 @@ export class MigrateNoteTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
|
||||
if (rowsUpdated > 0) {
|
||||
this.logger.log(
|
||||
`Updated fieldMetadata: ${fieldToMigrate.name} → ${newFieldName} (type: MORPH_RELATION)`,
|
||||
`Updated fieldMetadata: ${fieldToMigrate.name} → ${newFieldName} (label: ${newFieldLabel}, type: MORPH_RELATION)`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
+7
-3
@@ -18,8 +18,8 @@ import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/service
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { getMetadataRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-related-metadata-names.util';
|
||||
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
|
||||
@@ -152,6 +152,7 @@ export class MigrateTaskTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
|
||||
const fieldMigrations = taskTargetRelationFields.map((field) => {
|
||||
const newFieldName = `target${capitalize(field.name)}`;
|
||||
const newFieldLabel = 'Target';
|
||||
const relationSettings: RelationFieldMetadataSettings = field.settings;
|
||||
const oldJoinColumnName =
|
||||
relationSettings?.joinColumnName ??
|
||||
@@ -163,6 +164,7 @@ export class MigrateTaskTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
return {
|
||||
field,
|
||||
newFieldName,
|
||||
newFieldLabel,
|
||||
oldJoinColumnName,
|
||||
newJoinColumnName,
|
||||
};
|
||||
@@ -204,6 +206,7 @@ export class MigrateTaskTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
for (const {
|
||||
field: fieldToMigrate,
|
||||
newFieldName,
|
||||
newFieldLabel,
|
||||
newJoinColumnName,
|
||||
} of fieldMigrations) {
|
||||
const settings = {
|
||||
@@ -214,7 +217,7 @@ export class MigrateTaskTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
try {
|
||||
const result = await queryRunner.query(
|
||||
`UPDATE core."fieldMetadata"
|
||||
SET name = $1, type = $5, "morphId" = $3, settings = $4
|
||||
SET name = $1, type = $5, "morphId" = $3, settings = $4, "isActive" = true, label = $6
|
||||
WHERE id = $2`,
|
||||
[
|
||||
newFieldName,
|
||||
@@ -222,6 +225,7 @@ export class MigrateTaskTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
morphId,
|
||||
settings,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
newFieldLabel,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -229,7 +233,7 @@ export class MigrateTaskTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
|
||||
if (rowsUpdated > 0) {
|
||||
this.logger.log(
|
||||
`Updated fieldMetadata: ${fieldToMigrate.name} → ${newFieldName} (type: MORPH_RELATION)`,
|
||||
`Updated fieldMetadata: ${fieldToMigrate.name} → ${newFieldName} (label: ${newFieldLabel}, type: MORPH_RELATION)`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user