mirror of
https://github.com/twentyhq/twenty
synced 2026-04-21 13:37:22 +00:00
View field override (#18572)
## Context This PR introduces overrides for view fields which will be useful for page layout FIELDS widgets fields position/groups/visibility override + restore logic.
This commit is contained in:
parent
0b0ffcb8fa
commit
48172d60fd
50 changed files with 864 additions and 114 deletions
File diff suppressed because one or more lines are too long
|
|
@ -236,6 +236,7 @@ export const WithViewFieldGroups: Story = {
|
|||
name: 'Contact Info',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
isOverridden: false,
|
||||
viewId: FIELDS_VIEW_ID,
|
||||
viewFields: [
|
||||
{
|
||||
|
|
@ -245,6 +246,7 @@ export const WithViewFieldGroups: Story = {
|
|||
isVisible: true,
|
||||
size: 200,
|
||||
aggregateOperation: null,
|
||||
isOverridden: false,
|
||||
viewId: FIELDS_VIEW_ID,
|
||||
},
|
||||
{
|
||||
|
|
@ -254,6 +256,7 @@ export const WithViewFieldGroups: Story = {
|
|||
isVisible: true,
|
||||
size: 200,
|
||||
aggregateOperation: null,
|
||||
isOverridden: false,
|
||||
viewId: FIELDS_VIEW_ID,
|
||||
},
|
||||
{
|
||||
|
|
@ -263,6 +266,7 @@ export const WithViewFieldGroups: Story = {
|
|||
isVisible: true,
|
||||
size: 200,
|
||||
aggregateOperation: null,
|
||||
isOverridden: false,
|
||||
viewId: FIELDS_VIEW_ID,
|
||||
},
|
||||
],
|
||||
|
|
@ -272,6 +276,7 @@ export const WithViewFieldGroups: Story = {
|
|||
name: 'Business',
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
isOverridden: false,
|
||||
viewId: FIELDS_VIEW_ID,
|
||||
viewFields: [
|
||||
{
|
||||
|
|
@ -281,6 +286,7 @@ export const WithViewFieldGroups: Story = {
|
|||
isVisible: true,
|
||||
size: 200,
|
||||
aggregateOperation: null,
|
||||
isOverridden: false,
|
||||
viewId: FIELDS_VIEW_ID,
|
||||
},
|
||||
{
|
||||
|
|
@ -290,6 +296,7 @@ export const WithViewFieldGroups: Story = {
|
|||
isVisible: true,
|
||||
size: 200,
|
||||
aggregateOperation: null,
|
||||
isOverridden: false,
|
||||
viewId: FIELDS_VIEW_ID,
|
||||
},
|
||||
{
|
||||
|
|
@ -299,6 +306,7 @@ export const WithViewFieldGroups: Story = {
|
|||
isVisible: true,
|
||||
size: 200,
|
||||
aggregateOperation: null,
|
||||
isOverridden: false,
|
||||
viewId: FIELDS_VIEW_ID,
|
||||
},
|
||||
],
|
||||
|
|
@ -393,6 +401,7 @@ export const WithInlineViewFields: Story = {
|
|||
isVisible: true,
|
||||
size: 200,
|
||||
aggregateOperation: null,
|
||||
isOverridden: false,
|
||||
viewId: FIELDS_VIEW_ID,
|
||||
},
|
||||
{
|
||||
|
|
@ -402,6 +411,7 @@ export const WithInlineViewFields: Story = {
|
|||
isVisible: true,
|
||||
size: 200,
|
||||
aggregateOperation: null,
|
||||
isOverridden: false,
|
||||
viewId: FIELDS_VIEW_ID,
|
||||
},
|
||||
{
|
||||
|
|
@ -411,6 +421,7 @@ export const WithInlineViewFields: Story = {
|
|||
isVisible: true,
|
||||
size: 200,
|
||||
aggregateOperation: null,
|
||||
isOverridden: false,
|
||||
viewId: FIELDS_VIEW_ID,
|
||||
},
|
||||
],
|
||||
|
|
@ -501,6 +512,7 @@ export const Empty: Story = {
|
|||
name: 'Empty Group',
|
||||
position: 0,
|
||||
isVisible: false,
|
||||
isOverridden: false,
|
||||
viewId: FIELDS_VIEW_ID,
|
||||
viewFields: [],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export const VIEW_FIELD_FRAGMENT = gql`
|
|||
position
|
||||
size
|
||||
aggregateOperation
|
||||
isOverridden
|
||||
createdAt
|
||||
updatedAt
|
||||
deletedAt
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export const VIEW_FIELD_GROUP_FRAGMENT = gql`
|
|||
position
|
||||
isVisible
|
||||
viewId
|
||||
isOverridden
|
||||
createdAt
|
||||
updatedAt
|
||||
deletedAt
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export type ViewField = {
|
|||
isVisible: boolean;
|
||||
size: number;
|
||||
aggregateOperation?: AggregateOperations | null;
|
||||
isOverridden: boolean;
|
||||
definition:
|
||||
| ColumnDefinition<FieldMetadata>
|
||||
| RecordBoardFieldDefinition<FieldMetadata>;
|
||||
|
|
|
|||
|
|
@ -7,5 +7,6 @@ export type ViewFieldGroup = {
|
|||
position: number;
|
||||
isVisible: boolean;
|
||||
viewId: string;
|
||||
isOverridden: boolean;
|
||||
viewFields: ViewField[];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ describe('convertCoreViewFieldGroupToViewFieldGroup', () => {
|
|||
name: 'Group 1',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
isOverridden: false,
|
||||
viewId: 'view-1',
|
||||
viewFields: [
|
||||
{
|
||||
|
|
@ -16,6 +17,7 @@ describe('convertCoreViewFieldGroupToViewFieldGroup', () => {
|
|||
isVisible: true,
|
||||
size: 150,
|
||||
aggregateOperation: null,
|
||||
isOverridden: false,
|
||||
},
|
||||
{
|
||||
id: 'vf-2',
|
||||
|
|
@ -24,6 +26,7 @@ describe('convertCoreViewFieldGroupToViewFieldGroup', () => {
|
|||
isVisible: false,
|
||||
size: 200,
|
||||
aggregateOperation: null,
|
||||
isOverridden: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -37,6 +40,7 @@ describe('convertCoreViewFieldGroupToViewFieldGroup', () => {
|
|||
name: 'Group 1',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
isOverridden: false,
|
||||
viewId: 'view-1',
|
||||
viewFields: [
|
||||
{
|
||||
|
|
@ -47,6 +51,7 @@ describe('convertCoreViewFieldGroupToViewFieldGroup', () => {
|
|||
isVisible: true,
|
||||
size: 150,
|
||||
aggregateOperation: null,
|
||||
isOverridden: false,
|
||||
definition: undefined,
|
||||
},
|
||||
{
|
||||
|
|
@ -57,6 +62,7 @@ describe('convertCoreViewFieldGroupToViewFieldGroup', () => {
|
|||
isVisible: false,
|
||||
size: 200,
|
||||
aggregateOperation: null,
|
||||
isOverridden: false,
|
||||
definition: undefined,
|
||||
},
|
||||
],
|
||||
|
|
@ -69,6 +75,7 @@ describe('convertCoreViewFieldGroupToViewFieldGroup', () => {
|
|||
name: 'Empty Group',
|
||||
position: 1,
|
||||
isVisible: false,
|
||||
isOverridden: false,
|
||||
viewId: 'view-1',
|
||||
viewFields: [],
|
||||
};
|
||||
|
|
@ -82,6 +89,7 @@ describe('convertCoreViewFieldGroupToViewFieldGroup', () => {
|
|||
name: 'Empty Group',
|
||||
position: 1,
|
||||
isVisible: false,
|
||||
isOverridden: false,
|
||||
viewId: 'view-1',
|
||||
viewFields: [],
|
||||
});
|
||||
|
|
@ -93,6 +101,7 @@ describe('convertCoreViewFieldGroupToViewFieldGroup', () => {
|
|||
name: 'Test',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
isOverridden: false,
|
||||
viewId: 'view-2',
|
||||
viewFields: [],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ describe('mapViewFieldsToColumnDefinitions', () => {
|
|||
position: 1,
|
||||
size: 1,
|
||||
isVisible: false,
|
||||
isOverridden: false,
|
||||
definition: {
|
||||
fieldMetadataId: '1',
|
||||
label: 'label 1',
|
||||
|
|
@ -81,6 +82,7 @@ describe('mapViewFieldsToColumnDefinitions', () => {
|
|||
position: 2,
|
||||
size: 2,
|
||||
isVisible: false,
|
||||
isOverridden: false,
|
||||
definition: {
|
||||
fieldMetadataId: '2',
|
||||
label: 'label 2',
|
||||
|
|
@ -100,6 +102,7 @@ describe('mapViewFieldsToColumnDefinitions', () => {
|
|||
position: 3,
|
||||
size: 3,
|
||||
isVisible: true,
|
||||
isOverridden: false,
|
||||
definition: {
|
||||
fieldMetadataId: '3',
|
||||
label: 'label 3',
|
||||
|
|
@ -193,6 +196,7 @@ describe('mapColumnDefinitionsToViewFields', () => {
|
|||
fieldMetadataId: 1,
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
isOverridden: false,
|
||||
definition: columnDefinitions[0],
|
||||
size: undefined,
|
||||
},
|
||||
|
|
@ -203,6 +207,7 @@ describe('mapColumnDefinitionsToViewFields', () => {
|
|||
position: 2,
|
||||
size: 200,
|
||||
isVisible: false,
|
||||
isOverridden: false,
|
||||
definition: columnDefinitions[1],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
|
||||
type CoreViewFieldGroupInput = Pick<
|
||||
CoreViewFieldGroup,
|
||||
'id' | 'name' | 'position' | 'isVisible' | 'viewId'
|
||||
'id' | 'name' | 'position' | 'isVisible' | 'viewId' | 'isOverridden'
|
||||
> & {
|
||||
viewFields: Pick<
|
||||
CoreViewField,
|
||||
|
|
@ -17,6 +17,7 @@ type CoreViewFieldGroupInput = Pick<
|
|||
| 'isVisible'
|
||||
| 'size'
|
||||
| 'aggregateOperation'
|
||||
| 'isOverridden'
|
||||
>[];
|
||||
};
|
||||
|
||||
|
|
@ -29,6 +30,7 @@ export const convertCoreViewFieldGroupToViewFieldGroup = (
|
|||
name: coreViewFieldGroup.name,
|
||||
position: coreViewFieldGroup.position,
|
||||
isVisible: coreViewFieldGroup.isVisible,
|
||||
isOverridden: coreViewFieldGroup.isOverridden ?? false,
|
||||
viewId: coreViewFieldGroup.viewId,
|
||||
viewFields: coreViewFieldGroup.viewFields.map(
|
||||
convertCoreViewFieldToViewField,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const convertCoreViewFieldToViewField = (
|
|||
| 'isVisible'
|
||||
| 'size'
|
||||
| 'aggregateOperation'
|
||||
| 'isOverridden'
|
||||
>,
|
||||
): ViewField => {
|
||||
const viewField: ViewField = {
|
||||
|
|
@ -22,6 +23,7 @@ export const convertCoreViewFieldToViewField = (
|
|||
isVisible: coreViewField.isVisible,
|
||||
size: coreViewField.size,
|
||||
aggregateOperation: coreViewField.aggregateOperation ?? null,
|
||||
isOverridden: coreViewField.isOverridden ?? false,
|
||||
// TODO: remove this once we have refactored the view field definition
|
||||
definition: undefined as unknown as ColumnDefinition<FieldMetadata>,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const mapBoardFieldDefinitionsToViewFields = (
|
|||
size: 0,
|
||||
position: fieldDefinition.position,
|
||||
isVisible: fieldDefinition.isVisible ?? true,
|
||||
isOverridden: false,
|
||||
definition: fieldDefinition,
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const mapColumnDefinitionsToViewFields = (
|
|||
position: columnDefinition.position,
|
||||
size: columnDefinition.size,
|
||||
isVisible: columnDefinition.isVisible ?? true,
|
||||
isOverridden: false,
|
||||
definition: columnDefinition,
|
||||
}));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export const mapRecordFieldToViewField = (recordField: RecordField) => {
|
|||
position: recordField.position,
|
||||
size: recordField.size,
|
||||
aggregateOperation: recordField.aggregateOperation,
|
||||
isOverridden: false,
|
||||
__typename: 'ViewField',
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -582,6 +582,7 @@ type CoreViewField {
|
|||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
deletedAt: DateTime
|
||||
isOverridden: Boolean!
|
||||
}
|
||||
|
||||
enum AggregateOperations {
|
||||
|
|
@ -690,6 +691,7 @@ type CoreViewFieldGroup {
|
|||
updatedAt: DateTime!
|
||||
deletedAt: DateTime
|
||||
viewFields: [CoreViewField!]!
|
||||
isOverridden: Boolean!
|
||||
}
|
||||
|
||||
type CoreView {
|
||||
|
|
|
|||
|
|
@ -416,6 +416,7 @@ export interface CoreViewField {
|
|||
createdAt: Scalars['DateTime']
|
||||
updatedAt: Scalars['DateTime']
|
||||
deletedAt?: Scalars['DateTime']
|
||||
isOverridden: Scalars['Boolean']
|
||||
__typename: 'CoreViewField'
|
||||
}
|
||||
|
||||
|
|
@ -492,6 +493,7 @@ export interface CoreViewFieldGroup {
|
|||
updatedAt: Scalars['DateTime']
|
||||
deletedAt?: Scalars['DateTime']
|
||||
viewFields: CoreViewField[]
|
||||
isOverridden: Scalars['Boolean']
|
||||
__typename: 'CoreViewFieldGroup'
|
||||
}
|
||||
|
||||
|
|
@ -3295,6 +3297,7 @@ export interface CoreViewFieldGenqlSelection{
|
|||
createdAt?: boolean | number
|
||||
updatedAt?: boolean | number
|
||||
deletedAt?: boolean | number
|
||||
isOverridden?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
|
@ -3368,6 +3371,7 @@ export interface CoreViewFieldGroupGenqlSelection{
|
|||
updatedAt?: boolean | number
|
||||
deletedAt?: boolean | number
|
||||
viewFields?: CoreViewFieldGenqlSelection
|
||||
isOverridden?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1267,6 +1267,9 @@ export default {
|
|||
"deletedAt": [
|
||||
4
|
||||
],
|
||||
"isOverridden": [
|
||||
6
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
|
|
@ -1440,6 +1443,9 @@ export default {
|
|||
"viewFields": [
|
||||
50
|
||||
],
|
||||
"isOverridden": [
|
||||
6
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddOverridesToViewFieldAndViewFieldGroup1773246310000
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddOverridesToViewFieldAndViewFieldGroup1773246310000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewField" ADD "overrides" jsonb`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewFieldGroup" ADD "overrides" jsonb`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewFieldGroup" DROP COLUMN "overrides"`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewField" DROP COLUMN "overrides"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ export const fromViewFieldGroupManifestToUniversalFlatViewFieldGroup = ({
|
|||
name: viewFieldGroupManifest.name ?? '',
|
||||
position: viewFieldGroupManifest.position,
|
||||
isVisible: viewFieldGroupManifest.isVisible ?? true,
|
||||
overrides: null,
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export const fromViewFieldManifestToUniversalFlatViewField = ({
|
|||
size: viewFieldManifest.size ?? 0,
|
||||
position: viewFieldManifest.position,
|
||||
aggregateOperation: viewFieldManifest.aggregateOperation ?? null,
|
||||
universalOverrides: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
|
|
|
|||
|
|
@ -630,6 +630,36 @@ export class DataloaderService {
|
|||
},
|
||||
);
|
||||
|
||||
const viewFieldsByResolvedGroupId = new Map<
|
||||
string,
|
||||
ReturnType<typeof fromFlatViewFieldToViewFieldDto>[]
|
||||
>();
|
||||
|
||||
for (const flatViewField of Object.values(
|
||||
flatViewFieldMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (!isDefined(flatViewField) || flatViewField.deletedAt !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const resolvedGroupId =
|
||||
flatViewField.overrides?.viewFieldGroupId !== undefined
|
||||
? flatViewField.overrides.viewFieldGroupId
|
||||
: flatViewField.viewFieldGroupId;
|
||||
|
||||
if (!isDefined(resolvedGroupId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!viewFieldsByResolvedGroupId.has(resolvedGroupId)) {
|
||||
viewFieldsByResolvedGroupId.set(resolvedGroupId, []);
|
||||
}
|
||||
|
||||
viewFieldsByResolvedGroupId
|
||||
.get(resolvedGroupId)!
|
||||
.push(fromFlatViewFieldToViewFieldDto(flatViewField));
|
||||
}
|
||||
|
||||
return dataLoaderParams.map(({ viewFieldGroupId }) => {
|
||||
const flatViewFieldGroup = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: viewFieldGroupId,
|
||||
|
|
@ -640,12 +670,7 @@ export class DataloaderService {
|
|||
return [];
|
||||
}
|
||||
|
||||
return findManyFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityIds: flatViewFieldGroup.viewFieldIds,
|
||||
flatEntityMaps: flatViewFieldMaps,
|
||||
})
|
||||
.filter((flatViewField) => flatViewField.deletedAt === null)
|
||||
.map(fromFlatViewFieldToViewFieldDto);
|
||||
return viewFieldsByResolvedGroupId.get(viewFieldGroupId) ?? [];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -265,8 +265,11 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
|
|||
"aggregateOperation",
|
||||
"viewFieldGroupUniversalIdentifier",
|
||||
"deletedAt",
|
||||
"universalOverrides",
|
||||
],
|
||||
"propertiesToStringify": [
|
||||
"universalOverrides",
|
||||
],
|
||||
"propertiesToStringify": [],
|
||||
},
|
||||
"viewFieldGroup": {
|
||||
"propertiesToCompare": [
|
||||
|
|
@ -274,8 +277,11 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
|
|||
"position",
|
||||
"isVisible",
|
||||
"deletedAt",
|
||||
"overrides",
|
||||
],
|
||||
"propertiesToStringify": [
|
||||
"overrides",
|
||||
],
|
||||
"propertiesToStringify": [],
|
||||
},
|
||||
"viewFilter": {
|
||||
"propertiesToCompare": [
|
||||
|
|
|
|||
|
|
@ -350,16 +350,23 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
|||
},
|
||||
},
|
||||
viewFieldGroup: {
|
||||
name: { toStringify: false, universalProperty: undefined, toCompare: true },
|
||||
name: {
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
toCompare: true,
|
||||
isOverridable: true,
|
||||
},
|
||||
position: {
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
toCompare: true,
|
||||
isOverridable: true,
|
||||
},
|
||||
isVisible: {
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
toCompare: true,
|
||||
isOverridable: true,
|
||||
},
|
||||
deletedAt: {
|
||||
toStringify: false,
|
||||
|
|
@ -381,28 +388,42 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
|||
universalProperty: 'viewUniversalIdentifier',
|
||||
toStringify: false,
|
||||
},
|
||||
overrides: {
|
||||
toCompare: true,
|
||||
toStringify: true,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
},
|
||||
viewField: {
|
||||
isVisible: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
isOverridable: true,
|
||||
},
|
||||
size: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
isOverridable: true,
|
||||
},
|
||||
size: { toCompare: true, toStringify: false, universalProperty: undefined },
|
||||
position: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
isOverridable: true,
|
||||
},
|
||||
aggregateOperation: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
isOverridable: true,
|
||||
},
|
||||
viewFieldGroupId: {
|
||||
toStringify: false,
|
||||
universalProperty: 'viewFieldGroupUniversalIdentifier',
|
||||
toCompare: true,
|
||||
isOverridable: true,
|
||||
},
|
||||
deletedAt: {
|
||||
toCompare: true,
|
||||
|
|
@ -429,6 +450,11 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
|||
toStringify: false,
|
||||
universalProperty: 'viewUniversalIdentifier',
|
||||
},
|
||||
overrides: {
|
||||
toCompare: true,
|
||||
toStringify: true,
|
||||
universalProperty: 'universalOverrides',
|
||||
},
|
||||
},
|
||||
viewGroup: {
|
||||
isVisible: {
|
||||
|
|
|
|||
|
|
@ -82,6 +82,8 @@ export const recomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdate = ({
|
|||
aggregateOperation: null,
|
||||
viewFieldGroupId: null,
|
||||
viewFieldGroupUniversalIdentifier: null,
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
applicationId: existingFlatObjectMetadata.applicationId,
|
||||
applicationUniversalIdentifier:
|
||||
existingFlatObjectMetadata.applicationUniversalIdentifier,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ export const fromCreateViewFieldGroupInputToFlatViewFieldGroupToCreate = ({
|
|||
universalIdentifier: createViewFieldGroupInput.universalIdentifier ?? v4(),
|
||||
position: createViewFieldGroupInput.position ?? 0,
|
||||
isVisible: createViewFieldGroupInput.isVisible ?? true,
|
||||
overrides: null,
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ import {
|
|||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import { FLAT_VIEW_FIELD_GROUP_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-field-group/constants/flat-view-field-group-editable-properties.constant';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { FLAT_VIEW_FIELD_GROUP_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-field-group/constants/flat-view-field-group-editable-properties.constant';
|
||||
import { type FlatViewFieldGroupMaps } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group-maps.type';
|
||||
import { isCallerOverridingEntity } from 'src/engine/metadata-modules/utils/is-caller-overriding-entity.util';
|
||||
import { sanitizeOverridableEntityInput } from 'src/engine/metadata-modules/utils/sanitize-overridable-entity-input.util';
|
||||
import { type UpdateViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/update-view-field-group.input';
|
||||
import {
|
||||
ViewFieldGroupException,
|
||||
|
|
@ -20,9 +22,13 @@ export const fromUpdateViewFieldGroupInputToFlatViewFieldGroupToUpdateOrThrow =
|
|||
({
|
||||
updateViewFieldGroupInput: rawUpdateViewFieldGroupInput,
|
||||
flatViewFieldGroupMaps,
|
||||
callerApplicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
}: {
|
||||
updateViewFieldGroupInput: UpdateViewFieldGroupInput;
|
||||
flatViewFieldGroupMaps: FlatViewFieldGroupMaps;
|
||||
callerApplicationUniversalIdentifier: string;
|
||||
workspaceCustomApplicationUniversalIdentifier: string;
|
||||
}): UniversalFlatViewFieldGroup => {
|
||||
const { id: viewFieldGroupToUpdateId } =
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
|
||||
|
|
@ -43,14 +49,32 @@ export const fromUpdateViewFieldGroupInputToFlatViewFieldGroupToUpdateOrThrow =
|
|||
);
|
||||
}
|
||||
|
||||
const updatedEditableProperties = extractAndSanitizeObjectStringFields(
|
||||
const editableProperties = extractAndSanitizeObjectStringFields(
|
||||
rawUpdateViewFieldGroupInput.update,
|
||||
FLAT_VIEW_FIELD_GROUP_EDITABLE_PROPERTIES,
|
||||
);
|
||||
|
||||
return mergeUpdateInExistingRecord({
|
||||
existing: existingFlatViewFieldGroupToUpdate,
|
||||
properties: FLAT_VIEW_FIELD_GROUP_EDITABLE_PROPERTIES,
|
||||
update: updatedEditableProperties,
|
||||
const shouldOverride = isCallerOverridingEntity({
|
||||
callerApplicationUniversalIdentifier,
|
||||
entityApplicationUniversalIdentifier:
|
||||
existingFlatViewFieldGroupToUpdate.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties } =
|
||||
sanitizeOverridableEntityInput({
|
||||
metadataName: 'viewFieldGroup',
|
||||
existingFlatEntity: existingFlatViewFieldGroupToUpdate,
|
||||
updatedEditableProperties: editableProperties,
|
||||
shouldOverride,
|
||||
});
|
||||
|
||||
return {
|
||||
...mergeUpdateInExistingRecord({
|
||||
existing: existingFlatViewFieldGroupToUpdate,
|
||||
properties: [...FLAT_VIEW_FIELD_GROUP_EDITABLE_PROPERTIES],
|
||||
update: updatedEditableProperties,
|
||||
}),
|
||||
overrides,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@ export const computeFlatViewFieldsFromFieldsWidgets = ({
|
|||
size: DEFAULT_VIEW_FIELD_SIZE,
|
||||
position,
|
||||
aggregateOperation: null,
|
||||
universalOverrides: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ export const fromCreateViewFieldInputToFlatViewFieldToCreate = ({
|
|||
size: createViewFieldInput.size ?? DEFAULT_VIEW_FIELD_SIZE,
|
||||
position: createViewFieldInput.position ?? 0,
|
||||
aggregateOperation: createViewFieldInput.aggregateOperation ?? null,
|
||||
universalOverrides: null,
|
||||
viewFieldGroupUniversalIdentifier,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/
|
|||
import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util';
|
||||
import { FLAT_VIEW_FIELD_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-field/constants/flat-view-field-editable-properties.constant';
|
||||
import { type FlatViewFieldMaps } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field-maps.type';
|
||||
import { fromViewFieldOverridesToUniversalOverrides } from 'src/engine/metadata-modules/flat-view-field/utils/from-view-field-overrides-to-universal-overrides.util';
|
||||
import { isCallerOverridingEntity } from 'src/engine/metadata-modules/utils/is-caller-overriding-entity.util';
|
||||
import { sanitizeOverridableEntityInput } from 'src/engine/metadata-modules/utils/sanitize-overridable-entity-input.util';
|
||||
import { type UpdateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/update-view-field.input';
|
||||
import {
|
||||
ViewFieldException,
|
||||
|
|
@ -22,9 +25,13 @@ export const fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow = ({
|
|||
updateViewFieldInput: rawUpdateViewFieldInput,
|
||||
flatViewFieldMaps,
|
||||
flatViewFieldGroupMaps,
|
||||
callerApplicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
}: {
|
||||
updateViewFieldInput: UpdateViewFieldInput;
|
||||
flatViewFieldMaps: FlatViewFieldMaps;
|
||||
callerApplicationUniversalIdentifier: string;
|
||||
workspaceCustomApplicationUniversalIdentifier: string;
|
||||
} & Pick<
|
||||
AllFlatEntityMaps,
|
||||
'flatViewFieldGroupMaps'
|
||||
|
|
@ -47,23 +54,43 @@ export const fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow = ({
|
|||
);
|
||||
}
|
||||
|
||||
const updatedEditableFieldProperties = extractAndSanitizeObjectStringFields(
|
||||
const editableProperties = extractAndSanitizeObjectStringFields(
|
||||
rawUpdateViewFieldInput.update,
|
||||
FLAT_VIEW_FIELD_EDITABLE_PROPERTIES,
|
||||
);
|
||||
|
||||
const flatViewFieldToUpdate = mergeUpdateInExistingRecord({
|
||||
existing: existingFlatViewFieldToUpdate,
|
||||
properties: FLAT_VIEW_FIELD_EDITABLE_PROPERTIES,
|
||||
update: updatedEditableFieldProperties,
|
||||
const shouldOverride = isCallerOverridingEntity({
|
||||
callerApplicationUniversalIdentifier,
|
||||
entityApplicationUniversalIdentifier:
|
||||
existingFlatViewFieldToUpdate.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
if (updatedEditableFieldProperties.viewFieldGroupId !== undefined) {
|
||||
const { overrides, updatedEditableProperties } =
|
||||
sanitizeOverridableEntityInput({
|
||||
metadataName: 'viewField',
|
||||
existingFlatEntity: existingFlatViewFieldToUpdate,
|
||||
updatedEditableProperties: editableProperties,
|
||||
shouldOverride,
|
||||
});
|
||||
|
||||
const mergedRecord = mergeUpdateInExistingRecord({
|
||||
existing: existingFlatViewFieldToUpdate,
|
||||
properties: [...FLAT_VIEW_FIELD_EDITABLE_PROPERTIES],
|
||||
update: updatedEditableProperties,
|
||||
});
|
||||
|
||||
const flatViewFieldToUpdate = {
|
||||
...mergedRecord,
|
||||
overrides,
|
||||
} as UniversalFlatViewField;
|
||||
|
||||
if (updatedEditableProperties.viewFieldGroupId !== undefined) {
|
||||
const { viewFieldGroupUniversalIdentifier } =
|
||||
resolveEntityRelationUniversalIdentifiers({
|
||||
metadataName: 'viewField',
|
||||
foreignKeyValues: {
|
||||
viewFieldGroupId: flatViewFieldToUpdate.viewFieldGroupId,
|
||||
viewFieldGroupId: mergedRecord.viewFieldGroupId,
|
||||
},
|
||||
flatEntityMaps: { flatViewFieldGroupMaps },
|
||||
});
|
||||
|
|
@ -72,5 +99,16 @@ export const fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow = ({
|
|||
viewFieldGroupUniversalIdentifier;
|
||||
}
|
||||
|
||||
if (isDefined(overrides)) {
|
||||
flatViewFieldToUpdate.universalOverrides =
|
||||
fromViewFieldOverridesToUniversalOverrides({
|
||||
overrides,
|
||||
viewFieldGroupUniversalIdentifierById:
|
||||
flatViewFieldGroupMaps.universalIdentifierById,
|
||||
});
|
||||
} else {
|
||||
flatViewFieldToUpdate.universalOverrides = null;
|
||||
}
|
||||
|
||||
return flatViewFieldToUpdate;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util';
|
||||
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
|
||||
import { fromViewFieldOverridesToUniversalOverrides } from 'src/engine/metadata-modules/flat-view-field/utils/from-view-field-overrides-to-universal-overrides.util';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromViewFieldEntityToFlatViewField = ({
|
||||
|
|
@ -69,6 +70,18 @@ export const fromViewFieldEntityToFlatViewField = ({
|
|||
}
|
||||
}
|
||||
|
||||
const viewFieldGroupUniversalIdentifierById = Object.fromEntries(
|
||||
viewFieldGroupIdToUniversalIdentifierMap.entries(),
|
||||
);
|
||||
|
||||
const universalOverrides = isDefined(viewFieldEntity.overrides)
|
||||
? fromViewFieldOverridesToUniversalOverrides({
|
||||
overrides: viewFieldEntity.overrides,
|
||||
viewFieldGroupUniversalIdentifierById,
|
||||
shouldThrowOnMissingIdentifier: false,
|
||||
})
|
||||
: null;
|
||||
|
||||
return {
|
||||
...viewFieldEntityWithoutRelations,
|
||||
createdAt: viewFieldEntity.createdAt.toISOString(),
|
||||
|
|
@ -79,5 +92,6 @@ export const fromViewFieldEntityToFlatViewField = ({
|
|||
fieldMetadataUniversalIdentifier,
|
||||
viewUniversalIdentifier,
|
||||
viewFieldGroupUniversalIdentifier,
|
||||
universalOverrides,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
import { type FormatRecordSerializedRelationProperties } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { type ViewFieldOverrides } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
|
||||
type UniversalViewFieldOverrides =
|
||||
FormatRecordSerializedRelationProperties<ViewFieldOverrides>;
|
||||
|
||||
export const fromViewFieldOverridesToUniversalOverrides = ({
|
||||
overrides,
|
||||
viewFieldGroupUniversalIdentifierById,
|
||||
shouldThrowOnMissingIdentifier = true,
|
||||
}: {
|
||||
overrides: ViewFieldOverrides;
|
||||
viewFieldGroupUniversalIdentifierById: Partial<Record<string, string>>;
|
||||
shouldThrowOnMissingIdentifier?: boolean;
|
||||
}): UniversalViewFieldOverrides => {
|
||||
const { viewFieldGroupId, ...scalarOverrides } = overrides;
|
||||
|
||||
if (!isDefined(viewFieldGroupId)) {
|
||||
return {
|
||||
...scalarOverrides,
|
||||
...(viewFieldGroupId === null
|
||||
? { viewFieldGroupUniversalIdentifier: null }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
const viewFieldGroupUniversalIdentifier =
|
||||
viewFieldGroupUniversalIdentifierById[viewFieldGroupId];
|
||||
|
||||
if (!isDefined(viewFieldGroupUniversalIdentifier)) {
|
||||
if (shouldThrowOnMissingIdentifier) {
|
||||
throw new FlatEntityMapsException(
|
||||
`ViewFieldGroup universal identifier not found for id: ${viewFieldGroupId}`,
|
||||
FlatEntityMapsExceptionCode.RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...scalarOverrides,
|
||||
viewFieldGroupUniversalIdentifier: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...scalarOverrides,
|
||||
viewFieldGroupUniversalIdentifier,
|
||||
};
|
||||
};
|
||||
|
|
@ -38,6 +38,7 @@ export const computeFlatViewFieldsToCreate = ({
|
|||
size: DEFAULT_VIEW_FIELD_SIZE,
|
||||
position: index,
|
||||
aggregateOperation: null,
|
||||
universalOverrides: null,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
} from '@nestjs/graphql';
|
||||
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
|
|
@ -70,6 +71,11 @@ export class PageLayoutTabResolver {
|
|||
return resolveOverridableEntityProperty(tab, 'icon');
|
||||
}
|
||||
|
||||
@ResolveField(() => Boolean)
|
||||
isOverridden(@Parent() tab: PageLayoutTabDTO): boolean {
|
||||
return isDefined(tab.overrides) && Object.keys(tab.overrides).length > 0;
|
||||
}
|
||||
|
||||
@Query(() => [PageLayoutTabDTO])
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getPageLayoutTabs(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Args, Mutation, Parent, Query, ResolveField } from '@nestjs/graphql';
|
|||
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
|
@ -116,4 +117,11 @@ export class PageLayoutWidgetResolver {
|
|||
configuration(@Parent() widget: PageLayoutWidgetDTO) {
|
||||
return widget.configuration;
|
||||
}
|
||||
|
||||
@ResolveField(() => Boolean)
|
||||
isOverridden(@Parent() widget: PageLayoutWidgetDTO): boolean {
|
||||
return (
|
||||
isDefined(widget.overrides) && Object.keys(widget.overrides).length > 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
import { Field, HideField, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { type ViewFieldGroupOverrides } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
|
||||
import { ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto';
|
||||
|
||||
@ObjectType('CoreViewFieldGroup')
|
||||
|
|
@ -36,4 +37,10 @@ export class ViewFieldGroupDTO {
|
|||
|
||||
@Field(() => [ViewFieldDTO])
|
||||
viewFields?: ViewFieldDTO[];
|
||||
|
||||
@Field(() => Boolean, { nullable: false })
|
||||
isOverridden: boolean;
|
||||
|
||||
@HideField()
|
||||
overrides?: ViewFieldGroupOverrides | null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,13 +14,19 @@ import {
|
|||
|
||||
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
|
||||
import { OverridableEntity } from 'src/engine/workspace-manager/types/overridable-entity';
|
||||
|
||||
export type ViewFieldGroupOverrides = {
|
||||
name?: string;
|
||||
position?: number;
|
||||
isVisible?: boolean;
|
||||
};
|
||||
|
||||
@Entity({ name: 'viewFieldGroup', schema: 'core' })
|
||||
@Index('IDX_VIEW_FIELD_GROUP_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId'])
|
||||
@Index('IDX_VIEW_FIELD_GROUP_VIEW_ID', ['viewId'])
|
||||
export class ViewFieldGroupEntity
|
||||
extends SyncableEntity
|
||||
extends OverridableEntity<ViewFieldGroupOverrides>
|
||||
implements Required<ViewFieldGroupEntity>
|
||||
{
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { UseFilters, UseGuards } from '@nestjs/common';
|
|||
import {
|
||||
Args,
|
||||
Context,
|
||||
Float,
|
||||
Mutation,
|
||||
Parent,
|
||||
Query,
|
||||
|
|
@ -9,6 +10,7 @@ import {
|
|||
} from '@nestjs/graphql';
|
||||
|
||||
import { isArray } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
|
@ -16,6 +18,7 @@ import { type IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
|
|||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { resolveOverridableEntityProperty } from 'src/engine/metadata-modules/utils/resolve-overridable-entity-property.util';
|
||||
import { CreateViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/create-view-field-group.input';
|
||||
import { DeleteViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/delete-view-field-group.input';
|
||||
import { DestroyViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/destroy-view-field-group.input';
|
||||
|
|
@ -39,6 +42,29 @@ export class ViewFieldGroupResolver {
|
|||
private readonly fieldsWidgetUpsertService: FieldsWidgetUpsertService,
|
||||
) {}
|
||||
|
||||
@ResolveField(() => String)
|
||||
name(@Parent() viewFieldGroup: ViewFieldGroupDTO): string {
|
||||
return resolveOverridableEntityProperty(viewFieldGroup, 'name');
|
||||
}
|
||||
|
||||
@ResolveField(() => Float)
|
||||
position(@Parent() viewFieldGroup: ViewFieldGroupDTO): number {
|
||||
return resolveOverridableEntityProperty(viewFieldGroup, 'position');
|
||||
}
|
||||
|
||||
@ResolveField(() => Boolean)
|
||||
isVisible(@Parent() viewFieldGroup: ViewFieldGroupDTO): boolean {
|
||||
return resolveOverridableEntityProperty(viewFieldGroup, 'isVisible');
|
||||
}
|
||||
|
||||
@ResolveField(() => Boolean)
|
||||
isOverridden(@Parent() viewFieldGroup: ViewFieldGroupDTO): boolean {
|
||||
return (
|
||||
isDefined(viewFieldGroup.overrides) &&
|
||||
Object.keys(viewFieldGroup.overrides).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
@Query(() => [ViewFieldGroupDTO])
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getCoreViewFieldGroups(
|
||||
|
|
|
|||
|
|
@ -14,8 +14,11 @@ import { isFlatPageLayoutWidgetConfigurationOfType } from 'src/engine/metadata-m
|
|||
import { type FlatViewFieldGroupMaps } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group-maps.type';
|
||||
import { type FlatViewFieldGroup } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group.type';
|
||||
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
|
||||
import { fromViewFieldOverridesToUniversalOverrides } from 'src/engine/metadata-modules/flat-view-field/utils/from-view-field-overrides-to-universal-overrides.util';
|
||||
import { type FlatViewMaps } from 'src/engine/metadata-modules/flat-view/types/flat-view-maps.type';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { isCallerOverridingEntity } from 'src/engine/metadata-modules/utils/is-caller-overriding-entity.util';
|
||||
import { sanitizeOverridableEntityInput } from 'src/engine/metadata-modules/utils/sanitize-overridable-entity-input.util';
|
||||
import { type UpsertFieldsWidgetFieldInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/upsert-fields-widget-field.input';
|
||||
import { UpsertFieldsWidgetGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/upsert-fields-widget-group.input';
|
||||
import { UpsertFieldsWidgetInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/upsert-fields-widget.input';
|
||||
|
|
@ -204,11 +207,30 @@ export class FieldsWidgetUpsertService {
|
|||
}),
|
||||
);
|
||||
} else if (this.hasGroupChanged(existingGroup, inputGroup)) {
|
||||
const shouldOverride = isCallerOverridingEntity({
|
||||
callerApplicationUniversalIdentifier: applicationUniversalIdentifier,
|
||||
entityApplicationUniversalIdentifier:
|
||||
existingGroup.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties: sanitizedGroupProps } =
|
||||
sanitizeOverridableEntityInput({
|
||||
metadataName: 'viewFieldGroup',
|
||||
existingFlatEntity: existingGroup,
|
||||
updatedEditableProperties: {
|
||||
name: inputGroup.name,
|
||||
position: inputGroup.position,
|
||||
isVisible: inputGroup.isVisible,
|
||||
},
|
||||
shouldOverride,
|
||||
});
|
||||
|
||||
groupsToUpdate.push({
|
||||
...existingGroup,
|
||||
name: inputGroup.name,
|
||||
position: inputGroup.position,
|
||||
isVisible: inputGroup.isVisible,
|
||||
...sanitizedGroupProps,
|
||||
overrides,
|
||||
updatedAt: now,
|
||||
});
|
||||
}
|
||||
|
|
@ -251,10 +273,22 @@ export class FieldsWidgetUpsertService {
|
|||
|
||||
const newViewFieldGroupId = inputGroup.id;
|
||||
|
||||
const resolvedIsVisible = isDefined(existingField.overrides?.isVisible)
|
||||
? existingField.overrides.isVisible
|
||||
: existingField.isVisible;
|
||||
const resolvedPosition = isDefined(existingField.overrides?.position)
|
||||
? existingField.overrides.position
|
||||
: existingField.position;
|
||||
// null is a valid override value (meaning "ungrouped"), so use !== undefined
|
||||
const resolvedViewFieldGroupId =
|
||||
existingField.overrides?.viewFieldGroupId !== undefined
|
||||
? existingField.overrides.viewFieldGroupId
|
||||
: existingField.viewFieldGroupId;
|
||||
|
||||
const hasChanged =
|
||||
existingField.isVisible !== inputField.isVisible ||
|
||||
existingField.position !== inputField.position ||
|
||||
existingField.viewFieldGroupId !== newViewFieldGroupId;
|
||||
resolvedIsVisible !== inputField.isVisible ||
|
||||
resolvedPosition !== inputField.position ||
|
||||
resolvedViewFieldGroupId !== newViewFieldGroupId;
|
||||
|
||||
if (!hasChanged) {
|
||||
return [];
|
||||
|
|
@ -271,18 +305,72 @@ export class FieldsWidgetUpsertService {
|
|||
},
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
...existingField,
|
||||
isVisible: inputField.isVisible,
|
||||
position: inputField.position,
|
||||
viewFieldGroupId: newViewFieldGroupId,
|
||||
viewFieldGroupUniversalIdentifier,
|
||||
updatedAt: now,
|
||||
},
|
||||
];
|
||||
const shouldOverride = isCallerOverridingEntity({
|
||||
callerApplicationUniversalIdentifier: applicationUniversalIdentifier,
|
||||
entityApplicationUniversalIdentifier:
|
||||
existingField.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties: sanitizedFieldProps } =
|
||||
sanitizeOverridableEntityInput({
|
||||
metadataName: 'viewField',
|
||||
existingFlatEntity: existingField,
|
||||
updatedEditableProperties: {
|
||||
isVisible: inputField.isVisible,
|
||||
position: inputField.position,
|
||||
viewFieldGroupId: newViewFieldGroupId,
|
||||
},
|
||||
shouldOverride,
|
||||
});
|
||||
|
||||
const updatedField: FlatViewField = {
|
||||
...existingField,
|
||||
...sanitizedFieldProps,
|
||||
overrides,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
if (sanitizedFieldProps.viewFieldGroupId !== undefined) {
|
||||
const resolved = resolveEntityRelationUniversalIdentifiers({
|
||||
metadataName: 'viewField',
|
||||
foreignKeyValues: {
|
||||
viewFieldGroupId: updatedField.viewFieldGroupId,
|
||||
},
|
||||
flatEntityMaps: {
|
||||
flatViewFieldGroupMaps: optimisticFlatViewFieldGroupMaps,
|
||||
},
|
||||
});
|
||||
|
||||
updatedField.viewFieldGroupUniversalIdentifier =
|
||||
resolved.viewFieldGroupUniversalIdentifier;
|
||||
}
|
||||
|
||||
if (isDefined(overrides)) {
|
||||
updatedField.universalOverrides =
|
||||
fromViewFieldOverridesToUniversalOverrides({
|
||||
overrides,
|
||||
viewFieldGroupUniversalIdentifierById:
|
||||
optimisticFlatViewFieldGroupMaps.universalIdentifierById,
|
||||
});
|
||||
} else {
|
||||
updatedField.universalOverrides = null;
|
||||
}
|
||||
|
||||
return [updatedField];
|
||||
});
|
||||
|
||||
const fieldsWithStaleGroupOverrides =
|
||||
this.buildFieldUpdatesForStaleGroupOverrides({
|
||||
existingViewFields,
|
||||
groupsToDelete,
|
||||
alreadyUpdatedFieldIds: new Set(
|
||||
viewFieldsToUpdate.map((field) => field.id),
|
||||
),
|
||||
now,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
|
|
@ -295,7 +383,10 @@ export class FieldsWidgetUpsertService {
|
|||
viewField: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: viewFieldsToUpdate,
|
||||
flatEntityToUpdate: [
|
||||
...viewFieldsToUpdate,
|
||||
...fieldsWithStaleGroupOverrides,
|
||||
],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
|
|
@ -338,27 +429,80 @@ export class FieldsWidgetUpsertService {
|
|||
return [];
|
||||
}
|
||||
|
||||
const resolvedIsVisible = isDefined(existingField.overrides?.isVisible)
|
||||
? existingField.overrides.isVisible
|
||||
: existingField.isVisible;
|
||||
const resolvedPosition = isDefined(existingField.overrides?.position)
|
||||
? existingField.overrides.position
|
||||
: existingField.position;
|
||||
const resolvedViewFieldGroupId =
|
||||
existingField.overrides?.viewFieldGroupId !== undefined
|
||||
? existingField.overrides.viewFieldGroupId
|
||||
: existingField.viewFieldGroupId;
|
||||
|
||||
const hasChanged =
|
||||
existingField.isVisible !== inputField.isVisible ||
|
||||
existingField.position !== inputField.position ||
|
||||
existingField.viewFieldGroupId !== null;
|
||||
resolvedIsVisible !== inputField.isVisible ||
|
||||
resolvedPosition !== inputField.position ||
|
||||
resolvedViewFieldGroupId !== null;
|
||||
|
||||
if (!hasChanged) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
...existingField,
|
||||
isVisible: inputField.isVisible,
|
||||
position: inputField.position,
|
||||
viewFieldGroupId: null,
|
||||
viewFieldGroupUniversalIdentifier: null,
|
||||
updatedAt: now,
|
||||
},
|
||||
];
|
||||
const shouldOverride = isCallerOverridingEntity({
|
||||
callerApplicationUniversalIdentifier: applicationUniversalIdentifier,
|
||||
entityApplicationUniversalIdentifier:
|
||||
existingField.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties: sanitizedFieldProps } =
|
||||
sanitizeOverridableEntityInput({
|
||||
metadataName: 'viewField',
|
||||
existingFlatEntity: existingField,
|
||||
updatedEditableProperties: {
|
||||
isVisible: inputField.isVisible,
|
||||
position: inputField.position,
|
||||
viewFieldGroupId: null as string | null,
|
||||
},
|
||||
shouldOverride,
|
||||
});
|
||||
|
||||
const updatedField: FlatViewField = {
|
||||
...existingField,
|
||||
...sanitizedFieldProps,
|
||||
overrides,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
if (sanitizedFieldProps.viewFieldGroupId !== undefined) {
|
||||
updatedField.viewFieldGroupUniversalIdentifier = null;
|
||||
}
|
||||
|
||||
if (isDefined(overrides)) {
|
||||
updatedField.universalOverrides =
|
||||
fromViewFieldOverridesToUniversalOverrides({
|
||||
overrides,
|
||||
viewFieldGroupUniversalIdentifierById: {},
|
||||
});
|
||||
} else {
|
||||
updatedField.universalOverrides = null;
|
||||
}
|
||||
|
||||
return [updatedField];
|
||||
});
|
||||
|
||||
const fieldsWithStaleGroupOverrides =
|
||||
this.buildFieldUpdatesForStaleGroupOverrides({
|
||||
existingViewFields,
|
||||
groupsToDelete,
|
||||
alreadyUpdatedFieldIds: new Set(
|
||||
viewFieldsToUpdate.map((field) => field.id),
|
||||
),
|
||||
now: new Date().toISOString(),
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
|
|
@ -371,7 +515,10 @@ export class FieldsWidgetUpsertService {
|
|||
viewField: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: viewFieldsToUpdate,
|
||||
flatEntityToUpdate: [
|
||||
...viewFieldsToUpdate,
|
||||
...fieldsWithStaleGroupOverrides,
|
||||
],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
|
|
@ -388,6 +535,111 @@ export class FieldsWidgetUpsertService {
|
|||
}
|
||||
}
|
||||
|
||||
private buildFieldUpdatesForStaleGroupOverrides({
|
||||
existingViewFields,
|
||||
groupsToDelete,
|
||||
alreadyUpdatedFieldIds,
|
||||
now,
|
||||
}: {
|
||||
existingViewFields: FlatViewField[];
|
||||
groupsToDelete: FlatViewFieldGroup[];
|
||||
alreadyUpdatedFieldIds: Set<string>;
|
||||
now: string;
|
||||
}): FlatViewField[] {
|
||||
if (groupsToDelete.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const deletedGroupIds = new Set(groupsToDelete.map((group) => group.id));
|
||||
|
||||
return existingViewFields
|
||||
.filter((field) => {
|
||||
if (alreadyUpdatedFieldIds.has(field.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const overriddenGroupId = field.overrides?.viewFieldGroupId;
|
||||
|
||||
const hasStaleOverride =
|
||||
isDefined(overriddenGroupId) &&
|
||||
typeof overriddenGroupId === 'string' &&
|
||||
deletedGroupIds.has(overriddenGroupId);
|
||||
|
||||
const hasStaleBase =
|
||||
overriddenGroupId === undefined &&
|
||||
isDefined(field.viewFieldGroupId) &&
|
||||
deletedGroupIds.has(field.viewFieldGroupId);
|
||||
|
||||
const hasStaleBaseHiddenByNullOverride =
|
||||
overriddenGroupId === null &&
|
||||
isDefined(field.viewFieldGroupId) &&
|
||||
deletedGroupIds.has(field.viewFieldGroupId);
|
||||
|
||||
return (
|
||||
hasStaleOverride || hasStaleBase || hasStaleBaseHiddenByNullOverride
|
||||
);
|
||||
})
|
||||
.map((field) => {
|
||||
const overriddenGroupId = field.overrides?.viewFieldGroupId;
|
||||
const hasStaleOverride =
|
||||
isDefined(overriddenGroupId) &&
|
||||
typeof overriddenGroupId === 'string' &&
|
||||
deletedGroupIds.has(overriddenGroupId);
|
||||
|
||||
if (hasStaleOverride) {
|
||||
const { viewFieldGroupId: _, ...remainingOverrides } =
|
||||
field.overrides!;
|
||||
|
||||
const cleanedOverrides =
|
||||
Object.keys(remainingOverrides).length > 0
|
||||
? (remainingOverrides as typeof field.overrides)
|
||||
: null;
|
||||
|
||||
const baseGroupIsAlsoStale =
|
||||
isDefined(field.viewFieldGroupId) &&
|
||||
deletedGroupIds.has(field.viewFieldGroupId);
|
||||
|
||||
return {
|
||||
...field,
|
||||
...(baseGroupIsAlsoStale
|
||||
? {
|
||||
viewFieldGroupId: null,
|
||||
viewFieldGroupUniversalIdentifier: null,
|
||||
}
|
||||
: {}),
|
||||
overrides: cleanedOverrides,
|
||||
universalOverrides: isDefined(cleanedOverrides)
|
||||
? fromViewFieldOverridesToUniversalOverrides({
|
||||
overrides: cleanedOverrides,
|
||||
viewFieldGroupUniversalIdentifierById: {},
|
||||
})
|
||||
: null,
|
||||
updatedAt: now,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
overriddenGroupId === null &&
|
||||
isDefined(field.viewFieldGroupId) &&
|
||||
deletedGroupIds.has(field.viewFieldGroupId)
|
||||
) {
|
||||
return {
|
||||
...field,
|
||||
viewFieldGroupId: null,
|
||||
viewFieldGroupUniversalIdentifier: null,
|
||||
updatedAt: now,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...field,
|
||||
viewFieldGroupId: null,
|
||||
viewFieldGroupUniversalIdentifier: null,
|
||||
updatedAt: now,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private buildGroupToCreate({
|
||||
inputGroup,
|
||||
viewId,
|
||||
|
|
@ -423,6 +675,7 @@ export class FieldsWidgetUpsertService {
|
|||
isVisible: inputGroup.isVisible,
|
||||
viewId,
|
||||
viewUniversalIdentifier,
|
||||
overrides: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
|
|
@ -435,10 +688,20 @@ export class FieldsWidgetUpsertService {
|
|||
existing: FlatViewFieldGroup,
|
||||
input: UpsertFieldsWidgetGroupInput,
|
||||
): boolean {
|
||||
const resolvedName = isDefined(existing.overrides?.name)
|
||||
? existing.overrides.name
|
||||
: existing.name;
|
||||
const resolvedPosition = isDefined(existing.overrides?.position)
|
||||
? existing.overrides.position
|
||||
: existing.position;
|
||||
const resolvedIsVisible = isDefined(existing.overrides?.isVisible)
|
||||
? existing.overrides.isVisible
|
||||
: existing.isVisible;
|
||||
|
||||
return (
|
||||
existing.name !== input.name ||
|
||||
existing.position !== input.position ||
|
||||
existing.isVisible !== input.isVisible
|
||||
resolvedName !== input.name ||
|
||||
resolvedPosition !== input.position ||
|
||||
resolvedIsVisible !== input.isVisible
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,6 +157,10 @@ export class ViewFieldGroupService {
|
|||
fromUpdateViewFieldGroupInputToFlatViewFieldGroupToUpdateOrThrow({
|
||||
flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps,
|
||||
updateViewFieldGroupInput,
|
||||
callerApplicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatViewFieldGroup } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group.type';
|
||||
import { type ViewFieldGroupDTO } from 'src/engine/metadata-modules/view-field-group/dtos/view-field-group.dto';
|
||||
|
||||
|
|
@ -8,6 +10,8 @@ export const fromFlatViewFieldGroupToViewFieldGroupDto = (
|
|||
|
||||
return {
|
||||
...rest,
|
||||
isOverridden:
|
||||
isDefined(rest.overrides) && Object.keys(rest.overrides).length > 0,
|
||||
createdAt: new Date(createdAt),
|
||||
updatedAt: new Date(updatedAt),
|
||||
deletedAt: deletedAt ? new Date(deletedAt) : null,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
import {
|
||||
Field,
|
||||
HideField,
|
||||
ObjectType,
|
||||
registerEnumType,
|
||||
} from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
import { AggregateOperations } from 'twenty-shared/types';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { type ViewFieldOverrides } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
|
||||
registerEnumType(AggregateOperations, { name: 'AggregateOperations' });
|
||||
|
||||
|
|
@ -44,4 +50,10 @@ export class ViewFieldDTO {
|
|||
|
||||
@Field(() => Date, { nullable: true })
|
||||
deletedAt?: Date | null;
|
||||
|
||||
@Field(() => Boolean, { nullable: false })
|
||||
isOverridden: boolean;
|
||||
|
||||
@HideField()
|
||||
overrides?: ViewFieldOverrides | null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,23 @@ import {
|
|||
type Relation,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { AggregateOperations } from 'twenty-shared/types';
|
||||
import {
|
||||
AggregateOperations,
|
||||
type SerializedRelation,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
|
||||
import { OverridableEntity } from 'src/engine/workspace-manager/types/overridable-entity';
|
||||
|
||||
export type ViewFieldOverrides = {
|
||||
isVisible?: boolean;
|
||||
size?: number;
|
||||
position?: number;
|
||||
aggregateOperation?: AggregateOperations | null;
|
||||
viewFieldGroupId?: SerializedRelation | null;
|
||||
};
|
||||
|
||||
@Entity({ name: 'viewField', schema: 'core' })
|
||||
@Index('IDX_VIEW_FIELD_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId'])
|
||||
|
|
@ -30,7 +41,7 @@ import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-enti
|
|||
},
|
||||
)
|
||||
export class ViewFieldEntity
|
||||
extends SyncableEntity
|
||||
extends OverridableEntity<ViewFieldOverrides>
|
||||
implements Required<ViewFieldEntity>
|
||||
{
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
|
|
|
|||
|
|
@ -1,11 +1,24 @@
|
|||
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Query } from '@nestjs/graphql';
|
||||
import {
|
||||
Args,
|
||||
Float,
|
||||
Int,
|
||||
Mutation,
|
||||
Parent,
|
||||
Query,
|
||||
ResolveField,
|
||||
} from '@nestjs/graphql';
|
||||
|
||||
import { AggregateOperations } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { resolveOverridableEntityProperty } from 'src/engine/metadata-modules/utils/resolve-overridable-entity-property.util';
|
||||
import { CreateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input';
|
||||
import { DeleteViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/delete-view-field.input';
|
||||
import { DestroyViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/destroy-view-field.input';
|
||||
|
|
@ -25,6 +38,43 @@ import { ViewGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/view/
|
|||
export class ViewFieldResolver {
|
||||
constructor(private readonly viewFieldService: ViewFieldService) {}
|
||||
|
||||
@ResolveField(() => Boolean)
|
||||
isVisible(@Parent() viewField: ViewFieldDTO): boolean {
|
||||
return resolveOverridableEntityProperty(viewField, 'isVisible');
|
||||
}
|
||||
|
||||
@ResolveField(() => Int)
|
||||
size(@Parent() viewField: ViewFieldDTO): number {
|
||||
return resolveOverridableEntityProperty(viewField, 'size');
|
||||
}
|
||||
|
||||
@ResolveField(() => Float)
|
||||
position(@Parent() viewField: ViewFieldDTO): number {
|
||||
return resolveOverridableEntityProperty(viewField, 'position');
|
||||
}
|
||||
|
||||
@ResolveField(() => AggregateOperations, { nullable: true })
|
||||
aggregateOperation(
|
||||
@Parent() viewField: ViewFieldDTO,
|
||||
): AggregateOperations | null | undefined {
|
||||
return resolveOverridableEntityProperty(viewField, 'aggregateOperation');
|
||||
}
|
||||
|
||||
@ResolveField(() => UUIDScalarType, { nullable: true })
|
||||
viewFieldGroupId(
|
||||
@Parent() viewField: ViewFieldDTO,
|
||||
): string | null | undefined {
|
||||
return resolveOverridableEntityProperty(viewField, 'viewFieldGroupId');
|
||||
}
|
||||
|
||||
@ResolveField(() => Boolean)
|
||||
isOverridden(@Parent() viewField: ViewFieldDTO): boolean {
|
||||
return (
|
||||
isDefined(viewField.overrides) &&
|
||||
Object.keys(viewField.overrides).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
@Query(() => [ViewFieldDTO])
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getCoreViewFields(
|
||||
|
|
|
|||
|
|
@ -167,6 +167,10 @@ export class ViewFieldService {
|
|||
flatViewFieldMaps: existingFlatViewFieldMaps,
|
||||
flatViewFieldGroupMaps,
|
||||
updateViewFieldInput,
|
||||
callerApplicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
|
||||
import { type ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto';
|
||||
|
||||
|
|
@ -8,6 +10,8 @@ export const fromFlatViewFieldToViewFieldDto = (
|
|||
|
||||
return {
|
||||
...rest,
|
||||
isOverridden:
|
||||
isDefined(rest.overrides) && Object.keys(rest.overrides).length > 0,
|
||||
createdAt: new Date(createdAt),
|
||||
updatedAt: new Date(updatedAt),
|
||||
deletedAt: deletedAt ? new Date(deletedAt) : null,
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ export const createStandardViewFieldGroupFlatMetadata = <
|
|||
name,
|
||||
position,
|
||||
isVisible,
|
||||
overrides: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ export const createStandardViewFieldFlatMetadata = <
|
|||
isVisible,
|
||||
size,
|
||||
aggregateOperation,
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ export const ALL_JSONB_PROPERTIES_WITH_SERIALIZED_RELATION_BY_METADATA_NAME = {
|
|||
},
|
||||
objectMetadata: {},
|
||||
view: {},
|
||||
viewField: {},
|
||||
viewField: {
|
||||
overrides: 'overrides',
|
||||
},
|
||||
viewGroup: {},
|
||||
viewFieldGroup: {},
|
||||
viewFilter: {},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { v4 } from 'uuid';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
|
|
@ -10,6 +11,7 @@ import {
|
|||
FlatCreateViewFieldAction,
|
||||
UniversalCreateViewFieldAction,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field/types/workspace-migration-view-field-action.type';
|
||||
import { fromUniversalOverridesToViewFieldOverrides } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-field/services/utils/from-universal-overrides-to-view-field-overrides.util';
|
||||
import {
|
||||
WorkspaceMigrationActionRunnerArgs,
|
||||
WorkspaceMigrationActionRunnerContext,
|
||||
|
|
@ -37,6 +39,13 @@ export class CreateViewFieldActionHandlerService extends WorkspaceMigrationRunne
|
|||
universalForeignKeyValues: action.flatEntity,
|
||||
});
|
||||
|
||||
const overrides = isDefined(action.flatEntity.universalOverrides)
|
||||
? fromUniversalOverridesToViewFieldOverrides({
|
||||
universalOverrides: action.flatEntity.universalOverrides,
|
||||
flatViewFieldGroupMaps: allFlatEntityMaps.flatViewFieldGroupMaps,
|
||||
})
|
||||
: null;
|
||||
|
||||
const emptyUniversalForeignKeyAggregators =
|
||||
getUniversalFlatEntityEmptyForeignKeyAggregators({
|
||||
metadataName: 'viewField',
|
||||
|
|
@ -49,6 +58,7 @@ export class CreateViewFieldActionHandlerService extends WorkspaceMigrationRunne
|
|||
fieldMetadataId,
|
||||
viewId,
|
||||
viewFieldGroupId,
|
||||
overrides,
|
||||
id: action.id ?? v4(),
|
||||
applicationId: flatApplication.id,
|
||||
workspaceId,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
FlatUpdateViewFieldAction,
|
||||
UniversalUpdateViewFieldAction,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field/types/workspace-migration-view-field-action.type';
|
||||
import { fromUniversalOverridesToViewFieldOverrides } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-field/services/utils/from-universal-overrides-to-view-field-overrides.util';
|
||||
import {
|
||||
WorkspaceMigrationActionRunnerArgs,
|
||||
WorkspaceMigrationActionRunnerContext,
|
||||
|
|
@ -33,11 +34,26 @@ export class UpdateViewFieldActionHandlerService extends WorkspaceMigrationRunne
|
|||
universalIdentifier: action.universalIdentifier,
|
||||
});
|
||||
|
||||
const update = resolveUniversalUpdateRelationIdentifiersToIds({
|
||||
metadataName: 'viewField',
|
||||
universalUpdate: action.update,
|
||||
allFlatEntityMaps,
|
||||
});
|
||||
const { universalOverrides, ...updateWithResolvedForeignKeys } =
|
||||
resolveUniversalUpdateRelationIdentifiersToIds({
|
||||
metadataName: 'viewField',
|
||||
universalUpdate: action.update,
|
||||
allFlatEntityMaps,
|
||||
});
|
||||
|
||||
const update =
|
||||
universalOverrides === undefined
|
||||
? updateWithResolvedForeignKeys
|
||||
: universalOverrides === null
|
||||
? { ...updateWithResolvedForeignKeys, overrides: null }
|
||||
: {
|
||||
...updateWithResolvedForeignKeys,
|
||||
overrides: fromUniversalOverridesToViewFieldOverrides({
|
||||
universalOverrides,
|
||||
flatViewFieldGroupMaps:
|
||||
allFlatEntityMaps.flatViewFieldGroupMaps,
|
||||
}),
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'update',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
import { type FormatRecordSerializedRelationProperties } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
import { type FlatViewFieldGroupMaps } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group-maps.type';
|
||||
import { type FlatViewFieldGroup } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group.type';
|
||||
import { type ViewFieldOverrides } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
|
||||
type UniversalViewFieldOverrides =
|
||||
FormatRecordSerializedRelationProperties<ViewFieldOverrides>;
|
||||
|
||||
export const fromUniversalOverridesToViewFieldOverrides = ({
|
||||
universalOverrides,
|
||||
flatViewFieldGroupMaps,
|
||||
}: {
|
||||
universalOverrides: UniversalViewFieldOverrides;
|
||||
flatViewFieldGroupMaps: FlatViewFieldGroupMaps;
|
||||
}): ViewFieldOverrides => {
|
||||
const { viewFieldGroupUniversalIdentifier, ...scalarOverrides } =
|
||||
universalOverrides;
|
||||
|
||||
if (!isDefined(viewFieldGroupUniversalIdentifier)) {
|
||||
return {
|
||||
...scalarOverrides,
|
||||
...(viewFieldGroupUniversalIdentifier === null
|
||||
? { viewFieldGroupId: null }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
const flatViewFieldGroup =
|
||||
findFlatEntityByUniversalIdentifier<FlatViewFieldGroup>({
|
||||
flatEntityMaps: flatViewFieldGroupMaps,
|
||||
universalIdentifier: viewFieldGroupUniversalIdentifier,
|
||||
});
|
||||
|
||||
return {
|
||||
...scalarOverrides,
|
||||
viewFieldGroupId: flatViewFieldGroup?.id ?? null,
|
||||
};
|
||||
};
|
||||
Loading…
Reference in a new issue