mirror of
https://github.com/lingble/twenty.git
synced 2025-10-30 20:27:55 +00:00
fix: when field metadata SELECT type is edited update view groups (#8344)
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@@ -21,6 +21,7 @@ const baseFields = `
|
|||||||
settings
|
settings
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
export const queries = {
|
export const queries = {
|
||||||
deleteMetadataField: gql`
|
deleteMetadataField: gql`
|
||||||
mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {
|
mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {
|
||||||
@@ -29,6 +30,37 @@ export const queries = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
findManyViewsQuery: gql`
|
||||||
|
query FindManyViews($filter: ViewFilterInput, $orderBy: [ViewOrderByInput], $lastCursor: String, $limit: Int) {
|
||||||
|
views(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
viewGroups {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
__typename
|
||||||
|
fieldMetadataId
|
||||||
|
fieldValue
|
||||||
|
id
|
||||||
|
isVisible
|
||||||
|
position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor
|
||||||
|
}
|
||||||
|
pageInfo {
|
||||||
|
hasNextPage
|
||||||
|
hasPreviousPage
|
||||||
|
startCursor
|
||||||
|
endCursor
|
||||||
|
}
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
}`,
|
||||||
deleteMetadataFieldRelation: gql`
|
deleteMetadataFieldRelation: gql`
|
||||||
mutation DeleteOneRelationMetadataItem($idToDelete: UUID!) {
|
mutation DeleteOneRelationMetadataItem($idToDelete: UUID!) {
|
||||||
deleteOneRelation(input: { id: $idToDelete }) {
|
deleteOneRelation(input: { id: $idToDelete }) {
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { MockedProvider } from '@apollo/client/testing';
|
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { act, ReactNode } from 'react';
|
import { act } from 'react';
|
||||||
import { RecoilRoot } from 'recoil';
|
|
||||||
|
|
||||||
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
|
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
|
||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { FieldMetadataType, RelationDefinitionType } from '~/generated/graphql';
|
import { FieldMetadataType, RelationDefinitionType } from '~/generated/graphql';
|
||||||
|
|
||||||
|
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||||
import {
|
import {
|
||||||
FIELD_METADATA_ID,
|
FIELD_METADATA_ID,
|
||||||
FIELD_RELATION_METADATA_ID,
|
FIELD_RELATION_METADATA_ID,
|
||||||
@@ -58,6 +57,31 @@ const fieldRelationMetadataItem: FieldMetadataItem = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const mocks = [
|
const mocks = [
|
||||||
|
{
|
||||||
|
request: {
|
||||||
|
query: queries.findManyViewsQuery,
|
||||||
|
variables: {
|
||||||
|
filter: {
|
||||||
|
objectMetadataId: { eq: '25611fce-6637-4089-b0ca-91afeec95784' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: jest.fn(() => ({
|
||||||
|
data: {
|
||||||
|
views: {
|
||||||
|
__typename: 'ViewConnection',
|
||||||
|
totalCount: 0,
|
||||||
|
pageInfo: {
|
||||||
|
__typename: 'PageInfo',
|
||||||
|
hasNextPage: false,
|
||||||
|
startCursor: '',
|
||||||
|
endCursor: '',
|
||||||
|
},
|
||||||
|
edges: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
request: {
|
request: {
|
||||||
query: queries.deleteMetadataField,
|
query: queries.deleteMetadataField,
|
||||||
@@ -115,13 +139,9 @@ const mocks = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
const Wrapper = getJestMetadataAndApolloMocksWrapper({
|
||||||
<RecoilRoot>
|
apolloMocks: mocks,
|
||||||
<MockedProvider mocks={mocks} addTypename={false}>
|
});
|
||||||
{children}
|
|
||||||
</MockedProvider>
|
|
||||||
</RecoilRoot>
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('useFieldMetadataItem', () => {
|
describe('useFieldMetadataItem', () => {
|
||||||
it('should activateMetadataField', async () => {
|
it('should activateMetadataField', async () => {
|
||||||
@@ -130,7 +150,10 @@ describe('useFieldMetadataItem', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
const res = await result.current.activateMetadataField(fieldMetadataItem);
|
const res = await result.current.activateMetadataField(
|
||||||
|
fieldMetadataItem.id,
|
||||||
|
objectMetadataId,
|
||||||
|
);
|
||||||
|
|
||||||
expect(res.data).toEqual({
|
expect(res.data).toEqual({
|
||||||
updateOneField: responseData.default,
|
updateOneField: responseData.default,
|
||||||
@@ -162,8 +185,10 @@ describe('useFieldMetadataItem', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
const res =
|
const res = await result.current.deactivateMetadataField(
|
||||||
await result.current.deactivateMetadataField(fieldMetadataItem);
|
fieldMetadataItem.id,
|
||||||
|
objectMetadataId,
|
||||||
|
);
|
||||||
|
|
||||||
expect(res.data).toEqual({
|
expect(res.data).toEqual({
|
||||||
updateOneField: responseData.default,
|
updateOneField: responseData.default,
|
||||||
|
|||||||
@@ -40,15 +40,23 @@ export const useFieldMetadataItem = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const activateMetadataField = (metadataField: FieldMetadataItem) =>
|
const activateMetadataField = (
|
||||||
|
fieldMetadataId: string,
|
||||||
|
objectMetadataId: string,
|
||||||
|
) =>
|
||||||
updateOneFieldMetadataItem({
|
updateOneFieldMetadataItem({
|
||||||
fieldMetadataIdToUpdate: metadataField.id,
|
objectMetadataId: objectMetadataId,
|
||||||
|
fieldMetadataIdToUpdate: fieldMetadataId,
|
||||||
updatePayload: { isActive: true },
|
updatePayload: { isActive: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
const deactivateMetadataField = (metadataField: FieldMetadataItem) =>
|
const deactivateMetadataField = (
|
||||||
|
fieldMetadataId: string,
|
||||||
|
objectMetadataId: string,
|
||||||
|
) =>
|
||||||
updateOneFieldMetadataItem({
|
updateOneFieldMetadataItem({
|
||||||
fieldMetadataIdToUpdate: metadataField.id,
|
objectMetadataId: objectMetadataId,
|
||||||
|
fieldMetadataIdToUpdate: fieldMetadataId,
|
||||||
updatePayload: { isActive: false },
|
updatePayload: { isActive: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMutation } from '@apollo/client';
|
import { useApolloClient, useMutation } from '@apollo/client';
|
||||||
import { getOperationName } from '@apollo/client/utilities';
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -9,10 +9,27 @@ import {
|
|||||||
import { UPDATE_ONE_FIELD_METADATA_ITEM } from '../graphql/mutations';
|
import { UPDATE_ONE_FIELD_METADATA_ITEM } from '../graphql/mutations';
|
||||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '../graphql/queries';
|
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '../graphql/queries';
|
||||||
|
|
||||||
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery';
|
||||||
import { useApolloMetadataClient } from './useApolloMetadataClient';
|
import { useApolloMetadataClient } from './useApolloMetadataClient';
|
||||||
|
|
||||||
export const useUpdateOneFieldMetadataItem = () => {
|
export const useUpdateOneFieldMetadataItem = () => {
|
||||||
const apolloMetadataClient = useApolloMetadataClient();
|
const apolloMetadataClient = useApolloMetadataClient();
|
||||||
|
const apolloClient = useApolloClient();
|
||||||
|
|
||||||
|
const { findManyRecordsQuery } = useFindManyRecordsQuery({
|
||||||
|
objectNameSingular: CoreObjectNameSingular.View,
|
||||||
|
recordGqlFields: {
|
||||||
|
id: true,
|
||||||
|
viewGroups: {
|
||||||
|
id: true,
|
||||||
|
fieldMetadataId: true,
|
||||||
|
isVisible: true,
|
||||||
|
fieldValue: true,
|
||||||
|
position: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const [mutate] = useMutation<
|
const [mutate] = useMutation<
|
||||||
UpdateOneFieldMetadataItemMutation,
|
UpdateOneFieldMetadataItemMutation,
|
||||||
@@ -22,9 +39,11 @@ export const useUpdateOneFieldMetadataItem = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const updateOneFieldMetadataItem = async ({
|
const updateOneFieldMetadataItem = async ({
|
||||||
|
objectMetadataId,
|
||||||
fieldMetadataIdToUpdate,
|
fieldMetadataIdToUpdate,
|
||||||
updatePayload,
|
updatePayload,
|
||||||
}: {
|
}: {
|
||||||
|
objectMetadataId: string;
|
||||||
fieldMetadataIdToUpdate: UpdateOneFieldMetadataItemMutationVariables['idToUpdate'];
|
fieldMetadataIdToUpdate: UpdateOneFieldMetadataItemMutationVariables['idToUpdate'];
|
||||||
updatePayload: Pick<
|
updatePayload: Pick<
|
||||||
UpdateOneFieldMetadataItemMutationVariables['updatePayload'],
|
UpdateOneFieldMetadataItemMutationVariables['updatePayload'],
|
||||||
@@ -37,7 +56,7 @@ export const useUpdateOneFieldMetadataItem = () => {
|
|||||||
| 'options'
|
| 'options'
|
||||||
>;
|
>;
|
||||||
}) => {
|
}) => {
|
||||||
return await mutate({
|
const result = await mutate({
|
||||||
variables: {
|
variables: {
|
||||||
idToUpdate: fieldMetadataIdToUpdate,
|
idToUpdate: fieldMetadataIdToUpdate,
|
||||||
updatePayload: {
|
updatePayload: {
|
||||||
@@ -48,6 +67,20 @@ export const useUpdateOneFieldMetadataItem = () => {
|
|||||||
awaitRefetchQueries: true,
|
awaitRefetchQueries: true,
|
||||||
refetchQueries: [getOperationName(FIND_MANY_OBJECT_METADATA_ITEMS) ?? ''],
|
refetchQueries: [getOperationName(FIND_MANY_OBJECT_METADATA_ITEMS) ?? ''],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await apolloClient.query({
|
||||||
|
query: findManyRecordsQuery,
|
||||||
|
variables: {
|
||||||
|
filter: {
|
||||||
|
objectMetadataId: {
|
||||||
|
eq: objectMetadataId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fetchPolicy: 'network-only',
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
|
||||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||||
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
|
|
||||||
export const useSetRecordBoardColumns = (recordBoardId?: string) => {
|
export const useSetRecordBoardColumns = (recordBoardId?: string) => {
|
||||||
const { scopeId, columnIdsState, columnsFamilySelector } =
|
const { scopeId, columnIdsState, columnsFamilySelector } =
|
||||||
@@ -19,12 +19,10 @@ export const useSetRecordBoardColumns = (recordBoardId?: string) => {
|
|||||||
.filter(({ isVisible }) => isVisible)
|
.filter(({ isVisible }) => isVisible)
|
||||||
.map(({ id }) => id);
|
.map(({ id }) => id);
|
||||||
|
|
||||||
if (isDeeplyEqual(currentColumnsIds, columnIds)) {
|
if (!isDeeplyEqual(currentColumnsIds, columnIds)) {
|
||||||
return;
|
set(columnIdsState, columnIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
set(columnIdsState, columnIds);
|
|
||||||
|
|
||||||
columns.forEach((column) => {
|
columns.forEach((column) => {
|
||||||
const currentColumn = snapshot
|
const currentColumn = snapshot
|
||||||
.getLoadable(columnsFamilySelector(column.id))
|
.getLoadable(columnsFamilySelector(column.id))
|
||||||
|
|||||||
@@ -127,7 +127,10 @@ export const SettingsObjectFieldItemTableRow = ({
|
|||||||
const handleDisableField = async (
|
const handleDisableField = async (
|
||||||
activeFieldMetadatItem: FieldMetadataItem,
|
activeFieldMetadatItem: FieldMetadataItem,
|
||||||
) => {
|
) => {
|
||||||
await deactivateMetadataField(activeFieldMetadatItem);
|
await deactivateMetadataField(
|
||||||
|
activeFieldMetadatItem.id,
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
const deletedViewIds = allViews
|
const deletedViewIds = allViews
|
||||||
.map((view) => {
|
.map((view) => {
|
||||||
@@ -272,7 +275,9 @@ export const SettingsObjectFieldItemTableRow = ({
|
|||||||
isCustomField={fieldMetadataItem.isCustom === true}
|
isCustomField={fieldMetadataItem.isCustom === true}
|
||||||
scopeKey={fieldMetadataItem.id}
|
scopeKey={fieldMetadataItem.id}
|
||||||
onEdit={() => navigate(linkToNavigate)}
|
onEdit={() => navigate(linkToNavigate)}
|
||||||
onActivate={() => activateMetadataField(fieldMetadataItem)}
|
onActivate={() =>
|
||||||
|
activateMetadataField(fieldMetadataItem.id, objectMetadataItem.id)
|
||||||
|
}
|
||||||
onDelete={() => deleteMetadataField(fieldMetadataItem)}
|
onDelete={() => deleteMetadataField(fieldMetadataItem)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ export const SettingsObjectFieldEdit = () => {
|
|||||||
|
|
||||||
if (isDefined(relationFieldMetadataItem)) {
|
if (isDefined(relationFieldMetadataItem)) {
|
||||||
await updateOneFieldMetadataItem({
|
await updateOneFieldMetadataItem({
|
||||||
|
objectMetadataId: objectMetadataItem.id,
|
||||||
fieldMetadataIdToUpdate: relationFieldMetadataItem.id,
|
fieldMetadataIdToUpdate: relationFieldMetadataItem.id,
|
||||||
updatePayload: formValues.relation.field,
|
updatePayload: formValues.relation.field,
|
||||||
});
|
});
|
||||||
@@ -152,6 +153,7 @@ export const SettingsObjectFieldEdit = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await updateOneFieldMetadataItem({
|
await updateOneFieldMetadataItem({
|
||||||
|
objectMetadataId: objectMetadataItem.id,
|
||||||
fieldMetadataIdToUpdate: fieldMetadataItem.id,
|
fieldMetadataIdToUpdate: fieldMetadataItem.id,
|
||||||
updatePayload: formattedInput,
|
updatePayload: formattedInput,
|
||||||
});
|
});
|
||||||
@@ -168,12 +170,12 @@ export const SettingsObjectFieldEdit = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeactivate = async () => {
|
const handleDeactivate = async () => {
|
||||||
await deactivateMetadataField(fieldMetadataItem);
|
await deactivateMetadataField(fieldMetadataItem.id, objectMetadataItem.id);
|
||||||
navigate(`/settings/objects/${objectSlug}`);
|
navigate(`/settings/objects/${objectSlug}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleActivate = async () => {
|
const handleActivate = async () => {
|
||||||
await activateMetadataField(fieldMetadataItem);
|
await activateMetadataField(fieldMetadataItem.id, objectMetadataItem.id);
|
||||||
navigate(`/settings/objects/${objectSlug}`);
|
navigate(`/settings/objects/${objectSlug}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dto
|
|||||||
import { FieldMetadataValidationService } from 'src/engine/metadata-modules/field-metadata/field-metadata-validation.service';
|
import { FieldMetadataValidationService } from 'src/engine/metadata-modules/field-metadata/field-metadata-validation.service';
|
||||||
import { FieldMetadataResolver } from 'src/engine/metadata-modules/field-metadata/field-metadata.resolver';
|
import { FieldMetadataResolver } from 'src/engine/metadata-modules/field-metadata/field-metadata.resolver';
|
||||||
import { FieldMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/field-metadata/interceptors/field-metadata-graphql-api-exception.interceptor';
|
import { FieldMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/field-metadata/interceptors/field-metadata-graphql-api-exception.interceptor';
|
||||||
|
import { FieldMetadataRelatedRecordsService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-related-records.service';
|
||||||
import { IsFieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-default-value.validator';
|
import { IsFieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-default-value.validator';
|
||||||
import { IsFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-options.validator';
|
import { IsFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-options.validator';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
@@ -48,6 +49,7 @@ import { UpdateFieldInput } from './dtos/update-field.input';
|
|||||||
services: [
|
services: [
|
||||||
IsFieldMetadataDefaultValue,
|
IsFieldMetadataDefaultValue,
|
||||||
FieldMetadataService,
|
FieldMetadataService,
|
||||||
|
FieldMetadataRelatedRecordsService,
|
||||||
FieldMetadataValidationService,
|
FieldMetadataValidationService,
|
||||||
],
|
],
|
||||||
resolvers: [
|
resolvers: [
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
FieldMetadataException,
|
FieldMetadataException,
|
||||||
FieldMetadataExceptionCode,
|
FieldMetadataExceptionCode,
|
||||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||||
|
import { FieldMetadataRelatedRecordsService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-related-records.service';
|
||||||
import { assertDoesNotNullifyDefaultValueForNonNullableField } from 'src/engine/metadata-modules/field-metadata/utils/assert-does-not-nullify-default-value-for-non-nullable-field.util';
|
import { assertDoesNotNullifyDefaultValueForNonNullableField } from 'src/engine/metadata-modules/field-metadata/utils/assert-does-not-nullify-default-value-for-non-nullable-field.util';
|
||||||
import {
|
import {
|
||||||
computeColumnName,
|
computeColumnName,
|
||||||
@@ -28,6 +29,7 @@ import {
|
|||||||
} from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
} from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||||
import { generateNullable } from 'src/engine/metadata-modules/field-metadata/utils/generate-nullable';
|
import { generateNullable } from 'src/engine/metadata-modules/field-metadata/utils/generate-nullable';
|
||||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||||
|
import { isSelectFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-select-field-metadata-type.util';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
import {
|
import {
|
||||||
@@ -83,6 +85,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly fieldMetadataValidationService: FieldMetadataValidationService,
|
private readonly fieldMetadataValidationService: FieldMetadataValidationService,
|
||||||
|
private readonly fieldMetadataRelatedRecordsService: FieldMetadataRelatedRecordsService,
|
||||||
) {
|
) {
|
||||||
super(fieldMetadataRepository);
|
super(fieldMetadataRepository);
|
||||||
}
|
}
|
||||||
@@ -418,6 +421,16 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
updatedFieldMetadata.isActive &&
|
||||||
|
isSelectFieldMetadataType(updatedFieldMetadata.type)
|
||||||
|
) {
|
||||||
|
await this.fieldMetadataRelatedRecordsService.updateRelatedViewGroups(
|
||||||
|
existingFieldMetadata,
|
||||||
|
updatedFieldMetadata,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
fieldMetadataInput.name ||
|
fieldMetadataInput.name ||
|
||||||
updatableFieldInput.options ||
|
updatableFieldInput.options ||
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { In } from 'typeorm';
|
||||||
|
|
||||||
|
import {
|
||||||
|
FieldMetadataComplexOption,
|
||||||
|
FieldMetadataDefaultOption,
|
||||||
|
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||||
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
import { isSelectFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-select-field-metadata-type.util';
|
||||||
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
import { ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-group.workspace-entity';
|
||||||
|
import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity';
|
||||||
|
|
||||||
|
type Differences<T> = {
|
||||||
|
created: T[];
|
||||||
|
updated: { old: T; new: T }[];
|
||||||
|
deleted: T[];
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FieldMetadataRelatedRecordsService {
|
||||||
|
constructor(
|
||||||
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async updateRelatedViewGroups(
|
||||||
|
oldFieldMetadata: FieldMetadataEntity,
|
||||||
|
newFieldMetadata: FieldMetadataEntity,
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!isSelectFieldMetadataType(newFieldMetadata.type) ||
|
||||||
|
!isSelectFieldMetadataType(oldFieldMetadata.type)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const views = await this.getFieldMetadataViews(newFieldMetadata);
|
||||||
|
|
||||||
|
const { created, updated, deleted } = this.getOptionsDifferences(
|
||||||
|
oldFieldMetadata.options,
|
||||||
|
newFieldMetadata.options,
|
||||||
|
);
|
||||||
|
|
||||||
|
const viewGroupRepository =
|
||||||
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewGroupWorkspaceEntity>(
|
||||||
|
newFieldMetadata.workspaceId,
|
||||||
|
'viewGroup',
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const view of views) {
|
||||||
|
const maxPosition = view.viewGroups.reduce(
|
||||||
|
(max, viewGroup) => Math.max(max, viewGroup.position),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const viewGroupsToCreate = created.map((option, index) =>
|
||||||
|
viewGroupRepository.create({
|
||||||
|
fieldMetadataId: newFieldMetadata.id,
|
||||||
|
fieldValue: option.value,
|
||||||
|
position: maxPosition + index,
|
||||||
|
isVisible: true,
|
||||||
|
viewId: view.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await viewGroupRepository.insert(viewGroupsToCreate);
|
||||||
|
|
||||||
|
for (const { old: oldOption, new: newOption } of updated) {
|
||||||
|
const viewGroup = view.viewGroups.find(
|
||||||
|
(viewGroup) => viewGroup.fieldValue === oldOption.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!viewGroup) {
|
||||||
|
throw new Error(`View group not found for option ${oldOption.value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await viewGroupRepository.update(
|
||||||
|
{
|
||||||
|
id: viewGroup.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldValue: newOption.value,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const valuesToDelete = deleted.map((option) => option.value);
|
||||||
|
|
||||||
|
await viewGroupRepository.delete({
|
||||||
|
fieldMetadataId: newFieldMetadata.id,
|
||||||
|
fieldValue: In(valuesToDelete),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOptionsDifferences(
|
||||||
|
oldOptions: (FieldMetadataDefaultOption | FieldMetadataComplexOption)[],
|
||||||
|
newOptions: (FieldMetadataDefaultOption | FieldMetadataComplexOption)[],
|
||||||
|
) {
|
||||||
|
const differences: Differences<
|
||||||
|
FieldMetadataDefaultOption | FieldMetadataComplexOption
|
||||||
|
> = {
|
||||||
|
created: [],
|
||||||
|
updated: [],
|
||||||
|
deleted: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const oldOptionsMap = new Map(
|
||||||
|
oldOptions.map((option) => [option.id, option]),
|
||||||
|
);
|
||||||
|
const newOptionsMap = new Map(
|
||||||
|
newOptions.map((option) => [option.id, option]),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const newOption of newOptions) {
|
||||||
|
const oldOption = oldOptionsMap.get(newOption.id);
|
||||||
|
|
||||||
|
if (!oldOption) {
|
||||||
|
differences.created.push(newOption);
|
||||||
|
} else if (oldOption.value !== newOption.value) {
|
||||||
|
differences.updated.push({ old: oldOption, new: newOption });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const oldOption of oldOptions) {
|
||||||
|
if (!newOptionsMap.has(oldOption.id)) {
|
||||||
|
differences.deleted.push(oldOption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return differences;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getFieldMetadataViews(
|
||||||
|
fieldMetadata: FieldMetadataEntity,
|
||||||
|
): Promise<ViewWorkspaceEntity[]> {
|
||||||
|
const viewRepository =
|
||||||
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewWorkspaceEntity>(
|
||||||
|
fieldMetadata.workspaceId,
|
||||||
|
'view',
|
||||||
|
);
|
||||||
|
|
||||||
|
return await viewRepository.find({
|
||||||
|
where: {
|
||||||
|
kanbanFieldMetadataId: fieldMetadata.id,
|
||||||
|
},
|
||||||
|
relations: ['viewGroups'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
|
||||||
|
export const isSelectFieldMetadataType = (
|
||||||
|
type: FieldMetadataType,
|
||||||
|
): type is FieldMetadataType.SELECT => {
|
||||||
|
return type === FieldMetadataType.SELECT;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user