Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5ebe7a757 |
+2
-10
@@ -6,13 +6,11 @@ import { splitViewWithRelated } from '@/metadata-store/utils/splitViewWithRelate
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
||||
import { transformPageLayout } from '@/page-layout/utils/transformPageLayout';
|
||||
import { logicFunctionsState } from '@/settings/logic-functions/states/logicFunctionsState';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useApolloClient } from '@apollo/client/react';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
FeatureFlagKey,
|
||||
FindAllViewsDocument,
|
||||
FindManyCommandMenuItemsDocument,
|
||||
FindAllRecordPageLayoutsDocument,
|
||||
@@ -57,9 +55,6 @@ export const useLoadStaleMetadataEntities = () => {
|
||||
const client = useApolloClient();
|
||||
const store = useStore();
|
||||
const { replaceDraft, applyChanges } = useUpdateMetadataStoreDraft();
|
||||
const isCommandMenuItemEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
);
|
||||
|
||||
const loadStaleMetadataEntities = useCallback(
|
||||
async (staleEntityKeys: MetadataEntityKey[]) => {
|
||||
@@ -199,10 +194,7 @@ export const useLoadStaleMetadataEntities = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
staleEntityKeys.includes('commandMenuItems') &&
|
||||
isCommandMenuItemEnabled
|
||||
) {
|
||||
if (staleEntityKeys.includes('commandMenuItems')) {
|
||||
fetchPromises.push(
|
||||
client
|
||||
.query({
|
||||
@@ -222,7 +214,7 @@ export const useLoadStaleMetadataEntities = () => {
|
||||
await Promise.all(fetchPromises);
|
||||
applyChanges();
|
||||
},
|
||||
[client, store, replaceDraft, applyChanges, isCommandMenuItemEnabled],
|
||||
[client, store, replaceDraft, applyChanges],
|
||||
);
|
||||
|
||||
return { loadStaleMetadataEntities };
|
||||
|
||||
+1
-4
@@ -8,17 +8,14 @@ const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
background: ${themeCssVariables.background.transparent.light};
|
||||
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
border-radius: 4px;
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
display: inline-flex;
|
||||
font-size: ${themeCssVariables.font.size.md};
|
||||
font-weight: ${themeCssVariables.font.weight.regular};
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
height: ${themeCssVariables.spacing[3]};
|
||||
overflow: hidden;
|
||||
|
||||
padding: ${themeCssVariables.spacing[1]};
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
export const ForbiddenFieldDisplay = () => {
|
||||
|
||||
+2
@@ -25,6 +25,7 @@ export const useRecordIndexTableQuery = (objectNameSingular: string) => {
|
||||
loading,
|
||||
totalCount,
|
||||
fetchMoreRecords,
|
||||
error,
|
||||
} = useFindManyRecords({
|
||||
...params,
|
||||
recordGqlFields,
|
||||
@@ -38,5 +39,6 @@ export const useRecordIndexTableQuery = (objectNameSingular: string) => {
|
||||
queryIdentifier,
|
||||
totalCount,
|
||||
fetchMoreRecords,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
+13
-7
@@ -1,11 +1,12 @@
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableEmptyStateAccessDenied } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateAccessDenied';
|
||||
import { RecordTableEmptyStateNoGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll';
|
||||
import { RecordTableEmptyStateNoRecordFoundForFilter } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter';
|
||||
import { RecordTableEmptyStateReadOnly } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateReadOnly';
|
||||
import { RecordTableEmptyStateRemote } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote';
|
||||
import { RecordTableEmptyStateSoftDelete } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete';
|
||||
import { isRecordTableAccessDeniedComponentState } from '@/object-record/record-table/states/isRecordTableAccessDeniedComponentState';
|
||||
import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
|
||||
@@ -13,10 +14,12 @@ export const RecordTableEmptyState = () => {
|
||||
const { recordTableId, objectNameSingular, objectMetadataItem } =
|
||||
useRecordTableContextOrThrow();
|
||||
|
||||
const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 });
|
||||
const noRecordAtAll = totalCount === 0;
|
||||
const isRecordTableAccessDenied = useAtomComponentStateValue(
|
||||
isRecordTableAccessDeniedComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const isRemote = objectMetadataItem.isRemote;
|
||||
const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 });
|
||||
|
||||
const isSoftDeleteFilterActive = useAtomComponentStateValue(
|
||||
isSoftDeleteFilterActiveComponentState,
|
||||
@@ -27,15 +30,18 @@ export const RecordTableEmptyState = () => {
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
if (isRecordTableAccessDenied) {
|
||||
return <RecordTableEmptyStateAccessDenied />;
|
||||
}
|
||||
|
||||
const noRecordAtAll = totalCount === 0;
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
if (!hasObjectUpdatePermissions) {
|
||||
return <RecordTableEmptyStateReadOnly />;
|
||||
}
|
||||
|
||||
if (isRemote) {
|
||||
return <RecordTableEmptyStateRemote />;
|
||||
} else if (isSoftDeleteFilterActive === true) {
|
||||
if (isSoftDeleteFilterActive === true) {
|
||||
return <RecordTableEmptyStateSoftDelete />;
|
||||
} else if (noRecordAtAll) {
|
||||
return <RecordTableEmptyStateNoGroupNoRecordAtAll />;
|
||||
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
/* oxlint-disable twenty/no-navigate-prefer-link */
|
||||
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { IconSettings } from 'twenty-ui/display';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
export const RecordTableEmptyStateRemote = () => {
|
||||
const navigate = useNavigateSettings();
|
||||
|
||||
const handleButtonClick = () => {
|
||||
navigate(SettingsPath.Integrations);
|
||||
};
|
||||
|
||||
return (
|
||||
<RecordTableEmptyStateDisplay
|
||||
buttonTitle={t`Go to Settings`}
|
||||
subTitle={t`If this is unexpected, please verify your settings.`}
|
||||
title={t`No Data Available for Remote Table`}
|
||||
ButtonIcon={IconSettings}
|
||||
animatedPlaceholderType="noRecord"
|
||||
onClick={handleButtonClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
||||
import { ScrollWrapperDecorator } from '~/testing/decorators/ScrollWrapperDecorator';
|
||||
|
||||
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
|
||||
import { RecordTableEmptyStateRemote } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
import { RecordTableDecorator } from '~/testing/decorators/RecordTableDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'Modules/ObjectRecord/RecordTable/RecordTableEmptyStateRemote',
|
||||
component: RecordTableEmptyStateRemote,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<RecordTableContextProvider
|
||||
recordTableId="persons"
|
||||
viewBarId="view-bar"
|
||||
objectNameSingular="person"
|
||||
>
|
||||
<Story />
|
||||
</RecordTableContextProvider>
|
||||
),
|
||||
ComponentDecorator,
|
||||
MemoryRouterDecorator,
|
||||
RecordTableDecorator,
|
||||
ContextStoreDecorator,
|
||||
SnackBarDecorator,
|
||||
ObjectMetadataItemsDecorator,
|
||||
ScrollWrapperDecorator,
|
||||
],
|
||||
parameters: {
|
||||
recordTableObjectNameSingular: 'person',
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof RecordTableEmptyStateRemote>;
|
||||
|
||||
export const Default: Story = {};
|
||||
+26
-2
@@ -1,4 +1,7 @@
|
||||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { useEffect } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { lastShowPageRecordIdState } from '@/object-record/record-field/ui/states/lastShowPageRecordId';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
@@ -10,10 +13,10 @@ import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useSetRecordTableData } from '@/object-record/record-table/hooks/internal/useSetRecordTableData';
|
||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||
import { isRecordTableAccessDeniedComponentState } from '@/object-record/record-table/states/isRecordTableAccessDeniedComponentState';
|
||||
import { useScrollToPosition } from '@/ui/utilities/scroll/hooks/useScrollToPosition';
|
||||
import { useSetAtomComponentFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentFamilyState';
|
||||
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
export const RecordTableRecordGroupBodyEffect = () => {
|
||||
const { objectNameSingular } = useRecordTableContextOrThrow();
|
||||
@@ -27,9 +30,14 @@ export const RecordTableRecordGroupBodyEffect = () => {
|
||||
isRecordTableInitialLoadingComponentState,
|
||||
);
|
||||
|
||||
const setIsAccessDenied = useSetAtomComponentState(
|
||||
isRecordTableAccessDeniedComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const recordGroupId = useCurrentRecordGroupId();
|
||||
|
||||
const { records, loading, hasNextPage } =
|
||||
const { records, loading, hasNextPage, error } =
|
||||
useRecordIndexTableQuery(objectNameSingular);
|
||||
|
||||
const setRecordIndexHasFetchedAllRecordsByGroup =
|
||||
@@ -43,7 +51,21 @@ export const RecordTableRecordGroupBodyEffect = () => {
|
||||
const { scrollToPosition } = useScrollToPosition();
|
||||
|
||||
useEffect(() => {
|
||||
if (isDefined(error) && CombinedGraphQLErrors.is(error)) {
|
||||
const isForbidden = error.errors.some(
|
||||
(graphqlError) => graphqlError.extensions?.code === 'FORBIDDEN',
|
||||
);
|
||||
|
||||
if (isForbidden) {
|
||||
setIsAccessDenied(true);
|
||||
setIsRecordTableInitialLoading(false);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!loading) {
|
||||
setIsAccessDenied(false);
|
||||
setRecordTableData({
|
||||
records,
|
||||
currentRecordGroupId: recordGroupId,
|
||||
@@ -52,10 +74,12 @@ export const RecordTableRecordGroupBodyEffect = () => {
|
||||
setRecordIndexHasFetchedAllRecordsByGroup(!hasNextPage);
|
||||
}
|
||||
}, [
|
||||
error,
|
||||
hasNextPage,
|
||||
loading,
|
||||
records,
|
||||
recordGroupId,
|
||||
setIsAccessDenied,
|
||||
setRecordIndexHasFetchedAllRecordsByGroup,
|
||||
setIsRecordTableInitialLoading,
|
||||
setRecordTableData,
|
||||
|
||||
+50
-4
@@ -1,5 +1,8 @@
|
||||
import { type ErrorLike } from '@apollo/client';
|
||||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { useCallback } from 'react';
|
||||
import { useStore } from 'jotai';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { useRecordIndexTableLazyQuery } from '@/object-record/record-index/hooks/useRecordIndexTableLazyQuery';
|
||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||
@@ -11,10 +14,10 @@ import { useScrollTableToPosition } from '@/object-record/record-table/hooks/use
|
||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||
import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState';
|
||||
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
|
||||
import { isRecordTableAccessDeniedComponentState } from '@/object-record/record-table/states/isRecordTableAccessDeniedComponentState';
|
||||
import { updateRecordTableCSSVariable } from '@/object-record/record-table/utils/updateRecordTableCSSVariable';
|
||||
import { useLoadRecordsToVirtualRows } from '@/object-record/record-table/virtualization/hooks/useLoadRecordsToVirtualRows';
|
||||
import { useReapplyRowSelection } from '@/object-record/record-table/virtualization/hooks/useReapplyRowSelection';
|
||||
|
||||
import { useResetTableFocuses } from '@/object-record/record-table/virtualization/hooks/useResetTableFocuses';
|
||||
import { useResetVirtualizedRowTreadmill } from '@/object-record/record-table/virtualization/hooks/useResetVirtualizedRowTreadmill';
|
||||
import { dataPagesLoadedComponentState } from '@/object-record/record-table/virtualization/states/dataPagesLoadedComponentState';
|
||||
@@ -31,7 +34,16 @@ import { dataLoadingStatusByRealIndexComponentState } from '@/object-record/reco
|
||||
import { recordIdByRealIndexComponentState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentState';
|
||||
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
||||
import { useAtomComponentSelectorCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorCallbackState';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const isForbiddenError = (error: ErrorLike): boolean => {
|
||||
if (!CombinedGraphQLErrors.is(error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return error.errors.some(
|
||||
(graphqlError) => graphqlError.extensions?.code === 'FORBIDDEN',
|
||||
);
|
||||
};
|
||||
|
||||
export const useTriggerInitialRecordTableDataLoad = () => {
|
||||
const { recordTableId, objectNameSingular } = useRecordTableContextOrThrow();
|
||||
@@ -107,6 +119,11 @@ export const useTriggerInitialRecordTableDataLoad = () => {
|
||||
totalNumberOfRecordsToVirtualizeComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableAccessDenied = useAtomComponentStateCallbackState(
|
||||
isRecordTableAccessDeniedComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const triggerInitialRecordTableDataLoad = useCallback(
|
||||
async ({
|
||||
shouldScrollToStart = true,
|
||||
@@ -120,6 +137,7 @@ export const useTriggerInitialRecordTableDataLoad = () => {
|
||||
}
|
||||
|
||||
store.set(isInitializingVirtualTableDataLoadingCallbackState, true);
|
||||
store.set(isRecordTableAccessDenied, false);
|
||||
|
||||
try {
|
||||
resetTableFocuses();
|
||||
@@ -165,8 +183,23 @@ export const useTriggerInitialRecordTableDataLoad = () => {
|
||||
newDataLoadingStatusByRealIndex,
|
||||
);
|
||||
|
||||
const { records: findManyRecords, totalCount: findManyTotalCount } =
|
||||
await findManyRecordsLazy();
|
||||
const {
|
||||
records: findManyRecords,
|
||||
totalCount: findManyTotalCount,
|
||||
error: queryError,
|
||||
} = await findManyRecordsLazy();
|
||||
|
||||
if (isDefined(queryError) && isForbiddenError(queryError)) {
|
||||
store.set(isRecordTableAccessDenied, true);
|
||||
store.set(totalNumberOfRecordsToVirtualizeCallbackState, 0);
|
||||
store.set(recordIdByRealIndexCallbackState, new Map());
|
||||
store.set(
|
||||
dataLoadingStatusByRealIndexCallbackState,
|
||||
new Map(),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
records = findManyRecords;
|
||||
totalCount = findManyTotalCount;
|
||||
@@ -201,6 +234,18 @@ export const useTriggerInitialRecordTableDataLoad = () => {
|
||||
verticalScrollInPx: 0,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (
|
||||
isDefined(error) &&
|
||||
typeof error === 'object' &&
|
||||
'name' in error &&
|
||||
isForbiddenError(error as ErrorLike)
|
||||
) {
|
||||
store.set(isRecordTableAccessDenied, true);
|
||||
}
|
||||
store.set(totalNumberOfRecordsToVirtualizeCallbackState, 0);
|
||||
store.set(recordIdByRealIndexCallbackState, new Map());
|
||||
store.set(dataLoadingStatusByRealIndexCallbackState, new Map());
|
||||
} finally {
|
||||
store.set(isInitializingVirtualTableDataLoadingCallbackState, false);
|
||||
store.set(isRecordTableInitialLoading, false);
|
||||
@@ -229,6 +274,7 @@ export const useTriggerInitialRecordTableDataLoad = () => {
|
||||
loadRecordsToVirtualRows,
|
||||
reapplyRowSelection,
|
||||
recordTableId,
|
||||
isRecordTableAccessDenied,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -76,12 +76,6 @@ export const useEditPageLayoutWidget = (pageLayoutIdFromProps?: string) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (widgetType === WidgetType.STANDALONE_RICH_TEXT) {
|
||||
setPageLayoutEditingWidgetId(widgetId);
|
||||
closeSidePanelMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
setSidePanelPage(SidePanelPages.CommandMenuDisplay);
|
||||
closeSidePanelMenu();
|
||||
},
|
||||
|
||||
+13
-29
@@ -1,20 +1,8 @@
|
||||
import { useResetContextStoreStates } from '@/command-menu/hooks/useResetContextStoreStates';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { addToNavPayloadRegistryState } from '@/navigation-menu-item/common/states/addToNavPayloadRegistryState';
|
||||
import { pendingInsertionNavigationMenuItemState } from '@/navigation-menu-item/common/states/pendingInsertionNavigationMenuItemState';
|
||||
import { selectedNavigationMenuItemIdInEditModeState } from '@/navigation-menu-item/common/states/selectedNavigationMenuItemIdInEditModeState';
|
||||
import { viewableRecordIdState } from '@/object-record/record-side-panel/states/viewableRecordIdState';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLayoutDraggedAreaComponentState';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
|
||||
import { SIDE_PANEL_COMPONENT_INSTANCE_ID } from '@/side-panel/constants/SidePanelComponentInstanceId';
|
||||
import { SIDE_PANEL_CONTEXT_CHIP_GROUPS_DROPDOWN_ID } from '@/side-panel/constants/SidePanelContextChipGroupsDropdownId';
|
||||
import { SIDE_PANEL_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/side-panel/constants/SidePanelPreviousComponentInstanceId';
|
||||
import { SIDE_PANEL_SELECTABLE_LIST_ID } from '@/side-panel/constants/SidePanelSelectableListId';
|
||||
import { hasUserSelectedSidePanelListItemState } from '@/side-panel/states/hasUserSelectedSidePanelListItemState';
|
||||
import { isSidePanelClosingState } from '@/side-panel/states/isSidePanelClosingState';
|
||||
import { isSidePanelOpenedState } from '@/side-panel/states/isSidePanelOpenedState';
|
||||
import { SIDE_PANEL_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/side-panel/constants/SidePanelPreviousComponentInstanceId';
|
||||
import { useResetContextStoreStates } from '@/command-menu/hooks/useResetContextStoreStates';
|
||||
import { sidePanelNavigationMorphItemsByPageState } from '@/side-panel/states/sidePanelNavigationMorphItemsByPageState';
|
||||
import { sidePanelNavigationStackState } from '@/side-panel/states/sidePanelNavigationStackState';
|
||||
import { sidePanelPageInfoState } from '@/side-panel/states/sidePanelPageInfoState';
|
||||
@@ -22,10 +10,19 @@ import { sidePanelPageState } from '@/side-panel/states/sidePanelPageState';
|
||||
import { sidePanelSearchObjectFilterState } from '@/side-panel/states/sidePanelSearchObjectFilterState';
|
||||
import { sidePanelSearchState } from '@/side-panel/states/sidePanelSearchState';
|
||||
import { sidePanelShowHiddenObjectsState } from '@/side-panel/states/sidePanelShowHiddenObjectsState';
|
||||
import { hasUserSelectedSidePanelListItemState } from '@/side-panel/states/hasUserSelectedSidePanelListItemState';
|
||||
import { isSidePanelClosingState } from '@/side-panel/states/isSidePanelClosingState';
|
||||
import { isSidePanelOpenedState } from '@/side-panel/states/isSidePanelOpenedState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { viewableRecordIdState } from '@/object-record/record-side-panel/states/viewableRecordIdState';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLayoutDraggedAreaComponentState';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { emitSidePanelCloseEvent } from '@/ui/layout/side-panel/utils/emitSidePanelCloseEvent';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
|
||||
import { emitSidePanelCloseEvent } from '@/ui/layout/side-panel/utils/emitSidePanelCloseEvent';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowLogicFunctionTabListComponentId';
|
||||
import { WorkflowLogicFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowLogicFunctionTabId';
|
||||
@@ -44,12 +41,6 @@ export const useSidePanelCloseAnimationCompleteCleanup = () => {
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const resetNavigationMenuItemState = () => {
|
||||
store.set(selectedNavigationMenuItemIdInEditModeState.atom, null);
|
||||
store.set(pendingInsertionNavigationMenuItemState.atom, null);
|
||||
store.set(addToNavPayloadRegistryState.atom, new Map());
|
||||
};
|
||||
|
||||
const sidePanelCloseAnimationCompleteCleanup = useCallback(
|
||||
(options?: { emitSidePanelCloseEvent?: boolean }) => {
|
||||
closeDropdown(SIDE_PANEL_CONTEXT_CHIP_GROUPS_DROPDOWN_ID);
|
||||
@@ -119,7 +110,6 @@ export const useSidePanelCloseAnimationCompleteCleanup = () => {
|
||||
store.set(sidePanelShowHiddenObjectsState.atom, false);
|
||||
store.set(sidePanelNavigationMorphItemsByPageState.atom, new Map());
|
||||
store.set(sidePanelNavigationStackState.atom, []);
|
||||
resetNavigationMenuItemState();
|
||||
resetSelectedItem();
|
||||
store.set(hasUserSelectedSidePanelListItemState.atom, false);
|
||||
|
||||
@@ -146,13 +136,7 @@ export const useSidePanelCloseAnimationCompleteCleanup = () => {
|
||||
);
|
||||
}
|
||||
},
|
||||
[
|
||||
closeDropdown,
|
||||
resetContextStoreStates,
|
||||
resetNavigationMenuItemState,
|
||||
resetSelectedItem,
|
||||
store,
|
||||
],
|
||||
[closeDropdown, resetContextStoreStates, resetSelectedItem, store],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
|
||||
import { addToNavPayloadRegistryState } from '@/navigation-menu-item/common/states/addToNavPayloadRegistryState';
|
||||
import { selectedNavigationMenuItemIdInEditModeState } from '@/navigation-menu-item/common/states/selectedNavigationMenuItemIdInEditModeState';
|
||||
import { SIDE_PANEL_FOCUS_ID } from '@/side-panel/constants/SidePanelFocusId';
|
||||
import { useNavigateSidePanel } from '@/side-panel/hooks/useNavigateSidePanel';
|
||||
@@ -28,6 +29,7 @@ export const useSidePanelMenu = () => {
|
||||
const isSidePanelOpened = store.get(isSidePanelOpenedState.atom);
|
||||
|
||||
if (isSidePanelOpened) {
|
||||
store.set(addToNavPayloadRegistryState.atom, new Map());
|
||||
store.set(isSidePanelOpenedState.atom, false);
|
||||
store.set(isSidePanelClosingState.atom, true);
|
||||
closeAnyOpenDropdown();
|
||||
|
||||
+20
-11
@@ -92,17 +92,26 @@ export const useComputeRecordRelationFilterLabelValue = ({
|
||||
? `${labelValueItems.length} ${relationObjectLabelPlural.toLowerCase()}`
|
||||
: labelValueItems.join(', ');
|
||||
|
||||
if (labelValueItems.length > 0) {
|
||||
return {
|
||||
labelValue: getRecordFilterChipLabelValue({
|
||||
recordFilter: {
|
||||
...recordFilter,
|
||||
displayValue: filterDisplayValue,
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const hasUnresolvedRecords =
|
||||
selectedRecordIds.length > 0 && selectedRecords.length === 0;
|
||||
|
||||
return {
|
||||
labelValue:
|
||||
labelValueItems.length > 0
|
||||
? getRecordFilterChipLabelValue({
|
||||
recordFilter: {
|
||||
...recordFilter,
|
||||
displayValue: filterDisplayValue,
|
||||
},
|
||||
})
|
||||
: getRecordFilterChipLabelValue({
|
||||
recordFilter,
|
||||
}),
|
||||
labelValue: getRecordFilterChipLabelValue({
|
||||
recordFilter: {
|
||||
...recordFilter,
|
||||
...(hasUnresolvedRecords ? { displayValue: t`Not found` } : {}),
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
+431
@@ -0,0 +1,431 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import {
|
||||
CoreObjectNameSingular,
|
||||
FieldMetadataType,
|
||||
PageLayoutTabLayoutMode,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { FieldDisplayMode } from 'src/engine/metadata-modules/page-layout-widget/enums/field-display-mode.enum';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import {
|
||||
GRID_POSITIONS,
|
||||
VERTICAL_LIST_LAYOUT_POSITIONS,
|
||||
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
const HOME_TAB_POSITION = 10;
|
||||
|
||||
const isActivityTargetField = (
|
||||
fieldName: string,
|
||||
objectNameSingular: string,
|
||||
): boolean =>
|
||||
(objectNameSingular === CoreObjectNameSingular.Note &&
|
||||
fieldName === 'noteTargets') ||
|
||||
(objectNameSingular === CoreObjectNameSingular.Task &&
|
||||
fieldName === 'taskTargets');
|
||||
|
||||
const isJunctionRelationField = (field: FlatFieldMetadata): boolean => {
|
||||
const settings = field.settings as
|
||||
| { junctionTargetFieldId?: string }
|
||||
| undefined;
|
||||
|
||||
return (
|
||||
isDefined(settings?.junctionTargetFieldId) &&
|
||||
typeof settings?.junctionTargetFieldId === 'string' &&
|
||||
settings.junctionTargetFieldId.length > 0
|
||||
);
|
||||
};
|
||||
|
||||
const isRelationTargetAvailable = (
|
||||
targetObject: FlatObjectMetadata | undefined,
|
||||
): boolean => {
|
||||
if (!isDefined(targetObject)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetObject.isRemote) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
targetObject.isSystem &&
|
||||
targetObject.nameSingular !== CoreObjectNameSingular.WorkspaceMember
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-20:backfill-field-widgets',
|
||||
description:
|
||||
'Backfill FIELD widgets for relation fields in existing page layouts',
|
||||
})
|
||||
export class BackfillFieldWidgetsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Starting backfill of FIELD widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const { twentyStandardFlatApplication, workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatPageLayoutMaps,
|
||||
flatPageLayoutTabMaps,
|
||||
flatPageLayoutWidgetMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatPageLayoutMaps',
|
||||
'flatPageLayoutTabMaps',
|
||||
'flatPageLayoutWidgetMaps',
|
||||
]);
|
||||
|
||||
// Build a set of fieldMetadataIds that already have a FIELD widget
|
||||
const existingFieldWidgetFieldIds = new Set<string>();
|
||||
|
||||
for (const widget of Object.values(
|
||||
flatPageLayoutWidgetMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (
|
||||
isDefined(widget) &&
|
||||
widget.configuration?.configurationType ===
|
||||
WidgetConfigurationType.FIELD &&
|
||||
isDefined(widget.configuration.fieldMetadataId)
|
||||
) {
|
||||
existingFieldWidgetFieldIds.add(widget.configuration.fieldMetadataId);
|
||||
}
|
||||
}
|
||||
|
||||
// Build object ID → objectMetadata map
|
||||
const objectById = new Map<string, FlatObjectMetadata>();
|
||||
|
||||
for (const obj of Object.values(
|
||||
flatObjectMetadataMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (isDefined(obj)) {
|
||||
objectById.set(obj.id, obj);
|
||||
}
|
||||
}
|
||||
|
||||
// Build object ID → RECORD_PAGE layout map
|
||||
const recordPageLayoutByObjectId = new Map<
|
||||
string,
|
||||
{ id: string; universalIdentifier: string }
|
||||
>();
|
||||
|
||||
for (const layout of Object.values(
|
||||
flatPageLayoutMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (
|
||||
isDefined(layout) &&
|
||||
layout.type === PageLayoutType.RECORD_PAGE &&
|
||||
isDefined(layout.objectMetadataId)
|
||||
) {
|
||||
recordPageLayoutByObjectId.set(layout.objectMetadataId, {
|
||||
id: layout.id,
|
||||
universalIdentifier: layout.universalIdentifier,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Build pageLayoutId → home tab map
|
||||
const homeTabByPageLayoutId = new Map<
|
||||
string,
|
||||
{
|
||||
id: string;
|
||||
universalIdentifier: string;
|
||||
widgetCount: number;
|
||||
}
|
||||
>();
|
||||
|
||||
for (const tab of Object.values(
|
||||
flatPageLayoutTabMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (
|
||||
isDefined(tab) &&
|
||||
tab.position === HOME_TAB_POSITION &&
|
||||
tab.layoutMode === PageLayoutTabLayoutMode.VERTICAL_LIST
|
||||
) {
|
||||
homeTabByPageLayoutId.set(tab.pageLayoutId, {
|
||||
id: tab.id,
|
||||
universalIdentifier: tab.universalIdentifier,
|
||||
widgetCount: tab.widgetIds?.length ?? 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Group fields by objectMetadataId
|
||||
const fieldsByObjectId = new Map<string, FlatFieldMetadata[]>();
|
||||
|
||||
// Map morphId → all field IDs sharing that morphId (for dedup)
|
||||
const fieldIdsByMorphId = new Map<string, string[]>();
|
||||
|
||||
for (const field of Object.values(
|
||||
flatFieldMetadataMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (!isDefined(field) || !isDefined(field.objectMetadataId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const list = fieldsByObjectId.get(field.objectMetadataId) ?? [];
|
||||
|
||||
list.push(field);
|
||||
fieldsByObjectId.set(field.objectMetadataId, list);
|
||||
|
||||
if (
|
||||
field.type === FieldMetadataType.MORPH_RELATION &&
|
||||
isDefined(field.morphId)
|
||||
) {
|
||||
const morphFieldIds = fieldIdsByMorphId.get(field.morphId) ?? [];
|
||||
|
||||
morphFieldIds.push(field.id);
|
||||
fieldIdsByMorphId.set(field.morphId, morphFieldIds);
|
||||
}
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const standardWidgetsToCreate: FlatPageLayoutWidget[] = [];
|
||||
const customWidgetsToCreate: FlatPageLayoutWidget[] = [];
|
||||
const processedMorphIds = new Set<string>();
|
||||
|
||||
for (const object of objectById.values()) {
|
||||
const pageLayout = recordPageLayoutByObjectId.get(object.id);
|
||||
|
||||
if (!isDefined(pageLayout)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const homeTab = homeTabByPageLayoutId.get(pageLayout.id);
|
||||
|
||||
if (!isDefined(homeTab)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const flatApplication: FlatApplication = object.isCustom
|
||||
? workspaceCustomFlatApplication
|
||||
: twentyStandardFlatApplication;
|
||||
|
||||
const targetList: FlatPageLayoutWidget[] = object.isCustom
|
||||
? customWidgetsToCreate
|
||||
: standardWidgetsToCreate;
|
||||
|
||||
const fields = fieldsByObjectId.get(object.id) ?? [];
|
||||
let nextWidgetIndex = homeTab.widgetCount;
|
||||
|
||||
for (const field of fields) {
|
||||
if (
|
||||
field.type !== FieldMetadataType.RELATION &&
|
||||
field.type !== FieldMetadataType.MORPH_RELATION
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isActivityTargetField(field.name, object.nameSingular)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isJunctionRelationField(field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.RELATION) {
|
||||
const targetObject = isDefined(field.relationTargetObjectMetadataId)
|
||||
? objectById.get(field.relationTargetObjectMetadataId)
|
||||
: undefined;
|
||||
|
||||
if (!isRelationTargetAvailable(targetObject)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// For morph relations, skip if any sibling field (same morphId)
|
||||
// already has a widget or was already processed in this run
|
||||
if (
|
||||
field.type === FieldMetadataType.MORPH_RELATION &&
|
||||
isDefined(field.morphId)
|
||||
) {
|
||||
if (processedMorphIds.has(field.morphId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const siblingFieldIds = fieldIdsByMorphId.get(field.morphId) ?? [];
|
||||
const alreadyHasWidget = siblingFieldIds.some((id) =>
|
||||
existingFieldWidgetFieldIds.has(id),
|
||||
);
|
||||
|
||||
if (alreadyHasWidget) {
|
||||
continue;
|
||||
}
|
||||
|
||||
processedMorphIds.add(field.morphId);
|
||||
}
|
||||
|
||||
if (existingFieldWidgetFieldIds.has(field.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const widget: FlatPageLayoutWidget = {
|
||||
id: v4(),
|
||||
universalIdentifier: v4(),
|
||||
applicationId: flatApplication.id,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
workspaceId,
|
||||
pageLayoutTabId: homeTab.id,
|
||||
pageLayoutTabUniversalIdentifier: homeTab.universalIdentifier,
|
||||
title: field.label,
|
||||
type: WidgetType.FIELD,
|
||||
gridPosition: { ...GRID_POSITIONS.FULL_WIDTH },
|
||||
position: {
|
||||
...VERTICAL_LIST_LAYOUT_POSITIONS.SECOND,
|
||||
index: nextWidgetIndex,
|
||||
},
|
||||
configuration: {
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: field.id,
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
universalConfiguration: {
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: field.universalIdentifier,
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
objectMetadataId: object.id,
|
||||
objectMetadataUniversalIdentifier: object.universalIdentifier,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
conditionalDisplay: null,
|
||||
overrides: null,
|
||||
};
|
||||
|
||||
targetList.push(widget);
|
||||
existingFieldWidgetFieldIds.add(field.id);
|
||||
nextWidgetIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
const totalWidgets =
|
||||
standardWidgetsToCreate.length + customWidgetsToCreate.length;
|
||||
|
||||
if (totalWidgets === 0) {
|
||||
this.logger.log(
|
||||
`All FIELD widgets already exist for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Found ${totalWidgets} FIELD widget(s) to create for workspace ${workspaceId} (${standardWidgetsToCreate.length} standard, ${customWidgetsToCreate.length} custom)`,
|
||||
);
|
||||
|
||||
if (isDryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would create ${totalWidgets} FIELD widget(s) for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (standardWidgetsToCreate.length > 0) {
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: standardWidgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
twentyStandardFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (result.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create standard FIELD widgets:\n${JSON.stringify(result, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create standard FIELD widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (customWidgetsToCreate.length > 0) {
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: customWidgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (result.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create custom FIELD widgets:\n${JSON.stringify(result, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create custom FIELD widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully created ${totalWidgets} FIELD widget(s) for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
-868
@@ -1,868 +0,0 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import {
|
||||
CoreObjectNameSingular,
|
||||
FeatureFlagKey,
|
||||
FieldMetadataType,
|
||||
PageLayoutTabLayoutMode,
|
||||
ViewType,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
|
||||
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type';
|
||||
import { computeFlatDefaultRecordPageLayoutToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-default-record-page-layout-to-create.util';
|
||||
import { computeFlatRecordPageFieldsViewToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-record-page-fields-view-to-create.util';
|
||||
import { computeFlatViewFieldsToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util';
|
||||
import { FieldDisplayMode } from 'src/engine/metadata-modules/page-layout-widget/enums/field-display-mode.enum';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import {
|
||||
GRID_POSITIONS,
|
||||
VERTICAL_LIST_LAYOUT_POSITIONS,
|
||||
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
|
||||
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
import { type UniversalFlatViewField } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field.type';
|
||||
import { type UniversalFlatView } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view.type';
|
||||
|
||||
const HOME_TAB_POSITION = 10;
|
||||
|
||||
const isActivityTargetField = (
|
||||
fieldName: string,
|
||||
objectNameSingular: string,
|
||||
): boolean =>
|
||||
(objectNameSingular === CoreObjectNameSingular.Note &&
|
||||
fieldName === 'noteTargets') ||
|
||||
(objectNameSingular === CoreObjectNameSingular.Task &&
|
||||
fieldName === 'taskTargets');
|
||||
|
||||
const isJunctionRelationField = (field: FlatFieldMetadata): boolean => {
|
||||
const settings = field.settings as
|
||||
| { junctionTargetFieldId?: string }
|
||||
| undefined;
|
||||
|
||||
return (
|
||||
isDefined(settings?.junctionTargetFieldId) &&
|
||||
typeof settings?.junctionTargetFieldId === 'string' &&
|
||||
settings.junctionTargetFieldId.length > 0
|
||||
);
|
||||
};
|
||||
|
||||
const isRelationTargetAvailable = (
|
||||
targetObject: FlatObjectMetadata | undefined,
|
||||
): boolean => {
|
||||
if (!isDefined(targetObject)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetObject.isRemote) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
targetObject.isSystem &&
|
||||
targetObject.nameSingular !== CoreObjectNameSingular.WorkspaceMember
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-20:backfill-page-layouts-and-fields-widget-view-fields',
|
||||
description:
|
||||
'Backfill RECORD_PAGE page layouts, sync FIELDS_WIDGET view fields, create FIELD widgets, and enable IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED',
|
||||
})
|
||||
export class BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Starting backfill of page layouts and field widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const isAlreadyEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isAlreadyEnabled) {
|
||||
this.logger.log(
|
||||
`IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED already enabled for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would create page layouts, sync view fields, create FIELD widgets and enable IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { twentyStandardFlatApplication, workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
await this.backfillStandardPageLayoutsAndSyncViews({
|
||||
workspaceId,
|
||||
twentyStandardFlatApplication,
|
||||
});
|
||||
|
||||
await this.backfillCustomObjectPageLayouts({
|
||||
workspaceId,
|
||||
workspaceCustomFlatApplication,
|
||||
});
|
||||
|
||||
await this.backfillFieldWidgets({
|
||||
workspaceId,
|
||||
twentyStandardFlatApplication,
|
||||
workspaceCustomFlatApplication,
|
||||
});
|
||||
|
||||
await this.featureFlagService.enableFeatureFlags(
|
||||
[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Successfully backfilled page layouts and field widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
private async backfillStandardPageLayoutsAndSyncViews({
|
||||
workspaceId,
|
||||
twentyStandardFlatApplication,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
twentyStandardFlatApplication: FlatApplication;
|
||||
}): Promise<void> {
|
||||
const { allFlatEntityMaps: standardAllFlatEntityMaps } =
|
||||
computeTwentyStandardApplicationAllFlatEntityMaps({
|
||||
shouldIncludeRecordPageLayouts: true,
|
||||
now: new Date().toISOString(),
|
||||
workspaceId,
|
||||
twentyStandardApplicationId: twentyStandardFlatApplication.id,
|
||||
});
|
||||
|
||||
const recordPageLayoutUniversalIdentifiers = new Set<string>();
|
||||
|
||||
const recordPageLayoutsFromStandard = Object.values(
|
||||
standardAllFlatEntityMaps.flatPageLayoutMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((pageLayout) => {
|
||||
if (pageLayout.type !== PageLayoutType.RECORD_PAGE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
recordPageLayoutUniversalIdentifiers.add(
|
||||
pageLayout.universalIdentifier,
|
||||
);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const tabUniversalIdentifiers = new Set<string>();
|
||||
|
||||
const tabsFromStandard = Object.values(
|
||||
standardAllFlatEntityMaps.flatPageLayoutTabMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((tab) => {
|
||||
if (
|
||||
!recordPageLayoutUniversalIdentifiers.has(
|
||||
tab.pageLayoutUniversalIdentifier,
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tabUniversalIdentifiers.add(tab.universalIdentifier);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const widgetsFromStandard = Object.values(
|
||||
standardAllFlatEntityMaps.flatPageLayoutWidgetMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((widget) =>
|
||||
tabUniversalIdentifiers.has(widget.pageLayoutTabUniversalIdentifier),
|
||||
);
|
||||
|
||||
const {
|
||||
flatPageLayoutMaps: existingFlatPageLayoutMaps,
|
||||
flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps,
|
||||
flatPageLayoutWidgetMaps: existingFlatPageLayoutWidgetMaps,
|
||||
flatViewMaps: existingFlatViewMaps,
|
||||
flatViewFieldMaps: existingFlatViewFieldMaps,
|
||||
flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatPageLayoutMaps',
|
||||
'flatPageLayoutTabMaps',
|
||||
'flatPageLayoutWidgetMaps',
|
||||
'flatViewMaps',
|
||||
'flatViewFieldMaps',
|
||||
'flatViewFieldGroupMaps',
|
||||
]);
|
||||
|
||||
// Filter out page layouts, tabs, and widgets that already exist in the workspace
|
||||
const pageLayoutsToCreate = recordPageLayoutsFromStandard.filter(
|
||||
(pageLayout) =>
|
||||
!isDefined(
|
||||
existingFlatPageLayoutMaps.byUniversalIdentifier[
|
||||
pageLayout.universalIdentifier
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
const pageLayoutTabsToCreate = tabsFromStandard.filter(
|
||||
(tab) =>
|
||||
!isDefined(
|
||||
existingFlatPageLayoutTabMaps.byUniversalIdentifier[
|
||||
tab.universalIdentifier
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
const pageLayoutWidgetsToCreate = widgetsFromStandard.filter(
|
||||
(widget) =>
|
||||
!isDefined(
|
||||
existingFlatPageLayoutWidgetMaps.byUniversalIdentifier[
|
||||
widget.universalIdentifier
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
// Collect all standard FIELDS_WIDGET view universal identifiers
|
||||
// This includes both new views to create AND existing views (created by 1-19)
|
||||
const allStandardFieldsWidgetViewUniversalIds = new Set<string>();
|
||||
|
||||
const viewsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatViewMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((view) => {
|
||||
if (view.type !== ViewType.FIELDS_WIDGET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
allStandardFieldsWidgetViewUniversalIds.add(view.universalIdentifier);
|
||||
|
||||
if (
|
||||
isDefined(
|
||||
existingFlatViewMaps.byUniversalIdentifier[
|
||||
view.universalIdentifier
|
||||
],
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Delete all existing viewFields for standard FIELDS_WIDGET views and recreate from current standard.
|
||||
// This ensures positions, visibility, and groups match the current standard definition.
|
||||
const viewFieldsToDelete = Object.values(
|
||||
existingFlatViewFieldMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((viewField) =>
|
||||
allStandardFieldsWidgetViewUniversalIds.has(
|
||||
viewField.viewUniversalIdentifier,
|
||||
),
|
||||
);
|
||||
|
||||
const viewFieldsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatViewFieldMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((viewField) =>
|
||||
allStandardFieldsWidgetViewUniversalIds.has(
|
||||
viewField.viewUniversalIdentifier,
|
||||
),
|
||||
);
|
||||
|
||||
// Same delete-and-recreate approach for viewFieldGroups
|
||||
const viewFieldGroupsToDelete = Object.values(
|
||||
existingFlatViewFieldGroupMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((viewFieldGroup) =>
|
||||
allStandardFieldsWidgetViewUniversalIds.has(
|
||||
viewFieldGroup.viewUniversalIdentifier,
|
||||
),
|
||||
);
|
||||
|
||||
const viewFieldGroupsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatViewFieldGroupMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((viewFieldGroup) =>
|
||||
allStandardFieldsWidgetViewUniversalIds.has(
|
||||
viewFieldGroup.viewUniversalIdentifier,
|
||||
),
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Creating ${pageLayoutsToCreate.length} page layouts, ${viewsToCreate.length} views, deleting ${viewFieldsToDelete.length} and creating ${viewFieldsToCreate.length} view fields, deleting ${viewFieldGroupsToDelete.length} and creating ${viewFieldGroupsToCreate.length} view field groups for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayout: {
|
||||
flatEntityToCreate: pageLayoutsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: pageLayoutTabsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: pageLayoutWidgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
view: {
|
||||
flatEntityToCreate: viewsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
viewField: {
|
||||
flatEntityToCreate: viewFieldsToCreate,
|
||||
flatEntityToDelete: viewFieldsToDelete,
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
viewFieldGroup: {
|
||||
flatEntityToCreate: viewFieldGroupsToCreate,
|
||||
flatEntityToDelete: viewFieldGroupsToDelete,
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
twentyStandardFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (validateAndBuildResult.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create standard page layouts and sync view fields:\n${JSON.stringify(validateAndBuildResult, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create standard page layouts and sync view fields for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async backfillCustomObjectPageLayouts({
|
||||
workspaceId,
|
||||
workspaceCustomFlatApplication,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workspaceCustomFlatApplication: FlatApplication;
|
||||
}): Promise<void> {
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatPageLayoutMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatPageLayoutMaps',
|
||||
]);
|
||||
|
||||
const existingPageLayouts = Object.values(
|
||||
flatPageLayoutMaps.byUniversalIdentifier,
|
||||
).filter(isDefined);
|
||||
|
||||
const objectIdsWithRecordPageLayout = new Set(
|
||||
existingPageLayouts
|
||||
.filter(
|
||||
(layout: FlatPageLayout) =>
|
||||
layout.type === PageLayoutType.RECORD_PAGE &&
|
||||
isDefined(layout.objectMetadataId),
|
||||
)
|
||||
.map((layout: FlatPageLayout) => layout.objectMetadataId),
|
||||
);
|
||||
|
||||
const customObjectsWithoutPageLayout = Object.values(
|
||||
flatObjectMetadataMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(objectMetadata) =>
|
||||
objectMetadata.isCustom &&
|
||||
!objectMetadata.isRemote &&
|
||||
!objectIdsWithRecordPageLayout.has(objectMetadata.id),
|
||||
);
|
||||
|
||||
if (customObjectsWithoutPageLayout.length === 0) {
|
||||
this.logger.log(
|
||||
`No custom objects without page layouts found for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Creating page layouts for ${customObjectsWithoutPageLayout.length} custom object(s) in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const allCustomPageLayoutsToCreate: FlatPageLayout[] = [];
|
||||
const allCustomPageLayoutTabsToCreate: FlatPageLayoutTab[] = [];
|
||||
const allCustomPageLayoutWidgetsToCreate: FlatPageLayoutWidget[] = [];
|
||||
const allCustomViewsToCreate: (UniversalFlatView & { id: string })[] = [];
|
||||
const allCustomViewFieldsToCreate: UniversalFlatViewField[] = [];
|
||||
|
||||
for (const customObject of customObjectsWithoutPageLayout) {
|
||||
const flatRecordPageFieldsView = computeFlatRecordPageFieldsViewToCreate({
|
||||
objectMetadata: customObject,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
});
|
||||
|
||||
const objectFieldMetadatas = Object.values(
|
||||
flatFieldMetadataMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((field) => field.objectMetadataId === customObject.id);
|
||||
|
||||
const viewFields = computeFlatViewFieldsToCreate({
|
||||
objectFlatFieldMetadatas: objectFieldMetadatas,
|
||||
viewUniversalIdentifier: flatRecordPageFieldsView.universalIdentifier,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
customObject.labelIdentifierFieldMetadataUniversalIdentifier,
|
||||
excludeLabelIdentifier: true,
|
||||
});
|
||||
|
||||
const { pageLayouts, pageLayoutTabs, pageLayoutWidgets } =
|
||||
computeFlatDefaultRecordPageLayoutToCreate({
|
||||
objectMetadata: customObject,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
recordPageFieldsView: flatRecordPageFieldsView,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
allCustomPageLayoutsToCreate.push(...pageLayouts);
|
||||
allCustomPageLayoutTabsToCreate.push(...pageLayoutTabs);
|
||||
allCustomPageLayoutWidgetsToCreate.push(...pageLayoutWidgets);
|
||||
allCustomViewsToCreate.push(flatRecordPageFieldsView);
|
||||
allCustomViewFieldsToCreate.push(...viewFields);
|
||||
}
|
||||
|
||||
const customValidateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayout: {
|
||||
flatEntityToCreate: allCustomPageLayoutsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: allCustomPageLayoutTabsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: allCustomPageLayoutWidgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
view: {
|
||||
flatEntityToCreate: allCustomViewsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
viewField: {
|
||||
flatEntityToCreate: allCustomViewFieldsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (customValidateAndBuildResult.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create custom object page layouts:\n${JSON.stringify(customValidateAndBuildResult, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create custom object page layouts for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully created page layouts for ${customObjectsWithoutPageLayout.length} custom object(s) in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
private async backfillFieldWidgets({
|
||||
workspaceId,
|
||||
twentyStandardFlatApplication,
|
||||
workspaceCustomFlatApplication,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
twentyStandardFlatApplication: FlatApplication;
|
||||
workspaceCustomFlatApplication: FlatApplication;
|
||||
}): Promise<void> {
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatPageLayoutMaps,
|
||||
flatPageLayoutTabMaps,
|
||||
flatPageLayoutWidgetMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatPageLayoutMaps',
|
||||
'flatPageLayoutTabMaps',
|
||||
'flatPageLayoutWidgetMaps',
|
||||
]);
|
||||
|
||||
// Build a set of fieldMetadataIds that already have a FIELD widget
|
||||
const existingFieldWidgetFieldIds = new Set<string>();
|
||||
|
||||
for (const widget of Object.values(
|
||||
flatPageLayoutWidgetMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (
|
||||
isDefined(widget) &&
|
||||
widget.configuration?.configurationType ===
|
||||
WidgetConfigurationType.FIELD &&
|
||||
isDefined(widget.configuration.fieldMetadataId)
|
||||
) {
|
||||
existingFieldWidgetFieldIds.add(widget.configuration.fieldMetadataId);
|
||||
}
|
||||
}
|
||||
|
||||
// Build object ID → objectMetadata map
|
||||
const objectById = new Map<string, FlatObjectMetadata>();
|
||||
|
||||
for (const obj of Object.values(
|
||||
flatObjectMetadataMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (isDefined(obj)) {
|
||||
objectById.set(obj.id, obj);
|
||||
}
|
||||
}
|
||||
|
||||
// Build object ID → RECORD_PAGE layout map
|
||||
const recordPageLayoutByObjectId = new Map<
|
||||
string,
|
||||
{ id: string; universalIdentifier: string }
|
||||
>();
|
||||
|
||||
for (const layout of Object.values(
|
||||
flatPageLayoutMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (
|
||||
isDefined(layout) &&
|
||||
layout.type === PageLayoutType.RECORD_PAGE &&
|
||||
isDefined(layout.objectMetadataId)
|
||||
) {
|
||||
recordPageLayoutByObjectId.set(layout.objectMetadataId, {
|
||||
id: layout.id,
|
||||
universalIdentifier: layout.universalIdentifier,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Build pageLayoutId → home tab map
|
||||
const homeTabByPageLayoutId = new Map<
|
||||
string,
|
||||
{
|
||||
id: string;
|
||||
universalIdentifier: string;
|
||||
widgetCount: number;
|
||||
}
|
||||
>();
|
||||
|
||||
for (const tab of Object.values(
|
||||
flatPageLayoutTabMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (
|
||||
isDefined(tab) &&
|
||||
tab.position === HOME_TAB_POSITION &&
|
||||
tab.layoutMode === PageLayoutTabLayoutMode.VERTICAL_LIST
|
||||
) {
|
||||
homeTabByPageLayoutId.set(tab.pageLayoutId, {
|
||||
id: tab.id,
|
||||
universalIdentifier: tab.universalIdentifier,
|
||||
widgetCount: tab.widgetIds?.length ?? 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Group fields by objectMetadataId
|
||||
const fieldsByObjectId = new Map<string, FlatFieldMetadata[]>();
|
||||
|
||||
// Map morphId → all field IDs sharing that morphId (for dedup)
|
||||
const fieldIdsByMorphId = new Map<string, string[]>();
|
||||
|
||||
for (const field of Object.values(
|
||||
flatFieldMetadataMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (!isDefined(field) || !isDefined(field.objectMetadataId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const list = fieldsByObjectId.get(field.objectMetadataId) ?? [];
|
||||
|
||||
list.push(field);
|
||||
fieldsByObjectId.set(field.objectMetadataId, list);
|
||||
|
||||
if (
|
||||
field.type === FieldMetadataType.MORPH_RELATION &&
|
||||
isDefined(field.morphId)
|
||||
) {
|
||||
const morphFieldIds = fieldIdsByMorphId.get(field.morphId) ?? [];
|
||||
|
||||
morphFieldIds.push(field.id);
|
||||
fieldIdsByMorphId.set(field.morphId, morphFieldIds);
|
||||
}
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const standardWidgetsToCreate: FlatPageLayoutWidget[] = [];
|
||||
const customWidgetsToCreate: FlatPageLayoutWidget[] = [];
|
||||
const processedMorphIds = new Set<string>();
|
||||
|
||||
for (const object of objectById.values()) {
|
||||
const pageLayout = recordPageLayoutByObjectId.get(object.id);
|
||||
|
||||
if (!isDefined(pageLayout)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const homeTab = homeTabByPageLayoutId.get(pageLayout.id);
|
||||
|
||||
if (!isDefined(homeTab)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const flatApplication: FlatApplication = object.isCustom
|
||||
? workspaceCustomFlatApplication
|
||||
: twentyStandardFlatApplication;
|
||||
|
||||
const targetList: FlatPageLayoutWidget[] = object.isCustom
|
||||
? customWidgetsToCreate
|
||||
: standardWidgetsToCreate;
|
||||
|
||||
const fields = fieldsByObjectId.get(object.id) ?? [];
|
||||
let nextWidgetIndex = homeTab.widgetCount;
|
||||
|
||||
for (const field of fields) {
|
||||
if (
|
||||
field.type !== FieldMetadataType.RELATION &&
|
||||
field.type !== FieldMetadataType.MORPH_RELATION
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isActivityTargetField(field.name, object.nameSingular)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isJunctionRelationField(field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.RELATION) {
|
||||
const targetObject = isDefined(field.relationTargetObjectMetadataId)
|
||||
? objectById.get(field.relationTargetObjectMetadataId)
|
||||
: undefined;
|
||||
|
||||
if (!isRelationTargetAvailable(targetObject)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// For morph relations, skip if any sibling field (same morphId)
|
||||
// already has a widget or was already processed in this run
|
||||
if (
|
||||
field.type === FieldMetadataType.MORPH_RELATION &&
|
||||
isDefined(field.morphId)
|
||||
) {
|
||||
if (processedMorphIds.has(field.morphId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const siblingFieldIds = fieldIdsByMorphId.get(field.morphId) ?? [];
|
||||
const alreadyHasWidget = siblingFieldIds.some((id) =>
|
||||
existingFieldWidgetFieldIds.has(id),
|
||||
);
|
||||
|
||||
if (alreadyHasWidget) {
|
||||
continue;
|
||||
}
|
||||
|
||||
processedMorphIds.add(field.morphId);
|
||||
}
|
||||
|
||||
if (existingFieldWidgetFieldIds.has(field.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const widget: FlatPageLayoutWidget = {
|
||||
id: v4(),
|
||||
universalIdentifier: v4(),
|
||||
applicationId: flatApplication.id,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
workspaceId,
|
||||
pageLayoutTabId: homeTab.id,
|
||||
pageLayoutTabUniversalIdentifier: homeTab.universalIdentifier,
|
||||
title: field.label,
|
||||
type: WidgetType.FIELD,
|
||||
gridPosition: { ...GRID_POSITIONS.FULL_WIDTH },
|
||||
position: {
|
||||
...VERTICAL_LIST_LAYOUT_POSITIONS.SECOND,
|
||||
index: nextWidgetIndex,
|
||||
},
|
||||
configuration: {
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: field.id,
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
universalConfiguration: {
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: field.universalIdentifier,
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
objectMetadataId: object.id,
|
||||
objectMetadataUniversalIdentifier: object.universalIdentifier,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
conditionalDisplay: null,
|
||||
overrides: null,
|
||||
};
|
||||
|
||||
targetList.push(widget);
|
||||
existingFieldWidgetFieldIds.add(field.id);
|
||||
nextWidgetIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
const totalWidgets =
|
||||
standardWidgetsToCreate.length + customWidgetsToCreate.length;
|
||||
|
||||
if (totalWidgets === 0) {
|
||||
this.logger.log(
|
||||
`All FIELD widgets already exist for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Found ${totalWidgets} FIELD widget(s) to create for workspace ${workspaceId} (${standardWidgetsToCreate.length} standard, ${customWidgetsToCreate.length} custom)`,
|
||||
);
|
||||
|
||||
if (standardWidgetsToCreate.length > 0) {
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: standardWidgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
twentyStandardFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (result.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create standard FIELD widgets:\n${JSON.stringify(result, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create standard FIELD widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (customWidgetsToCreate.length > 0) {
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: customWidgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (result.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create custom FIELD widgets:\n${JSON.stringify(result, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create custom FIELD widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully created ${totalWidgets} FIELD widget(s) for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+485
@@ -0,0 +1,485 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { FeatureFlagKey, ViewType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
|
||||
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type';
|
||||
import { computeFlatDefaultRecordPageLayoutToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-default-record-page-layout-to-create.util';
|
||||
import { computeFlatRecordPageFieldsViewToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-record-page-fields-view-to-create.util';
|
||||
import { computeFlatViewFieldsToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
import { type UniversalFlatViewField } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field.type';
|
||||
import { type UniversalFlatView } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view.type';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-20:backfill-page-layouts',
|
||||
description:
|
||||
'Backfill RECORD_PAGE page layouts for legacy workspaces and enable the IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED feature flag',
|
||||
})
|
||||
export class BackfillPageLayoutsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Starting backfill of page layouts for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const isAlreadyEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isAlreadyEnabled) {
|
||||
this.logger.log(
|
||||
`IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED already enabled for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { twentyStandardFlatApplication, workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
await this.backfillStandardObjectPageLayouts({
|
||||
workspaceId,
|
||||
twentyStandardFlatApplication,
|
||||
isDryRun,
|
||||
});
|
||||
|
||||
await this.backfillCustomObjectPageLayouts({
|
||||
workspaceId,
|
||||
workspaceCustomFlatApplication,
|
||||
isDryRun,
|
||||
});
|
||||
|
||||
if (!isDryRun) {
|
||||
await this.featureFlagService.enableFeatureFlags(
|
||||
[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED],
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
isDryRun
|
||||
? `[DRY RUN] Would create page layouts and enable IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED for workspace ${workspaceId}`
|
||||
: `Successfully created page layouts and enabled IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
private async backfillStandardObjectPageLayouts({
|
||||
workspaceId,
|
||||
twentyStandardFlatApplication,
|
||||
isDryRun,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
twentyStandardFlatApplication: FlatApplication;
|
||||
isDryRun: boolean;
|
||||
}): Promise<void> {
|
||||
const { allFlatEntityMaps: standardAllFlatEntityMaps } =
|
||||
computeTwentyStandardApplicationAllFlatEntityMaps({
|
||||
shouldIncludeRecordPageLayouts: true,
|
||||
now: new Date().toISOString(),
|
||||
workspaceId,
|
||||
twentyStandardApplicationId: twentyStandardFlatApplication.id,
|
||||
});
|
||||
|
||||
const {
|
||||
flatPageLayoutMaps: existingFlatPageLayoutMaps,
|
||||
flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps,
|
||||
flatPageLayoutWidgetMaps: existingFlatPageLayoutWidgetMaps,
|
||||
flatViewMaps: existingFlatViewMaps,
|
||||
flatViewFieldMaps: existingFlatViewFieldMaps,
|
||||
flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatPageLayoutMaps',
|
||||
'flatPageLayoutTabMaps',
|
||||
'flatPageLayoutWidgetMaps',
|
||||
'flatViewMaps',
|
||||
'flatViewFieldMaps',
|
||||
'flatViewFieldGroupMaps',
|
||||
]);
|
||||
|
||||
const recordPageLayoutUniversalIdentifiers = new Set<string>();
|
||||
|
||||
const pageLayoutsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatPageLayoutMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((pageLayout) => {
|
||||
if (pageLayout.type !== PageLayoutType.RECORD_PAGE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
recordPageLayoutUniversalIdentifiers.add(
|
||||
pageLayout.universalIdentifier,
|
||||
);
|
||||
|
||||
if (
|
||||
isDefined(
|
||||
existingFlatPageLayoutMaps.byUniversalIdentifier[
|
||||
pageLayout.universalIdentifier
|
||||
],
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const tabUniversalIdentifiers = new Set<string>();
|
||||
|
||||
const pageLayoutTabsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatPageLayoutTabMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((tab) => {
|
||||
if (
|
||||
!recordPageLayoutUniversalIdentifiers.has(
|
||||
tab.pageLayoutUniversalIdentifier,
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tabUniversalIdentifiers.add(tab.universalIdentifier);
|
||||
|
||||
if (
|
||||
isDefined(
|
||||
existingFlatPageLayoutTabMaps.byUniversalIdentifier[
|
||||
tab.universalIdentifier
|
||||
],
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const pageLayoutWidgetsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatPageLayoutWidgetMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(widget) =>
|
||||
tabUniversalIdentifiers.has(
|
||||
widget.pageLayoutTabUniversalIdentifier,
|
||||
) &&
|
||||
!isDefined(
|
||||
existingFlatPageLayoutWidgetMaps.byUniversalIdentifier[
|
||||
widget.universalIdentifier
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
const viewUniversalIdentifiers = new Set<string>();
|
||||
|
||||
const viewsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatViewMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((view) => {
|
||||
if (view.type !== ViewType.FIELDS_WIDGET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(
|
||||
existingFlatViewMaps.byUniversalIdentifier[
|
||||
view.universalIdentifier
|
||||
],
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
viewUniversalIdentifiers.add(view.universalIdentifier);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const viewFieldsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatViewFieldMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(viewField) =>
|
||||
viewUniversalIdentifiers.has(viewField.viewUniversalIdentifier) &&
|
||||
!isDefined(
|
||||
existingFlatViewFieldMaps.byUniversalIdentifier[
|
||||
viewField.universalIdentifier
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
const viewFieldGroupsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatViewFieldGroupMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(viewFieldGroup) =>
|
||||
viewUniversalIdentifiers.has(
|
||||
viewFieldGroup.viewUniversalIdentifier,
|
||||
) &&
|
||||
!isDefined(
|
||||
existingFlatViewFieldGroupMaps.byUniversalIdentifier[
|
||||
viewFieldGroup.universalIdentifier
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Found standard entities to create for workspace ${workspaceId}: ` +
|
||||
`${pageLayoutsToCreate.length} page layout(s), ` +
|
||||
`${pageLayoutTabsToCreate.length} tab(s), ` +
|
||||
`${pageLayoutWidgetsToCreate.length} widget(s), ` +
|
||||
`${viewsToCreate.length} view(s), ` +
|
||||
`${viewFieldsToCreate.length} view field(s), ` +
|
||||
`${viewFieldGroupsToCreate.length} view field group(s)`,
|
||||
);
|
||||
|
||||
if (isDryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayout: {
|
||||
flatEntityToCreate: pageLayoutsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: pageLayoutTabsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: pageLayoutWidgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
view: {
|
||||
flatEntityToCreate: viewsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
viewField: {
|
||||
flatEntityToCreate: viewFieldsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
viewFieldGroup: {
|
||||
flatEntityToCreate: viewFieldGroupsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
twentyStandardFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (validateAndBuildResult.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create standard page layouts:\n${JSON.stringify(validateAndBuildResult, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create standard page layouts for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async backfillCustomObjectPageLayouts({
|
||||
workspaceId,
|
||||
workspaceCustomFlatApplication,
|
||||
isDryRun,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workspaceCustomFlatApplication: FlatApplication;
|
||||
isDryRun: boolean;
|
||||
}): Promise<void> {
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatPageLayoutMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatPageLayoutMaps',
|
||||
]);
|
||||
|
||||
const existingPageLayouts = Object.values(
|
||||
flatPageLayoutMaps.byUniversalIdentifier,
|
||||
).filter(isDefined);
|
||||
|
||||
const objectIdsWithRecordPageLayout = new Set(
|
||||
existingPageLayouts
|
||||
.filter(
|
||||
(layout: FlatPageLayout) =>
|
||||
layout.type === PageLayoutType.RECORD_PAGE &&
|
||||
isDefined(layout.objectMetadataId),
|
||||
)
|
||||
.map((layout: FlatPageLayout) => layout.objectMetadataId),
|
||||
);
|
||||
|
||||
const customObjectsWithoutPageLayout = Object.values(
|
||||
flatObjectMetadataMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(objectMetadata) =>
|
||||
objectMetadata.isCustom &&
|
||||
!objectMetadata.isRemote &&
|
||||
!objectIdsWithRecordPageLayout.has(objectMetadata.id),
|
||||
);
|
||||
|
||||
if (customObjectsWithoutPageLayout.length === 0) {
|
||||
this.logger.log(
|
||||
`No custom objects without page layouts found for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const allCustomPageLayoutsToCreate: FlatPageLayout[] = [];
|
||||
const allCustomPageLayoutTabsToCreate: FlatPageLayoutTab[] = [];
|
||||
const allCustomPageLayoutWidgetsToCreate: FlatPageLayoutWidget[] = [];
|
||||
const allCustomViewsToCreate: (UniversalFlatView & { id: string })[] = [];
|
||||
const allCustomViewFieldsToCreate: UniversalFlatViewField[] = [];
|
||||
|
||||
for (const customObject of customObjectsWithoutPageLayout) {
|
||||
const flatRecordPageFieldsView = computeFlatRecordPageFieldsViewToCreate({
|
||||
objectMetadata: customObject,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
});
|
||||
|
||||
const objectFieldMetadatas = Object.values(
|
||||
flatFieldMetadataMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((field) => field.objectMetadataId === customObject.id);
|
||||
|
||||
const viewFields = computeFlatViewFieldsToCreate({
|
||||
objectFlatFieldMetadatas: objectFieldMetadatas,
|
||||
viewUniversalIdentifier: flatRecordPageFieldsView.universalIdentifier,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
customObject.labelIdentifierFieldMetadataUniversalIdentifier,
|
||||
});
|
||||
|
||||
const { pageLayouts, pageLayoutTabs, pageLayoutWidgets } =
|
||||
computeFlatDefaultRecordPageLayoutToCreate({
|
||||
objectMetadata: customObject,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
recordPageFieldsView: flatRecordPageFieldsView,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
allCustomPageLayoutsToCreate.push(...pageLayouts);
|
||||
allCustomPageLayoutTabsToCreate.push(...pageLayoutTabs);
|
||||
allCustomPageLayoutWidgetsToCreate.push(...pageLayoutWidgets);
|
||||
allCustomViewsToCreate.push(flatRecordPageFieldsView);
|
||||
allCustomViewFieldsToCreate.push(...viewFields);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Found custom entities to create for ${customObjectsWithoutPageLayout.length} custom object(s) in workspace ${workspaceId}: ` +
|
||||
`${allCustomPageLayoutsToCreate.length} page layout(s), ` +
|
||||
`${allCustomPageLayoutTabsToCreate.length} tab(s), ` +
|
||||
`${allCustomPageLayoutWidgetsToCreate.length} widget(s), ` +
|
||||
`${allCustomViewsToCreate.length} view(s), ` +
|
||||
`${allCustomViewFieldsToCreate.length} view field(s)`,
|
||||
);
|
||||
|
||||
if (isDryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
const customValidateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayout: {
|
||||
flatEntityToCreate: allCustomPageLayoutsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: allCustomPageLayoutTabsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: allCustomPageLayoutWidgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
view: {
|
||||
flatEntityToCreate: allCustomViewsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
viewField: {
|
||||
flatEntityToCreate: allCustomViewFieldsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (customValidateAndBuildResult.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create custom object page layouts:\n${JSON.stringify(customValidateAndBuildResult, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create custom object page layouts for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully created page layouts for ${customObjectsWithoutPageLayout.length} custom object(s) in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { SdkClientGenerationService } from 'src/engine/core-modules/sdk-client/sdk-client-generation.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-20:generate-application-sdk-clients',
|
||||
description:
|
||||
'Generate SDK client archives for all existing applications so drivers do not crash with ARCHIVE_NOT_FOUND',
|
||||
})
|
||||
export class GenerateApplicationSdkClientsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
private readonly sdkClientGenerationService: SdkClientGenerationService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const dryRun = options.dryRun ?? false;
|
||||
|
||||
const applications = await this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Found ${applications.length} application(s) in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
for (const application of applications) {
|
||||
if (dryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would generate SDK client for application ${application.universalIdentifier} (${application.id})`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.sdkClientGenerationService.generateSdkClientForApplication({
|
||||
workspaceId,
|
||||
applicationId: application.id,
|
||||
applicationUniversalIdentifier: application.universalIdentifier,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to generate SDK client for application ${application.universalIdentifier} (${application.id}): ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-3
@@ -2,10 +2,12 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
|
||||
import { BackfillFieldWidgetsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-field-widgets.command';
|
||||
import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command';
|
||||
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts-and-fields-widget-view-fields.command';
|
||||
import { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts.command';
|
||||
import { BackfillSelectFieldOptionIdsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-select-field-option-ids.command';
|
||||
import { DeleteOrphanNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-delete-orphan-navigation-menu-items.command';
|
||||
import { GenerateApplicationSdkClientsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-generate-application-sdk-clients.command';
|
||||
import { IdentifyObjectPermissionMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-object-permission-metadata.command';
|
||||
import { IdentifyPermissionFlagMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-permission-flag-metadata.command';
|
||||
import { MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-object-permission-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
@@ -16,8 +18,10 @@ import { MigrateRichTextToTextCommand } from 'src/database/commands/upgrade-vers
|
||||
import { SeedCliApplicationRegistrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-seed-cli-application-registration.command';
|
||||
import { UpdateStandardIndexViewNamesCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-update-standard-index-view-names.command';
|
||||
import { ApplicationRegistrationModule } from 'src/engine/core-modules/application/application-registration/application-registration.module';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { SdkClientModule } from 'src/engine/core-modules/sdk-client/sdk-client.module';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
|
||||
@@ -37,6 +41,7 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
WorkspaceEntity,
|
||||
ApplicationEntity,
|
||||
ConnectedAccountEntity,
|
||||
MessageChannelEntity,
|
||||
CalendarChannelEntity,
|
||||
@@ -53,6 +58,7 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
|
||||
ApplicationRegistrationModule,
|
||||
WorkspaceMigrationModule,
|
||||
FeatureFlagModule,
|
||||
SdkClientModule,
|
||||
WorkflowCommonModule,
|
||||
],
|
||||
providers: [
|
||||
@@ -61,10 +67,12 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
|
||||
IdentifyObjectPermissionMetadataCommand,
|
||||
MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
BackfillCommandMenuItemsCommand,
|
||||
BackfillFieldWidgetsCommand,
|
||||
BackfillNavigationMenuItemTypeCommand,
|
||||
BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
|
||||
BackfillPageLayoutsCommand,
|
||||
BackfillSelectFieldOptionIdsCommand,
|
||||
DeleteOrphanNavigationMenuItemsCommand,
|
||||
GenerateApplicationSdkClientsCommand,
|
||||
SeedCliApplicationRegistrationCommand,
|
||||
MigrateRichTextToTextCommand,
|
||||
MigrateMessagingInfrastructureToMetadataCommand,
|
||||
@@ -77,10 +85,12 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
|
||||
IdentifyObjectPermissionMetadataCommand,
|
||||
MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
BackfillCommandMenuItemsCommand,
|
||||
BackfillFieldWidgetsCommand,
|
||||
BackfillNavigationMenuItemTypeCommand,
|
||||
BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
|
||||
BackfillPageLayoutsCommand,
|
||||
BackfillSelectFieldOptionIdsCommand,
|
||||
DeleteOrphanNavigationMenuItemsCommand,
|
||||
GenerateApplicationSdkClientsCommand,
|
||||
SeedCliApplicationRegistrationCommand,
|
||||
MigrateRichTextToTextCommand,
|
||||
MigrateMessagingInfrastructureToMetadataCommand,
|
||||
|
||||
+10
-4
@@ -34,10 +34,11 @@ import { BackfillSystemFieldsIsSystemCommand } from 'src/database/commands/upgra
|
||||
import { FixInvalidStandardUniversalIdentifiersCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-fix-invalid-standard-universal-identifiers.command';
|
||||
import { SeedServerIdCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-seed-server-id.command';
|
||||
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
|
||||
import { BackfillFieldWidgetsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-field-widgets.command';
|
||||
import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command';
|
||||
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts-and-fields-widget-view-fields.command';
|
||||
import { BackfillSelectFieldOptionIdsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-select-field-option-ids.command';
|
||||
import { DeleteOrphanNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-delete-orphan-navigation-menu-items.command';
|
||||
import { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts.command';
|
||||
import { BackfillSelectFieldOptionIdsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-select-field-option-ids.command';
|
||||
import { IdentifyObjectPermissionMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-object-permission-metadata.command';
|
||||
import { IdentifyPermissionFlagMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-permission-flag-metadata.command';
|
||||
import { MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-object-permission-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
@@ -45,6 +46,7 @@ import { MakePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrati
|
||||
import { MakeWorkflowSearchableCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-workflow-searchable.command';
|
||||
import { MigrateMessagingInfrastructureToMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-migrate-messaging-infrastructure-to-metadata.command';
|
||||
import { MigrateRichTextToTextCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-migrate-rich-text-to-text.command';
|
||||
import { GenerateApplicationSdkClientsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-generate-application-sdk-clients.command';
|
||||
import { SeedCliApplicationRegistrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-seed-cli-application-registration.command';
|
||||
import { UpdateStandardIndexViewNamesCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-update-standard-index-view-names.command';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
@@ -104,10 +106,12 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
protected readonly backfillNavigationMenuItemTypeCommand: BackfillNavigationMenuItemTypeCommand,
|
||||
protected readonly backfillCommandMenuItemsCommand: BackfillCommandMenuItemsCommand,
|
||||
protected readonly deleteOrphanNavigationMenuItemsCommand: DeleteOrphanNavigationMenuItemsCommand,
|
||||
protected readonly backfillPageLayoutsAndFieldsWidgetViewFieldsCommand: BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
|
||||
protected readonly backfillPageLayoutsCommand: BackfillPageLayoutsCommand,
|
||||
protected readonly generateApplicationSdkClientsCommand: GenerateApplicationSdkClientsCommand,
|
||||
protected readonly seedCliApplicationRegistrationCommand: SeedCliApplicationRegistrationCommand,
|
||||
protected readonly migrateRichTextToTextCommand: MigrateRichTextToTextCommand,
|
||||
protected readonly migrateMessagingInfrastructureToMetadataCommand: MigrateMessagingInfrastructureToMetadataCommand,
|
||||
protected readonly backfillFieldWidgetsCommand: BackfillFieldWidgetsCommand,
|
||||
protected readonly backfillSelectFieldOptionIdsCommand: BackfillSelectFieldOptionIdsCommand,
|
||||
protected readonly updateStandardIndexViewNamesCommand: UpdateStandardIndexViewNamesCommand,
|
||||
protected readonly makeWorkflowSearchableCommand: MakeWorkflowSearchableCommand,
|
||||
@@ -168,12 +172,14 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
this.migrateRichTextToTextCommand,
|
||||
this.deleteOrphanNavigationMenuItemsCommand,
|
||||
this.backfillCommandMenuItemsCommand,
|
||||
this.backfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
|
||||
this.backfillPageLayoutsCommand,
|
||||
this.seedCliApplicationRegistrationCommand,
|
||||
this.migrateMessagingInfrastructureToMetadataCommand,
|
||||
this.backfillFieldWidgetsCommand,
|
||||
this.backfillSelectFieldOptionIdsCommand,
|
||||
this.updateStandardIndexViewNamesCommand,
|
||||
this.makeWorkflowSearchableCommand,
|
||||
this.generateApplicationSdkClientsCommand,
|
||||
];
|
||||
|
||||
this.allCommands = {
|
||||
|
||||
-1
@@ -721,7 +721,6 @@ export class LambdaDriver implements LogicFunctionDriver {
|
||||
const sdkArchiveBuffer =
|
||||
await this.sdkClientArchiveService.downloadArchiveBuffer({
|
||||
workspaceId: flatApplication.workspaceId,
|
||||
applicationId: flatApplication.id,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
|
||||
-1
@@ -113,7 +113,6 @@ export class LocalDriver implements LogicFunctionDriver {
|
||||
|
||||
await this.sdkClientArchiveService.downloadAndExtractToPackage({
|
||||
workspaceId: flatApplication.workspaceId,
|
||||
applicationId: flatApplication.id,
|
||||
applicationUniversalIdentifier,
|
||||
targetPackagePath: sdkPackagePath,
|
||||
});
|
||||
|
||||
-1
@@ -59,7 +59,6 @@ export class SdkClientController {
|
||||
const fileBuffer =
|
||||
await this.sdkClientArchiveService.getClientModuleFromArchive({
|
||||
workspaceId: workspace.id,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier: application.universalIdentifier,
|
||||
moduleName,
|
||||
});
|
||||
|
||||
+41
-45
@@ -1,7 +1,11 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { createWriteStream } from 'fs';
|
||||
import * as fs from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import { type Readable } from 'stream';
|
||||
import { pipeline } from 'stream/promises';
|
||||
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
import { Repository } from 'typeorm';
|
||||
@@ -12,12 +16,12 @@ import {
|
||||
FileStorageException,
|
||||
FileStorageExceptionCode,
|
||||
} from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
|
||||
import { TemporaryDirManager } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/temporary-dir-manager';
|
||||
import { type SdkModuleName } from 'src/engine/core-modules/sdk-client/constants/allowed-sdk-modules';
|
||||
import {
|
||||
SdkClientException,
|
||||
SdkClientExceptionCode,
|
||||
} from 'src/engine/core-modules/sdk-client/exceptions/sdk-client.exception';
|
||||
import { SdkClientGenerationService } from 'src/engine/core-modules/sdk-client/sdk-client-generation.service';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
|
||||
@@ -25,74 +29,75 @@ const SDK_CLIENT_ARCHIVE_NAME = 'twenty-client-sdk.zip';
|
||||
|
||||
@Injectable()
|
||||
export class SdkClientArchiveService {
|
||||
private readonly logger = new Logger(SdkClientArchiveService.name);
|
||||
|
||||
constructor(
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly sdkClientGenerationService: SdkClientGenerationService,
|
||||
) {}
|
||||
|
||||
async downloadAndExtractToPackage({
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
targetPackagePath,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
targetPackagePath: string;
|
||||
}): Promise<void> {
|
||||
const archiveBuffer = await this.downloadArchiveBufferOrGenerate({
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
const temporaryDirManager = new TemporaryDirManager();
|
||||
|
||||
await fs.rm(targetPackagePath, { recursive: true, force: true });
|
||||
await fs.mkdir(targetPackagePath, { recursive: true });
|
||||
try {
|
||||
const { sourceTemporaryDir } = await temporaryDirManager.init();
|
||||
const archivePath = join(sourceTemporaryDir, SDK_CLIENT_ARCHIVE_NAME);
|
||||
|
||||
const { default: unzipper } = await import('unzipper');
|
||||
const directory = await unzipper.Open.buffer(archiveBuffer);
|
||||
const archiveStream = await this.readArchiveStream({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
await directory.extract({ path: targetPackagePath });
|
||||
await pipeline(archiveStream, createWriteStream(archivePath));
|
||||
|
||||
await fs.rm(targetPackagePath, { recursive: true, force: true });
|
||||
await fs.mkdir(targetPackagePath, { recursive: true });
|
||||
|
||||
const { default: unzipper } = await import('unzipper');
|
||||
const directory = await unzipper.Open.file(archivePath);
|
||||
|
||||
await directory.extract({ path: targetPackagePath });
|
||||
} finally {
|
||||
await temporaryDirManager.clean();
|
||||
}
|
||||
}
|
||||
|
||||
async downloadArchiveBuffer({
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
}): Promise<Buffer> {
|
||||
return this.downloadArchiveBufferOrGenerate({
|
||||
const archiveStream = await this.readArchiveStream({
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
return streamToBuffer(archiveStream);
|
||||
}
|
||||
|
||||
async getClientModuleFromArchive({
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
moduleName,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
moduleName: SdkModuleName;
|
||||
}): Promise<Buffer> {
|
||||
const filePath = `dist/${moduleName}.mjs`;
|
||||
|
||||
const archiveBuffer = await this.downloadArchiveBufferOrGenerate({
|
||||
const archiveBuffer = await this.downloadArchiveBuffer({
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
@@ -130,41 +135,32 @@ export class SdkClientArchiveService {
|
||||
]);
|
||||
}
|
||||
|
||||
private async downloadArchiveBufferOrGenerate({
|
||||
private async readArchiveStream({
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
}): Promise<Buffer> {
|
||||
}): Promise<Readable> {
|
||||
try {
|
||||
const stream = await this.fileStorageService.readFile({
|
||||
return await this.fileStorageService.readFile({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.GeneratedSdkClient,
|
||||
resourcePath: SDK_CLIENT_ARCHIVE_NAME,
|
||||
});
|
||||
|
||||
return await streamToBuffer(stream);
|
||||
} catch (error) {
|
||||
if (
|
||||
!(error instanceof FileStorageException) ||
|
||||
error.code !== FileStorageExceptionCode.FILE_NOT_FOUND
|
||||
error instanceof FileStorageException &&
|
||||
error.code === FileStorageExceptionCode.FILE_NOT_FOUND
|
||||
) {
|
||||
throw error;
|
||||
throw new SdkClientException(
|
||||
`SDK client archive "${SDK_CLIENT_ARCHIVE_NAME}" not found for application "${applicationUniversalIdentifier}" in workspace "${workspaceId}".`,
|
||||
SdkClientExceptionCode.ARCHIVE_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.logger.warn(
|
||||
`SDK client archive missing for application "${applicationUniversalIdentifier}" in workspace "${workspaceId}", generating on-the-fly`,
|
||||
);
|
||||
|
||||
return this.sdkClientGenerationService.generateSdkClientForApplication({
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+4
-10
@@ -44,13 +44,13 @@ export class SdkClientGenerationService {
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
}): Promise<Buffer> {
|
||||
}): Promise<void> {
|
||||
const graphqlSchema = await this.workspaceSchemaFactory.createGraphQLSchema(
|
||||
{ id: workspaceId } as WorkspaceEntity,
|
||||
applicationId,
|
||||
);
|
||||
|
||||
const archiveBuffer = await this.generateAndStore({
|
||||
await this.generateAndStore({
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
@@ -60,8 +60,6 @@ export class SdkClientGenerationService {
|
||||
this.logger.log(
|
||||
`Generated SDK client for application ${applicationUniversalIdentifier}`,
|
||||
);
|
||||
|
||||
return archiveBuffer;
|
||||
}
|
||||
|
||||
private async generateAndStore({
|
||||
@@ -74,7 +72,7 @@ export class SdkClientGenerationService {
|
||||
applicationId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
schema: string;
|
||||
}): Promise<Buffer> {
|
||||
}): Promise<void> {
|
||||
const temporaryDirManager = new TemporaryDirManager();
|
||||
|
||||
try {
|
||||
@@ -103,14 +101,12 @@ export class SdkClientGenerationService {
|
||||
|
||||
await createZipFile(tempPackageRoot, archivePath);
|
||||
|
||||
const archiveBuffer = await fs.readFile(archivePath);
|
||||
|
||||
await this.fileStorageService.writeFile({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.GeneratedSdkClient,
|
||||
resourcePath: SDK_CLIENT_ARCHIVE_NAME,
|
||||
sourceFile: archiveBuffer,
|
||||
sourceFile: await fs.readFile(archivePath),
|
||||
mimeType: 'application/zip',
|
||||
settings: { isTemporaryFile: false, toDelete: false },
|
||||
});
|
||||
@@ -123,8 +119,6 @@ export class SdkClientGenerationService {
|
||||
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
|
||||
'flatApplicationMaps',
|
||||
]);
|
||||
|
||||
return archiveBuffer;
|
||||
} catch (error) {
|
||||
throw new SdkClientException(
|
||||
`Failed to generate SDK client for application "${applicationUniversalIdentifier}" in workspace "${workspaceId}": ${error instanceof Error ? error.message : String(error)}`,
|
||||
|
||||
+1027
-1027
File diff suppressed because it is too large
Load Diff
-1
@@ -455,7 +455,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
flatObjectMetadataToCreate.labelIdentifierFieldMetadataUniversalIdentifier,
|
||||
viewUniversalIdentifier:
|
||||
flatRecordPageFieldsViewToCreate.universalIdentifier,
|
||||
excludeLabelIdentifier: true,
|
||||
});
|
||||
|
||||
flatDefaultRecordPageLayoutsToCreate =
|
||||
|
||||
-209
@@ -1,209 +0,0 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
|
||||
|
||||
import { computeFlatViewFieldsToCreate } from '../compute-flat-view-fields-to-create.util';
|
||||
|
||||
const makeFieldMetadata = (
|
||||
overrides: Partial<UniversalFlatFieldMetadata> & {
|
||||
name: string;
|
||||
type: FieldMetadataType;
|
||||
},
|
||||
): UniversalFlatFieldMetadata => {
|
||||
const universalIdentifier = overrides.universalIdentifier ?? v4();
|
||||
|
||||
return {
|
||||
universalIdentifier,
|
||||
objectMetadataUniversalIdentifier: 'object-uid',
|
||||
applicationUniversalIdentifier: 'app-uid',
|
||||
name: overrides.name,
|
||||
label: overrides.label ?? overrides.name,
|
||||
type: overrides.type,
|
||||
isCustom: overrides.isCustom ?? false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isUIReadOnly: false,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
isLabelSyncedWithName: false,
|
||||
defaultValue: null,
|
||||
description: null,
|
||||
icon: null,
|
||||
options: null,
|
||||
settings: null,
|
||||
standardOverrides: null,
|
||||
relationTargetObjectMetadataUniversalIdentifier: null,
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
updatedAt: '2026-01-01T00:00:00.000Z',
|
||||
deletedAt: null,
|
||||
} as unknown as UniversalFlatFieldMetadata;
|
||||
};
|
||||
|
||||
const flatApplication = {
|
||||
universalIdentifier: 'app-uid',
|
||||
id: 'app-id',
|
||||
} as never;
|
||||
|
||||
const viewUniversalIdentifier = 'view-uid';
|
||||
|
||||
describe('computeFlatViewFieldsToCreate', () => {
|
||||
it('should exclude TS_VECTOR fields', () => {
|
||||
const fields = [
|
||||
makeFieldMetadata({
|
||||
name: 'name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
}),
|
||||
makeFieldMetadata({
|
||||
name: 'searchVector',
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
}),
|
||||
];
|
||||
|
||||
const result = computeFlatViewFieldsToCreate({
|
||||
objectFlatFieldMetadatas: fields,
|
||||
viewUniversalIdentifier,
|
||||
flatApplication,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier: null,
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
|
||||
fields[0].universalIdentifier,
|
||||
);
|
||||
});
|
||||
|
||||
it('should exclude POSITION fields', () => {
|
||||
const fields = [
|
||||
makeFieldMetadata({
|
||||
name: 'name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
}),
|
||||
makeFieldMetadata({
|
||||
name: 'position',
|
||||
type: FieldMetadataType.POSITION,
|
||||
}),
|
||||
];
|
||||
|
||||
const result = computeFlatViewFieldsToCreate({
|
||||
objectFlatFieldMetadatas: fields,
|
||||
viewUniversalIdentifier,
|
||||
flatApplication,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier: null,
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
|
||||
fields[0].universalIdentifier,
|
||||
);
|
||||
});
|
||||
|
||||
it('should exclude RELATION and MORPH_RELATION fields', () => {
|
||||
const fields = [
|
||||
makeFieldMetadata({
|
||||
name: 'name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
}),
|
||||
makeFieldMetadata({
|
||||
name: 'company',
|
||||
type: FieldMetadataType.RELATION,
|
||||
}),
|
||||
makeFieldMetadata({
|
||||
name: 'target',
|
||||
type: FieldMetadataType.MORPH_RELATION,
|
||||
}),
|
||||
];
|
||||
|
||||
const result = computeFlatViewFieldsToCreate({
|
||||
objectFlatFieldMetadatas: fields,
|
||||
viewUniversalIdentifier,
|
||||
flatApplication,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier: null,
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
|
||||
fields[0].universalIdentifier,
|
||||
);
|
||||
});
|
||||
|
||||
it('should exclude deletedAt field', () => {
|
||||
const fields = [
|
||||
makeFieldMetadata({
|
||||
name: 'name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
}),
|
||||
makeFieldMetadata({
|
||||
name: 'deletedAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
}),
|
||||
];
|
||||
|
||||
const result = computeFlatViewFieldsToCreate({
|
||||
objectFlatFieldMetadatas: fields,
|
||||
viewUniversalIdentifier,
|
||||
flatApplication,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier: null,
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
|
||||
fields[0].universalIdentifier,
|
||||
);
|
||||
});
|
||||
|
||||
it('should place label identifier field first', () => {
|
||||
const labelField = makeFieldMetadata({
|
||||
name: 'name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
});
|
||||
const otherField = makeFieldMetadata({
|
||||
name: 'createdAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
});
|
||||
const fields = [otherField, labelField];
|
||||
|
||||
const result = computeFlatViewFieldsToCreate({
|
||||
objectFlatFieldMetadatas: fields,
|
||||
viewUniversalIdentifier,
|
||||
flatApplication,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
labelField.universalIdentifier,
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
|
||||
labelField.universalIdentifier,
|
||||
);
|
||||
expect(result[0].position).toBe(0);
|
||||
expect(result[1].fieldMetadataUniversalIdentifier).toBe(
|
||||
otherField.universalIdentifier,
|
||||
);
|
||||
expect(result[1].position).toBe(1);
|
||||
});
|
||||
|
||||
it('should exclude label identifier when excludeLabelIdentifier is true', () => {
|
||||
const labelField = makeFieldMetadata({
|
||||
name: 'name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
});
|
||||
const otherField = makeFieldMetadata({
|
||||
name: 'createdAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
});
|
||||
|
||||
const result = computeFlatViewFieldsToCreate({
|
||||
objectFlatFieldMetadatas: [labelField, otherField],
|
||||
viewUniversalIdentifier,
|
||||
flatApplication,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
labelField.universalIdentifier,
|
||||
excludeLabelIdentifier: true,
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
|
||||
otherField.universalIdentifier,
|
||||
);
|
||||
});
|
||||
});
|
||||
-8
@@ -11,30 +11,22 @@ export const computeFlatViewFieldsToCreate = ({
|
||||
viewUniversalIdentifier,
|
||||
flatApplication,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier,
|
||||
excludeLabelIdentifier = false,
|
||||
}: {
|
||||
flatApplication: FlatApplication;
|
||||
objectFlatFieldMetadatas: UniversalFlatFieldMetadata[];
|
||||
viewUniversalIdentifier: string;
|
||||
labelIdentifierFieldMetadataUniversalIdentifier: string | null;
|
||||
excludeLabelIdentifier?: boolean;
|
||||
}): UniversalFlatViewField[] => {
|
||||
const createdAt = new Date().toISOString();
|
||||
const defaultViewFields = objectFlatFieldMetadatas
|
||||
.filter(
|
||||
(field) =>
|
||||
field.name !== 'deletedAt' &&
|
||||
field.type !== FieldMetadataType.TS_VECTOR &&
|
||||
field.type !== FieldMetadataType.POSITION &&
|
||||
field.type !== FieldMetadataType.MORPH_RELATION &&
|
||||
field.type !== FieldMetadataType.RELATION &&
|
||||
// Include 'id' only if it's the label identifier (e.g., for junction tables)
|
||||
(field.name !== 'id' ||
|
||||
field.universalIdentifier ===
|
||||
labelIdentifierFieldMetadataUniversalIdentifier) &&
|
||||
// Exclude label identifier field when requested (e.g., for FIELDS_WIDGET views)
|
||||
(!excludeLabelIdentifier ||
|
||||
field.universalIdentifier !==
|
||||
labelIdentifierFieldMetadataUniversalIdentifier),
|
||||
)
|
||||
.sort((a, b) => {
|
||||
|
||||
+1518
-1647
File diff suppressed because it is too large
Load Diff
+92
-86
@@ -6,37 +6,40 @@ exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard pa
|
||||
"id": "00000000-0000-0000-0000-000000000011",
|
||||
"tabs": {
|
||||
"calendar": {
|
||||
"id": "00000000-0000-0000-0000-000000000026",
|
||||
"id": "00000000-0000-0000-0000-000000000027",
|
||||
"widgets": {
|
||||
"calendar": {
|
||||
"id": "00000000-0000-0000-0000-000000000027",
|
||||
"id": "00000000-0000-0000-0000-000000000028",
|
||||
},
|
||||
},
|
||||
},
|
||||
"emails": {
|
||||
"id": "00000000-0000-0000-0000-000000000024",
|
||||
"id": "00000000-0000-0000-0000-000000000025",
|
||||
"widgets": {
|
||||
"emails": {
|
||||
"id": "00000000-0000-0000-0000-000000000025",
|
||||
"id": "00000000-0000-0000-0000-000000000026",
|
||||
},
|
||||
},
|
||||
},
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000022",
|
||||
"id": "00000000-0000-0000-0000-000000000023",
|
||||
"widgets": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000023",
|
||||
"id": "00000000-0000-0000-0000-000000000024",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000012",
|
||||
"widgets": {
|
||||
"accountOwner": {
|
||||
"id": "00000000-0000-0000-0000-000000000015",
|
||||
},
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000013",
|
||||
},
|
||||
"opportunities": {
|
||||
"id": "00000000-0000-0000-0000-000000000015",
|
||||
"id": "00000000-0000-0000-0000-000000000016",
|
||||
},
|
||||
"people": {
|
||||
"id": "00000000-0000-0000-0000-000000000014",
|
||||
@@ -44,26 +47,26 @@ exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard pa
|
||||
},
|
||||
},
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000020",
|
||||
"id": "00000000-0000-0000-0000-000000000021",
|
||||
"widgets": {
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000021",
|
||||
"id": "00000000-0000-0000-0000-000000000022",
|
||||
},
|
||||
},
|
||||
},
|
||||
"tasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000018",
|
||||
"id": "00000000-0000-0000-0000-000000000019",
|
||||
"widgets": {
|
||||
"tasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000019",
|
||||
"id": "00000000-0000-0000-0000-000000000020",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000016",
|
||||
"id": "00000000-0000-0000-0000-000000000017",
|
||||
"widgets": {
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000017",
|
||||
"id": "00000000-0000-0000-0000-000000000018",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -104,278 +107,281 @@ exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard pa
|
||||
},
|
||||
},
|
||||
"noteRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000063",
|
||||
"id": "00000000-0000-0000-0000-000000000064",
|
||||
"tabs": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000071",
|
||||
"id": "00000000-0000-0000-0000-000000000072",
|
||||
"widgets": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000072",
|
||||
"id": "00000000-0000-0000-0000-000000000073",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000064",
|
||||
"id": "00000000-0000-0000-0000-000000000065",
|
||||
"widgets": {
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000065",
|
||||
"id": "00000000-0000-0000-0000-000000000066",
|
||||
},
|
||||
"noteRichText": {
|
||||
"id": "00000000-0000-0000-0000-000000000066",
|
||||
"id": "00000000-0000-0000-0000-000000000067",
|
||||
},
|
||||
},
|
||||
},
|
||||
"note": {
|
||||
"id": "00000000-0000-0000-0000-000000000067",
|
||||
"id": "00000000-0000-0000-0000-000000000068",
|
||||
"widgets": {
|
||||
"noteRichText": {
|
||||
"id": "00000000-0000-0000-0000-000000000068",
|
||||
"id": "00000000-0000-0000-0000-000000000069",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000069",
|
||||
"id": "00000000-0000-0000-0000-000000000070",
|
||||
"widgets": {
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000070",
|
||||
"id": "00000000-0000-0000-0000-000000000071",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"opportunityRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000045",
|
||||
"id": "00000000-0000-0000-0000-000000000046",
|
||||
"tabs": {
|
||||
"calendar": {
|
||||
"id": "00000000-0000-0000-0000-000000000061",
|
||||
"id": "00000000-0000-0000-0000-000000000062",
|
||||
"widgets": {
|
||||
"calendar": {
|
||||
"id": "00000000-0000-0000-0000-000000000062",
|
||||
"id": "00000000-0000-0000-0000-000000000063",
|
||||
},
|
||||
},
|
||||
},
|
||||
"emails": {
|
||||
"id": "00000000-0000-0000-0000-000000000059",
|
||||
"id": "00000000-0000-0000-0000-000000000060",
|
||||
"widgets": {
|
||||
"emails": {
|
||||
"id": "00000000-0000-0000-0000-000000000060",
|
||||
"id": "00000000-0000-0000-0000-000000000061",
|
||||
},
|
||||
},
|
||||
},
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000057",
|
||||
"id": "00000000-0000-0000-0000-000000000058",
|
||||
"widgets": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000058",
|
||||
"id": "00000000-0000-0000-0000-000000000059",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000046",
|
||||
"id": "00000000-0000-0000-0000-000000000047",
|
||||
"widgets": {
|
||||
"company": {
|
||||
"id": "00000000-0000-0000-0000-000000000049",
|
||||
},
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000047",
|
||||
},
|
||||
"owner": {
|
||||
"id": "00000000-0000-0000-0000-000000000050",
|
||||
},
|
||||
"pointOfContact": {
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000048",
|
||||
},
|
||||
"owner": {
|
||||
"id": "00000000-0000-0000-0000-000000000051",
|
||||
},
|
||||
"pointOfContact": {
|
||||
"id": "00000000-0000-0000-0000-000000000049",
|
||||
},
|
||||
},
|
||||
},
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000055",
|
||||
"id": "00000000-0000-0000-0000-000000000056",
|
||||
"widgets": {
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000056",
|
||||
"id": "00000000-0000-0000-0000-000000000057",
|
||||
},
|
||||
},
|
||||
},
|
||||
"tasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000053",
|
||||
"id": "00000000-0000-0000-0000-000000000054",
|
||||
"widgets": {
|
||||
"tasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000054",
|
||||
"id": "00000000-0000-0000-0000-000000000055",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000051",
|
||||
"id": "00000000-0000-0000-0000-000000000052",
|
||||
"widgets": {
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000052",
|
||||
"id": "00000000-0000-0000-0000-000000000053",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"personRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000028",
|
||||
"id": "00000000-0000-0000-0000-000000000029",
|
||||
"tabs": {
|
||||
"calendar": {
|
||||
"id": "00000000-0000-0000-0000-000000000043",
|
||||
"id": "00000000-0000-0000-0000-000000000044",
|
||||
"widgets": {
|
||||
"calendar": {
|
||||
"id": "00000000-0000-0000-0000-000000000044",
|
||||
"id": "00000000-0000-0000-0000-000000000045",
|
||||
},
|
||||
},
|
||||
},
|
||||
"emails": {
|
||||
"id": "00000000-0000-0000-0000-000000000041",
|
||||
"id": "00000000-0000-0000-0000-000000000042",
|
||||
"widgets": {
|
||||
"emails": {
|
||||
"id": "00000000-0000-0000-0000-000000000042",
|
||||
"id": "00000000-0000-0000-0000-000000000043",
|
||||
},
|
||||
},
|
||||
},
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000039",
|
||||
"id": "00000000-0000-0000-0000-000000000040",
|
||||
"widgets": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000040",
|
||||
"id": "00000000-0000-0000-0000-000000000041",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000029",
|
||||
"id": "00000000-0000-0000-0000-000000000030",
|
||||
"widgets": {
|
||||
"company": {
|
||||
"id": "00000000-0000-0000-0000-000000000031",
|
||||
"id": "00000000-0000-0000-0000-000000000032",
|
||||
},
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000030",
|
||||
"id": "00000000-0000-0000-0000-000000000031",
|
||||
},
|
||||
"pointOfContactForOpportunities": {
|
||||
"id": "00000000-0000-0000-0000-000000000032",
|
||||
"id": "00000000-0000-0000-0000-000000000033",
|
||||
},
|
||||
},
|
||||
},
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000037",
|
||||
"id": "00000000-0000-0000-0000-000000000038",
|
||||
"widgets": {
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000038",
|
||||
"id": "00000000-0000-0000-0000-000000000039",
|
||||
},
|
||||
},
|
||||
},
|
||||
"tasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000035",
|
||||
"id": "00000000-0000-0000-0000-000000000036",
|
||||
"widgets": {
|
||||
"tasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000036",
|
||||
"id": "00000000-0000-0000-0000-000000000037",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000033",
|
||||
"id": "00000000-0000-0000-0000-000000000034",
|
||||
"widgets": {
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000034",
|
||||
"id": "00000000-0000-0000-0000-000000000035",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"taskRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000073",
|
||||
"id": "00000000-0000-0000-0000-000000000074",
|
||||
"tabs": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000081",
|
||||
"id": "00000000-0000-0000-0000-000000000083",
|
||||
"widgets": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000082",
|
||||
"id": "00000000-0000-0000-0000-000000000084",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000074",
|
||||
"id": "00000000-0000-0000-0000-000000000075",
|
||||
"widgets": {
|
||||
"assignee": {
|
||||
"id": "00000000-0000-0000-0000-000000000078",
|
||||
},
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000075",
|
||||
"id": "00000000-0000-0000-0000-000000000076",
|
||||
},
|
||||
"taskRichText": {
|
||||
"id": "00000000-0000-0000-0000-000000000076",
|
||||
"id": "00000000-0000-0000-0000-000000000077",
|
||||
},
|
||||
},
|
||||
},
|
||||
"note": {
|
||||
"id": "00000000-0000-0000-0000-000000000077",
|
||||
"id": "00000000-0000-0000-0000-000000000079",
|
||||
"widgets": {
|
||||
"taskRichText": {
|
||||
"id": "00000000-0000-0000-0000-000000000078",
|
||||
"id": "00000000-0000-0000-0000-000000000080",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000079",
|
||||
"id": "00000000-0000-0000-0000-000000000081",
|
||||
"widgets": {
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000080",
|
||||
"id": "00000000-0000-0000-0000-000000000082",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"workflowRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000083",
|
||||
"id": "00000000-0000-0000-0000-000000000085",
|
||||
"tabs": {
|
||||
"flow": {
|
||||
"id": "00000000-0000-0000-0000-000000000084",
|
||||
"id": "00000000-0000-0000-0000-000000000086",
|
||||
"widgets": {
|
||||
"workflow": {
|
||||
"id": "00000000-0000-0000-0000-000000000085",
|
||||
"id": "00000000-0000-0000-0000-000000000087",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"workflowRunRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000092",
|
||||
"id": "00000000-0000-0000-0000-000000000094",
|
||||
"tabs": {
|
||||
"flow": {
|
||||
"id": "00000000-0000-0000-0000-000000000096",
|
||||
"id": "00000000-0000-0000-0000-000000000098",
|
||||
"widgets": {
|
||||
"workflowRun": {
|
||||
"id": "00000000-0000-0000-0000-000000000097",
|
||||
"id": "00000000-0000-0000-0000-000000000099",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000093",
|
||||
"id": "00000000-0000-0000-0000-000000000095",
|
||||
"widgets": {
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000094",
|
||||
"id": "00000000-0000-0000-0000-000000000096",
|
||||
},
|
||||
"workflow": {
|
||||
"id": "00000000-0000-0000-0000-000000000095",
|
||||
"id": "00000000-0000-0000-0000-000000000097",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"workflowVersionRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000086",
|
||||
"id": "00000000-0000-0000-0000-000000000088",
|
||||
"tabs": {
|
||||
"flow": {
|
||||
"id": "00000000-0000-0000-0000-000000000090",
|
||||
"id": "00000000-0000-0000-0000-000000000092",
|
||||
"widgets": {
|
||||
"workflowVersion": {
|
||||
"id": "00000000-0000-0000-0000-000000000091",
|
||||
"id": "00000000-0000-0000-0000-000000000093",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000087",
|
||||
"id": "00000000-0000-0000-0000-000000000089",
|
||||
"widgets": {
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000088",
|
||||
"id": "00000000-0000-0000-0000-000000000090",
|
||||
},
|
||||
"workflow": {
|
||||
"id": "00000000-0000-0000-0000-000000000089",
|
||||
"id": "00000000-0000-0000-0000-000000000091",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+10
-1
@@ -31,12 +31,21 @@ const COMPANY_PAGE_TABS = {
|
||||
fieldUniversalIdentifier:
|
||||
STANDARD_OBJECTS.company.fields.people.universalIdentifier,
|
||||
},
|
||||
accountOwner: {
|
||||
universalIdentifier: '20202020-ac01-4001-8001-c0aba11c0113',
|
||||
title: 'Account Owner',
|
||||
type: WidgetType.FIELD,
|
||||
gridPosition: GRID_POSITIONS.FULL_WIDTH,
|
||||
position: VERTICAL_LIST_LAYOUT_POSITIONS.THIRD,
|
||||
fieldUniversalIdentifier:
|
||||
STANDARD_OBJECTS.company.fields.accountOwner.universalIdentifier,
|
||||
},
|
||||
opportunities: {
|
||||
universalIdentifier: '20202020-ac01-4001-8001-c0aba11c0114',
|
||||
title: 'Opportunities',
|
||||
type: WidgetType.FIELD,
|
||||
gridPosition: GRID_POSITIONS.FULL_WIDTH,
|
||||
position: VERTICAL_LIST_LAYOUT_POSITIONS.THIRD,
|
||||
position: VERTICAL_LIST_LAYOUT_POSITIONS.FOURTH,
|
||||
fieldUniversalIdentifier:
|
||||
STANDARD_OBJECTS.company.fields.opportunities.universalIdentifier,
|
||||
},
|
||||
|
||||
+12
@@ -1,10 +1,13 @@
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
import {
|
||||
CONDITIONAL_DISPLAY_DEVICE_DESKTOP,
|
||||
CONDITIONAL_DISPLAY_DEVICE_MOBILE,
|
||||
GRID_POSITIONS,
|
||||
TAB_PROPS,
|
||||
VERTICAL_LIST_LAYOUT_POSITIONS,
|
||||
WIDGET_PROPS,
|
||||
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
|
||||
import {
|
||||
@@ -29,6 +32,15 @@ const TASK_PAGE_TABS = {
|
||||
position: { layoutMode: TAB_PROPS.home.layoutMode, index: 1 },
|
||||
conditionalDisplay: CONDITIONAL_DISPLAY_DEVICE_MOBILE,
|
||||
},
|
||||
assignee: {
|
||||
universalIdentifier: '20202020-ac05-4005-8005-ba5ca11a5513',
|
||||
title: 'Assignee',
|
||||
type: WidgetType.FIELD,
|
||||
gridPosition: GRID_POSITIONS.FULL_WIDTH,
|
||||
position: VERTICAL_LIST_LAYOUT_POSITIONS.THIRD,
|
||||
fieldUniversalIdentifier:
|
||||
STANDARD_OBJECTS.task.fields.assignee.universalIdentifier,
|
||||
},
|
||||
},
|
||||
},
|
||||
note: {
|
||||
|
||||
+13
@@ -45,6 +45,19 @@ export const computeStandardBlocklistViewFields = (
|
||||
},
|
||||
}),
|
||||
|
||||
blocklistRecordPageFieldsHandle: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'blocklist',
|
||||
context: {
|
||||
viewName: 'blocklistRecordPageFields',
|
||||
viewFieldName: 'handle',
|
||||
fieldName: 'handle',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
blocklistRecordPageFieldsWorkspaceMember:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
|
||||
+13
@@ -81,6 +81,19 @@ export const computeStandardCalendarChannelViewFields = (
|
||||
},
|
||||
}),
|
||||
|
||||
calendarChannelRecordPageFieldsHandle: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'calendarChannel',
|
||||
context: {
|
||||
viewName: 'calendarChannelRecordPageFields',
|
||||
viewFieldName: 'handle',
|
||||
fieldName: 'handle',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
calendarChannelRecordPageFieldsConnectedAccount:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
|
||||
-118
@@ -245,123 +245,5 @@ export const computeStandardCompanyViewFields = (
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
companyRecordPageFieldsUpdatedAt: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'company',
|
||||
context: {
|
||||
viewName: 'companyRecordPageFields',
|
||||
viewFieldName: 'updatedAt',
|
||||
fieldName: 'updatedAt',
|
||||
position: 6,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
companyRecordPageFieldsUpdatedBy: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'company',
|
||||
context: {
|
||||
viewName: 'companyRecordPageFields',
|
||||
viewFieldName: 'updatedBy',
|
||||
fieldName: 'updatedBy',
|
||||
position: 7,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
companyRecordPageFieldsPeople: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'company',
|
||||
context: {
|
||||
viewName: 'companyRecordPageFields',
|
||||
viewFieldName: 'people',
|
||||
fieldName: 'people',
|
||||
position: 4,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
companyRecordPageFieldsTaskTargets: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'company',
|
||||
context: {
|
||||
viewName: 'companyRecordPageFields',
|
||||
viewFieldName: 'taskTargets',
|
||||
fieldName: 'taskTargets',
|
||||
position: 5,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
companyRecordPageFieldsNoteTargets: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'company',
|
||||
context: {
|
||||
viewName: 'companyRecordPageFields',
|
||||
viewFieldName: 'noteTargets',
|
||||
fieldName: 'noteTargets',
|
||||
position: 6,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
companyRecordPageFieldsOpportunities: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'company',
|
||||
context: {
|
||||
viewName: 'companyRecordPageFields',
|
||||
viewFieldName: 'opportunities',
|
||||
fieldName: 'opportunities',
|
||||
position: 7,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
companyRecordPageFieldsFavorites: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'company',
|
||||
context: {
|
||||
viewName: 'companyRecordPageFields',
|
||||
viewFieldName: 'favorites',
|
||||
fieldName: 'favorites',
|
||||
position: 8,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
companyRecordPageFieldsAttachments: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'company',
|
||||
context: {
|
||||
viewName: 'companyRecordPageFields',
|
||||
viewFieldName: 'attachments',
|
||||
fieldName: 'attachments',
|
||||
position: 9,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
companyRecordPageFieldsTimelineActivities:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'company',
|
||||
context: {
|
||||
viewName: 'companyRecordPageFields',
|
||||
viewFieldName: 'timelineActivities',
|
||||
fieldName: 'timelineActivities',
|
||||
position: 10,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
+15
@@ -69,6 +69,21 @@ export const computeStandardConnectedAccountViewFields = (
|
||||
},
|
||||
}),
|
||||
|
||||
connectedAccountRecordPageFieldsHandle: createStandardViewFieldFlatMetadata(
|
||||
{
|
||||
...args,
|
||||
objectName: 'connectedAccount',
|
||||
context: {
|
||||
viewName: 'connectedAccountRecordPageFields',
|
||||
viewFieldName: 'handle',
|
||||
fieldName: 'handle',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
},
|
||||
),
|
||||
connectedAccountRecordPageFieldsProvider:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
|
||||
+13
@@ -33,6 +33,19 @@ export const computeStandardFavoriteFolderViewFields = (
|
||||
},
|
||||
}),
|
||||
|
||||
favoriteFolderRecordPageFieldsName: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'favoriteFolder',
|
||||
context: {
|
||||
viewName: 'favoriteFolderRecordPageFields',
|
||||
viewFieldName: 'name',
|
||||
fieldName: 'name',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
favoriteFolderRecordPageFieldsCreatedAt:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
|
||||
+13
@@ -93,6 +93,19 @@ export const computeStandardMessageChannelViewFields = (
|
||||
},
|
||||
}),
|
||||
|
||||
messageChannelRecordPageFieldsHandle: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'messageChannel',
|
||||
context: {
|
||||
viewName: 'messageChannelRecordPageFields',
|
||||
viewFieldName: 'handle',
|
||||
fieldName: 'handle',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
messageChannelRecordPageFieldsConnectedAccount:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
|
||||
+13
@@ -69,6 +69,19 @@ export const computeStandardMessageFolderViewFields = (
|
||||
},
|
||||
}),
|
||||
|
||||
messageFolderRecordPageFieldsName: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'messageFolder',
|
||||
context: {
|
||||
viewName: 'messageFolderRecordPageFields',
|
||||
viewFieldName: 'name',
|
||||
fieldName: 'name',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
messageFolderRecordPageFieldsMessageChannel:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
|
||||
+14
@@ -93,6 +93,20 @@ export const computeStandardMessageParticipantViewFields = (
|
||||
},
|
||||
}),
|
||||
|
||||
messageParticipantRecordPageFieldsHandle:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'messageParticipant',
|
||||
context: {
|
||||
viewName: 'messageParticipantRecordPageFields',
|
||||
viewFieldName: 'handle',
|
||||
fieldName: 'handle',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
messageParticipantRecordPageFieldsMessage:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
|
||||
+13
-80
@@ -70,6 +70,19 @@ export const computeStandardNoteViewFields = (
|
||||
}),
|
||||
|
||||
// noteRecordPageFields view fields
|
||||
noteRecordPageFieldsTitle: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'note',
|
||||
context: {
|
||||
viewName: 'noteRecordPageFields',
|
||||
viewFieldName: 'title',
|
||||
fieldName: 'title',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
noteRecordPageFieldsNoteTargets: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'note',
|
||||
@@ -109,85 +122,5 @@ export const computeStandardNoteViewFields = (
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
noteRecordPageFieldsBodyV2: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'note',
|
||||
context: {
|
||||
viewName: 'noteRecordPageFields',
|
||||
viewFieldName: 'bodyV2',
|
||||
fieldName: 'bodyV2',
|
||||
position: 0,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'additional',
|
||||
},
|
||||
}),
|
||||
noteRecordPageFieldsUpdatedAt: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'note',
|
||||
context: {
|
||||
viewName: 'noteRecordPageFields',
|
||||
viewFieldName: 'updatedAt',
|
||||
fieldName: 'updatedAt',
|
||||
position: 2,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
noteRecordPageFieldsUpdatedBy: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'note',
|
||||
context: {
|
||||
viewName: 'noteRecordPageFields',
|
||||
viewFieldName: 'updatedBy',
|
||||
fieldName: 'updatedBy',
|
||||
position: 3,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
noteRecordPageFieldsAttachments: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'note',
|
||||
context: {
|
||||
viewName: 'noteRecordPageFields',
|
||||
viewFieldName: 'attachments',
|
||||
fieldName: 'attachments',
|
||||
position: 2,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
noteRecordPageFieldsTimelineActivities: createStandardViewFieldFlatMetadata(
|
||||
{
|
||||
...args,
|
||||
objectName: 'note',
|
||||
context: {
|
||||
viewName: 'noteRecordPageFields',
|
||||
viewFieldName: 'timelineActivities',
|
||||
fieldName: 'timelineActivities',
|
||||
position: 3,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
},
|
||||
),
|
||||
noteRecordPageFieldsFavorites: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'note',
|
||||
context: {
|
||||
viewName: 'noteRecordPageFields',
|
||||
viewFieldName: 'favorites',
|
||||
fieldName: 'favorites',
|
||||
position: 4,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
-98
@@ -266,103 +266,5 @@ export const computeStandardOpportunityViewFields = (
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
opportunityRecordPageFieldsUpdatedAt: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'opportunity',
|
||||
context: {
|
||||
viewName: 'opportunityRecordPageFields',
|
||||
viewFieldName: 'updatedAt',
|
||||
fieldName: 'updatedAt',
|
||||
position: 2,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
opportunityRecordPageFieldsUpdatedBy: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'opportunity',
|
||||
context: {
|
||||
viewName: 'opportunityRecordPageFields',
|
||||
viewFieldName: 'updatedBy',
|
||||
fieldName: 'updatedBy',
|
||||
position: 3,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
opportunityRecordPageFieldsFavorites: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'opportunity',
|
||||
context: {
|
||||
viewName: 'opportunityRecordPageFields',
|
||||
viewFieldName: 'favorites',
|
||||
fieldName: 'favorites',
|
||||
position: 5,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
opportunityRecordPageFieldsTaskTargets: createStandardViewFieldFlatMetadata(
|
||||
{
|
||||
...args,
|
||||
objectName: 'opportunity',
|
||||
context: {
|
||||
viewName: 'opportunityRecordPageFields',
|
||||
viewFieldName: 'taskTargets',
|
||||
fieldName: 'taskTargets',
|
||||
position: 6,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
},
|
||||
),
|
||||
opportunityRecordPageFieldsNoteTargets: createStandardViewFieldFlatMetadata(
|
||||
{
|
||||
...args,
|
||||
objectName: 'opportunity',
|
||||
context: {
|
||||
viewName: 'opportunityRecordPageFields',
|
||||
viewFieldName: 'noteTargets',
|
||||
fieldName: 'noteTargets',
|
||||
position: 7,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
},
|
||||
),
|
||||
opportunityRecordPageFieldsAttachments: createStandardViewFieldFlatMetadata(
|
||||
{
|
||||
...args,
|
||||
objectName: 'opportunity',
|
||||
context: {
|
||||
viewName: 'opportunityRecordPageFields',
|
||||
viewFieldName: 'attachments',
|
||||
fieldName: 'attachments',
|
||||
position: 8,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
},
|
||||
),
|
||||
opportunityRecordPageFieldsTimelineActivities:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'opportunity',
|
||||
context: {
|
||||
viewName: 'opportunityRecordPageFields',
|
||||
viewFieldName: 'timelineActivities',
|
||||
fieldName: 'timelineActivities',
|
||||
position: 9,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
-147
@@ -266,152 +266,5 @@ export const computeStandardPersonViewFields = (
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
personRecordPageFieldsUpdatedAt: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'person',
|
||||
context: {
|
||||
viewName: 'personRecordPageFields',
|
||||
viewFieldName: 'updatedAt',
|
||||
fieldName: 'updatedAt',
|
||||
position: 4,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
personRecordPageFieldsUpdatedBy: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'person',
|
||||
context: {
|
||||
viewName: 'personRecordPageFields',
|
||||
viewFieldName: 'updatedBy',
|
||||
fieldName: 'updatedBy',
|
||||
position: 5,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
personRecordPageFieldsAvatarFile: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'person',
|
||||
context: {
|
||||
viewName: 'personRecordPageFields',
|
||||
viewFieldName: 'avatarFile',
|
||||
fieldName: 'avatarFile',
|
||||
position: 0,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'additional',
|
||||
},
|
||||
}),
|
||||
personRecordPageFieldsPointOfContactForOpportunities:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'person',
|
||||
context: {
|
||||
viewName: 'personRecordPageFields',
|
||||
viewFieldName: 'pointOfContactForOpportunities',
|
||||
fieldName: 'pointOfContactForOpportunities',
|
||||
position: 5,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
personRecordPageFieldsTaskTargets: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'person',
|
||||
context: {
|
||||
viewName: 'personRecordPageFields',
|
||||
viewFieldName: 'taskTargets',
|
||||
fieldName: 'taskTargets',
|
||||
position: 6,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
personRecordPageFieldsNoteTargets: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'person',
|
||||
context: {
|
||||
viewName: 'personRecordPageFields',
|
||||
viewFieldName: 'noteTargets',
|
||||
fieldName: 'noteTargets',
|
||||
position: 7,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
personRecordPageFieldsFavorites: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'person',
|
||||
context: {
|
||||
viewName: 'personRecordPageFields',
|
||||
viewFieldName: 'favorites',
|
||||
fieldName: 'favorites',
|
||||
position: 8,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
personRecordPageFieldsAttachments: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'person',
|
||||
context: {
|
||||
viewName: 'personRecordPageFields',
|
||||
viewFieldName: 'attachments',
|
||||
fieldName: 'attachments',
|
||||
position: 9,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
personRecordPageFieldsMessageParticipants:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'person',
|
||||
context: {
|
||||
viewName: 'personRecordPageFields',
|
||||
viewFieldName: 'messageParticipants',
|
||||
fieldName: 'messageParticipants',
|
||||
position: 10,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
personRecordPageFieldsCalendarEventParticipants:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'person',
|
||||
context: {
|
||||
viewName: 'personRecordPageFields',
|
||||
viewFieldName: 'calendarEventParticipants',
|
||||
fieldName: 'calendarEventParticipants',
|
||||
position: 11,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
personRecordPageFieldsTimelineActivities:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'person',
|
||||
context: {
|
||||
viewName: 'personRecordPageFields',
|
||||
viewFieldName: 'timelineActivities',
|
||||
fieldName: 'timelineActivities',
|
||||
position: 12,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
+13
-80
@@ -255,6 +255,19 @@ export const computeStandardTaskViewFields = (
|
||||
}),
|
||||
|
||||
// taskRecordPageFields view fields
|
||||
taskRecordPageFieldsTitle: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'task',
|
||||
context: {
|
||||
viewName: 'taskRecordPageFields',
|
||||
viewFieldName: 'title',
|
||||
fieldName: 'title',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
taskRecordPageFieldsDueAt: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'task',
|
||||
@@ -333,85 +346,5 @@ export const computeStandardTaskViewFields = (
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
taskRecordPageFieldsBodyV2: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'task',
|
||||
context: {
|
||||
viewName: 'taskRecordPageFields',
|
||||
viewFieldName: 'bodyV2',
|
||||
fieldName: 'bodyV2',
|
||||
position: 0,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'additional',
|
||||
},
|
||||
}),
|
||||
taskRecordPageFieldsUpdatedAt: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'task',
|
||||
context: {
|
||||
viewName: 'taskRecordPageFields',
|
||||
viewFieldName: 'updatedAt',
|
||||
fieldName: 'updatedAt',
|
||||
position: 2,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
taskRecordPageFieldsUpdatedBy: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'task',
|
||||
context: {
|
||||
viewName: 'taskRecordPageFields',
|
||||
viewFieldName: 'updatedBy',
|
||||
fieldName: 'updatedBy',
|
||||
position: 3,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
taskRecordPageFieldsAttachments: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'task',
|
||||
context: {
|
||||
viewName: 'taskRecordPageFields',
|
||||
viewFieldName: 'attachments',
|
||||
fieldName: 'attachments',
|
||||
position: 5,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
taskRecordPageFieldsTimelineActivities: createStandardViewFieldFlatMetadata(
|
||||
{
|
||||
...args,
|
||||
objectName: 'task',
|
||||
context: {
|
||||
viewName: 'taskRecordPageFields',
|
||||
viewFieldName: 'timelineActivities',
|
||||
fieldName: 'timelineActivities',
|
||||
position: 6,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
},
|
||||
),
|
||||
taskRecordPageFieldsFavorites: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'task',
|
||||
context: {
|
||||
viewName: 'taskRecordPageFields',
|
||||
viewFieldName: 'favorites',
|
||||
fieldName: 'favorites',
|
||||
position: 7,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
+13
-105
@@ -46,6 +46,19 @@ export const computeStandardWorkflowRunViewFields = (
|
||||
}),
|
||||
|
||||
// workflowRunRecordPageFields view fields
|
||||
workflowRunRecordPageFieldsName: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowRun',
|
||||
context: {
|
||||
viewName: 'workflowRunRecordPageFields',
|
||||
viewFieldName: 'name',
|
||||
fieldName: 'name',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
workflowRunRecordPageFieldsStatus: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowRun',
|
||||
@@ -138,110 +151,5 @@ export const computeStandardWorkflowRunViewFields = (
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
workflowRunRecordPageFieldsEnqueuedAt: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowRun',
|
||||
context: {
|
||||
viewName: 'workflowRunRecordPageFields',
|
||||
viewFieldName: 'enqueuedAt',
|
||||
fieldName: 'enqueuedAt',
|
||||
position: 0,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'additional',
|
||||
},
|
||||
}),
|
||||
workflowRunRecordPageFieldsOutput: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowRun',
|
||||
context: {
|
||||
viewName: 'workflowRunRecordPageFields',
|
||||
viewFieldName: 'output',
|
||||
fieldName: 'output',
|
||||
position: 6,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
workflowRunRecordPageFieldsContext: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowRun',
|
||||
context: {
|
||||
viewName: 'workflowRunRecordPageFields',
|
||||
viewFieldName: 'context',
|
||||
fieldName: 'context',
|
||||
position: 7,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
workflowRunRecordPageFieldsState: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowRun',
|
||||
context: {
|
||||
viewName: 'workflowRunRecordPageFields',
|
||||
viewFieldName: 'state',
|
||||
fieldName: 'state',
|
||||
position: 8,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
workflowRunRecordPageFieldsUpdatedAt: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowRun',
|
||||
context: {
|
||||
viewName: 'workflowRunRecordPageFields',
|
||||
viewFieldName: 'updatedAt',
|
||||
fieldName: 'updatedAt',
|
||||
position: 2,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
workflowRunRecordPageFieldsUpdatedBy: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowRun',
|
||||
context: {
|
||||
viewName: 'workflowRunRecordPageFields',
|
||||
viewFieldName: 'updatedBy',
|
||||
fieldName: 'updatedBy',
|
||||
position: 3,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
workflowRunRecordPageFieldsFavorites: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowRun',
|
||||
context: {
|
||||
viewName: 'workflowRunRecordPageFields',
|
||||
viewFieldName: 'favorites',
|
||||
fieldName: 'favorites',
|
||||
position: 9,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
workflowRunRecordPageFieldsTimelineActivities:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowRun',
|
||||
context: {
|
||||
viewName: 'workflowRunRecordPageFields',
|
||||
viewFieldName: 'timelineActivities',
|
||||
fieldName: 'timelineActivities',
|
||||
position: 10,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
+13
-96
@@ -70,6 +70,19 @@ export const computeStandardWorkflowVersionViewFields = (
|
||||
}),
|
||||
|
||||
// workflowVersionRecordPageFields view fields
|
||||
workflowVersionRecordPageFieldsName: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowVersion',
|
||||
context: {
|
||||
viewName: 'workflowVersionRecordPageFields',
|
||||
viewFieldName: 'name',
|
||||
fieldName: 'name',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
workflowVersionRecordPageFieldsStatus: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowVersion',
|
||||
@@ -126,101 +139,5 @@ export const computeStandardWorkflowVersionViewFields = (
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
workflowVersionRecordPageFieldsSteps: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowVersion',
|
||||
context: {
|
||||
viewName: 'workflowVersionRecordPageFields',
|
||||
viewFieldName: 'steps',
|
||||
fieldName: 'steps',
|
||||
position: 0,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'additional',
|
||||
},
|
||||
}),
|
||||
workflowVersionRecordPageFieldsCreatedBy:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowVersion',
|
||||
context: {
|
||||
viewName: 'workflowVersionRecordPageFields',
|
||||
viewFieldName: 'createdBy',
|
||||
fieldName: 'createdBy',
|
||||
position: 1,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
workflowVersionRecordPageFieldsUpdatedAt:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowVersion',
|
||||
context: {
|
||||
viewName: 'workflowVersionRecordPageFields',
|
||||
viewFieldName: 'updatedAt',
|
||||
fieldName: 'updatedAt',
|
||||
position: 2,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
workflowVersionRecordPageFieldsUpdatedBy:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowVersion',
|
||||
context: {
|
||||
viewName: 'workflowVersionRecordPageFields',
|
||||
viewFieldName: 'updatedBy',
|
||||
fieldName: 'updatedBy',
|
||||
position: 3,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'other',
|
||||
},
|
||||
}),
|
||||
workflowVersionRecordPageFieldsRuns: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowVersion',
|
||||
context: {
|
||||
viewName: 'workflowVersionRecordPageFields',
|
||||
viewFieldName: 'runs',
|
||||
fieldName: 'runs',
|
||||
position: 4,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
workflowVersionRecordPageFieldsFavorites:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowVersion',
|
||||
context: {
|
||||
viewName: 'workflowVersionRecordPageFields',
|
||||
viewFieldName: 'favorites',
|
||||
fieldName: 'favorites',
|
||||
position: 5,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
workflowVersionRecordPageFieldsTimelineActivities:
|
||||
createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'workflowVersion',
|
||||
context: {
|
||||
viewName: 'workflowVersionRecordPageFields',
|
||||
viewFieldName: 'timelineActivities',
|
||||
fieldName: 'timelineActivities',
|
||||
position: 6,
|
||||
isVisible: false,
|
||||
size: 150,
|
||||
viewFieldGroupName: 'general',
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
+1
@@ -4,4 +4,5 @@ export const DEFAULT_FEATURE_FLAGS = [
|
||||
FeatureFlagKey.IS_ATTACHMENT_MIGRATED,
|
||||
FeatureFlagKey.IS_NOTE_TARGET_MIGRATED,
|
||||
FeatureFlagKey.IS_TASK_TARGET_MIGRATED,
|
||||
FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED,
|
||||
] as const satisfies FeatureFlagKey[];
|
||||
|
||||
+6
-18
@@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
import { ViewType } 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';
|
||||
@@ -96,9 +95,8 @@ export class FlatViewFieldValidatorService {
|
||||
}
|
||||
|
||||
if (
|
||||
flatView.type !== ViewType.FIELDS_WIDGET &&
|
||||
flatObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier ===
|
||||
updatedFlatViewField.fieldMetadataUniversalIdentifier
|
||||
updatedFlatViewField.fieldMetadataUniversalIdentifier
|
||||
) {
|
||||
const otherFlatViewFields =
|
||||
findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow(
|
||||
@@ -129,7 +127,6 @@ export class FlatViewFieldValidatorService {
|
||||
flatViewFieldMaps: optimisticFlatViewFieldMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatViewMaps,
|
||||
},
|
||||
}: UniversalFlatEntityValidationArgs<
|
||||
typeof ALL_METADATA_NAME.viewField
|
||||
@@ -180,18 +177,11 @@ export class FlatViewFieldValidatorService {
|
||||
flatObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier ===
|
||||
existingFlatViewField.fieldMetadataUniversalIdentifier
|
||||
) {
|
||||
const flatView = findFlatEntityByUniversalIdentifier({
|
||||
universalIdentifier: existingFlatViewField.viewUniversalIdentifier,
|
||||
flatEntityMaps: flatViewMaps,
|
||||
validationResult.errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`Label identifier view field cannot be deleted`,
|
||||
userFriendlyMessage: msg`Label identifier view field cannot be deleted`,
|
||||
});
|
||||
|
||||
if (!isDefined(flatView) || flatView.type !== ViewType.FIELDS_WIDGET) {
|
||||
validationResult.errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`Label identifier view field cannot be deleted`,
|
||||
userFriendlyMessage: msg`Label identifier view field cannot be deleted`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
@@ -299,9 +289,8 @@ export class FlatViewFieldValidatorService {
|
||||
}
|
||||
|
||||
if (
|
||||
flatView.type !== ViewType.FIELDS_WIDGET &&
|
||||
flatObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier ===
|
||||
flatViewFieldToValidate.fieldMetadataUniversalIdentifier
|
||||
flatViewFieldToValidate.fieldMetadataUniversalIdentifier
|
||||
) {
|
||||
validationResult.errors.push(
|
||||
...validateLabelIdentifierFieldMetadataIdFlatViewField({
|
||||
@@ -310,7 +299,6 @@ export class FlatViewFieldValidatorService {
|
||||
}),
|
||||
);
|
||||
} else if (
|
||||
flatView.type !== ViewType.FIELDS_WIDGET &&
|
||||
otherFlatViewFields.some(
|
||||
(flatViewField) =>
|
||||
flatViewField.fieldMetadataUniversalIdentifier ===
|
||||
|
||||
+1695
-45
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -93,6 +93,6 @@ describe('View side effect on object creation', () => {
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(createdViewFields.length).toBe(5);
|
||||
expect(createdViewFields.length).toBe(7);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -188,6 +188,9 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
},
|
||||
viewFields: {
|
||||
handle: {
|
||||
universalIdentifier: 'e22a1d19-c1bb-4265-ae48-2054513c21fe',
|
||||
},
|
||||
workspaceMember: {
|
||||
universalIdentifier: 'f2f5732f-7435-44be-986b-4c4d834fdfeb',
|
||||
},
|
||||
@@ -402,6 +405,9 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
},
|
||||
viewFields: {
|
||||
handle: {
|
||||
universalIdentifier: 'cd1f641b-5746-49db-9a7e-82dd9a63593d',
|
||||
},
|
||||
connectedAccount: {
|
||||
universalIdentifier: 'bdb40f41-f9ba-4b59-a8cf-878c23701ab3',
|
||||
},
|
||||
@@ -802,33 +808,6 @@ export const STANDARD_OBJECTS = {
|
||||
createdBy: {
|
||||
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1210',
|
||||
},
|
||||
updatedAt: {
|
||||
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1212',
|
||||
},
|
||||
updatedBy: {
|
||||
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1213',
|
||||
},
|
||||
people: {
|
||||
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1214',
|
||||
},
|
||||
taskTargets: {
|
||||
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1215',
|
||||
},
|
||||
noteTargets: {
|
||||
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1216',
|
||||
},
|
||||
opportunities: {
|
||||
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1217',
|
||||
},
|
||||
favorites: {
|
||||
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1218',
|
||||
},
|
||||
attachments: {
|
||||
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1219',
|
||||
},
|
||||
timelineActivities: {
|
||||
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c121a',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -935,6 +914,9 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
},
|
||||
viewFields: {
|
||||
handle: {
|
||||
universalIdentifier: '41d86e23-ceb2-41ab-975f-8ec5a1023ece',
|
||||
},
|
||||
provider: {
|
||||
universalIdentifier: '83171d2a-0d11-42b1-991d-8d4346b02cff',
|
||||
},
|
||||
@@ -1231,6 +1213,9 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
},
|
||||
viewFields: {
|
||||
name: {
|
||||
universalIdentifier: 'cac4f0f7-3a6a-49b1-b86f-41b20f2455c0',
|
||||
},
|
||||
createdAt: {
|
||||
universalIdentifier: 'a4e42591-844c-47d1-b72e-5ded3d541694',
|
||||
},
|
||||
@@ -1569,6 +1554,9 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
},
|
||||
viewFields: {
|
||||
handle: {
|
||||
universalIdentifier: 'a9cbb9a5-a6b4-417e-93ad-a5e578c222db',
|
||||
},
|
||||
connectedAccount: {
|
||||
universalIdentifier: '19079cf6-2a9c-40b9-b6c2-58d63c6e37ad',
|
||||
},
|
||||
@@ -1682,6 +1670,9 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
},
|
||||
viewFields: {
|
||||
name: {
|
||||
universalIdentifier: '6fa8c474-ee22-47f1-b830-4f169ff82315',
|
||||
},
|
||||
messageChannel: {
|
||||
universalIdentifier: '2fb6ff09-bed5-4b31-af0f-7fa3df5612da',
|
||||
},
|
||||
@@ -1800,6 +1791,9 @@ export const STANDARD_OBJECTS = {
|
||||
role: {
|
||||
universalIdentifier: '5d1f9a65-85cc-41b2-a8bf-8e2c97aab4b3',
|
||||
},
|
||||
handle: {
|
||||
universalIdentifier: '97295fc0-cdb8-4d84-8c1b-327837255c0d',
|
||||
},
|
||||
displayName: {
|
||||
universalIdentifier: 'c50748fe-9f54-4e09-b572-111f076ec7db',
|
||||
},
|
||||
@@ -2024,6 +2018,9 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
},
|
||||
viewFields: {
|
||||
title: {
|
||||
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a115201',
|
||||
},
|
||||
createdAt: {
|
||||
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a115202',
|
||||
},
|
||||
@@ -2033,24 +2030,6 @@ export const STANDARD_OBJECTS = {
|
||||
noteTargets: {
|
||||
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a115204',
|
||||
},
|
||||
bodyV2: {
|
||||
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a115205',
|
||||
},
|
||||
updatedAt: {
|
||||
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a115206',
|
||||
},
|
||||
updatedBy: {
|
||||
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a115207',
|
||||
},
|
||||
attachments: {
|
||||
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a115208',
|
||||
},
|
||||
timelineActivities: {
|
||||
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a115209',
|
||||
},
|
||||
favorites: {
|
||||
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a11520a',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -2299,27 +2278,6 @@ export const STANDARD_OBJECTS = {
|
||||
createdBy: {
|
||||
universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca3208',
|
||||
},
|
||||
updatedAt: {
|
||||
universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca320a',
|
||||
},
|
||||
updatedBy: {
|
||||
universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca320b',
|
||||
},
|
||||
favorites: {
|
||||
universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca320c',
|
||||
},
|
||||
taskTargets: {
|
||||
universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca320d',
|
||||
},
|
||||
noteTargets: {
|
||||
universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca320e',
|
||||
},
|
||||
attachments: {
|
||||
universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca320f',
|
||||
},
|
||||
timelineActivities: {
|
||||
universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca3210',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -2479,39 +2437,6 @@ export const STANDARD_OBJECTS = {
|
||||
createdBy: {
|
||||
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12210',
|
||||
},
|
||||
updatedAt: {
|
||||
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12212',
|
||||
},
|
||||
updatedBy: {
|
||||
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12213',
|
||||
},
|
||||
avatarFile: {
|
||||
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12214',
|
||||
},
|
||||
pointOfContactForOpportunities: {
|
||||
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12215',
|
||||
},
|
||||
taskTargets: {
|
||||
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12216',
|
||||
},
|
||||
noteTargets: {
|
||||
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12217',
|
||||
},
|
||||
favorites: {
|
||||
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12218',
|
||||
},
|
||||
attachments: {
|
||||
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12219',
|
||||
},
|
||||
messageParticipants: {
|
||||
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea1221a',
|
||||
},
|
||||
calendarEventParticipants: {
|
||||
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea1221b',
|
||||
},
|
||||
timelineActivities: {
|
||||
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea1221c',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -2685,6 +2610,9 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
},
|
||||
viewFields: {
|
||||
title: {
|
||||
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a6201',
|
||||
},
|
||||
dueAt: {
|
||||
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a6202',
|
||||
},
|
||||
@@ -2703,24 +2631,6 @@ export const STANDARD_OBJECTS = {
|
||||
taskTargets: {
|
||||
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a6207',
|
||||
},
|
||||
bodyV2: {
|
||||
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a6208',
|
||||
},
|
||||
updatedAt: {
|
||||
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a6209',
|
||||
},
|
||||
updatedBy: {
|
||||
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a620a',
|
||||
},
|
||||
attachments: {
|
||||
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a620b',
|
||||
},
|
||||
timelineActivities: {
|
||||
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a620c',
|
||||
},
|
||||
favorites: {
|
||||
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a620d',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -3204,6 +3114,9 @@ export const STANDARD_OBJECTS = {
|
||||
workflowRunRecordPageFields: {
|
||||
universalIdentifier: '20202020-a011-4a11-8a11-a0bcf10abcf1',
|
||||
viewFields: {
|
||||
name: {
|
||||
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcf5',
|
||||
},
|
||||
status: {
|
||||
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcf6',
|
||||
},
|
||||
@@ -3225,30 +3138,6 @@ export const STANDARD_OBJECTS = {
|
||||
createdBy: {
|
||||
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcfc',
|
||||
},
|
||||
enqueuedAt: {
|
||||
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcfd',
|
||||
},
|
||||
output: {
|
||||
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcfe',
|
||||
},
|
||||
context: {
|
||||
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcff',
|
||||
},
|
||||
state: {
|
||||
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abd01',
|
||||
},
|
||||
updatedAt: {
|
||||
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abd02',
|
||||
},
|
||||
updatedBy: {
|
||||
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abd03',
|
||||
},
|
||||
favorites: {
|
||||
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abd04',
|
||||
},
|
||||
timelineActivities: {
|
||||
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abd05',
|
||||
},
|
||||
},
|
||||
viewFieldGroups: {
|
||||
general: {
|
||||
@@ -3340,6 +3229,9 @@ export const STANDARD_OBJECTS = {
|
||||
workflowVersionRecordPageFields: {
|
||||
universalIdentifier: '20202020-a010-4a10-8a10-a0bcf10aaef1',
|
||||
viewFields: {
|
||||
name: {
|
||||
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaef5',
|
||||
},
|
||||
status: {
|
||||
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaef6',
|
||||
},
|
||||
@@ -3352,27 +3244,6 @@ export const STANDARD_OBJECTS = {
|
||||
createdAt: {
|
||||
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaef9',
|
||||
},
|
||||
steps: {
|
||||
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaefa',
|
||||
},
|
||||
createdBy: {
|
||||
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaefb',
|
||||
},
|
||||
updatedAt: {
|
||||
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaefc',
|
||||
},
|
||||
updatedBy: {
|
||||
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaefd',
|
||||
},
|
||||
runs: {
|
||||
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaefe',
|
||||
},
|
||||
favorites: {
|
||||
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaeff',
|
||||
},
|
||||
timelineActivities: {
|
||||
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaf01',
|
||||
},
|
||||
},
|
||||
viewFieldGroups: {
|
||||
general: {
|
||||
|
||||
Reference in New Issue
Block a user