mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-22 19:41:53 +03:00
Merge remote-tracking branch 'origin/main' into feat/allow-to-use-custom-domain
This commit is contained in:
commit
7b746e6950
@ -40,6 +40,7 @@
|
||||
"@tiptap/extension-text-style": "^2.8.0",
|
||||
"@tiptap/react": "^2.8.0",
|
||||
"@xyflow/react": "^12.0.4",
|
||||
"buffer": "^6.0.3",
|
||||
"transliteration": "^2.3.5"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { MultipleRecordsActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/multiple-records/components/MultipleRecordsActionMenuEntrySetterEffect';
|
||||
import { NoSelectionActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/no-selection/components/NoSelectionActionMenuEntrySetterEffect';
|
||||
import { SingleRecordActionMenuEntrySetter } from '@/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetter';
|
||||
import { SingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect';
|
||||
import { WorkflowRunRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/workflow-run-record-actions/components/WorkflowRunRecordActionMenuEntrySetter';
|
||||
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
@ -59,7 +59,7 @@ const ActionEffects = ({
|
||||
{contextStoreTargetedRecordsRule.mode === 'selection' &&
|
||||
contextStoreTargetedRecordsRule.selectedRecordIds.length === 1 && (
|
||||
<>
|
||||
<SingleRecordActionMenuEntrySetter
|
||||
<SingleRecordActionMenuEntrySetterEffect
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
{isWorkflowEnabled && (
|
||||
|
@ -1,26 +0,0 @@
|
||||
import { SingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect';
|
||||
import { WorkflowSingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/workflow-actions/components/WorkflowSingleRecordActionMenuEntrySetterEffect';
|
||||
import { WorkflowVersionsSingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/components/WorkflowVersionsSingleRecordActionMenuEntrySetterEffect';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export const SingleRecordActionMenuEntrySetter = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<SingleRecordActionMenuEntrySetterEffect
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
{objectMetadataItem.nameSingular === CoreObjectNameSingular.Workflow && (
|
||||
<WorkflowSingleRecordActionMenuEntrySetterEffect />
|
||||
)}
|
||||
{objectMetadataItem.nameSingular ===
|
||||
CoreObjectNameSingular.WorkflowVersion && (
|
||||
<WorkflowVersionsSingleRecordActionMenuEntrySetterEffect />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,24 +1,72 @@
|
||||
import { getActionConfig } from '@/action-menu/actions/record-actions/single-record/utils/getActionConfig';
|
||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useEffect } from 'react';
|
||||
import { useSingleRecordActions } from '../hooks/useSingleRecordActions';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const SingleRecordActionMenuEntrySetterEffect = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const { registerSingleRecordActions, unregisterSingleRecordActions } =
|
||||
useSingleRecordActions({
|
||||
objectMetadataItem,
|
||||
});
|
||||
const isPageHeaderV2Enabled = useIsFeatureEnabled(
|
||||
'IS_PAGE_HEADER_V2_ENABLED',
|
||||
);
|
||||
|
||||
const actionConfig = getActionConfig(
|
||||
objectMetadataItem,
|
||||
isPageHeaderV2Enabled,
|
||||
);
|
||||
|
||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||
|
||||
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
const selectedRecordId =
|
||||
contextStoreTargetedRecordsRule.mode === 'selection'
|
||||
? contextStoreTargetedRecordsRule.selectedRecordIds[0]
|
||||
: undefined;
|
||||
|
||||
if (!isDefined(selectedRecordId)) {
|
||||
throw new Error('Selected record ID is required');
|
||||
}
|
||||
|
||||
const actionMenuEntries = Object.values(actionConfig ?? {})
|
||||
.map((action) => {
|
||||
const { shouldBeRegistered, onClick, ConfirmationModal } =
|
||||
action.actionHook({
|
||||
recordId: selectedRecordId,
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
if (shouldBeRegistered) {
|
||||
return {
|
||||
...action,
|
||||
onClick,
|
||||
ConfirmationModal,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
useEffect(() => {
|
||||
registerSingleRecordActions();
|
||||
for (const action of actionMenuEntries) {
|
||||
addActionMenuEntry(action);
|
||||
}
|
||||
|
||||
return () => {
|
||||
unregisterSingleRecordActions();
|
||||
for (const action of actionMenuEntries) {
|
||||
removeActionMenuEntry(action.key);
|
||||
}
|
||||
};
|
||||
}, [registerSingleRecordActions, unregisterSingleRecordActions]);
|
||||
}, [actionMenuEntries, addActionMenuEntry, removeActionMenuEntry]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
@ -0,0 +1,47 @@
|
||||
import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction';
|
||||
import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction';
|
||||
import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction';
|
||||
import { SingleRecordActionHook } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import {
|
||||
ActionMenuEntry,
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { IconHeart, IconHeartOff, IconTrash } from 'twenty-ui';
|
||||
|
||||
export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1: Record<
|
||||
string,
|
||||
ActionMenuEntry & {
|
||||
actionHook: SingleRecordActionHook;
|
||||
}
|
||||
> = {
|
||||
addToFavoritesSingleRecord: {
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
key: 'add-to-favorites-single-record',
|
||||
label: 'Add to favorites',
|
||||
position: 0,
|
||||
Icon: IconHeart,
|
||||
actionHook: useAddToFavoritesSingleRecordAction,
|
||||
},
|
||||
removeFromFavoritesSingleRecord: {
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
key: 'remove-from-favorites-single-record',
|
||||
label: 'Remove from favorites',
|
||||
position: 1,
|
||||
Icon: IconHeartOff,
|
||||
actionHook: useRemoveFromFavoritesSingleRecordAction,
|
||||
},
|
||||
deleteSingleRecord: {
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
key: 'delete-single-record',
|
||||
label: 'Delete',
|
||||
position: 2,
|
||||
Icon: IconTrash,
|
||||
accent: 'danger',
|
||||
isPinned: true,
|
||||
actionHook: useDeleteSingleRecordAction,
|
||||
},
|
||||
};
|
@ -0,0 +1,49 @@
|
||||
import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction';
|
||||
import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction';
|
||||
import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction';
|
||||
import { SingleRecordActionHook } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import {
|
||||
ActionMenuEntry,
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { IconHeart, IconHeartOff, IconTrash } from 'twenty-ui';
|
||||
|
||||
export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
|
||||
string,
|
||||
ActionMenuEntry & {
|
||||
actionHook: SingleRecordActionHook;
|
||||
}
|
||||
> = {
|
||||
addToFavoritesSingleRecord: {
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
key: 'add-to-favorites-single-record',
|
||||
label: 'Add to favorites',
|
||||
position: 0,
|
||||
isPinned: true,
|
||||
Icon: IconHeart,
|
||||
actionHook: useAddToFavoritesSingleRecordAction,
|
||||
},
|
||||
removeFromFavoritesSingleRecord: {
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
key: 'remove-from-favorites-single-record',
|
||||
label: 'Remove from favorites',
|
||||
isPinned: true,
|
||||
position: 1,
|
||||
Icon: IconHeartOff,
|
||||
actionHook: useRemoveFromFavoritesSingleRecordAction,
|
||||
},
|
||||
deleteSingleRecord: {
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
key: 'delete-single-record',
|
||||
label: 'Delete',
|
||||
position: 2,
|
||||
Icon: IconTrash,
|
||||
accent: 'danger',
|
||||
isPinned: true,
|
||||
actionHook: useDeleteSingleRecordAction,
|
||||
},
|
||||
};
|
@ -1 +0,0 @@
|
||||
export const NUMBER_OF_STANDARD_SINGLE_RECORD_ACTIONS_ON_ALL_OBJECTS = 2;
|
@ -0,0 +1,135 @@
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import {
|
||||
GetJestMetadataAndApolloMocksAndActionMenuWrapperProps,
|
||||
getJestMetadataAndApolloMocksAndActionMenuWrapper,
|
||||
} from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
import { getPeopleMock } from '~/testing/mock-data/people';
|
||||
import { useAddToFavoritesSingleRecordAction } from '../useAddToFavoritesSingleRecordAction';
|
||||
|
||||
const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||
(item) => item.nameSingular === 'person',
|
||||
)!;
|
||||
|
||||
const peopleMock = getPeopleMock();
|
||||
|
||||
const favoritesMock = [
|
||||
{
|
||||
id: '1',
|
||||
recordId: peopleMock[0].id,
|
||||
position: 0,
|
||||
avatarType: 'rounded',
|
||||
avatarUrl: '',
|
||||
labelIdentifier: ' ',
|
||||
link: `/object/${personMockObjectMetadataItem.nameSingular}/${peopleMock[0].id}`,
|
||||
objectNameSingular: personMockObjectMetadataItem.nameSingular,
|
||||
workspaceMemberId: '1',
|
||||
favoriteFolderId: undefined,
|
||||
},
|
||||
];
|
||||
|
||||
jest.mock('@/favorites/hooks/useFavorites', () => ({
|
||||
useFavorites: () => ({
|
||||
favorites: favoritesMock,
|
||||
sortedFavorites: favoritesMock,
|
||||
}),
|
||||
}));
|
||||
|
||||
const createFavoriteMock = jest.fn();
|
||||
|
||||
jest.mock('@/favorites/hooks/useCreateFavorite', () => ({
|
||||
useCreateFavorite: () => ({
|
||||
createFavorite: createFavoriteMock,
|
||||
}),
|
||||
}));
|
||||
|
||||
const wrapperConfigWithSelectedRecordAsFavorite: GetJestMetadataAndApolloMocksAndActionMenuWrapperProps =
|
||||
{
|
||||
apolloMocks: [],
|
||||
componentInstanceId: '1',
|
||||
contextStoreCurrentObjectMetadataNameSingular:
|
||||
personMockObjectMetadataItem.nameSingular,
|
||||
contextStoreTargetedRecordsRule: {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [peopleMock[0].id],
|
||||
},
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(recordStoreFamilyState(peopleMock[0].id), peopleMock[0]);
|
||||
snapshot.set(recordStoreFamilyState(peopleMock[1].id), peopleMock[1]);
|
||||
},
|
||||
};
|
||||
|
||||
const wrapperConfigWithSelectedRecordNotAsFavorite: GetJestMetadataAndApolloMocksAndActionMenuWrapperProps =
|
||||
{
|
||||
...wrapperConfigWithSelectedRecordAsFavorite,
|
||||
contextStoreTargetedRecordsRule: {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [peopleMock[1].id],
|
||||
},
|
||||
};
|
||||
|
||||
const wrapperWithSelectedRecordAsFavorite =
|
||||
getJestMetadataAndApolloMocksAndActionMenuWrapper(
|
||||
wrapperConfigWithSelectedRecordAsFavorite,
|
||||
);
|
||||
|
||||
const wrapperWithSelectedRecordNotAsFavorite =
|
||||
getJestMetadataAndApolloMocksAndActionMenuWrapper(
|
||||
wrapperConfigWithSelectedRecordNotAsFavorite,
|
||||
);
|
||||
|
||||
describe('useAddToFavoritesSingleRecordAction', () => {
|
||||
it('should be registered when the record is not a favorite', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useAddToFavoritesSingleRecordAction({
|
||||
recordId: peopleMock[1].id,
|
||||
objectMetadataItem: personMockObjectMetadataItem,
|
||||
}),
|
||||
{
|
||||
wrapper: wrapperWithSelectedRecordNotAsFavorite,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.shouldBeRegistered).toBe(true);
|
||||
});
|
||||
|
||||
it('should not be registered when the record is a favorite', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useAddToFavoritesSingleRecordAction({
|
||||
recordId: peopleMock[0].id,
|
||||
objectMetadataItem: personMockObjectMetadataItem,
|
||||
}),
|
||||
{
|
||||
wrapper: wrapperWithSelectedRecordAsFavorite,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.shouldBeRegistered).toBe(false);
|
||||
});
|
||||
|
||||
it('should call createFavorite on click', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useAddToFavoritesSingleRecordAction({
|
||||
recordId: peopleMock[1].id,
|
||||
objectMetadataItem: personMockObjectMetadataItem,
|
||||
}),
|
||||
{
|
||||
wrapper: wrapperWithSelectedRecordNotAsFavorite,
|
||||
},
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.onClick();
|
||||
});
|
||||
|
||||
expect(createFavoriteMock).toHaveBeenCalledWith(
|
||||
peopleMock[1],
|
||||
personMockObjectMetadataItem.nameSingular,
|
||||
);
|
||||
});
|
||||
});
|
@ -1,121 +1,64 @@
|
||||
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { expect } from '@storybook/test';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
import { getPeopleMock } from '~/testing/mock-data/people';
|
||||
import { useDeleteSingleRecordAction } from '../useDeleteSingleRecordAction';
|
||||
|
||||
const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||
(item) => item.nameSingular === 'person',
|
||||
)!;
|
||||
|
||||
const peopleMock = getPeopleMock();
|
||||
|
||||
const deleteOneRecordMock = jest.fn();
|
||||
|
||||
jest.mock('@/object-record/hooks/useDeleteOneRecord', () => ({
|
||||
useDeleteOneRecord: () => ({
|
||||
deleteOneRecord: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
jest.mock('@/favorites/hooks/useDeleteFavorite', () => ({
|
||||
useDeleteFavorite: () => ({
|
||||
deleteFavorite: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
jest.mock('@/favorites/hooks/useFavorites', () => ({
|
||||
useFavorites: () => ({
|
||||
sortedFavorites: [],
|
||||
}),
|
||||
}));
|
||||
jest.mock('@/object-record/record-table/hooks/useRecordTable', () => ({
|
||||
useRecordTable: () => ({
|
||||
resetTableRowSelection: jest.fn(),
|
||||
deleteOneRecord: deleteOneRecordMock,
|
||||
}),
|
||||
}));
|
||||
|
||||
const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||
(item) => item.nameSingular === 'company',
|
||||
)!;
|
||||
const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
|
||||
apolloMocks: [],
|
||||
componentInstanceId: '1',
|
||||
contextStoreCurrentObjectMetadataNameSingular:
|
||||
personMockObjectMetadataItem.nameSingular,
|
||||
contextStoreTargetedRecordsRule: {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [peopleMock[0].id],
|
||||
},
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(recordStoreFamilyState(peopleMock[0].id), peopleMock[0]);
|
||||
},
|
||||
});
|
||||
|
||||
describe('useDeleteSingleRecordAction', () => {
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<RecoilRoot>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: '1',
|
||||
}}
|
||||
>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: '1',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
it('should register delete action', () => {
|
||||
it('should call deleteOneRecord on click', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const actionMenuEntries = useRecoilComponentValueV2(
|
||||
actionMenuEntriesComponentState,
|
||||
);
|
||||
|
||||
return {
|
||||
actionMenuEntries,
|
||||
useDeleteSingleRecordAction: useDeleteSingleRecordAction({
|
||||
recordId: 'record1',
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
};
|
||||
() =>
|
||||
useDeleteSingleRecordAction({
|
||||
recordId: peopleMock[0].id,
|
||||
objectMetadataItem: personMockObjectMetadataItem,
|
||||
}),
|
||||
{
|
||||
wrapper,
|
||||
},
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.useDeleteSingleRecordAction.registerDeleteSingleRecordAction(
|
||||
{ position: 1 },
|
||||
);
|
||||
});
|
||||
|
||||
expect(result.current.actionMenuEntries.size).toBe(1);
|
||||
expect(
|
||||
result.current.actionMenuEntries.get('delete-single-record'),
|
||||
).toBeDefined();
|
||||
expect(
|
||||
result.current.actionMenuEntries.get('delete-single-record')?.position,
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
it('should unregister delete action', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const actionMenuEntries = useRecoilComponentValueV2(
|
||||
actionMenuEntriesComponentState,
|
||||
);
|
||||
|
||||
return {
|
||||
actionMenuEntries,
|
||||
useDeleteSingleRecordAction: useDeleteSingleRecordAction({
|
||||
recordId: 'record1',
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
};
|
||||
},
|
||||
{ wrapper },
|
||||
);
|
||||
expect(result.current.ConfirmationModal?.props?.isOpen).toBe(false);
|
||||
|
||||
act(() => {
|
||||
result.current.useDeleteSingleRecordAction.registerDeleteSingleRecordAction(
|
||||
{ position: 1 },
|
||||
);
|
||||
result.current.onClick();
|
||||
});
|
||||
|
||||
expect(result.current.actionMenuEntries.size).toBe(1);
|
||||
expect(result.current.ConfirmationModal?.props?.isOpen).toBe(true);
|
||||
|
||||
act(() => {
|
||||
result.current.useDeleteSingleRecordAction.unregisterDeleteSingleRecordAction();
|
||||
result.current.ConfirmationModal?.props?.onConfirmClick();
|
||||
});
|
||||
|
||||
expect(result.current.actionMenuEntries.size).toBe(0);
|
||||
expect(deleteOneRecordMock).toHaveBeenCalledWith(peopleMock[0].id);
|
||||
});
|
||||
});
|
||||
|
@ -1,108 +0,0 @@
|
||||
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { expect } from '@storybook/test';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
import { useManageFavoritesSingleRecordAction } from '../useManageFavoritesSingleRecordAction';
|
||||
|
||||
const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||
(item) => item.nameSingular === 'company',
|
||||
)!;
|
||||
|
||||
const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({
|
||||
apolloMocks: [],
|
||||
});
|
||||
|
||||
describe('useManageFavoritesSingleRecordAction', () => {
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<JestMetadataAndApolloMocksWrapper>
|
||||
<JestObjectMetadataItemSetter>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: '1',
|
||||
}}
|
||||
>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: '1',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</JestObjectMetadataItemSetter>
|
||||
</JestMetadataAndApolloMocksWrapper>
|
||||
);
|
||||
|
||||
it('should register manage favorites action', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const actionMenuEntries = useRecoilComponentValueV2(
|
||||
actionMenuEntriesComponentState,
|
||||
);
|
||||
|
||||
return {
|
||||
actionMenuEntries,
|
||||
useManageFavoritesSingleRecordAction:
|
||||
useManageFavoritesSingleRecordAction({
|
||||
recordId: 'record1',
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
};
|
||||
},
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.useManageFavoritesSingleRecordAction.registerManageFavoritesSingleRecordAction(
|
||||
{ position: 1 },
|
||||
);
|
||||
});
|
||||
|
||||
expect(result.current.actionMenuEntries.size).toBe(1);
|
||||
expect(
|
||||
result.current.actionMenuEntries.get('manage-favorites-single-record'),
|
||||
).toBeDefined();
|
||||
expect(
|
||||
result.current.actionMenuEntries.get('manage-favorites-single-record')
|
||||
?.position,
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
it('should unregister manage favorites action', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const actionMenuEntries = useRecoilComponentValueV2(
|
||||
actionMenuEntriesComponentState,
|
||||
);
|
||||
|
||||
return {
|
||||
actionMenuEntries,
|
||||
useManageFavoritesSingleRecordAction:
|
||||
useManageFavoritesSingleRecordAction({
|
||||
recordId: 'record1',
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
};
|
||||
},
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.useManageFavoritesSingleRecordAction.registerManageFavoritesSingleRecordAction(
|
||||
{ position: 1 },
|
||||
);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.useManageFavoritesSingleRecordAction.unregisterManageFavoritesSingleRecordAction();
|
||||
});
|
||||
|
||||
expect(result.current.actionMenuEntries.size).toBe(0);
|
||||
});
|
||||
});
|
@ -0,0 +1,132 @@
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import {
|
||||
GetJestMetadataAndApolloMocksAndActionMenuWrapperProps,
|
||||
getJestMetadataAndApolloMocksAndActionMenuWrapper,
|
||||
} from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
import { getPeopleMock } from '~/testing/mock-data/people';
|
||||
import { useRemoveFromFavoritesSingleRecordAction } from '../useRemoveFromFavoritesSingleRecordAction';
|
||||
|
||||
const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||
(item) => item.nameSingular === 'person',
|
||||
)!;
|
||||
|
||||
const peopleMock = getPeopleMock();
|
||||
|
||||
const favoritesMock = [
|
||||
{
|
||||
id: '1',
|
||||
recordId: peopleMock[0].id,
|
||||
position: 0,
|
||||
avatarType: 'rounded',
|
||||
avatarUrl: '',
|
||||
labelIdentifier: ' ',
|
||||
link: `/object/${personMockObjectMetadataItem.nameSingular}/${peopleMock[0].id}`,
|
||||
objectNameSingular: personMockObjectMetadataItem.nameSingular,
|
||||
workspaceMemberId: '1',
|
||||
favoriteFolderId: undefined,
|
||||
},
|
||||
];
|
||||
|
||||
jest.mock('@/favorites/hooks/useFavorites', () => ({
|
||||
useFavorites: () => ({
|
||||
favorites: favoritesMock,
|
||||
sortedFavorites: favoritesMock,
|
||||
}),
|
||||
}));
|
||||
|
||||
const deleteFavoriteMock = jest.fn();
|
||||
|
||||
jest.mock('@/favorites/hooks/useDeleteFavorite', () => ({
|
||||
useDeleteFavorite: () => ({
|
||||
deleteFavorite: deleteFavoriteMock,
|
||||
}),
|
||||
}));
|
||||
|
||||
const wrapperConfigWithSelectedRecordAsFavorite: GetJestMetadataAndApolloMocksAndActionMenuWrapperProps =
|
||||
{
|
||||
apolloMocks: [],
|
||||
componentInstanceId: '1',
|
||||
contextStoreCurrentObjectMetadataNameSingular:
|
||||
personMockObjectMetadataItem.nameSingular,
|
||||
contextStoreTargetedRecordsRule: {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [peopleMock[0].id],
|
||||
},
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(recordStoreFamilyState(peopleMock[0].id), peopleMock[0]);
|
||||
snapshot.set(recordStoreFamilyState(peopleMock[1].id), peopleMock[1]);
|
||||
},
|
||||
};
|
||||
|
||||
const wrapperConfigWithSelectedRecordNotAsFavorite: GetJestMetadataAndApolloMocksAndActionMenuWrapperProps =
|
||||
{
|
||||
...wrapperConfigWithSelectedRecordAsFavorite,
|
||||
contextStoreTargetedRecordsRule: {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [peopleMock[1].id],
|
||||
},
|
||||
};
|
||||
|
||||
const wrapperWithSelectedRecordAsFavorite =
|
||||
getJestMetadataAndApolloMocksAndActionMenuWrapper(
|
||||
wrapperConfigWithSelectedRecordAsFavorite,
|
||||
);
|
||||
|
||||
const wrapperWithSelectedRecordNotAsFavorite =
|
||||
getJestMetadataAndApolloMocksAndActionMenuWrapper(
|
||||
wrapperConfigWithSelectedRecordNotAsFavorite,
|
||||
);
|
||||
|
||||
describe('useRemoveFromFavoritesSingleRecordAction', () => {
|
||||
it('should be registered when the record is a favorite', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useRemoveFromFavoritesSingleRecordAction({
|
||||
recordId: peopleMock[0].id,
|
||||
objectMetadataItem: personMockObjectMetadataItem,
|
||||
}),
|
||||
{
|
||||
wrapper: wrapperWithSelectedRecordAsFavorite,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.shouldBeRegistered).toBe(true);
|
||||
});
|
||||
|
||||
it('should not be registered when the record is not a favorite', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useRemoveFromFavoritesSingleRecordAction({
|
||||
recordId: peopleMock[1].id,
|
||||
objectMetadataItem: personMockObjectMetadataItem,
|
||||
}),
|
||||
{
|
||||
wrapper: wrapperWithSelectedRecordNotAsFavorite,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.shouldBeRegistered).toBe(false);
|
||||
});
|
||||
|
||||
it('should call deleteFavorite on click', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useRemoveFromFavoritesSingleRecordAction({
|
||||
recordId: peopleMock[0].id,
|
||||
objectMetadataItem: personMockObjectMetadataItem,
|
||||
}),
|
||||
{
|
||||
wrapper: wrapperWithSelectedRecordAsFavorite,
|
||||
},
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.onClick();
|
||||
});
|
||||
|
||||
expect(deleteFavoriteMock).toHaveBeenCalledWith(favoritesMock[0].id);
|
||||
});
|
||||
});
|
@ -0,0 +1,40 @@
|
||||
import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite';
|
||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useAddToFavoritesSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem =
|
||||
({ recordId, objectMetadataItem }) => {
|
||||
const { sortedFavorites: favorites } = useFavorites();
|
||||
|
||||
const { createFavorite } = useCreateFavorite();
|
||||
|
||||
const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));
|
||||
|
||||
const foundFavorite = favorites?.find(
|
||||
(favorite) => favorite.recordId === recordId,
|
||||
);
|
||||
|
||||
const isFavorite = !!foundFavorite;
|
||||
|
||||
const shouldBeRegistered =
|
||||
isDefined(objectMetadataItem) &&
|
||||
isDefined(selectedRecord) &&
|
||||
!objectMetadataItem.isRemote &&
|
||||
!isFavorite;
|
||||
|
||||
const onClick = () => {
|
||||
if (!shouldBeRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
createFavorite(selectedRecord, objectMetadataItem.nameSingular);
|
||||
};
|
||||
|
||||
return {
|
||||
shouldBeRegistered,
|
||||
onClick,
|
||||
};
|
||||
};
|
@ -1,90 +1,70 @@
|
||||
import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||
import {
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
|
||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { useCallback, useContext, useState } from 'react';
|
||||
import { IconTrash, isDefined } from 'twenty-ui';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useDeleteSingleRecordAction = ({
|
||||
recordId,
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
recordId: string;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||
export const useDeleteSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem =
|
||||
({ recordId, objectMetadataItem }) => {
|
||||
const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] =
|
||||
useState(false);
|
||||
|
||||
const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] =
|
||||
useState(false);
|
||||
const { resetTableRowSelection } = useRecordTable({
|
||||
recordTableId: objectMetadataItem.namePlural,
|
||||
});
|
||||
|
||||
const { resetTableRowSelection } = useRecordTable({
|
||||
recordTableId: objectMetadataItem.namePlural,
|
||||
});
|
||||
const { deleteOneRecord } = useDeleteOneRecord({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
const { deleteOneRecord } = useDeleteOneRecord({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
const { sortedFavorites: favorites } = useFavorites();
|
||||
const { deleteFavorite } = useDeleteFavorite();
|
||||
|
||||
const { sortedFavorites: favorites } = useFavorites();
|
||||
const { deleteFavorite } = useDeleteFavorite();
|
||||
const { closeRightDrawer } = useRightDrawer();
|
||||
|
||||
const { closeRightDrawer } = useRightDrawer();
|
||||
const handleDeleteClick = useCallback(async () => {
|
||||
resetTableRowSelection();
|
||||
|
||||
const handleDeleteClick = useCallback(async () => {
|
||||
resetTableRowSelection();
|
||||
const foundFavorite = favorites?.find(
|
||||
(favorite) => favorite.recordId === recordId,
|
||||
);
|
||||
|
||||
const foundFavorite = favorites?.find(
|
||||
(favorite) => favorite.recordId === recordId,
|
||||
);
|
||||
if (isDefined(foundFavorite)) {
|
||||
deleteFavorite(foundFavorite.id);
|
||||
}
|
||||
|
||||
if (isDefined(foundFavorite)) {
|
||||
deleteFavorite(foundFavorite.id);
|
||||
}
|
||||
await deleteOneRecord(recordId);
|
||||
}, [
|
||||
deleteFavorite,
|
||||
deleteOneRecord,
|
||||
favorites,
|
||||
resetTableRowSelection,
|
||||
recordId,
|
||||
]);
|
||||
|
||||
await deleteOneRecord(recordId);
|
||||
}, [
|
||||
deleteFavorite,
|
||||
deleteOneRecord,
|
||||
favorites,
|
||||
resetTableRowSelection,
|
||||
recordId,
|
||||
]);
|
||||
const isRemoteObject = objectMetadataItem.isRemote;
|
||||
|
||||
const isRemoteObject = objectMetadataItem.isRemote;
|
||||
const { isInRightDrawer, onActionExecutedCallback } =
|
||||
useContext(ActionMenuContext);
|
||||
|
||||
const { isInRightDrawer, onActionExecutedCallback } =
|
||||
useContext(ActionMenuContext);
|
||||
const shouldBeRegistered = !isRemoteObject;
|
||||
|
||||
const registerDeleteSingleRecordAction = ({
|
||||
position,
|
||||
}: {
|
||||
position: number;
|
||||
}) => {
|
||||
if (isRemoteObject) {
|
||||
return;
|
||||
}
|
||||
const onClick = () => {
|
||||
if (!shouldBeRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
addActionMenuEntry({
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
key: 'delete-single-record',
|
||||
label: 'Delete',
|
||||
position,
|
||||
Icon: IconTrash,
|
||||
accent: 'danger',
|
||||
isPinned: true,
|
||||
onClick: () => {
|
||||
setIsDeleteRecordsModalOpen(true);
|
||||
},
|
||||
setIsDeleteRecordsModalOpen(true);
|
||||
};
|
||||
|
||||
return {
|
||||
shouldBeRegistered,
|
||||
onClick,
|
||||
ConfirmationModal: (
|
||||
<ConfirmationModal
|
||||
isOpen={isDeleteRecordsModalOpen}
|
||||
@ -103,15 +83,5 @@ export const useDeleteSingleRecordAction = ({
|
||||
deleteButtonText={'Delete Record'}
|
||||
/>
|
||||
),
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
const unregisterDeleteSingleRecordAction = () => {
|
||||
removeActionMenuEntry('delete-single-record');
|
||||
};
|
||||
|
||||
return {
|
||||
registerDeleteSingleRecordAction,
|
||||
unregisterDeleteSingleRecordAction,
|
||||
};
|
||||
};
|
||||
|
@ -1,77 +0,0 @@
|
||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||
import {
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite';
|
||||
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
|
||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconHeart, IconHeartOff, isDefined } from 'twenty-ui';
|
||||
|
||||
export const useManageFavoritesSingleRecordAction = ({
|
||||
recordId,
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
recordId: string;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||
|
||||
const { sortedFavorites: favorites } = useFavorites();
|
||||
|
||||
const { createFavorite } = useCreateFavorite();
|
||||
|
||||
const { deleteFavorite } = useDeleteFavorite();
|
||||
|
||||
const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));
|
||||
|
||||
const foundFavorite = favorites?.find(
|
||||
(favorite) => favorite.recordId === recordId,
|
||||
);
|
||||
|
||||
const isFavorite = !!foundFavorite;
|
||||
|
||||
const isPageHeaderV2Enabled = useIsFeatureEnabled(
|
||||
'IS_PAGE_HEADER_V2_ENABLED',
|
||||
);
|
||||
|
||||
const registerManageFavoritesSingleRecordAction = ({
|
||||
position,
|
||||
}: {
|
||||
position: number;
|
||||
}) => {
|
||||
if (!isDefined(objectMetadataItem) || objectMetadataItem.isRemote) {
|
||||
return;
|
||||
}
|
||||
|
||||
addActionMenuEntry({
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
key: 'manage-favorites-single-record',
|
||||
isPinned: isPageHeaderV2Enabled,
|
||||
label: isFavorite ? 'Remove from favorites' : 'Add to favorites',
|
||||
position,
|
||||
Icon: isFavorite ? IconHeartOff : IconHeart,
|
||||
onClick: () => {
|
||||
if (isFavorite && isDefined(foundFavorite?.id)) {
|
||||
deleteFavorite(foundFavorite.id);
|
||||
} else if (isDefined(selectedRecord)) {
|
||||
createFavorite(selectedRecord, objectMetadataItem.nameSingular);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const unregisterManageFavoritesSingleRecordAction = () => {
|
||||
removeActionMenuEntry('manage-favorites-single-record');
|
||||
};
|
||||
|
||||
return {
|
||||
registerManageFavoritesSingleRecordAction,
|
||||
unregisterManageFavoritesSingleRecordAction,
|
||||
};
|
||||
};
|
@ -0,0 +1,35 @@
|
||||
import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
|
||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useRemoveFromFavoritesSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem =
|
||||
({ recordId, objectMetadataItem }) => {
|
||||
const { sortedFavorites: favorites } = useFavorites();
|
||||
|
||||
const { deleteFavorite } = useDeleteFavorite();
|
||||
|
||||
const foundFavorite = favorites?.find(
|
||||
(favorite) => favorite.recordId === recordId,
|
||||
);
|
||||
|
||||
const isFavorite = !!foundFavorite;
|
||||
|
||||
const shouldBeRegistered =
|
||||
isDefined(objectMetadataItem) &&
|
||||
!objectMetadataItem.isRemote &&
|
||||
isFavorite;
|
||||
|
||||
const onClick = () => {
|
||||
if (!shouldBeRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteFavorite(foundFavorite.id);
|
||||
};
|
||||
|
||||
return {
|
||||
shouldBeRegistered,
|
||||
onClick,
|
||||
};
|
||||
};
|
@ -1,56 +0,0 @@
|
||||
import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction';
|
||||
import { useManageFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useManageFavoritesSingleRecordAction';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useSingleRecordActions = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
const selectedRecordId =
|
||||
contextStoreTargetedRecordsRule.mode === 'selection'
|
||||
? contextStoreTargetedRecordsRule.selectedRecordIds[0]
|
||||
: undefined;
|
||||
|
||||
if (!isDefined(selectedRecordId)) {
|
||||
throw new Error('Selected record ID is required');
|
||||
}
|
||||
|
||||
const {
|
||||
registerManageFavoritesSingleRecordAction,
|
||||
unregisterManageFavoritesSingleRecordAction,
|
||||
} = useManageFavoritesSingleRecordAction({
|
||||
recordId: selectedRecordId,
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const {
|
||||
registerDeleteSingleRecordAction,
|
||||
unregisterDeleteSingleRecordAction,
|
||||
} = useDeleteSingleRecordAction({
|
||||
recordId: selectedRecordId,
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const registerSingleRecordActions = () => {
|
||||
registerManageFavoritesSingleRecordAction({ position: 1 });
|
||||
registerDeleteSingleRecordAction({ position: 2 });
|
||||
};
|
||||
|
||||
const unregisterSingleRecordActions = () => {
|
||||
unregisterManageFavoritesSingleRecordAction();
|
||||
unregisterDeleteSingleRecordAction();
|
||||
};
|
||||
|
||||
return {
|
||||
registerSingleRecordActions,
|
||||
unregisterSingleRecordActions,
|
||||
};
|
||||
};
|
@ -0,0 +1,23 @@
|
||||
import { DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1 } from '@/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV1';
|
||||
import { DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2 } from '@/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV2';
|
||||
import { WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig';
|
||||
import { WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export const getActionConfig = (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
isPageHeaderV2Enabled: boolean,
|
||||
) => {
|
||||
if (objectMetadataItem.nameSingular === CoreObjectNameSingular.Workflow) {
|
||||
return WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG;
|
||||
}
|
||||
if (
|
||||
objectMetadataItem.nameSingular === CoreObjectNameSingular.WorkflowVersion
|
||||
) {
|
||||
return WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG;
|
||||
}
|
||||
return isPageHeaderV2Enabled
|
||||
? DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2
|
||||
: DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1;
|
||||
};
|
@ -1,21 +0,0 @@
|
||||
import { NUMBER_OF_STANDARD_SINGLE_RECORD_ACTIONS_ON_ALL_OBJECTS } from '@/action-menu/actions/record-actions/single-record/constants/NumberOfStandardSingleRecordActionsOnAllObjects';
|
||||
import { useEffect } from 'react';
|
||||
import { useWorkflowSingleRecordActions } from '../hooks/useWorkflowSingleRecordActions';
|
||||
|
||||
export const WorkflowSingleRecordActionMenuEntrySetterEffect = () => {
|
||||
const { registerSingleRecordActions, unregisterSingleRecordActions } =
|
||||
useWorkflowSingleRecordActions();
|
||||
|
||||
useEffect(() => {
|
||||
registerSingleRecordActions({
|
||||
startPosition:
|
||||
NUMBER_OF_STANDARD_SINGLE_RECORD_ACTIONS_ON_ALL_OBJECTS + 1,
|
||||
});
|
||||
|
||||
return () => {
|
||||
unregisterSingleRecordActions();
|
||||
};
|
||||
}, [registerSingleRecordActions, unregisterSingleRecordActions]);
|
||||
|
||||
return null;
|
||||
};
|
@ -0,0 +1,110 @@
|
||||
import { useActivateDraftWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateDraftWorkflowSingleRecordAction';
|
||||
import { useActivateLastPublishedVersionWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateLastPublishedVersionWorkflowSingleRecordAction';
|
||||
import { useDeactivateWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction';
|
||||
import { useDiscardDraftWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDiscardDraftWorkflowSingleRecordAction';
|
||||
import { useSeeActiveVersionWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeActiveVersionWorkflowSingleRecordAction';
|
||||
import { useSeeRunsWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeRunsWorkflowSingleRecordAction';
|
||||
import { useSeeVersionsWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction';
|
||||
import { useTestWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowSingleRecordAction';
|
||||
import { SingleRecordActionHook } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import {
|
||||
ActionMenuEntry,
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import {
|
||||
IconHistory,
|
||||
IconHistoryToggle,
|
||||
IconPlayerPause,
|
||||
IconPlayerPlay,
|
||||
IconPower,
|
||||
IconTrash,
|
||||
} from 'twenty-ui';
|
||||
|
||||
export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record<
|
||||
string,
|
||||
ActionMenuEntry & {
|
||||
actionHook: SingleRecordActionHook;
|
||||
}
|
||||
> = {
|
||||
activateWorkflowDraftSingleRecord: {
|
||||
key: 'activate-workflow-draft-single-record',
|
||||
label: 'Activate Draft',
|
||||
isPinned: true,
|
||||
position: 1,
|
||||
Icon: IconPower,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
actionHook: useActivateDraftWorkflowSingleRecordAction,
|
||||
},
|
||||
activateWorkflowLastPublishedVersionSingleRecord: {
|
||||
key: 'activate-workflow-last-published-version-single-record',
|
||||
label: 'Activate last published version',
|
||||
isPinned: true,
|
||||
position: 2,
|
||||
Icon: IconPower,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
actionHook: useActivateLastPublishedVersionWorkflowSingleRecordAction,
|
||||
},
|
||||
deactivateWorkflowSingleRecord: {
|
||||
key: 'deactivate-workflow-single-record',
|
||||
label: 'Deactivate Workflow',
|
||||
isPinned: true,
|
||||
position: 3,
|
||||
Icon: IconPlayerPause,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
actionHook: useDeactivateWorkflowSingleRecordAction,
|
||||
},
|
||||
discardWorkflowDraftSingleRecord: {
|
||||
key: 'discard-workflow-draft-single-record',
|
||||
label: 'Discard Draft',
|
||||
isPinned: true,
|
||||
position: 4,
|
||||
Icon: IconTrash,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
actionHook: useDiscardDraftWorkflowSingleRecordAction,
|
||||
},
|
||||
seeWorkflowActiveVersionSingleRecord: {
|
||||
key: 'see-workflow-active-version-single-record',
|
||||
label: 'See active version',
|
||||
isPinned: false,
|
||||
position: 5,
|
||||
Icon: IconHistory,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
actionHook: useSeeActiveVersionWorkflowSingleRecordAction,
|
||||
},
|
||||
seeWorkflowRunsSingleRecord: {
|
||||
key: 'see-workflow-runs-single-record',
|
||||
label: 'See runs',
|
||||
isPinned: false,
|
||||
position: 6,
|
||||
Icon: IconHistoryToggle,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
actionHook: useSeeRunsWorkflowSingleRecordAction,
|
||||
},
|
||||
seeWorkflowVersionsHistorySingleRecord: {
|
||||
key: 'see-workflow-versions-history-single-record',
|
||||
label: 'See versions history',
|
||||
isPinned: false,
|
||||
position: 7,
|
||||
Icon: IconHistory,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
actionHook: useSeeVersionsWorkflowSingleRecordAction,
|
||||
},
|
||||
testWorkflowSingleRecord: {
|
||||
key: 'test-workflow-single-record',
|
||||
label: 'Test Workflow',
|
||||
isPinned: true,
|
||||
position: 8,
|
||||
Icon: IconPlayerPlay,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
actionHook: useTestWorkflowSingleRecordAction,
|
||||
},
|
||||
};
|
@ -0,0 +1,90 @@
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
import { useActivateDraftWorkflowSingleRecordAction } from '../useActivateDraftWorkflowSingleRecordAction';
|
||||
|
||||
const workflowMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||
(item) => item.nameSingular === 'workflow',
|
||||
)!;
|
||||
|
||||
const workflowMock = {
|
||||
__typename: 'Workflow',
|
||||
id: 'workflowId',
|
||||
currentVersion: {
|
||||
__typename: 'WorkflowVersion',
|
||||
id: 'currentVersionId',
|
||||
trigger: 'trigger',
|
||||
status: 'DRAFT',
|
||||
steps: [
|
||||
{
|
||||
__typename: 'WorkflowStep',
|
||||
id: 'stepId1',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({
|
||||
useWorkflowWithCurrentVersion: () => workflowMock,
|
||||
}));
|
||||
|
||||
const activateWorkflowVersionMock = jest.fn();
|
||||
|
||||
jest.mock('@/workflow/hooks/useActivateWorkflowVersion', () => ({
|
||||
useActivateWorkflowVersion: () => ({
|
||||
activateWorkflowVersion: activateWorkflowVersionMock,
|
||||
}),
|
||||
}));
|
||||
|
||||
const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
|
||||
apolloMocks: [],
|
||||
componentInstanceId: '1',
|
||||
contextStoreCurrentObjectMetadataNameSingular:
|
||||
workflowMockObjectMetadataItem.nameSingular,
|
||||
contextStoreTargetedRecordsRule: {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [workflowMock.id],
|
||||
},
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(recordStoreFamilyState(workflowMock.id), workflowMock);
|
||||
},
|
||||
});
|
||||
|
||||
describe('useActivateDraftWorkflowSingleRecordAction', () => {
|
||||
it('should be registered', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useActivateDraftWorkflowSingleRecordAction({
|
||||
recordId: workflowMock.id,
|
||||
}),
|
||||
{
|
||||
wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.shouldBeRegistered).toBe(true);
|
||||
});
|
||||
|
||||
it('should call activateWorkflowVersion on click', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useActivateDraftWorkflowSingleRecordAction({
|
||||
recordId: workflowMock.id,
|
||||
}),
|
||||
{
|
||||
wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.onClick();
|
||||
});
|
||||
|
||||
expect(activateWorkflowVersionMock).toHaveBeenCalledWith({
|
||||
workflowId: workflowMock.id,
|
||||
workflowVersionId: workflowMock.currentVersion.id,
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,91 @@
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
import { useActivateLastPublishedVersionWorkflowSingleRecordAction } from '../useActivateLastPublishedVersionWorkflowSingleRecordAction';
|
||||
|
||||
const workflowMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||
(item) => item.nameSingular === 'workflow',
|
||||
)!;
|
||||
|
||||
const workflowMock = {
|
||||
__typename: 'Workflow',
|
||||
id: 'workflowId',
|
||||
lastPublishedVersionId: 'lastPublishedVersionId',
|
||||
currentVersion: {
|
||||
__typename: 'WorkflowVersion',
|
||||
id: 'lastPublishedVersionId',
|
||||
trigger: 'trigger',
|
||||
status: 'DEACTIVATED',
|
||||
steps: [
|
||||
{
|
||||
__typename: 'WorkflowStep',
|
||||
id: 'stepId1',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({
|
||||
useWorkflowWithCurrentVersion: () => workflowMock,
|
||||
}));
|
||||
|
||||
const activateWorkflowVersionMock = jest.fn();
|
||||
|
||||
jest.mock('@/workflow/hooks/useActivateWorkflowVersion', () => ({
|
||||
useActivateWorkflowVersion: () => ({
|
||||
activateWorkflowVersion: activateWorkflowVersionMock,
|
||||
}),
|
||||
}));
|
||||
|
||||
const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
|
||||
apolloMocks: [],
|
||||
componentInstanceId: '1',
|
||||
contextStoreCurrentObjectMetadataNameSingular:
|
||||
workflowMockObjectMetadataItem.nameSingular,
|
||||
contextStoreTargetedRecordsRule: {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [workflowMock.id],
|
||||
},
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(recordStoreFamilyState(workflowMock.id), workflowMock);
|
||||
},
|
||||
});
|
||||
|
||||
describe('useActivateLastPublishedVersionWorkflowSingleRecordAction', () => {
|
||||
it('should be registered', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useActivateLastPublishedVersionWorkflowSingleRecordAction({
|
||||
recordId: workflowMock.id,
|
||||
}),
|
||||
{
|
||||
wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.shouldBeRegistered).toBe(true);
|
||||
});
|
||||
|
||||
it('should call activateWorkflowVersion on click', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useActivateLastPublishedVersionWorkflowSingleRecordAction({
|
||||
recordId: workflowMock.id,
|
||||
}),
|
||||
{
|
||||
wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.onClick();
|
||||
});
|
||||
|
||||
expect(activateWorkflowVersionMock).toHaveBeenCalledWith({
|
||||
workflowId: workflowMock.id,
|
||||
workflowVersionId: workflowMock.currentVersion.id,
|
||||
});
|
||||
});
|
||||
});
|
@ -1,123 +0,0 @@
|
||||
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { expect } from '@storybook/test';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
import { useActivateWorkflowDraftWorkflowSingleRecordAction } from '../useActivateWorkflowDraftWorkflowSingleRecordAction';
|
||||
|
||||
const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({
|
||||
apolloMocks: [],
|
||||
});
|
||||
|
||||
jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({
|
||||
useWorkflowWithCurrentVersion: () => ({
|
||||
id: 'workflowId',
|
||||
currentVersion: {
|
||||
id: 'currentVersionId',
|
||||
trigger: 'trigger',
|
||||
status: 'DRAFT',
|
||||
steps: [
|
||||
{
|
||||
id: 'stepId1',
|
||||
},
|
||||
{
|
||||
id: 'stepId2',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('useActivateWorkflowDraftWorkflowSingleRecordAction', () => {
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<JestMetadataAndApolloMocksWrapper>
|
||||
<JestObjectMetadataItemSetter>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: '1',
|
||||
}}
|
||||
>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: '1',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</JestObjectMetadataItemSetter>
|
||||
</JestMetadataAndApolloMocksWrapper>
|
||||
);
|
||||
|
||||
it('should register activate workflow draft workflow action', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const actionMenuEntries = useRecoilComponentValueV2(
|
||||
actionMenuEntriesComponentState,
|
||||
);
|
||||
|
||||
return {
|
||||
actionMenuEntries,
|
||||
useActivateWorkflowDraftWorkflowSingleRecordAction:
|
||||
useActivateWorkflowDraftWorkflowSingleRecordAction({
|
||||
workflowId: 'workflowId',
|
||||
}),
|
||||
};
|
||||
},
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.useActivateWorkflowDraftWorkflowSingleRecordAction.registerActivateWorkflowDraftWorkflowSingleRecordAction(
|
||||
{ position: 1 },
|
||||
);
|
||||
});
|
||||
|
||||
expect(result.current.actionMenuEntries.size).toBe(1);
|
||||
expect(
|
||||
result.current.actionMenuEntries.get(
|
||||
'activate-workflow-draft-single-record',
|
||||
),
|
||||
).toBeDefined();
|
||||
expect(
|
||||
result.current.actionMenuEntries.get(
|
||||
'activate-workflow-draft-single-record',
|
||||
)?.position,
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
it('should unregister activate workflow draft workflow action', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const actionMenuEntries = useRecoilComponentValueV2(
|
||||
actionMenuEntriesComponentState,
|
||||
);
|
||||
|
||||
return {
|
||||
actionMenuEntries,
|
||||
useActivateWorkflowDraftWorkflowSingleRecordAction:
|
||||
useActivateWorkflowDraftWorkflowSingleRecordAction({
|
||||
workflowId: 'workflow1',
|
||||
}),
|
||||
};
|
||||
},
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.useActivateWorkflowDraftWorkflowSingleRecordAction.registerActivateWorkflowDraftWorkflowSingleRecordAction(
|
||||
{ position: 1 },
|
||||
);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.useActivateWorkflowDraftWorkflowSingleRecordAction.unregisterActivateWorkflowDraftWorkflowSingleRecordAction();
|
||||
});
|
||||
|
||||
expect(result.current.actionMenuEntries.size).toBe(0);
|
||||
});
|
||||
});
|
@ -1,124 +0,0 @@
|
||||
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { expect } from '@storybook/test';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
import { useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction } from '../useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction';
|
||||
|
||||
const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({
|
||||
apolloMocks: [],
|
||||
});
|
||||
|
||||
jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({
|
||||
useWorkflowWithCurrentVersion: () => ({
|
||||
id: 'workflowId',
|
||||
currentVersion: {
|
||||
id: 'currentVersionId',
|
||||
trigger: 'trigger',
|
||||
status: 'DEACTIVATED',
|
||||
steps: [
|
||||
{
|
||||
id: 'stepId1',
|
||||
},
|
||||
{
|
||||
id: 'stepId2',
|
||||
},
|
||||
],
|
||||
},
|
||||
lastPublishedVersionId: 'lastPublishedVersionId',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction', () => {
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<JestMetadataAndApolloMocksWrapper>
|
||||
<JestObjectMetadataItemSetter>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: '1',
|
||||
}}
|
||||
>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: '1',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</JestObjectMetadataItemSetter>
|
||||
</JestMetadataAndApolloMocksWrapper>
|
||||
);
|
||||
|
||||
it('should register activate workflow last published version workflow action', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const actionMenuEntries = useRecoilComponentValueV2(
|
||||
actionMenuEntriesComponentState,
|
||||
);
|
||||
|
||||
return {
|
||||
actionMenuEntries,
|
||||
useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction:
|
||||
useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction({
|
||||
workflowId: 'workflowId',
|
||||
}),
|
||||
};
|
||||
},
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction(
|
||||
{ position: 1 },
|
||||
);
|
||||
});
|
||||
|
||||
expect(result.current.actionMenuEntries.size).toBe(1);
|
||||
expect(
|
||||
result.current.actionMenuEntries.get(
|
||||
'activate-workflow-last-published-version-single-record',
|
||||
),
|
||||
).toBeDefined();
|
||||
expect(
|
||||
result.current.actionMenuEntries.get(
|
||||
'activate-workflow-last-published-version-single-record',
|
||||
)?.position,
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
it('should unregister activate workflow last published version workflow action', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const actionMenuEntries = useRecoilComponentValueV2(
|
||||
actionMenuEntriesComponentState,
|
||||
);
|
||||
|
||||
return {
|
||||
actionMenuEntries,
|
||||
useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction:
|
||||
useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction({
|
||||
workflowId: 'workflow1',
|
||||
}),
|
||||
};
|
||||
},
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction(
|
||||
{ position: 1 },
|
||||
);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.unregisterActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction();
|
||||
});
|
||||
|
||||
expect(result.current.actionMenuEntries.size).toBe(0);
|
||||
});
|
||||
});
|
@ -0,0 +1,158 @@
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
import { useDeactivateWorkflowSingleRecordAction } from '../useDeactivateWorkflowSingleRecordAction';
|
||||
const workflowMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||
(item) => item.nameSingular === 'workflow',
|
||||
)!;
|
||||
|
||||
const activeWorkflowMock = {
|
||||
__typename: 'Workflow',
|
||||
id: 'workflowId',
|
||||
lastPublishedVersionId: 'lastPublishedVersionId',
|
||||
currentVersion: {
|
||||
__typename: 'WorkflowVersion',
|
||||
id: 'currentVersionId',
|
||||
trigger: 'trigger',
|
||||
status: 'ACTIVE',
|
||||
steps: [
|
||||
{
|
||||
__typename: 'WorkflowStep',
|
||||
id: 'stepId1',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const deactivatedWorkflowMock = {
|
||||
__typename: 'Workflow',
|
||||
id: 'workflowId',
|
||||
lastPublishedVersionId: 'lastPublishedVersionId',
|
||||
currentVersion: {
|
||||
__typename: 'WorkflowVersion',
|
||||
id: 'currentVersionId',
|
||||
trigger: 'trigger',
|
||||
status: 'DEACTIVATED',
|
||||
steps: [
|
||||
{
|
||||
__typename: 'WorkflowStep',
|
||||
id: 'stepId1',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({
|
||||
useWorkflowWithCurrentVersion: jest.fn(),
|
||||
}));
|
||||
|
||||
const deactivateWorkflowVersionMock = jest.fn();
|
||||
|
||||
jest.mock('@/workflow/hooks/useDeactivateWorkflowVersion', () => ({
|
||||
useDeactivateWorkflowVersion: () => ({
|
||||
deactivateWorkflowVersion: deactivateWorkflowVersionMock,
|
||||
}),
|
||||
}));
|
||||
|
||||
const activeWorkflowWrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper(
|
||||
{
|
||||
apolloMocks: [],
|
||||
componentInstanceId: '1',
|
||||
contextStoreCurrentObjectMetadataNameSingular:
|
||||
workflowMockObjectMetadataItem.nameSingular,
|
||||
contextStoreTargetedRecordsRule: {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [activeWorkflowMock.id],
|
||||
},
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(
|
||||
recordStoreFamilyState(activeWorkflowMock.id),
|
||||
activeWorkflowMock,
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const deactivatedWorkflowWrapper =
|
||||
getJestMetadataAndApolloMocksAndActionMenuWrapper({
|
||||
apolloMocks: [],
|
||||
componentInstanceId: '1',
|
||||
contextStoreCurrentObjectMetadataNameSingular:
|
||||
workflowMockObjectMetadataItem.nameSingular,
|
||||
contextStoreTargetedRecordsRule: {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [deactivatedWorkflowMock.id],
|
||||
},
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(
|
||||
recordStoreFamilyState(deactivatedWorkflowMock.id),
|
||||
deactivatedWorkflowMock,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
describe('useDeactivateWorkflowSingleRecordAction', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should not be registered when the workflow is deactivated', () => {
|
||||
(useWorkflowWithCurrentVersion as jest.Mock).mockImplementation(
|
||||
() => deactivatedWorkflowMock,
|
||||
);
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useDeactivateWorkflowSingleRecordAction({
|
||||
recordId: deactivatedWorkflowMock.id,
|
||||
}),
|
||||
{
|
||||
wrapper: deactivatedWorkflowWrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.shouldBeRegistered).toBe(false);
|
||||
});
|
||||
|
||||
it('should be registered when the workflow is active', () => {
|
||||
(useWorkflowWithCurrentVersion as jest.Mock).mockImplementation(
|
||||
() => activeWorkflowMock,
|
||||
);
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useDeactivateWorkflowSingleRecordAction({
|
||||
recordId: activeWorkflowMock.id,
|
||||
}),
|
||||
{
|
||||
wrapper: activeWorkflowWrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.shouldBeRegistered).toBe(true);
|
||||
});
|
||||
|
||||
it('should call deactivateWorkflowVersion on click', () => {
|
||||
(useWorkflowWithCurrentVersion as jest.Mock).mockImplementation(
|
||||
() => activeWorkflowMock,
|
||||
);
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useDeactivateWorkflowSingleRecordAction({
|
||||
recordId: activeWorkflowMock.id,
|
||||
}),
|
||||
{
|
||||
wrapper: activeWorkflowWrapper,
|
||||
},
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.onClick();
|
||||
});
|
||||
|
||||
expect(deactivateWorkflowVersionMock).toHaveBeenCalledWith(
|
||||
activeWorkflowMock.currentVersion.id,
|
||||
);
|
||||
});
|
||||
});
|
@ -1,113 +0,0 @@
|
||||
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { expect } from '@storybook/test';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
import { useDeactivateWorkflowWorkflowSingleRecordAction } from '../useDeactivateWorkflowWorkflowSingleRecordAction';
|
||||
|
||||
const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({
|
||||
apolloMocks: [],
|
||||
});
|
||||
|
||||
jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({
|
||||
useWorkflowWithCurrentVersion: () => ({
|
||||
id: 'workflowId',
|
||||
currentVersion: {
|
||||
id: 'currentVersionId',
|
||||
trigger: 'trigger',
|
||||
status: 'ACTIVE',
|
||||
},
|
||||
lastPublishedVersionId: 'lastPublishedVersionId',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('useDeactivateWorkflowWorkflowSingleRecordAction', () => {
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<JestMetadataAndApolloMocksWrapper>
|
||||
<JestObjectMetadataItemSetter>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: '1',
|
||||
}}
|
||||
>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: '1',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</JestObjectMetadataItemSetter>
|
||||
</JestMetadataAndApolloMocksWrapper>
|
||||
);
|
||||
|
||||
it('should register activate workflow last published version workflow action', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const actionMenuEntries = useRecoilComponentValueV2(
|
||||
actionMenuEntriesComponentState,
|
||||
);
|
||||
|
||||
return {
|
||||
actionMenuEntries,
|
||||
useDeactivateWorkflowWorkflowSingleRecordAction:
|
||||
useDeactivateWorkflowWorkflowSingleRecordAction({
|
||||
workflowId: 'workflowId',
|
||||
}),
|
||||
};
|
||||
},
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.useDeactivateWorkflowWorkflowSingleRecordAction.registerDeactivateWorkflowWorkflowSingleRecordAction(
|
||||
{ position: 1 },
|
||||
);
|
||||
});
|
||||
|
||||
expect(result.current.actionMenuEntries.size).toBe(1);
|
||||
expect(
|
||||
result.current.actionMenuEntries.get('deactivate-workflow-single-record'),
|
||||
).toBeDefined();
|
||||
expect(
|
||||
result.current.actionMenuEntries.get('deactivate-workflow-single-record')
|
||||
?.position,
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
it('should unregister deactivate workflow workflow action', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const actionMenuEntries = useRecoilComponentValueV2(
|
||||
actionMenuEntriesComponentState,
|
||||
);
|
||||
|
||||
return {
|
||||
actionMenuEntries,
|
||||
useDeactivateWorkflowWorkflowSingleRecordAction:
|
||||
useDeactivateWorkflowWorkflowSingleRecordAction({
|
||||
workflowId: 'workflow1',
|
||||
}),
|
||||
};
|
||||
},
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.useDeactivateWorkflowWorkflowSingleRecordAction.registerDeactivateWorkflowWorkflowSingleRecordAction(
|
||||
{ position: 1 },
|
||||
);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.useDeactivateWorkflowWorkflowSingleRecordAction.unregisterDeactivateWorkflowWorkflowSingleRecordAction();
|
||||
});
|
||||
|
||||
expect(result.current.actionMenuEntries.size).toBe(0);
|
||||
});
|
||||
});
|
@ -0,0 +1,250 @@
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
import { useDiscardDraftWorkflowSingleRecordAction } from '../useDiscardDraftWorkflowSingleRecordAction';
|
||||
|
||||
const workflowMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||
(item) => item.nameSingular === 'workflow',
|
||||
)!;
|
||||
|
||||
const noDraftWorkflowMock = {
|
||||
__typename: 'Workflow',
|
||||
id: 'workflowId',
|
||||
lastPublishedVersionId: 'lastPublishedVersionId',
|
||||
currentVersion: {
|
||||
__typename: 'WorkflowVersion',
|
||||
id: 'currentVersionId',
|
||||
trigger: 'trigger',
|
||||
status: 'ACTIVE',
|
||||
steps: [
|
||||
{
|
||||
__typename: 'WorkflowStep',
|
||||
id: 'stepId1',
|
||||
},
|
||||
],
|
||||
},
|
||||
versions: [
|
||||
{
|
||||
__typename: 'WorkflowVersion',
|
||||
id: 'currentVersionId',
|
||||
trigger: 'trigger',
|
||||
status: 'ACTIVE',
|
||||
steps: [
|
||||
{
|
||||
__typename: 'WorkflowStep',
|
||||
id: 'stepId1',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
__typename: 'WorkflowVersion',
|
||||
id: 'versionId2',
|
||||
trigger: 'trigger',
|
||||
status: 'ACTIVE',
|
||||
steps: [
|
||||
{
|
||||
__typename: 'WorkflowStep',
|
||||
id: 'stepId2',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const draftWorkflowMock = {
|
||||
__typename: 'Workflow',
|
||||
id: 'workflowId',
|
||||
lastPublishedVersionId: 'lastPublishedVersionId',
|
||||
currentVersion: {
|
||||
__typename: 'WorkflowVersion',
|
||||
id: 'currentVersionId',
|
||||
trigger: 'trigger',
|
||||
status: 'DRAFT',
|
||||
steps: [
|
||||
{
|
||||
__typename: 'WorkflowStep',
|
||||
id: 'stepId1',
|
||||
},
|
||||
],
|
||||
},
|
||||
versions: [
|
||||
{
|
||||
__typename: 'WorkflowVersion',
|
||||
id: 'currentVersionId',
|
||||
trigger: 'trigger',
|
||||
status: 'DRAFT',
|
||||
steps: [
|
||||
{
|
||||
__typename: 'WorkflowStep',
|
||||
id: 'stepId1',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
__typename: 'WorkflowVersion',
|
||||
id: 'versionId2',
|
||||
trigger: 'trigger',
|
||||
status: 'ACTIVE',
|
||||
steps: [
|
||||
{
|
||||
__typename: 'WorkflowStep',
|
||||
id: 'stepId2',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const draftWorkflowMockWithOneVersion = {
|
||||
...draftWorkflowMock,
|
||||
versions: [draftWorkflowMock.currentVersion],
|
||||
};
|
||||
|
||||
jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({
|
||||
useWorkflowWithCurrentVersion: jest.fn(),
|
||||
}));
|
||||
|
||||
const deleteOneWorkflowVersionMock = jest.fn();
|
||||
|
||||
jest.mock('@/workflow/hooks/useDeleteOneWorkflowVersion', () => ({
|
||||
useDeleteOneWorkflowVersion: () => ({
|
||||
deleteOneWorkflowVersion: deleteOneWorkflowVersionMock,
|
||||
}),
|
||||
}));
|
||||
|
||||
const noDraftWorkflowWrapper =
|
||||
getJestMetadataAndApolloMocksAndActionMenuWrapper({
|
||||
apolloMocks: [],
|
||||
componentInstanceId: '1',
|
||||
contextStoreCurrentObjectMetadataNameSingular:
|
||||
workflowMockObjectMetadataItem.nameSingular,
|
||||
contextStoreTargetedRecordsRule: {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [noDraftWorkflowMock.id],
|
||||
},
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(
|
||||
recordStoreFamilyState(noDraftWorkflowMock.id),
|
||||
noDraftWorkflowMock,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const draftWorkflowWrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
|
||||
apolloMocks: [],
|
||||
componentInstanceId: '1',
|
||||
contextStoreCurrentObjectMetadataNameSingular:
|
||||
workflowMockObjectMetadataItem.nameSingular,
|
||||
contextStoreTargetedRecordsRule: {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [draftWorkflowMock.id],
|
||||
},
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(
|
||||
recordStoreFamilyState(draftWorkflowMock.id),
|
||||
draftWorkflowMock,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const draftWorkflowWithOneVersionWrapper =
|
||||
getJestMetadataAndApolloMocksAndActionMenuWrapper({
|
||||
apolloMocks: [],
|
||||
componentInstanceId: '1',
|
||||
contextStoreCurrentObjectMetadataNameSingular:
|
||||
workflowMockObjectMetadataItem.nameSingular,
|
||||
contextStoreTargetedRecordsRule: {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [draftWorkflowMockWithOneVersion.id],
|
||||
},
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(
|
||||
recordStoreFamilyState(draftWorkflowMockWithOneVersion.id),
|
||||
draftWorkflowMockWithOneVersion,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
describe('useDiscardDraftWorkflowSingleRecordAction', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should not be registered when there is no draft', () => {
|
||||
(useWorkflowWithCurrentVersion as jest.Mock).mockImplementation(
|
||||
() => noDraftWorkflowMock,
|
||||
);
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useDiscardDraftWorkflowSingleRecordAction({
|
||||
recordId: noDraftWorkflowMock.id,
|
||||
}),
|
||||
{
|
||||
wrapper: noDraftWorkflowWrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.shouldBeRegistered).toBe(false);
|
||||
});
|
||||
|
||||
it('should not be registered when there is only one version', () => {
|
||||
(useWorkflowWithCurrentVersion as jest.Mock).mockImplementation(
|
||||
() => draftWorkflowMockWithOneVersion,
|
||||
);
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useDiscardDraftWorkflowSingleRecordAction({
|
||||
recordId: draftWorkflowMockWithOneVersion.id,
|
||||
}),
|
||||
{
|
||||
wrapper: draftWorkflowWithOneVersionWrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.shouldBeRegistered).toBe(false);
|
||||
});
|
||||
|
||||
it('should be registered when the workflow is draft', () => {
|
||||
(useWorkflowWithCurrentVersion as jest.Mock).mockImplementation(
|
||||
() => draftWorkflowMock,
|
||||
);
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useDiscardDraftWorkflowSingleRecordAction({
|
||||
recordId: draftWorkflowMock.id,
|
||||
}),
|
||||
{
|
||||
wrapper: draftWorkflowWrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.shouldBeRegistered).toBe(true);
|
||||
});
|
||||
|
||||
it('should call deactivateWorkflowVersion on click', () => {
|
||||
(useWorkflowWithCurrentVersion as jest.Mock).mockImplementation(
|
||||
() => draftWorkflowMock,
|
||||
);
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useDiscardDraftWorkflowSingleRecordAction({
|
||||
recordId: draftWorkflowMock.id,
|
||||
}),
|
||||
{
|
||||
wrapper: draftWorkflowWrapper,
|
||||
},
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.onClick();
|
||||
});
|
||||
|
||||
expect(deleteOneWorkflowVersionMock).toHaveBeenCalledWith({
|
||||
workflowVersionId: draftWorkflowMock.currentVersion.id,
|
||||
});
|
||||
});
|
||||
});
|
@ -1,128 +0,0 @@
|
||||
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { expect } from '@storybook/test';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
import { useDiscardDraftWorkflowSingleRecordAction } from '../useDiscardDraftWorkflowSingleRecordAction';
|
||||
|
||||
const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({
|
||||
apolloMocks: [],
|
||||
});
|
||||
|
||||
jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({
|
||||
useWorkflowWithCurrentVersion: () => ({
|
||||
id: 'workflowId',
|
||||
currentVersion: {
|
||||
id: 'currentVersionId',
|
||||
trigger: 'trigger',
|
||||
status: 'DRAFT',
|
||||
},
|
||||
lastPublishedVersionId: 'lastPublishedVersionId',
|
||||
versions: [
|
||||
{
|
||||
id: 'currentVersionId',
|
||||
trigger: 'trigger',
|
||||
status: 'DRAFT',
|
||||
},
|
||||
{
|
||||
id: 'lastPublishedVersionId',
|
||||
trigger: 'trigger',
|
||||
status: 'ACTIVE',
|
||||
},
|
||||
],
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('useDiscardDraftWorkflowSingleRecordAction', () => {
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<JestMetadataAndApolloMocksWrapper>
|
||||
<JestObjectMetadataItemSetter>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: '1',
|
||||
}}
|
||||
>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: '1',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</JestObjectMetadataItemSetter>
|
||||
</JestMetadataAndApolloMocksWrapper>
|
||||
);
|
||||
|
||||
it('should register discard workflow draft workflow action', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const actionMenuEntries = useRecoilComponentValueV2(
|
||||
actionMenuEntriesComponentState,
|
||||
);
|
||||
|
||||
return {
|
||||
actionMenuEntries,
|
||||
useDiscardDraftWorkflowSingleRecordAction:
|
||||
useDiscardDraftWorkflowSingleRecordAction({
|
||||
workflowId: 'workflowId',
|
||||
}),
|
||||
};
|
||||
},
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.useDiscardDraftWorkflowSingleRecordAction.registerDiscardDraftWorkflowSingleRecordAction(
|
||||
{ position: 1 },
|
||||
);
|
||||
});
|
||||
|
||||
expect(result.current.actionMenuEntries.size).toBe(1);
|
||||
expect(
|
||||
result.current.actionMenuEntries.get(
|
||||
'discard-workflow-draft-single-record',
|
||||
),
|
||||
).toBeDefined();
|
||||
expect(
|
||||
result.current.actionMenuEntries.get(
|
||||
'discard-workflow-draft-single-record',
|
||||
)?.position,
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
it('should unregister deactivate workflow workflow action', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const actionMenuEntries = useRecoilComponentValueV2(
|
||||
actionMenuEntriesComponentState,
|
||||
);
|
||||
|
||||
return {
|
||||
actionMenuEntries,
|
||||
useDiscardDraftWorkflowSingleRecordAction:
|
||||
useDiscardDraftWorkflowSingleRecordAction({
|
||||
workflowId: 'workflow1',
|
||||
}),
|
||||
};
|
||||
},
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.useDiscardDraftWorkflowSingleRecordAction.registerDiscardDraftWorkflowSingleRecordAction(
|
||||
{ position: 1 },
|
||||
);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.useDiscardDraftWorkflowSingleRecordAction.unregisterDiscardDraftWorkflowSingleRecordAction();
|
||||
});
|
||||
|
||||
expect(result.current.actionMenuEntries.size).toBe(0);
|
||||
});
|
||||
});
|
@ -0,0 +1,32 @@
|
||||
import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useActivateDraftWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem =
|
||||
({ recordId }) => {
|
||||
const { activateWorkflowVersion } = useActivateWorkflowVersion();
|
||||
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
|
||||
|
||||
const shouldBeRegistered =
|
||||
isDefined(workflowWithCurrentVersion?.currentVersion?.trigger) &&
|
||||
isDefined(workflowWithCurrentVersion.currentVersion?.steps) &&
|
||||
workflowWithCurrentVersion.currentVersion.status === 'DRAFT';
|
||||
|
||||
const onClick = () => {
|
||||
if (!shouldBeRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
activateWorkflowVersion({
|
||||
workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
|
||||
workflowId: workflowWithCurrentVersion.id,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
shouldBeRegistered,
|
||||
onClick,
|
||||
};
|
||||
};
|
@ -0,0 +1,35 @@
|
||||
import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useActivateLastPublishedVersionWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem =
|
||||
({ recordId }) => {
|
||||
const { activateWorkflowVersion } = useActivateWorkflowVersion();
|
||||
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
|
||||
|
||||
const shouldBeRegistered =
|
||||
isDefined(workflowWithCurrentVersion) &&
|
||||
isDefined(workflowWithCurrentVersion.currentVersion.trigger) &&
|
||||
isDefined(workflowWithCurrentVersion.lastPublishedVersionId) &&
|
||||
workflowWithCurrentVersion.currentVersion.status !== 'ACTIVE' &&
|
||||
isDefined(workflowWithCurrentVersion.currentVersion?.steps) &&
|
||||
workflowWithCurrentVersion.currentVersion?.steps.length !== 0;
|
||||
|
||||
const onClick = () => {
|
||||
if (!shouldBeRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
activateWorkflowVersion({
|
||||
workflowVersionId: workflowWithCurrentVersion.lastPublishedVersionId,
|
||||
workflowId: workflowWithCurrentVersion.id,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
shouldBeRegistered,
|
||||
onClick,
|
||||
};
|
||||
};
|
@ -1,64 +0,0 @@
|
||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||
import {
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { IconPower, isDefined } from 'twenty-ui';
|
||||
|
||||
export const useActivateWorkflowDraftWorkflowSingleRecordAction = ({
|
||||
workflowId,
|
||||
}: {
|
||||
workflowId: string;
|
||||
}) => {
|
||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||
|
||||
const { activateWorkflowVersion } = useActivateWorkflowVersion();
|
||||
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
|
||||
|
||||
const registerActivateWorkflowDraftWorkflowSingleRecordAction = ({
|
||||
position,
|
||||
}: {
|
||||
position: number;
|
||||
}) => {
|
||||
if (
|
||||
!isDefined(workflowWithCurrentVersion?.currentVersion?.trigger) ||
|
||||
!isDefined(workflowWithCurrentVersion.currentVersion?.steps)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isDraft =
|
||||
workflowWithCurrentVersion.currentVersion.status === 'DRAFT';
|
||||
|
||||
if (!isDraft) {
|
||||
return;
|
||||
}
|
||||
|
||||
addActionMenuEntry({
|
||||
key: 'activate-workflow-draft-single-record',
|
||||
label: 'Activate Draft',
|
||||
position,
|
||||
Icon: IconPower,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
onClick: () => {
|
||||
activateWorkflowVersion({
|
||||
workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
|
||||
workflowId: workflowWithCurrentVersion.id,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const unregisterActivateWorkflowDraftWorkflowSingleRecordAction = () => {
|
||||
removeActionMenuEntry('activate-workflow-draft-single-record');
|
||||
};
|
||||
|
||||
return {
|
||||
registerActivateWorkflowDraftWorkflowSingleRecordAction,
|
||||
unregisterActivateWorkflowDraftWorkflowSingleRecordAction,
|
||||
};
|
||||
};
|
@ -1,61 +0,0 @@
|
||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||
import {
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { IconPower, isDefined } from 'twenty-ui';
|
||||
|
||||
export const useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction =
|
||||
({ workflowId }: { workflowId: string }) => {
|
||||
const { addActionMenuEntry, removeActionMenuEntry } =
|
||||
useActionMenuEntries();
|
||||
|
||||
const { activateWorkflowVersion } = useActivateWorkflowVersion();
|
||||
|
||||
const workflowWithCurrentVersion =
|
||||
useWorkflowWithCurrentVersion(workflowId);
|
||||
|
||||
const registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction =
|
||||
({ position }: { position: number }) => {
|
||||
if (
|
||||
!isDefined(workflowWithCurrentVersion) ||
|
||||
!isDefined(workflowWithCurrentVersion.currentVersion.trigger) ||
|
||||
!isDefined(workflowWithCurrentVersion.lastPublishedVersionId) ||
|
||||
workflowWithCurrentVersion.currentVersion.status === 'ACTIVE' ||
|
||||
!isDefined(workflowWithCurrentVersion.currentVersion?.steps) ||
|
||||
workflowWithCurrentVersion.currentVersion?.steps.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
addActionMenuEntry({
|
||||
key: 'activate-workflow-last-published-version-single-record',
|
||||
label: 'Activate last published version',
|
||||
position,
|
||||
Icon: IconPower,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
onClick: () => {
|
||||
activateWorkflowVersion({
|
||||
workflowVersionId:
|
||||
workflowWithCurrentVersion.lastPublishedVersionId,
|
||||
workflowId: workflowWithCurrentVersion.id,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const unregisterActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction =
|
||||
() => {
|
||||
removeActionMenuEntry(
|
||||
'activate-workflow-last-published-version-single-record',
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction,
|
||||
unregisterActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction,
|
||||
};
|
||||
};
|
@ -0,0 +1,28 @@
|
||||
import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import { useDeactivateWorkflowVersion } from '@/workflow/hooks/useDeactivateWorkflowVersion';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useDeactivateWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem =
|
||||
({ recordId }) => {
|
||||
const { deactivateWorkflowVersion } = useDeactivateWorkflowVersion();
|
||||
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
|
||||
|
||||
const shouldBeRegistered =
|
||||
isDefined(workflowWithCurrentVersion) &&
|
||||
workflowWithCurrentVersion.currentVersion.status === 'ACTIVE';
|
||||
|
||||
const onClick = () => {
|
||||
if (!shouldBeRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
deactivateWorkflowVersion(workflowWithCurrentVersion.currentVersion.id);
|
||||
};
|
||||
|
||||
return {
|
||||
shouldBeRegistered,
|
||||
onClick,
|
||||
};
|
||||
};
|
@ -1,55 +0,0 @@
|
||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||
import {
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { useDeactivateWorkflowVersion } from '@/workflow/hooks/useDeactivateWorkflowVersion';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { IconPlayerPause, isDefined } from 'twenty-ui';
|
||||
|
||||
export const useDeactivateWorkflowWorkflowSingleRecordAction = ({
|
||||
workflowId,
|
||||
}: {
|
||||
workflowId: string;
|
||||
}) => {
|
||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||
|
||||
const { deactivateWorkflowVersion } = useDeactivateWorkflowVersion();
|
||||
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
|
||||
|
||||
const isWorkflowActive =
|
||||
isDefined(workflowWithCurrentVersion) &&
|
||||
workflowWithCurrentVersion.currentVersion.status === 'ACTIVE';
|
||||
|
||||
const registerDeactivateWorkflowWorkflowSingleRecordAction = ({
|
||||
position,
|
||||
}: {
|
||||
position: number;
|
||||
}) => {
|
||||
if (!isDefined(workflowWithCurrentVersion) || !isWorkflowActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
addActionMenuEntry({
|
||||
key: 'deactivate-workflow-single-record',
|
||||
label: 'Deactivate Workflow',
|
||||
position,
|
||||
Icon: IconPlayerPause,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
onClick: () => {
|
||||
deactivateWorkflowVersion(workflowWithCurrentVersion.currentVersion.id);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const unregisterDeactivateWorkflowWorkflowSingleRecordAction = () => {
|
||||
removeActionMenuEntry('deactivate-workflow-single-record');
|
||||
};
|
||||
|
||||
return {
|
||||
registerDeactivateWorkflowWorkflowSingleRecordAction,
|
||||
unregisterDeactivateWorkflowWorkflowSingleRecordAction,
|
||||
};
|
||||
};
|
@ -1,63 +1,31 @@
|
||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||
import {
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import { useDeleteOneWorkflowVersion } from '@/workflow/hooks/useDeleteOneWorkflowVersion';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { IconTrash, isDefined } from 'twenty-ui';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useDiscardDraftWorkflowSingleRecordAction = ({
|
||||
workflowId,
|
||||
}: {
|
||||
workflowId: string;
|
||||
}) => {
|
||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||
export const useDiscardDraftWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem =
|
||||
({ recordId }) => {
|
||||
const { deleteOneWorkflowVersion } = useDeleteOneWorkflowVersion();
|
||||
|
||||
const { deleteOneWorkflowVersion } = useDeleteOneWorkflowVersion();
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
|
||||
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
|
||||
|
||||
const registerDiscardDraftWorkflowSingleRecordAction = ({
|
||||
position,
|
||||
}: {
|
||||
position: number;
|
||||
}) => {
|
||||
if (
|
||||
!isDefined(workflowWithCurrentVersion) ||
|
||||
workflowWithCurrentVersion.versions.length < 2
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isDraft =
|
||||
const shouldBeRegistered =
|
||||
isDefined(workflowWithCurrentVersion) &&
|
||||
workflowWithCurrentVersion.versions.length > 1 &&
|
||||
workflowWithCurrentVersion.currentVersion.status === 'DRAFT';
|
||||
|
||||
if (!isDraft) {
|
||||
return;
|
||||
}
|
||||
const onClick = () => {
|
||||
if (!shouldBeRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
addActionMenuEntry({
|
||||
key: 'discard-workflow-draft-single-record',
|
||||
label: 'Discard Draft',
|
||||
position,
|
||||
Icon: IconTrash,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
onClick: () => {
|
||||
deleteOneWorkflowVersion({
|
||||
workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
deleteOneWorkflowVersion({
|
||||
workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
|
||||
});
|
||||
};
|
||||
|
||||
const unregisterDiscardDraftWorkflowSingleRecordAction = () => {
|
||||
removeActionMenuEntry('discard-workflow-draft-single-record');
|
||||
return {
|
||||
shouldBeRegistered,
|
||||
onClick,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
registerDiscardDraftWorkflowSingleRecordAction,
|
||||
unregisterDiscardDraftWorkflowSingleRecordAction,
|
||||
};
|
||||
};
|
||||
|
@ -0,0 +1,34 @@
|
||||
import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useActiveWorkflowVersion } from '@/workflow/hooks/useActiveWorkflowVersion';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useSeeActiveVersionWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem =
|
||||
({ recordId }) => {
|
||||
const workflow = useWorkflowWithCurrentVersion(recordId);
|
||||
|
||||
const isDraft = workflow?.statuses?.includes('DRAFT') || false;
|
||||
|
||||
const workflowActiveVersion = useActiveWorkflowVersion(recordId);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const shouldBeRegistered = isDefined(workflowActiveVersion) && isDraft;
|
||||
|
||||
const onClick = () => {
|
||||
if (!shouldBeRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(
|
||||
`/object/${CoreObjectNameSingular.WorkflowVersion}/${workflowActiveVersion.id}`,
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
shouldBeRegistered,
|
||||
onClick,
|
||||
};
|
||||
};
|
@ -0,0 +1,41 @@
|
||||
import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import qs from 'qs';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useSeeRunsWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem =
|
||||
({ recordId }) => {
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const shouldBeRegistered = isDefined(workflowWithCurrentVersion);
|
||||
|
||||
const onClick = () => {
|
||||
if (!shouldBeRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterQueryParams: FilterQueryParams = {
|
||||
filter: {
|
||||
workflow: {
|
||||
[ViewFilterOperand.Is]: [workflowWithCurrentVersion.id],
|
||||
},
|
||||
},
|
||||
};
|
||||
const filterLinkHref = `/objects/${CoreObjectNamePlural.WorkflowRun}?${qs.stringify(
|
||||
filterQueryParams,
|
||||
)}`;
|
||||
|
||||
navigate(filterLinkHref);
|
||||
};
|
||||
|
||||
return {
|
||||
shouldBeRegistered,
|
||||
onClick,
|
||||
};
|
||||
};
|
@ -0,0 +1,41 @@
|
||||
import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import qs from 'qs';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useSeeVersionsWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem =
|
||||
({ recordId }) => {
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const shouldBeRegistered = isDefined(workflowWithCurrentVersion);
|
||||
|
||||
const onClick = () => {
|
||||
if (!shouldBeRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterQueryParams: FilterQueryParams = {
|
||||
filter: {
|
||||
workflow: {
|
||||
[ViewFilterOperand.Is]: [workflowWithCurrentVersion.id],
|
||||
},
|
||||
},
|
||||
};
|
||||
const filterLinkHref = `/objects/${CoreObjectNamePlural.WorkflowVersion}?${qs.stringify(
|
||||
filterQueryParams,
|
||||
)}`;
|
||||
|
||||
navigate(filterLinkHref);
|
||||
};
|
||||
|
||||
return {
|
||||
shouldBeRegistered,
|
||||
onClick,
|
||||
};
|
||||
};
|
@ -1,60 +0,0 @@
|
||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||
import {
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useActiveWorkflowVersion } from '@/workflow/hooks/useActiveWorkflowVersion';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconHistory, isDefined } from 'twenty-ui';
|
||||
|
||||
export const useSeeWorkflowActiveVersionWorkflowSingleRecordAction = ({
|
||||
workflowId,
|
||||
}: {
|
||||
workflowId: string;
|
||||
}) => {
|
||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||
|
||||
const workflow = useRecoilValue(recordStoreFamilyState(workflowId));
|
||||
|
||||
const isDraft = workflow?.statuses?.includes('DRAFT');
|
||||
|
||||
const workflowActiveVersion = useActiveWorkflowVersion(workflowId);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const registerSeeWorkflowActiveVersionWorkflowSingleRecordAction = ({
|
||||
position,
|
||||
}: {
|
||||
position: number;
|
||||
}) => {
|
||||
if (!isDefined(workflowActiveVersion) || !isDraft) {
|
||||
return;
|
||||
}
|
||||
|
||||
addActionMenuEntry({
|
||||
key: 'see-workflow-active-version-single-record',
|
||||
label: 'See active version',
|
||||
position,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
Icon: IconHistory,
|
||||
onClick: () => {
|
||||
navigate(
|
||||
`/object/${CoreObjectNameSingular.WorkflowVersion}/${workflowActiveVersion.id}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const unregisterSeeWorkflowActiveVersionWorkflowSingleRecordAction = () => {
|
||||
removeActionMenuEntry('see-workflow-active-version-single-record');
|
||||
};
|
||||
|
||||
return {
|
||||
registerSeeWorkflowActiveVersionWorkflowSingleRecordAction,
|
||||
unregisterSeeWorkflowActiveVersionWorkflowSingleRecordAction,
|
||||
};
|
||||
};
|
@ -1,66 +0,0 @@
|
||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||
import {
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import qs from 'qs';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { IconHistoryToggle, isDefined } from 'twenty-ui';
|
||||
|
||||
export const useSeeWorkflowRunsWorkflowSingleRecordAction = ({
|
||||
workflowId,
|
||||
}: {
|
||||
workflowId: string;
|
||||
}) => {
|
||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const registerSeeWorkflowRunsWorkflowSingleRecordAction = ({
|
||||
position,
|
||||
}: {
|
||||
position: number;
|
||||
}) => {
|
||||
if (!isDefined(workflowWithCurrentVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterQueryParams: FilterQueryParams = {
|
||||
filter: {
|
||||
workflow: {
|
||||
[ViewFilterOperand.Is]: [workflowWithCurrentVersion.id],
|
||||
},
|
||||
},
|
||||
};
|
||||
const filterLinkHref = `/objects/${CoreObjectNamePlural.WorkflowRun}?${qs.stringify(
|
||||
filterQueryParams,
|
||||
)}`;
|
||||
|
||||
addActionMenuEntry({
|
||||
key: 'see-workflow-runs-single-record',
|
||||
label: 'See runs',
|
||||
position,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
Icon: IconHistoryToggle,
|
||||
onClick: () => {
|
||||
navigate(filterLinkHref);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const unregisterSeeWorkflowRunsWorkflowSingleRecordAction = () => {
|
||||
removeActionMenuEntry('see-workflow-runs-single-record');
|
||||
};
|
||||
|
||||
return {
|
||||
registerSeeWorkflowRunsWorkflowSingleRecordAction,
|
||||
unregisterSeeWorkflowRunsWorkflowSingleRecordAction,
|
||||
};
|
||||
};
|
@ -1,66 +0,0 @@
|
||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||
import {
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import qs from 'qs';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { IconHistory, isDefined } from 'twenty-ui';
|
||||
|
||||
export const useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction = ({
|
||||
workflowId,
|
||||
}: {
|
||||
workflowId: string;
|
||||
}) => {
|
||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const registerSeeWorkflowVersionsHistoryWorkflowSingleRecordAction = ({
|
||||
position,
|
||||
}: {
|
||||
position: number;
|
||||
}) => {
|
||||
if (!isDefined(workflowWithCurrentVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterQueryParams: FilterQueryParams = {
|
||||
filter: {
|
||||
workflow: {
|
||||
[ViewFilterOperand.Is]: [workflowWithCurrentVersion.id],
|
||||
},
|
||||
},
|
||||
};
|
||||
const filterLinkHref = `/objects/${CoreObjectNamePlural.WorkflowVersion}?${qs.stringify(
|
||||
filterQueryParams,
|
||||
)}`;
|
||||
|
||||
addActionMenuEntry({
|
||||
key: 'see-workflow-versions-history-single-record',
|
||||
label: 'See versions history',
|
||||
position,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
Icon: IconHistory,
|
||||
onClick: () => {
|
||||
navigate(filterLinkHref);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const unregisterSeeWorkflowVersionsHistoryWorkflowSingleRecordAction = () => {
|
||||
removeActionMenuEntry('see-workflow-versions-history-single-record');
|
||||
};
|
||||
|
||||
return {
|
||||
registerSeeWorkflowVersionsHistoryWorkflowSingleRecordAction,
|
||||
unregisterSeeWorkflowVersionsHistoryWorkflowSingleRecordAction,
|
||||
};
|
||||
};
|
@ -0,0 +1,34 @@
|
||||
import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useTestWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem =
|
||||
({ recordId }) => {
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
|
||||
|
||||
const { runWorkflowVersion } = useRunWorkflowVersion();
|
||||
|
||||
const shouldBeRegistered =
|
||||
isDefined(workflowWithCurrentVersion?.currentVersion?.trigger) &&
|
||||
workflowWithCurrentVersion.currentVersion.trigger.type === 'MANUAL' &&
|
||||
!isDefined(
|
||||
workflowWithCurrentVersion.currentVersion.trigger.settings.objectType,
|
||||
);
|
||||
|
||||
const onClick = () => {
|
||||
if (!shouldBeRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
runWorkflowVersion({
|
||||
workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
|
||||
workflowName: workflowWithCurrentVersion.name,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
shouldBeRegistered,
|
||||
onClick,
|
||||
};
|
||||
};
|
@ -1,60 +0,0 @@
|
||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||
import {
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { IconPlayerPlay, isDefined } from 'twenty-ui';
|
||||
|
||||
export const useTestWorkflowWorkflowSingleRecordAction = ({
|
||||
workflowId,
|
||||
}: {
|
||||
workflowId: string;
|
||||
}) => {
|
||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
|
||||
|
||||
const { runWorkflowVersion } = useRunWorkflowVersion();
|
||||
|
||||
const registerTestWorkflowWorkflowSingleRecordAction = ({
|
||||
position,
|
||||
}: {
|
||||
position: number;
|
||||
}) => {
|
||||
if (
|
||||
!isDefined(workflowWithCurrentVersion?.currentVersion?.trigger) ||
|
||||
workflowWithCurrentVersion.currentVersion.trigger.type !== 'MANUAL' ||
|
||||
isDefined(
|
||||
workflowWithCurrentVersion.currentVersion.trigger.settings.objectType,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
addActionMenuEntry({
|
||||
key: 'test-workflow-single-record',
|
||||
label: 'Test workflow',
|
||||
position,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
Icon: IconPlayerPlay,
|
||||
onClick: () => {
|
||||
runWorkflowVersion({
|
||||
workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
|
||||
workflowName: workflowWithCurrentVersion.name,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const unregisterTestWorkflowWorkflowSingleRecordAction = () => {
|
||||
removeActionMenuEntry('test-workflow-single-record');
|
||||
};
|
||||
|
||||
return {
|
||||
registerTestWorkflowWorkflowSingleRecordAction,
|
||||
unregisterTestWorkflowWorkflowSingleRecordAction,
|
||||
};
|
||||
};
|
@ -1,127 +0,0 @@
|
||||
import { useActivateWorkflowDraftWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowDraftWorkflowSingleRecordAction';
|
||||
import { useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction';
|
||||
import { useDeactivateWorkflowWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowWorkflowSingleRecordAction';
|
||||
import { useDiscardDraftWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDiscardDraftWorkflowSingleRecordAction';
|
||||
import { useSeeWorkflowActiveVersionWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowActiveVersionWorkflowSingleRecordAction';
|
||||
import { useSeeWorkflowRunsWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowRunsWorkflowSingleRecordAction';
|
||||
import { useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction';
|
||||
import { useTestWorkflowWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowWorkflowSingleRecordAction';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useWorkflowSingleRecordActions = () => {
|
||||
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
const selectedRecordId =
|
||||
contextStoreTargetedRecordsRule.mode === 'selection'
|
||||
? contextStoreTargetedRecordsRule.selectedRecordIds?.[0]
|
||||
: undefined;
|
||||
|
||||
if (!isDefined(selectedRecordId)) {
|
||||
throw new Error('Selected record ID is required');
|
||||
}
|
||||
|
||||
const {
|
||||
registerTestWorkflowWorkflowSingleRecordAction,
|
||||
unregisterTestWorkflowWorkflowSingleRecordAction,
|
||||
} = useTestWorkflowWorkflowSingleRecordAction({
|
||||
workflowId: selectedRecordId,
|
||||
});
|
||||
|
||||
const {
|
||||
registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction,
|
||||
unregisterActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction,
|
||||
} = useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction({
|
||||
workflowId: selectedRecordId,
|
||||
});
|
||||
|
||||
const {
|
||||
registerDeactivateWorkflowWorkflowSingleRecordAction,
|
||||
unregisterDeactivateWorkflowWorkflowSingleRecordAction,
|
||||
} = useDeactivateWorkflowWorkflowSingleRecordAction({
|
||||
workflowId: selectedRecordId,
|
||||
});
|
||||
|
||||
const {
|
||||
registerSeeWorkflowRunsWorkflowSingleRecordAction,
|
||||
unregisterSeeWorkflowRunsWorkflowSingleRecordAction,
|
||||
} = useSeeWorkflowRunsWorkflowSingleRecordAction({
|
||||
workflowId: selectedRecordId,
|
||||
});
|
||||
|
||||
const {
|
||||
registerSeeWorkflowVersionsHistoryWorkflowSingleRecordAction,
|
||||
unregisterSeeWorkflowVersionsHistoryWorkflowSingleRecordAction,
|
||||
} = useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction({
|
||||
workflowId: selectedRecordId,
|
||||
});
|
||||
|
||||
const {
|
||||
registerSeeWorkflowActiveVersionWorkflowSingleRecordAction,
|
||||
unregisterSeeWorkflowActiveVersionWorkflowSingleRecordAction,
|
||||
} = useSeeWorkflowActiveVersionWorkflowSingleRecordAction({
|
||||
workflowId: selectedRecordId,
|
||||
});
|
||||
|
||||
const {
|
||||
registerActivateWorkflowDraftWorkflowSingleRecordAction,
|
||||
unregisterActivateWorkflowDraftWorkflowSingleRecordAction,
|
||||
} = useActivateWorkflowDraftWorkflowSingleRecordAction({
|
||||
workflowId: selectedRecordId,
|
||||
});
|
||||
|
||||
const {
|
||||
registerDiscardDraftWorkflowSingleRecordAction,
|
||||
unregisterDiscardDraftWorkflowSingleRecordAction,
|
||||
} = useDiscardDraftWorkflowSingleRecordAction({
|
||||
workflowId: selectedRecordId,
|
||||
});
|
||||
|
||||
const registerSingleRecordActions = ({
|
||||
startPosition,
|
||||
}: {
|
||||
startPosition: number;
|
||||
}) => {
|
||||
registerTestWorkflowWorkflowSingleRecordAction({ position: startPosition });
|
||||
registerDiscardDraftWorkflowSingleRecordAction({
|
||||
position: startPosition + 1,
|
||||
});
|
||||
registerActivateWorkflowDraftWorkflowSingleRecordAction({
|
||||
position: startPosition + 2,
|
||||
});
|
||||
registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction({
|
||||
position: startPosition + 3,
|
||||
});
|
||||
registerDeactivateWorkflowWorkflowSingleRecordAction({
|
||||
position: startPosition + 4,
|
||||
});
|
||||
registerSeeWorkflowRunsWorkflowSingleRecordAction({
|
||||
position: startPosition + 5,
|
||||
});
|
||||
registerSeeWorkflowActiveVersionWorkflowSingleRecordAction({
|
||||
position: startPosition + 6,
|
||||
});
|
||||
registerSeeWorkflowVersionsHistoryWorkflowSingleRecordAction({
|
||||
position: startPosition + 7,
|
||||
});
|
||||
};
|
||||
|
||||
const unregisterSingleRecordActions = () => {
|
||||
unregisterTestWorkflowWorkflowSingleRecordAction();
|
||||
unregisterActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction();
|
||||
unregisterDiscardDraftWorkflowSingleRecordAction();
|
||||
unregisterActivateWorkflowDraftWorkflowSingleRecordAction();
|
||||
unregisterDeactivateWorkflowWorkflowSingleRecordAction();
|
||||
unregisterSeeWorkflowRunsWorkflowSingleRecordAction();
|
||||
unregisterSeeWorkflowActiveVersionWorkflowSingleRecordAction();
|
||||
unregisterSeeWorkflowVersionsHistoryWorkflowSingleRecordAction();
|
||||
};
|
||||
|
||||
return {
|
||||
registerSingleRecordActions,
|
||||
unregisterSingleRecordActions,
|
||||
};
|
||||
};
|
@ -1,21 +0,0 @@
|
||||
import { NUMBER_OF_STANDARD_SINGLE_RECORD_ACTIONS_ON_ALL_OBJECTS } from '@/action-menu/actions/record-actions/single-record/constants/NumberOfStandardSingleRecordActionsOnAllObjects';
|
||||
import { useWorkflowVersionsSingleRecordActions } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useWorkflowVersionsSingleRecordActions';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const WorkflowVersionsSingleRecordActionMenuEntrySetterEffect = () => {
|
||||
const { registerSingleRecordActions, unregisterSingleRecordActions } =
|
||||
useWorkflowVersionsSingleRecordActions();
|
||||
|
||||
useEffect(() => {
|
||||
registerSingleRecordActions({
|
||||
startPosition:
|
||||
NUMBER_OF_STANDARD_SINGLE_RECORD_ACTIONS_ON_ALL_OBJECTS + 1,
|
||||
});
|
||||
|
||||
return () => {
|
||||
unregisterSingleRecordActions();
|
||||
};
|
||||
}, [registerSingleRecordActions, unregisterSingleRecordActions]);
|
||||
|
||||
return null;
|
||||
};
|
@ -0,0 +1,46 @@
|
||||
import { useSeeExecutionsWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeExecutionsWorkflowVersionSingleRecordAction';
|
||||
import { useSeeVersionsWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeVersionsWorkflowVersionSingleRecordAction';
|
||||
import { useUseAsDraftWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction';
|
||||
import { SingleRecordActionHook } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import {
|
||||
ActionMenuEntry,
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { IconHistory, IconHistoryToggle, IconPencil } from 'twenty-ui';
|
||||
|
||||
export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record<
|
||||
string,
|
||||
ActionMenuEntry & {
|
||||
actionHook: SingleRecordActionHook;
|
||||
}
|
||||
> = {
|
||||
useAsDraftWorkflowVersionSingleRecord: {
|
||||
key: 'use-as-draft-workflow-version-single-record',
|
||||
label: 'Use as draft',
|
||||
position: 1,
|
||||
isPinned: true,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
Icon: IconPencil,
|
||||
actionHook: useUseAsDraftWorkflowVersionSingleRecordAction,
|
||||
},
|
||||
seeWorkflowExecutionsSingleRecord: {
|
||||
key: 'see-workflow-executions-single-record',
|
||||
label: 'See executions',
|
||||
position: 2,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
Icon: IconHistoryToggle,
|
||||
actionHook: useSeeExecutionsWorkflowVersionSingleRecordAction,
|
||||
},
|
||||
seeWorkflowVersionsHistorySingleRecord: {
|
||||
key: 'see-workflow-versions-history-single-record',
|
||||
label: 'See versions history',
|
||||
position: 3,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
Icon: IconHistory,
|
||||
actionHook: useSeeVersionsWorkflowVersionSingleRecordAction,
|
||||
},
|
||||
};
|
@ -0,0 +1,48 @@
|
||||
import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import qs from 'qs';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useSeeExecutionsWorkflowVersionSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem =
|
||||
({ recordId }) => {
|
||||
const workflowVersion = useRecoilValue(recordStoreFamilyState(recordId));
|
||||
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(
|
||||
workflowVersion?.workflow.id,
|
||||
);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const shouldBeRegistered = isDefined(workflowWithCurrentVersion);
|
||||
|
||||
const onClick = () => {
|
||||
if (!shouldBeRegistered) return;
|
||||
|
||||
const filterQueryParams: FilterQueryParams = {
|
||||
filter: {
|
||||
workflow: {
|
||||
[ViewFilterOperand.Is]: [workflowWithCurrentVersion.id],
|
||||
},
|
||||
workflowVersion: {
|
||||
[ViewFilterOperand.Is]: [recordId],
|
||||
},
|
||||
},
|
||||
};
|
||||
const filterLinkHref = `/objects/${CoreObjectNamePlural.WorkflowRun}?${qs.stringify(
|
||||
filterQueryParams,
|
||||
)}`;
|
||||
|
||||
navigate(filterLinkHref);
|
||||
};
|
||||
|
||||
return {
|
||||
shouldBeRegistered,
|
||||
onClick,
|
||||
};
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
import { useSeeVersionsWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction';
|
||||
import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useSeeVersionsWorkflowVersionSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem =
|
||||
({ recordId }) => {
|
||||
const workflowVersion = useRecoilValue(recordStoreFamilyState(recordId));
|
||||
|
||||
if (!isDefined(workflowVersion)) {
|
||||
throw new Error('Workflow version not found');
|
||||
}
|
||||
|
||||
const { shouldBeRegistered, onClick } =
|
||||
useSeeVersionsWorkflowSingleRecordAction({
|
||||
recordId: workflowVersion.workflow.id,
|
||||
});
|
||||
|
||||
return {
|
||||
shouldBeRegistered,
|
||||
onClick,
|
||||
};
|
||||
};
|
@ -1,78 +0,0 @@
|
||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||
import {
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import qs from 'qs';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconHistoryToggle, isDefined } from 'twenty-ui';
|
||||
|
||||
export const useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction = ({
|
||||
workflowVersionId,
|
||||
}: {
|
||||
workflowVersionId: string;
|
||||
}) => {
|
||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||
|
||||
const workflowVersion = useRecoilValue(
|
||||
recordStoreFamilyState(workflowVersionId),
|
||||
);
|
||||
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(
|
||||
workflowVersion?.workflow.id,
|
||||
);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const registerSeeWorkflowExecutionsWorkflowVersionSingleRecordAction = ({
|
||||
position,
|
||||
}: {
|
||||
position: number;
|
||||
}) => {
|
||||
if (!isDefined(workflowWithCurrentVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterQueryParams: FilterQueryParams = {
|
||||
filter: {
|
||||
workflow: {
|
||||
[ViewFilterOperand.Is]: [workflowWithCurrentVersion.id],
|
||||
},
|
||||
workflowVersion: {
|
||||
[ViewFilterOperand.Is]: [workflowVersionId],
|
||||
},
|
||||
},
|
||||
};
|
||||
const filterLinkHref = `/objects/${CoreObjectNamePlural.WorkflowRun}?${qs.stringify(
|
||||
filterQueryParams,
|
||||
)}`;
|
||||
|
||||
addActionMenuEntry({
|
||||
key: 'see-workflow-executions-single-record',
|
||||
label: 'See executions',
|
||||
position,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
Icon: IconHistoryToggle,
|
||||
onClick: () => {
|
||||
navigate(filterLinkHref);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const unregisterSeeWorkflowExecutionsWorkflowVersionSingleRecordAction =
|
||||
() => {
|
||||
removeActionMenuEntry('see-workflow-executions-single-record');
|
||||
};
|
||||
|
||||
return {
|
||||
registerSeeWorkflowExecutionsWorkflowVersionSingleRecordAction,
|
||||
unregisterSeeWorkflowExecutionsWorkflowVersionSingleRecordAction,
|
||||
};
|
||||
};
|
@ -1,27 +0,0 @@
|
||||
import { useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
export const useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction = ({
|
||||
workflowVersionId,
|
||||
}: {
|
||||
workflowVersionId: string;
|
||||
}) => {
|
||||
const workflowVersion = useRecoilValue(
|
||||
recordStoreFamilyState(workflowVersionId),
|
||||
);
|
||||
|
||||
const {
|
||||
registerSeeWorkflowVersionsHistoryWorkflowSingleRecordAction:
|
||||
registerSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction,
|
||||
unregisterSeeWorkflowVersionsHistoryWorkflowSingleRecordAction:
|
||||
unregisterSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction,
|
||||
} = useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction({
|
||||
workflowId: workflowVersion?.workflow.id,
|
||||
});
|
||||
|
||||
return {
|
||||
registerSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction,
|
||||
unregisterSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction,
|
||||
};
|
||||
};
|
@ -1,92 +1,66 @@
|
||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||
import {
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import { OverrideWorkflowDraftConfirmationModal } from '@/workflow/components/OverrideWorkflowDraftConfirmationModal';
|
||||
import { useCreateNewWorkflowVersion } from '@/workflow/hooks/useCreateNewWorkflowVersion';
|
||||
import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { openOverrideWorkflowDraftConfirmationModalState } from '@/workflow/states/openOverrideWorkflowDraftConfirmationModalState';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { IconPencil, isDefined } from 'twenty-ui';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useUseAsDraftWorkflowVersionSingleRecordAction = ({
|
||||
workflowVersionId,
|
||||
}: {
|
||||
workflowVersionId: string;
|
||||
}) => {
|
||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||
export const useUseAsDraftWorkflowVersionSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem =
|
||||
({ recordId }) => {
|
||||
const workflowVersion = useWorkflowVersion(recordId);
|
||||
|
||||
const workflowVersion = useRecoilValue(
|
||||
recordStoreFamilyState(workflowVersionId),
|
||||
);
|
||||
const workflow = useWorkflowWithCurrentVersion(
|
||||
workflowVersion?.workflow?.id ?? '',
|
||||
);
|
||||
|
||||
const workflow = useWorkflowWithCurrentVersion(
|
||||
workflowVersion?.workflow?.id ?? '',
|
||||
);
|
||||
const { createNewWorkflowVersion } = useCreateNewWorkflowVersion();
|
||||
|
||||
const { createNewWorkflowVersion } = useCreateNewWorkflowVersion();
|
||||
const setOpenOverrideWorkflowDraftConfirmationModal = useSetRecoilState(
|
||||
openOverrideWorkflowDraftConfirmationModalState,
|
||||
);
|
||||
|
||||
const setOpenOverrideWorkflowDraftConfirmationModal = useSetRecoilState(
|
||||
openOverrideWorkflowDraftConfirmationModalState,
|
||||
);
|
||||
const workflowStatuses = workflow?.statuses;
|
||||
|
||||
const registerUseAsDraftWorkflowVersionSingleRecordAction = ({
|
||||
position,
|
||||
}: {
|
||||
position: number;
|
||||
}) => {
|
||||
if (
|
||||
!isDefined(workflowVersion) ||
|
||||
!isDefined(workflow) ||
|
||||
!isDefined(workflow.statuses) ||
|
||||
workflowVersion.status === 'DRAFT'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const shouldBeRegistered =
|
||||
isDefined(workflowVersion) &&
|
||||
isDefined(workflow) &&
|
||||
isDefined(workflowStatuses) &&
|
||||
workflowVersion.status !== 'DRAFT';
|
||||
|
||||
const hasAlreadyDraftVersion = workflow.statuses.includes('DRAFT');
|
||||
const onClick = async () => {
|
||||
if (!shouldBeRegistered) return;
|
||||
|
||||
addActionMenuEntry({
|
||||
key: 'use-workflow-version-as-draft-single-record',
|
||||
label: 'Use as draft',
|
||||
position,
|
||||
Icon: IconPencil,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
onClick: async () => {
|
||||
if (hasAlreadyDraftVersion) {
|
||||
setOpenOverrideWorkflowDraftConfirmationModal(true);
|
||||
} else {
|
||||
await createNewWorkflowVersion({
|
||||
workflowId: workflowVersion.workflow.id,
|
||||
name: `v${workflow.versions.length + 1}`,
|
||||
status: 'DRAFT',
|
||||
trigger: workflowVersion.trigger,
|
||||
steps: workflowVersion.steps,
|
||||
});
|
||||
}
|
||||
},
|
||||
ConfirmationModal: (
|
||||
<OverrideWorkflowDraftConfirmationModal
|
||||
draftWorkflowVersionId={workflow?.currentVersion?.id ?? ''}
|
||||
workflowId={workflow?.id ?? ''}
|
||||
workflowVersionUpdateInput={{
|
||||
steps: workflowVersion.steps,
|
||||
trigger: workflowVersion.trigger,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
const hasAlreadyDraftVersion = workflowStatuses.includes('DRAFT');
|
||||
|
||||
if (hasAlreadyDraftVersion) {
|
||||
setOpenOverrideWorkflowDraftConfirmationModal(true);
|
||||
} else {
|
||||
await createNewWorkflowVersion({
|
||||
workflowId: workflowVersion.workflow.id,
|
||||
name: `v${workflow.versions.length + 1}`,
|
||||
status: 'DRAFT',
|
||||
trigger: workflowVersion.trigger,
|
||||
steps: workflowVersion.steps,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const ConfirmationModal = shouldBeRegistered ? (
|
||||
<OverrideWorkflowDraftConfirmationModal
|
||||
draftWorkflowVersionId={workflow?.currentVersion?.id ?? ''}
|
||||
workflowId={workflow?.id ?? ''}
|
||||
workflowVersionUpdateInput={{
|
||||
steps: workflowVersion.steps,
|
||||
trigger: workflowVersion.trigger,
|
||||
}}
|
||||
/>
|
||||
) : undefined;
|
||||
|
||||
return {
|
||||
shouldBeRegistered,
|
||||
onClick,
|
||||
ConfirmationModal,
|
||||
};
|
||||
};
|
||||
|
||||
const unregisterUseAsDraftWorkflowVersionSingleRecordAction = () => {
|
||||
removeActionMenuEntry('use-workflow-version-as-draft-single-record');
|
||||
};
|
||||
|
||||
return {
|
||||
registerUseAsDraftWorkflowVersionSingleRecordAction,
|
||||
unregisterUseAsDraftWorkflowVersionSingleRecordAction,
|
||||
};
|
||||
};
|
||||
|
@ -1,70 +0,0 @@
|
||||
import { useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction';
|
||||
import { useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction';
|
||||
import { useUseAsDraftWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useWorkflowVersionsSingleRecordActions = () => {
|
||||
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
const selectedRecordId =
|
||||
contextStoreTargetedRecordsRule.mode === 'selection'
|
||||
? contextStoreTargetedRecordsRule.selectedRecordIds?.[0]
|
||||
: undefined;
|
||||
|
||||
if (!isDefined(selectedRecordId)) {
|
||||
throw new Error('Selected record ID is required');
|
||||
}
|
||||
|
||||
const {
|
||||
registerSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction,
|
||||
unregisterSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction,
|
||||
} = useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction({
|
||||
workflowVersionId: selectedRecordId,
|
||||
});
|
||||
|
||||
const {
|
||||
registerUseAsDraftWorkflowVersionSingleRecordAction,
|
||||
unregisterUseAsDraftWorkflowVersionSingleRecordAction,
|
||||
} = useUseAsDraftWorkflowVersionSingleRecordAction({
|
||||
workflowVersionId: selectedRecordId,
|
||||
});
|
||||
|
||||
const {
|
||||
registerSeeWorkflowExecutionsWorkflowVersionSingleRecordAction,
|
||||
unregisterSeeWorkflowExecutionsWorkflowVersionSingleRecordAction,
|
||||
} = useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction({
|
||||
workflowVersionId: selectedRecordId,
|
||||
});
|
||||
|
||||
const registerSingleRecordActions = ({
|
||||
startPosition,
|
||||
}: {
|
||||
startPosition: number;
|
||||
}) => {
|
||||
registerUseAsDraftWorkflowVersionSingleRecordAction({
|
||||
position: startPosition,
|
||||
});
|
||||
registerSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction({
|
||||
position: startPosition + 1,
|
||||
});
|
||||
|
||||
registerSeeWorkflowExecutionsWorkflowVersionSingleRecordAction({
|
||||
position: startPosition + 2,
|
||||
});
|
||||
};
|
||||
|
||||
const unregisterSingleRecordActions = () => {
|
||||
unregisterUseAsDraftWorkflowVersionSingleRecordAction();
|
||||
unregisterSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction();
|
||||
unregisterSeeWorkflowExecutionsWorkflowVersionSingleRecordAction();
|
||||
};
|
||||
|
||||
return {
|
||||
registerSingleRecordActions,
|
||||
unregisterSingleRecordActions,
|
||||
};
|
||||
};
|
@ -0,0 +1,5 @@
|
||||
export type ActionHookResult = {
|
||||
shouldBeRegistered: boolean;
|
||||
onClick: () => void;
|
||||
ConfirmationModal?: React.ReactElement;
|
||||
};
|
@ -0,0 +1,20 @@
|
||||
import { ActionHookResult } from '@/action-menu/actions/types/actionHookResult';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export type SingleRecordActionHook =
|
||||
| SingleRecordActionHookWithoutObjectMetadataItem
|
||||
| SingleRecordActionHookWithObjectMetadataItem;
|
||||
|
||||
export type SingleRecordActionHookWithoutObjectMetadataItem = ({
|
||||
recordId,
|
||||
}: {
|
||||
recordId: string;
|
||||
}) => ActionHookResult;
|
||||
|
||||
export type SingleRecordActionHookWithObjectMetadataItem = ({
|
||||
recordId,
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
recordId: string;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => ActionHookResult;
|
@ -1,4 +1,4 @@
|
||||
import { MouseEvent, ReactNode } from 'react';
|
||||
import { MouseEvent, ReactElement } from 'react';
|
||||
import { IconComponent, MenuItemAccent } from 'twenty-ui';
|
||||
|
||||
export enum ActionMenuEntryType {
|
||||
@ -21,5 +21,5 @@ export type ActionMenuEntry = {
|
||||
isPinned?: boolean;
|
||||
accent?: MenuItemAccent;
|
||||
onClick?: (event?: MouseEvent<HTMLElement>) => void;
|
||||
ConfirmationModal?: ReactNode;
|
||||
ConfirmationModal?: ReactElement;
|
||||
};
|
||||
|
@ -31,7 +31,7 @@ export const useOpenCreateActivityDrawer = ({
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
const { createOneRecord: createOneActivity } = useCreateOneRecord<
|
||||
Task | Note
|
||||
(Task | Note) & { position: 'first' | 'last' }
|
||||
>({
|
||||
objectNameSingular: activityObjectNameSingular,
|
||||
});
|
||||
@ -74,6 +74,7 @@ export const useOpenCreateActivityDrawer = ({
|
||||
|
||||
const activity = await createOneActivity({
|
||||
assigneeId: customAssignee?.id,
|
||||
position: 'last',
|
||||
});
|
||||
|
||||
if (targetableObjects.length > 0) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { ApolloCache, StoreObject } from '@apollo/client';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
@ -75,10 +76,21 @@ export const triggerCreateRecordsOptimisticEffect = ({
|
||||
rootQueryCachedObjectRecordConnection,
|
||||
);
|
||||
|
||||
const rootQueryCachedPageInfo = readField<{
|
||||
startCursor?: string;
|
||||
endCursor?: string;
|
||||
hasNextPage?: boolean;
|
||||
hasPreviousPage?: boolean;
|
||||
}>('pageInfo', rootQueryCachedObjectRecordConnection);
|
||||
|
||||
const nextRootQueryCachedRecordEdges = rootQueryCachedRecordEdges
|
||||
? [...rootQueryCachedRecordEdges]
|
||||
: [];
|
||||
|
||||
const nextQueryCachedPageInfo = isDefined(rootQueryCachedPageInfo)
|
||||
? { ...rootQueryCachedPageInfo }
|
||||
: {};
|
||||
|
||||
const hasAddedRecords = recordsToCreate
|
||||
.map((recordToCreate) => {
|
||||
if (isNonEmptyString(recordToCreate.id)) {
|
||||
@ -116,11 +128,61 @@ export const triggerCreateRecordsOptimisticEffect = ({
|
||||
);
|
||||
|
||||
if (recordToCreateReference && !recordAlreadyInCache) {
|
||||
nextRootQueryCachedRecordEdges.unshift({
|
||||
const cursor = Buffer.from(
|
||||
JSON.stringify({
|
||||
position: recordToCreate.position,
|
||||
id: recordToCreate.id,
|
||||
}),
|
||||
'utf-8',
|
||||
).toString('base64');
|
||||
|
||||
const edge = {
|
||||
__typename: getEdgeTypename(objectMetadataItem.nameSingular),
|
||||
node: recordToCreateReference,
|
||||
cursor: '',
|
||||
});
|
||||
cursor,
|
||||
};
|
||||
|
||||
if (
|
||||
!isDefined(recordToCreate.position) ||
|
||||
recordToCreate.position === 'first'
|
||||
) {
|
||||
nextRootQueryCachedRecordEdges.unshift(edge);
|
||||
nextQueryCachedPageInfo.startCursor = cursor;
|
||||
} else if (recordToCreate.position === 'last') {
|
||||
nextRootQueryCachedRecordEdges.push(edge);
|
||||
nextQueryCachedPageInfo.endCursor = cursor;
|
||||
} else if (typeof recordToCreate.position === 'number') {
|
||||
let index = Math.round(
|
||||
nextRootQueryCachedRecordEdges.length *
|
||||
recordToCreate.position,
|
||||
);
|
||||
|
||||
if (recordToCreate.position < 0) {
|
||||
index = Math.max(
|
||||
0,
|
||||
nextRootQueryCachedRecordEdges.length +
|
||||
Math.round(recordToCreate.position),
|
||||
);
|
||||
} else if (recordToCreate.position > 1) {
|
||||
index = nextRootQueryCachedRecordEdges.length;
|
||||
}
|
||||
|
||||
index = Math.max(
|
||||
0,
|
||||
Math.min(index, nextRootQueryCachedRecordEdges.length),
|
||||
);
|
||||
|
||||
nextRootQueryCachedRecordEdges.splice(index, 0, edge);
|
||||
|
||||
if (index === 0) {
|
||||
nextQueryCachedPageInfo.startCursor = cursor;
|
||||
} else if (
|
||||
index ===
|
||||
nextRootQueryCachedRecordEdges.length - 1
|
||||
) {
|
||||
nextQueryCachedPageInfo.endCursor = cursor;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -140,6 +202,7 @@ export const triggerCreateRecordsOptimisticEffect = ({
|
||||
totalCount: isDefined(rootQueryCachedRecordTotalCount)
|
||||
? rootQueryCachedRecordTotalCount + 1
|
||||
: undefined,
|
||||
pageInfo: nextQueryCachedPageInfo,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -48,6 +48,7 @@ import { useReadWorkspaceSubdomainFromCurrentLocation } from '@/domain-manager/h
|
||||
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
||||
import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState';
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||
|
||||
export const useAuth = () => {
|
||||
const setTokenPair = useSetRecoilState(tokenPairState);
|
||||
@ -65,6 +66,7 @@ export const useAuth = () => {
|
||||
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
|
||||
const setIsVerifyPendingState = useSetRecoilState(isVerifyPendingState);
|
||||
const setWorkspaces = useSetRecoilState(workspacesState);
|
||||
const { redirect } = useRedirect();
|
||||
|
||||
const [challenge] = useChallengeMutation();
|
||||
const [signUp] = useSignUpMutation();
|
||||
@ -367,9 +369,9 @@ export const useAuth = () => {
|
||||
workspacePersonalInviteToken?: string;
|
||||
workspaceInviteHash?: string;
|
||||
}) => {
|
||||
window.location.href = buildRedirectUrl('/auth/google', params);
|
||||
redirect(buildRedirectUrl('/auth/google', params));
|
||||
},
|
||||
[buildRedirectUrl],
|
||||
[buildRedirectUrl, redirect],
|
||||
);
|
||||
|
||||
const handleMicrosoftLogin = useCallback(
|
||||
@ -377,9 +379,9 @@ export const useAuth = () => {
|
||||
workspacePersonalInviteToken?: string;
|
||||
workspaceInviteHash?: string;
|
||||
}) => {
|
||||
window.location.href = buildRedirectUrl('/auth/microsoft', params);
|
||||
redirect(buildRedirectUrl('/auth/microsoft', params));
|
||||
},
|
||||
[buildRedirectUrl],
|
||||
[buildRedirectUrl, redirect],
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -121,12 +121,50 @@ export const useCommandMenu = () => {
|
||||
);
|
||||
|
||||
const closeCommandMenu = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
({ snapshot, set }) =>
|
||||
() => {
|
||||
const isCommandMenuOpened = snapshot
|
||||
.getLoadable(isCommandMenuOpenedState)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
contextStoreCurrentObjectMetadataIdComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
null,
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreTargetedRecordsRuleComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
{
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [],
|
||||
},
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreFiltersComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
null,
|
||||
);
|
||||
|
||||
if (isCommandMenuOpened) {
|
||||
setIsCommandMenuOpened(false);
|
||||
resetSelectedItem();
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
|
||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||
import { useIsCurrentLocationOnDefaultDomain } from '@/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain';
|
||||
import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain';
|
||||
import { useRedirectToDefaultDomain } from '@/domain-manager/hooks/useRedirectToDefaultDomain';
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
@ -19,8 +18,6 @@ export const useGetPublicWorkspaceDataBySubdomain = () => {
|
||||
const setWorkspacePublicDataState = useSetRecoilState(
|
||||
workspacePublicDataState,
|
||||
);
|
||||
const { setLastAuthenticateWorkspaceDomain } =
|
||||
useLastAuthenticatedWorkspaceDomain();
|
||||
|
||||
const { loading } = useGetPublicWorkspaceDataBySubdomainQuery({
|
||||
skip:
|
||||
@ -35,7 +32,6 @@ export const useGetPublicWorkspaceDataBySubdomain = () => {
|
||||
onError: (error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
setLastAuthenticateWorkspaceDomain(null);
|
||||
redirectToDefaultDomain();
|
||||
},
|
||||
});
|
||||
|
@ -0,0 +1,14 @@
|
||||
// Don't use this hook directly! Prefer the high level hooks like:
|
||||
// useRedirectToDefaultDomain and useRedirectToWorkspaceDomain
|
||||
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
export const useRedirect = () => {
|
||||
const redirect = useDebouncedCallback((url: string) => {
|
||||
window.location.href = url;
|
||||
}, 1);
|
||||
|
||||
return {
|
||||
redirect,
|
||||
};
|
||||
};
|
@ -1,12 +1,19 @@
|
||||
import { useReadDefaultDomainFromConfiguration } from '@/domain-manager/hooks/useReadDefaultDomainFromConfiguration';
|
||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||
import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain';
|
||||
|
||||
export const useRedirectToDefaultDomain = () => {
|
||||
const { defaultDomain } = useReadDefaultDomainFromConfiguration();
|
||||
const { setLastAuthenticateWorkspaceDomain } =
|
||||
useLastAuthenticatedWorkspaceDomain();
|
||||
|
||||
const { redirect } = useRedirect();
|
||||
const redirectToDefaultDomain = () => {
|
||||
const url = new URL(window.location.href);
|
||||
if (url.hostname !== defaultDomain) {
|
||||
setLastAuthenticateWorkspaceDomain(null);
|
||||
url.hostname = defaultDomain;
|
||||
window.location.href = url.toString();
|
||||
redirect(url.toString());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUrl';
|
||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||
|
||||
export const useRedirectToWorkspaceDomain = () => {
|
||||
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||
const { buildWorkspaceUrl } = useBuildWorkspaceUrl();
|
||||
const { redirect } = useRedirect();
|
||||
|
||||
const redirectToWorkspaceDomain = (
|
||||
subdomain: string,
|
||||
@ -12,7 +14,7 @@ export const useRedirectToWorkspaceDomain = () => {
|
||||
searchParams?: Record<string, string>,
|
||||
) => {
|
||||
if (!isMultiWorkspaceEnabled) return;
|
||||
window.location.href = buildWorkspaceUrl(subdomain, pathname, searchParams);
|
||||
redirect(buildWorkspaceUrl(subdomain, pathname, searchParams));
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -14,7 +14,7 @@ import { ViewType } from '@/views/types/ViewType';
|
||||
import { MockedResponse } from '@apollo/client/testing';
|
||||
import { expect } from '@storybook/test';
|
||||
import gql from 'graphql-tag';
|
||||
import { getJestMetadataAndApolloMocksAndContextStoreWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper';
|
||||
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
|
||||
const defaultResponseData = {
|
||||
@ -130,17 +130,15 @@ const mocks: MockedResponse[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const WrapperWithResponse = getJestMetadataAndApolloMocksAndContextStoreWrapper(
|
||||
{
|
||||
apolloMocks: mocks,
|
||||
componentInstanceId: 'recordIndexId',
|
||||
contextStoreTargetedRecordsRule: {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [],
|
||||
},
|
||||
contextStoreCurrentObjectMetadataNameSingular: 'person',
|
||||
const WrapperWithResponse = getJestMetadataAndApolloMocksAndActionMenuWrapper({
|
||||
apolloMocks: mocks,
|
||||
componentInstanceId: 'recordIndexId',
|
||||
contextStoreTargetedRecordsRule: {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [],
|
||||
},
|
||||
);
|
||||
contextStoreCurrentObjectMetadataNameSingular: 'person',
|
||||
});
|
||||
|
||||
const graphqlEmptyResponse = [
|
||||
{
|
||||
@ -157,7 +155,7 @@ const graphqlEmptyResponse = [
|
||||
];
|
||||
|
||||
const WrapperWithEmptyResponse =
|
||||
getJestMetadataAndApolloMocksAndContextStoreWrapper({
|
||||
getJestMetadataAndApolloMocksAndActionMenuWrapper({
|
||||
apolloMocks: graphqlEmptyResponse,
|
||||
componentInstanceId: 'recordIndexId',
|
||||
contextStoreTargetedRecordsRule: {
|
||||
|
@ -56,8 +56,8 @@ export const RecordTableRecordGroupRows = () => {
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<RecordTableRecordGroupSectionLoadMore />
|
||||
<RecordTablePendingRecordGroupRow />
|
||||
<RecordTableRecordGroupSectionLoadMore />
|
||||
<RecordTableRecordGroupSectionAddNew />
|
||||
</>
|
||||
);
|
||||
|
@ -4,6 +4,8 @@ import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/get
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector';
|
||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
|
||||
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||
@ -26,6 +28,11 @@ export const useUpsertTableRecordInGroup = (recordGroupId: string) => {
|
||||
recordTablePendingRecordIdByGroupComponentFamilyState,
|
||||
);
|
||||
|
||||
const recordIndexRecordIdsByGroupFamilyState =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
recordIndexRecordIdsByGroupComponentFamilyState,
|
||||
);
|
||||
|
||||
const upsertTableRecordInGroup = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(persistField: () => void, recordId: string, fieldName: string) => {
|
||||
@ -54,11 +61,23 @@ export const useUpsertTableRecordInGroup = (recordGroupId: string) => {
|
||||
recordGroupDefinitionFamilyState(recordGroupId),
|
||||
);
|
||||
|
||||
const recordGroupIds = getSnapshotValue(
|
||||
snapshot,
|
||||
recordIndexRecordIdsByGroupFamilyState(recordGroupId),
|
||||
);
|
||||
|
||||
const recordGroupFieldMetadataItem = objectMetadataItem.fields.find(
|
||||
(fieldMetadata) =>
|
||||
fieldMetadata.id === recordGroupDefinition?.fieldMetadataId,
|
||||
);
|
||||
|
||||
const lastId = recordGroupIds?.[recordGroupIds.length - 1];
|
||||
|
||||
const objectRecord = getSnapshotValue(
|
||||
snapshot,
|
||||
recordStoreFamilyState(lastId),
|
||||
);
|
||||
|
||||
if (
|
||||
isDefined(recordTablePendingRecordId) &&
|
||||
isDefined(recordGroupDefinition) &&
|
||||
@ -69,7 +88,7 @@ export const useUpsertTableRecordInGroup = (recordGroupId: string) => {
|
||||
id: recordTablePendingRecordId,
|
||||
[labelIdentifierFieldMetadataItem?.name ?? 'name']: draftValue,
|
||||
[recordGroupFieldMetadataItem.name]: recordGroupDefinition.value,
|
||||
position: 'first',
|
||||
position: (objectRecord?.position ?? 0) + 0.0001,
|
||||
});
|
||||
} else if (!recordTablePendingRecordId) {
|
||||
persistField();
|
||||
@ -79,6 +98,7 @@ export const useUpsertTableRecordInGroup = (recordGroupId: string) => {
|
||||
createOneRecord,
|
||||
objectMetadataItem,
|
||||
recordGroupId,
|
||||
recordIndexRecordIdsByGroupFamilyState,
|
||||
recordTablePendingRecordIdByGroupFamilyState,
|
||||
],
|
||||
);
|
||||
|
@ -3,22 +3,14 @@ import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
|
||||
import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow';
|
||||
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { IconPlus } from 'twenty-ui';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const RecordTableRecordGroupSectionAddNew = () => {
|
||||
const { recordTableId } = useRecordTableContextOrThrow();
|
||||
|
||||
const currentRecordGroupId = useCurrentRecordGroupId();
|
||||
|
||||
const pendingRecordId = useRecoilComponentFamilyValueV2(
|
||||
recordTablePendingRecordIdByGroupComponentFamilyState,
|
||||
currentRecordGroupId,
|
||||
);
|
||||
|
||||
const recordIds = useRecoilComponentValueV2(
|
||||
recordIndexAllRecordIdsComponentSelector,
|
||||
);
|
||||
@ -30,8 +22,6 @@ export const RecordTableRecordGroupSectionAddNew = () => {
|
||||
createNewTableRecordInGroup(currentRecordGroupId);
|
||||
};
|
||||
|
||||
if (isDefined(pendingRecordId)) return null;
|
||||
|
||||
return (
|
||||
<RecordTableActionRow
|
||||
draggableId={`add-new-record-${currentRecordGroupId}`}
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
MessageChannelVisibility,
|
||||
useGenerateTransientTokenMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||
|
||||
const getProviderUrl = (provider: string) => {
|
||||
switch (provider) {
|
||||
@ -21,6 +22,7 @@ const getProviderUrl = (provider: string) => {
|
||||
|
||||
export const useTriggerApisOAuth = () => {
|
||||
const [generateTransientToken] = useGenerateTransientTokenMutation();
|
||||
const { redirect } = useRedirect();
|
||||
|
||||
const triggerApisOAuth = useCallback(
|
||||
async (
|
||||
@ -60,9 +62,9 @@ export const useTriggerApisOAuth = () => {
|
||||
|
||||
params += loginHint ? `&loginHint=${loginHint}` : '';
|
||||
|
||||
window.location.href = `${authServerUrl}/auth/${getProviderUrl(provider)}?${params}`;
|
||||
redirect(`${authServerUrl}/auth/${getProviderUrl(provider)}?${params}`);
|
||||
},
|
||||
[generateTransientToken],
|
||||
[generateTransientToken, redirect],
|
||||
);
|
||||
|
||||
return { triggerApisOAuth };
|
||||
|
@ -6,10 +6,11 @@ import { useState } from 'react';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { useImpersonateMutation } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { sleep } from '~/utils/sleep';
|
||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||
|
||||
export const useImpersonate = () => {
|
||||
const { clearSession } = useAuth();
|
||||
const { redirect } = useRedirect();
|
||||
const [currentUser, setCurrentUser] = useRecoilState(currentUserState);
|
||||
const setTokenPair = useSetRecoilState(tokenPairState);
|
||||
const [impersonate] = useImpersonateMutation();
|
||||
@ -43,8 +44,7 @@ export const useImpersonate = () => {
|
||||
await clearSession();
|
||||
setCurrentUser(user);
|
||||
setTokenPair(tokens);
|
||||
await sleep(0); // This hacky workaround is necessary to ensure the tokens stored in the cookie are updated correctly.
|
||||
window.location.href = AppPath.Index;
|
||||
redirect(AppPath.Index);
|
||||
} catch (error) {
|
||||
setError('Failed to impersonate user. Please try again.');
|
||||
setIsLoading(false);
|
||||
|
@ -6,6 +6,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { MainButton, UndecoratedLink } from 'twenty-ui';
|
||||
import { useAuthorizeAppMutation } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||
|
||||
type App = { id: string; name: string; logo: string };
|
||||
|
||||
@ -55,6 +56,7 @@ const StyledButtonContainer = styled.div`
|
||||
export const Authorize = () => {
|
||||
const navigate = useNavigate();
|
||||
const [searchParam] = useSearchParams();
|
||||
const { redirect } = useRedirect();
|
||||
//TODO: Replace with db call for registered third party apps
|
||||
const [apps] = useState<App[]>([
|
||||
{
|
||||
@ -89,7 +91,7 @@ export const Authorize = () => {
|
||||
redirectUrl,
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
window.location.href = data.authorizeApp.redirectUrl;
|
||||
redirect(data.authorizeApp.redirectUrl);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import { PageContainer } from '@/ui/layout/page/components/PageContainer';
|
||||
import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle';
|
||||
import { RecordShowPageWorkflowHeader } from '@/workflow/components/RecordShowPageWorkflowHeader';
|
||||
import { RecordShowPageWorkflowVersionHeader } from '@/workflow/components/RecordShowPageWorkflowVersionHeader';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { RecordShowPageHeader } from '~/pages/object-record/RecordShowPageHeader';
|
||||
|
||||
export const RecordShowPage = () => {
|
||||
@ -38,6 +39,10 @@ export const RecordShowPage = () => {
|
||||
parameters.objectRecordId ?? '',
|
||||
);
|
||||
|
||||
const isPageHeaderV2Enabled = useIsFeatureEnabled(
|
||||
'IS_PAGE_HEADER_V2_ENABLED',
|
||||
);
|
||||
|
||||
return (
|
||||
<RecordFieldValueSelectorContextProvider>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
@ -57,25 +62,30 @@ export const RecordShowPage = () => {
|
||||
headerIcon={headerIcon}
|
||||
>
|
||||
<>
|
||||
{objectNameSingular === CoreObjectNameSingular.Workflow ? (
|
||||
<RecordShowPageWorkflowHeader workflowId={objectRecordId} />
|
||||
) : objectNameSingular ===
|
||||
CoreObjectNameSingular.WorkflowVersion ? (
|
||||
<RecordShowPageWorkflowVersionHeader
|
||||
workflowVersionId={objectRecordId}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<RecordShowActionMenu
|
||||
{...{
|
||||
isFavorite,
|
||||
record,
|
||||
handleFavoriteButtonClick,
|
||||
objectMetadataItem,
|
||||
objectNameSingular,
|
||||
}}
|
||||
{!isPageHeaderV2Enabled &&
|
||||
objectNameSingular === CoreObjectNameSingular.Workflow && (
|
||||
<RecordShowPageWorkflowHeader workflowId={objectRecordId} />
|
||||
)}
|
||||
{!isPageHeaderV2Enabled &&
|
||||
objectNameSingular ===
|
||||
CoreObjectNameSingular.WorkflowVersion && (
|
||||
<RecordShowPageWorkflowVersionHeader
|
||||
workflowVersionId={objectRecordId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{(isPageHeaderV2Enabled ||
|
||||
(objectNameSingular !== CoreObjectNameSingular.Workflow &&
|
||||
objectNameSingular !==
|
||||
CoreObjectNameSingular.WorkflowVersion)) && (
|
||||
<RecordShowActionMenu
|
||||
{...{
|
||||
isFavorite,
|
||||
record,
|
||||
handleFavoriteButtonClick,
|
||||
objectMetadataItem,
|
||||
objectNameSingular,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</RecordShowPageHeader>
|
||||
|
@ -17,7 +17,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useUpdateWorkspaceMutation } from '~/generated/graphql';
|
||||
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUrl';
|
||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||
|
||||
const validationSchema = z
|
||||
.object({
|
||||
@ -54,7 +54,7 @@ export const SettingsDomain = () => {
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
||||
const { buildWorkspaceUrl } = useBuildWorkspaceUrl();
|
||||
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
||||
|
||||
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
||||
currentWorkspaceState,
|
||||
@ -97,7 +97,7 @@ export const SettingsDomain = () => {
|
||||
subdomain: values.subdomain,
|
||||
});
|
||||
|
||||
window.location.href = buildWorkspaceUrl(values.subdomain);
|
||||
redirectToWorkspaceDomain(values.subdomain);
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { MockedResponse } from '@apollo/client/testing';
|
||||
@ -6,13 +7,7 @@ import { MutableSnapshot } from 'recoil';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter';
|
||||
|
||||
export const getJestMetadataAndApolloMocksAndContextStoreWrapper = ({
|
||||
apolloMocks,
|
||||
onInitializeRecoilSnapshot,
|
||||
contextStoreTargetedRecordsRule,
|
||||
contextStoreCurrentObjectMetadataNameSingular,
|
||||
componentInstanceId,
|
||||
}: {
|
||||
export type GetJestMetadataAndApolloMocksAndActionMenuWrapperProps = {
|
||||
apolloMocks:
|
||||
| readonly MockedResponse<Record<string, any>, Record<string, any>>[]
|
||||
| undefined;
|
||||
@ -20,11 +15,20 @@ export const getJestMetadataAndApolloMocksAndContextStoreWrapper = ({
|
||||
contextStoreTargetedRecordsRule?: ContextStoreTargetedRecordsRule;
|
||||
contextStoreCurrentObjectMetadataNameSingular?: string;
|
||||
componentInstanceId: string;
|
||||
}) => {
|
||||
};
|
||||
|
||||
export const getJestMetadataAndApolloMocksAndActionMenuWrapper = ({
|
||||
apolloMocks,
|
||||
onInitializeRecoilSnapshot,
|
||||
contextStoreTargetedRecordsRule,
|
||||
contextStoreCurrentObjectMetadataNameSingular,
|
||||
componentInstanceId,
|
||||
}: GetJestMetadataAndApolloMocksAndActionMenuWrapperProps) => {
|
||||
const Wrapper = getJestMetadataAndApolloMocksWrapper({
|
||||
apolloMocks,
|
||||
onInitializeRecoilSnapshot,
|
||||
});
|
||||
|
||||
return ({ children }: { children: ReactNode }) => (
|
||||
<Wrapper>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
@ -32,14 +36,20 @@ export const getJestMetadataAndApolloMocksAndContextStoreWrapper = ({
|
||||
instanceId: componentInstanceId,
|
||||
}}
|
||||
>
|
||||
<JestContextStoreSetter
|
||||
contextStoreTargetedRecordsRule={contextStoreTargetedRecordsRule}
|
||||
contextStoreCurrentObjectMetadataNameSingular={
|
||||
contextStoreCurrentObjectMetadataNameSingular
|
||||
}
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: componentInstanceId,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</JestContextStoreSetter>
|
||||
<JestContextStoreSetter
|
||||
contextStoreTargetedRecordsRule={contextStoreTargetedRecordsRule}
|
||||
contextStoreCurrentObjectMetadataNameSingular={
|
||||
contextStoreCurrentObjectMetadataNameSingular
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</JestContextStoreSetter>
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</Wrapper>
|
||||
);
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { DatabaseCommandModule } from 'src/database/commands/database-command.module';
|
||||
import { WorkspaceHealthCommandModule } from 'src/engine/workspace-manager/workspace-health/commands/workspace-health-command.module';
|
||||
import { WorkspaceCleanerModule } from 'src/engine/workspace-manager/workspace-cleaner/workspace-cleaner.module';
|
||||
import { AppModule } from 'src/app.module';
|
||||
import { WorkspaceMigrationRunnerCommandsModule } from 'src/engine/workspace-manager/workspace-migration-runner/commands/workspace-sync-metadata-commands.module';
|
||||
import { DatabaseCommandModule } from 'src/database/commands/database-command.module';
|
||||
import { WorkspaceCleanerModule } from 'src/engine/workspace-manager/workspace-cleaner/workspace-cleaner.module';
|
||||
import { WorkspaceHealthCommandModule } from 'src/engine/workspace-manager/workspace-health/commands/workspace-health-command.module';
|
||||
import { WorkspaceMigrationRunnerCommandsModule } from 'src/engine/workspace-manager/workspace-migration-runner/commands/workspace-migration-runner-commands.module';
|
||||
import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module';
|
||||
|
||||
@Module({
|
||||
|
@ -10,6 +10,7 @@ import { ConfirmationQuestion } from 'src/database/commands/questions/confirmati
|
||||
import { UpgradeTo0_32CommandModule } from 'src/database/commands/upgrade-version/0-32/0-32-upgrade-version.module';
|
||||
import { UpgradeTo0_33CommandModule } from 'src/database/commands/upgrade-version/0-33/0-33-upgrade-version.module';
|
||||
import { UpgradeTo0_34CommandModule } from 'src/database/commands/upgrade-version/0-34/0-34-upgrade-version.module';
|
||||
import { UpgradeTo0_40CommandModule } from 'src/database/commands/upgrade-version/0-40/0-40-upgrade-version.module';
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
@ -52,6 +53,7 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
|
||||
UpgradeTo0_33CommandModule,
|
||||
UpgradeTo0_34CommandModule,
|
||||
FeatureFlagModule,
|
||||
UpgradeTo0_40CommandModule,
|
||||
],
|
||||
providers: [
|
||||
DataSeedWorkspaceCommand,
|
||||
|
@ -0,0 +1,36 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
|
||||
import { BaseCommandOptions } from 'src/database/commands/base.command';
|
||||
import { RecordPositionBackfillService } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@Command({
|
||||
name: 'migrate-0.40:backfill-record-position',
|
||||
description: 'Backfill record position',
|
||||
})
|
||||
export class RecordPositionBackfillCommand extends ActiveWorkspacesCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(Workspace, 'core')
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly recordPositionBackfillService: RecordPositionBackfillService,
|
||||
) {
|
||||
super(workspaceRepository);
|
||||
}
|
||||
|
||||
async executeActiveWorkspacesCommand(
|
||||
_passedParam: string[],
|
||||
options: BaseCommandOptions,
|
||||
workspaceIds: string[],
|
||||
): Promise<void> {
|
||||
for (const workspaceId of workspaceIds) {
|
||||
await this.recordPositionBackfillService.backfill(
|
||||
workspaceId,
|
||||
options.dryRun ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
|
||||
import { BaseCommandOptions } from 'src/database/commands/base.command';
|
||||
import { RecordPositionBackfillCommand } from 'src/database/commands/upgrade-version/0-40/0-40-record-position-backfill.command';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade-0.40',
|
||||
description: 'Upgrade to 0.40',
|
||||
})
|
||||
export class UpgradeTo0_40Command extends ActiveWorkspacesCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(Workspace, 'core')
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly recordPositionBackfillCommand: RecordPositionBackfillCommand,
|
||||
private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
|
||||
) {
|
||||
super(workspaceRepository);
|
||||
}
|
||||
|
||||
async executeActiveWorkspacesCommand(
|
||||
passedParam: string[],
|
||||
options: BaseCommandOptions,
|
||||
workspaceIds: string[],
|
||||
): Promise<void> {
|
||||
await this.recordPositionBackfillCommand.executeActiveWorkspacesCommand(
|
||||
passedParam,
|
||||
options,
|
||||
workspaceIds,
|
||||
);
|
||||
|
||||
await this.syncWorkspaceMetadataCommand.executeActiveWorkspacesCommand(
|
||||
passedParam,
|
||||
{
|
||||
...options,
|
||||
force: true,
|
||||
},
|
||||
workspaceIds,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { RecordPositionBackfillCommand } from 'src/database/commands/upgrade-version/0-40/0-40-record-position-backfill.command';
|
||||
import { UpgradeTo0_40Command } from 'src/database/commands/upgrade-version/0-40/0-40-upgrade-version.command';
|
||||
import { RecordPositionBackfillModule } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-module';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Workspace], 'core'),
|
||||
WorkspaceSyncMetadataCommandsModule,
|
||||
RecordPositionBackfillModule,
|
||||
],
|
||||
providers: [UpgradeTo0_40Command, RecordPositionBackfillCommand],
|
||||
})
|
||||
export class UpgradeTo0_40CommandModule {}
|
@ -7,7 +7,7 @@ const tableName = 'user';
|
||||
export const DEMO_SEED_USER_IDS = {
|
||||
NOAH: '20202020-9e3b-46d4-a556-88b9ddc2b035',
|
||||
HUGO: '20202020-3957-4908-9c36-2929a23f8358',
|
||||
TIM: '20202020-7169-42cf-bc47-1cfef15264b9',
|
||||
TIM: '20202020-9e3b-46d4-a556-88b9ddc2b034',
|
||||
};
|
||||
|
||||
export const seedUsers = async (
|
||||
|
@ -18,6 +18,7 @@ import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/
|
||||
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
||||
import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory';
|
||||
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
|
||||
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
|
||||
import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
@ -56,77 +57,81 @@ export abstract class GraphqlQueryBaseResolverService<
|
||||
args: Input,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
operationName: WorkspaceResolverBuilderMethodNames,
|
||||
): Promise<Response> {
|
||||
const { authContext, objectMetadataItemWithFieldMaps } = options;
|
||||
): Promise<Response | undefined> {
|
||||
try {
|
||||
const { authContext, objectMetadataItemWithFieldMaps } = options;
|
||||
|
||||
await this.validate(args, options);
|
||||
await this.validate(args, options);
|
||||
|
||||
const hookedArgs =
|
||||
await this.workspaceQueryHookService.executePreQueryHooks(
|
||||
const hookedArgs =
|
||||
await this.workspaceQueryHookService.executePreQueryHooks(
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
operationName,
|
||||
args,
|
||||
);
|
||||
|
||||
const computedArgs = (await this.queryRunnerArgsFactory.create(
|
||||
hookedArgs,
|
||||
options,
|
||||
ResolverArgsType[capitalize(operationName)],
|
||||
)) as Input;
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
authContext.workspace.id,
|
||||
);
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
options.objectMetadataMaps,
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(options.info);
|
||||
|
||||
const graphqlQuerySelectedFieldsResult =
|
||||
graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
selectedFields,
|
||||
);
|
||||
|
||||
const graphqlQueryResolverExecutionArgs = {
|
||||
args: computedArgs,
|
||||
options,
|
||||
dataSource,
|
||||
repository,
|
||||
graphqlQueryParser,
|
||||
graphqlQuerySelectedFieldsResult,
|
||||
};
|
||||
|
||||
const results = await this.resolve(graphqlQueryResolverExecutionArgs);
|
||||
|
||||
const resultWithGetters = await this.queryResultGettersFactory.create(
|
||||
results,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
authContext.workspace.id,
|
||||
options.objectMetadataMaps,
|
||||
);
|
||||
|
||||
const resultWithGettersArray = Array.isArray(resultWithGetters)
|
||||
? resultWithGetters
|
||||
: [resultWithGetters];
|
||||
|
||||
await this.workspaceQueryHookService.executePostQueryHooks(
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
operationName,
|
||||
args,
|
||||
resultWithGettersArray,
|
||||
);
|
||||
|
||||
const computedArgs = (await this.queryRunnerArgsFactory.create(
|
||||
hookedArgs,
|
||||
options,
|
||||
ResolverArgsType[capitalize(operationName)],
|
||||
)) as Input;
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
authContext.workspace.id,
|
||||
);
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
options.objectMetadataMaps,
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(options.info);
|
||||
|
||||
const graphqlQuerySelectedFieldsResult =
|
||||
graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
selectedFields,
|
||||
);
|
||||
|
||||
const graphqlQueryResolverExecutionArgs = {
|
||||
args: computedArgs,
|
||||
options,
|
||||
dataSource,
|
||||
repository,
|
||||
graphqlQueryParser,
|
||||
graphqlQuerySelectedFieldsResult,
|
||||
};
|
||||
|
||||
const results = await this.resolve(graphqlQueryResolverExecutionArgs);
|
||||
|
||||
const resultWithGetters = await this.queryResultGettersFactory.create(
|
||||
results,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
authContext.workspace.id,
|
||||
options.objectMetadataMaps,
|
||||
);
|
||||
|
||||
const resultWithGettersArray = Array.isArray(resultWithGetters)
|
||||
? resultWithGetters
|
||||
: [resultWithGetters];
|
||||
|
||||
await this.workspaceQueryHookService.executePostQueryHooks(
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
operationName,
|
||||
resultWithGettersArray,
|
||||
);
|
||||
|
||||
return resultWithGetters;
|
||||
return resultWithGetters;
|
||||
} catch (error) {
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error, options);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract resolve(
|
||||
|
@ -83,8 +83,6 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv
|
||||
});
|
||||
}
|
||||
|
||||
async;
|
||||
|
||||
async validate(
|
||||
args: CreateOneResolverArgs<Partial<ObjectRecord>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
|
@ -45,7 +45,7 @@ export class RecordPositionQueryFactory {
|
||||
objectMetadata: { isCustom: boolean; nameSingular: string },
|
||||
dataSourceSchema: string,
|
||||
): [RecordPositionQuery, RecordPositionQueryParams] {
|
||||
const name = computeTableName(
|
||||
const tableName = computeTableName(
|
||||
objectMetadata.nameSingular,
|
||||
objectMetadata.isCustom,
|
||||
);
|
||||
@ -54,17 +54,17 @@ export class RecordPositionQueryFactory {
|
||||
case RecordPositionQueryType.FIND_BY_POSITION:
|
||||
return this.buildFindByPositionQuery(
|
||||
recordPositionQueryArgs satisfies FindByPositionQueryArgs,
|
||||
name,
|
||||
tableName,
|
||||
dataSourceSchema,
|
||||
);
|
||||
case RecordPositionQueryType.FIND_MIN_POSITION:
|
||||
return this.buildFindMinPositionQuery(name, dataSourceSchema);
|
||||
return this.buildFindMinPositionQuery(tableName, dataSourceSchema);
|
||||
case RecordPositionQueryType.FIND_MAX_POSITION:
|
||||
return this.buildFindMaxPositionQuery(name, dataSourceSchema);
|
||||
return this.buildFindMaxPositionQuery(tableName, dataSourceSchema);
|
||||
case RecordPositionQueryType.UPDATE_POSITION:
|
||||
return this.buildUpdatePositionQuery(
|
||||
recordPositionQueryArgs satisfies UpdatePositionQueryArgs,
|
||||
name,
|
||||
tableName,
|
||||
dataSourceSchema,
|
||||
);
|
||||
default:
|
||||
|
@ -1,56 +0,0 @@
|
||||
import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
|
||||
import {
|
||||
RecordPositionBackfillJob,
|
||||
RecordPositionBackfillJobData,
|
||||
} from 'src/engine/api/graphql/workspace-query-runner/jobs/record-position-backfill.job';
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||
|
||||
export type RecordPositionBackfillCommandOptions = {
|
||||
workspaceId: string;
|
||||
dryRun?: boolean;
|
||||
};
|
||||
|
||||
@Command({
|
||||
name: 'migrate-0.20:backfill-record-position',
|
||||
description: 'Backfill record position',
|
||||
})
|
||||
export class RecordPositionBackfillCommand extends CommandRunner {
|
||||
constructor(
|
||||
@InjectMessageQueue(MessageQueue.recordPositionBackfillQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-w, --workspace-id [workspace_id]',
|
||||
description: 'workspace id',
|
||||
required: true,
|
||||
})
|
||||
parseWorkspaceId(value: string): string {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-d, --dry-run [dry run]',
|
||||
description: 'Dry run: Log backfill actions.',
|
||||
required: false,
|
||||
})
|
||||
dryRun(value: string): boolean {
|
||||
return Boolean(value);
|
||||
}
|
||||
|
||||
async run(
|
||||
_passedParam: string[],
|
||||
options: RecordPositionBackfillCommandOptions,
|
||||
): Promise<void> {
|
||||
this.messageQueueService.add<RecordPositionBackfillJobData>(
|
||||
RecordPositionBackfillJob.name,
|
||||
{ workspaceId: options.workspaceId, dryRun: options.dryRun ?? false },
|
||||
{ retryLimit: 3 },
|
||||
);
|
||||
}
|
||||
}
|
@ -18,6 +18,23 @@ describe('QueryRunnerArgsFactory', () => {
|
||||
objectMetadataItemWithFieldMaps: {
|
||||
isCustom: true,
|
||||
nameSingular: 'testNumber',
|
||||
fields: [
|
||||
{
|
||||
type: FieldMetadataType.POSITION,
|
||||
isCustom: true,
|
||||
nameSingular: 'position',
|
||||
},
|
||||
{
|
||||
type: FieldMetadataType.NUMBER,
|
||||
isCustom: true,
|
||||
nameSingular: 'testNumber',
|
||||
},
|
||||
{
|
||||
type: FieldMetadataType.TEXT,
|
||||
isCustom: true,
|
||||
nameSingular: 'otherField',
|
||||
},
|
||||
],
|
||||
fieldsByName: {
|
||||
position: {
|
||||
type: FieldMetadataType.POSITION,
|
||||
|
@ -17,7 +17,6 @@ import {
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { hasPositionField } from 'src/engine/metadata-modules/object-metadata/utils/has-position-field.util';
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
||||
|
||||
import { RecordPositionFactory } from './record-position.factory';
|
||||
@ -39,9 +38,10 @@ export class QueryRunnerArgsFactory {
|
||||
const fieldMetadataMapByNameByName =
|
||||
options.objectMetadataItemWithFieldMaps.fieldsByName;
|
||||
|
||||
const shouldBackfillPosition = hasPositionField(
|
||||
options.objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
const shouldBackfillPosition =
|
||||
options.objectMetadataItemWithFieldMaps.fields.some(
|
||||
(field) => field.type === FieldMetadataType.POSITION,
|
||||
);
|
||||
|
||||
switch (resolverArgsType) {
|
||||
case ResolverArgsType.CreateOne:
|
||||
|
@ -2,12 +2,12 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'class-validator';
|
||||
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import {
|
||||
RecordPositionQueryArgs,
|
||||
RecordPositionQueryFactory,
|
||||
RecordPositionQueryType,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
|
||||
@Injectable()
|
||||
export class RecordPositionFactory {
|
||||
|
@ -1,24 +0,0 @@
|
||||
import { RecordPositionBackfillService } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-service';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
||||
|
||||
export type RecordPositionBackfillJobData = {
|
||||
workspaceId: string;
|
||||
dryRun: boolean;
|
||||
};
|
||||
|
||||
@Processor(MessageQueue.recordPositionBackfillQueue)
|
||||
export class RecordPositionBackfillJob {
|
||||
constructor(
|
||||
private readonly recordPositionBackfillService: RecordPositionBackfillService,
|
||||
) {}
|
||||
|
||||
@Process(RecordPositionBackfillJob.name)
|
||||
async handle(data: RecordPositionBackfillJobData): Promise<void> {
|
||||
await this.recordPositionBackfillService.backfill(
|
||||
data.workspaceId,
|
||||
data.dryRun,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { RecordPositionBackfillJob } from 'src/engine/api/graphql/workspace-query-runner/jobs/record-position-backfill.job';
|
||||
import { RecordPositionBackfillModule } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-module';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
WorkspaceDataSourceModule,
|
||||
DataSourceModule,
|
||||
RecordPositionBackfillModule,
|
||||
],
|
||||
providers: [RecordPositionBackfillJob],
|
||||
})
|
||||
export class WorkspaceQueryRunnerJobModule {}
|
@ -1,15 +1,17 @@
|
||||
import { TestingModule, Test } from '@nestjs/testing';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { RecordPositionQueryFactory } from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
|
||||
import { RecordPositionFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/record-position.factory';
|
||||
import { RecordPositionBackfillService } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-service';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
|
||||
describe('RecordPositionBackfillService', () => {
|
||||
let recordPositionQueryFactory;
|
||||
let recordPositionFactory;
|
||||
let objectMetadataService;
|
||||
let objectMetadataRepository;
|
||||
let workspaceDataSourceService;
|
||||
|
||||
let service: RecordPositionBackfillService;
|
||||
@ -27,8 +29,8 @@ describe('RecordPositionBackfillService', () => {
|
||||
]),
|
||||
};
|
||||
|
||||
objectMetadataService = {
|
||||
findManyWithinWorkspace: jest.fn().mockReturnValue([]),
|
||||
objectMetadataRepository = {
|
||||
find: jest.fn().mockReturnValue([]),
|
||||
};
|
||||
|
||||
workspaceDataSourceService = {
|
||||
@ -51,8 +53,8 @@ describe('RecordPositionBackfillService', () => {
|
||||
useValue: workspaceDataSourceService,
|
||||
},
|
||||
{
|
||||
provide: ObjectMetadataService,
|
||||
useValue: objectMetadataService,
|
||||
provide: getRepositoryToken(ObjectMetadataEntity, 'metadata'),
|
||||
useValue: objectMetadataRepository,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
@ -76,23 +78,23 @@ describe('RecordPositionBackfillService', () => {
|
||||
});
|
||||
|
||||
it('when objectMetadata without position, should do nothing', async () => {
|
||||
objectMetadataService.findManyWithinWorkspace.mockReturnValue([
|
||||
{
|
||||
id: '1',
|
||||
nameSingular: 'name',
|
||||
fields: [],
|
||||
},
|
||||
]);
|
||||
objectMetadataRepository.find.mockReturnValue([]);
|
||||
await service.backfill('workspaceId', false);
|
||||
expect(workspaceDataSourceService.executeRawQuery).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('when objectMetadata but all record with position, should create and run query once', async () => {
|
||||
objectMetadataService.findManyWithinWorkspace.mockReturnValue([
|
||||
objectMetadataRepository.find.mockReturnValue([
|
||||
{
|
||||
id: '1',
|
||||
nameSingular: 'company',
|
||||
fields: [],
|
||||
fields: [
|
||||
{
|
||||
type: FieldMetadataType.POSITION,
|
||||
isCustom: true,
|
||||
nameSingular: 'position',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
await service.backfill('workspaceId', false);
|
||||
@ -100,11 +102,17 @@ describe('RecordPositionBackfillService', () => {
|
||||
});
|
||||
|
||||
it('when record without position, should create and run query twice', async () => {
|
||||
objectMetadataService.findManyWithinWorkspace.mockReturnValue([
|
||||
objectMetadataRepository.find.mockReturnValue([
|
||||
{
|
||||
id: '1',
|
||||
nameSingular: 'company',
|
||||
fields: [],
|
||||
fields: [
|
||||
{
|
||||
type: FieldMetadataType.POSITION,
|
||||
isCustom: true,
|
||||
nameSingular: 'position',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
workspaceDataSourceService.executeRawQuery.mockResolvedValueOnce([
|
||||
@ -119,11 +127,17 @@ describe('RecordPositionBackfillService', () => {
|
||||
});
|
||||
|
||||
it('when dryRun is true, should not update position', async () => {
|
||||
objectMetadataService.findManyWithinWorkspace.mockReturnValue([
|
||||
objectMetadataRepository.find.mockReturnValue([
|
||||
{
|
||||
id: '1',
|
||||
nameSingular: 'company',
|
||||
fields: [],
|
||||
fields: [
|
||||
{
|
||||
type: FieldMetadataType.POSITION,
|
||||
isCustom: true,
|
||||
nameSingular: 'position',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
workspaceDataSourceService.executeRawQuery.mockResolvedValueOnce([
|
||||
|
@ -1,13 +1,17 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { RecordPositionQueryFactory } from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
|
||||
import { RecordPositionFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/record-position.factory';
|
||||
import { RecordPositionBackfillService } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-service';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
|
||||
@Module({
|
||||
imports: [WorkspaceDataSourceModule, ObjectMetadataModule],
|
||||
imports: [
|
||||
WorkspaceDataSourceModule,
|
||||
TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
|
||||
],
|
||||
providers: [
|
||||
RecordPositionFactory,
|
||||
RecordPositionQueryFactory,
|
||||
|
@ -1,21 +1,24 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'class-validator';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import {
|
||||
RecordPositionQueryFactory,
|
||||
RecordPositionQueryType,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
|
||||
import { RecordPositionFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/record-position.factory';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { hasPositionField } from 'src/engine/metadata-modules/object-metadata/utils/has-position-field.util';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
|
||||
@Injectable()
|
||||
export class RecordPositionBackfillService {
|
||||
private readonly logger = new Logger(RecordPositionBackfillService.name);
|
||||
constructor(
|
||||
private readonly objectMetadataService: ObjectMetadataService,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
private readonly recordPositionFactory: RecordPositionFactory,
|
||||
private readonly recordPositionQueryFactory: RecordPositionQueryFactory,
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
@ -29,15 +32,20 @@ export class RecordPositionBackfillService {
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
const objectMetadataEntities =
|
||||
await this.objectMetadataService.findManyWithinWorkspace(workspaceId, {
|
||||
where: { isSystem: false },
|
||||
});
|
||||
const objectMetadataCollection = await this.objectMetadataRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
fields: {
|
||||
name: 'position',
|
||||
type: FieldMetadataType.POSITION,
|
||||
},
|
||||
},
|
||||
relations: {
|
||||
fields: true,
|
||||
},
|
||||
});
|
||||
|
||||
const objectMetadataWithPosition =
|
||||
objectMetadataEntities.filter(hasPositionField);
|
||||
|
||||
for (const objectMetadata of objectMetadataWithPosition) {
|
||||
for (const objectMetadata of objectMetadataCollection) {
|
||||
const [recordsWithoutPositionQuery, recordsWithoutPositionQueryParams] =
|
||||
this.recordPositionQueryFactory.create(
|
||||
{
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { QueryFailedError } from 'typeorm';
|
||||
|
||||
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
@ -20,7 +20,7 @@ import {
|
||||
|
||||
export const workspaceQueryRunnerGraphqlApiExceptionHandler = (
|
||||
error: Error,
|
||||
context: WorkspaceSchemaBuilderContext,
|
||||
context: WorkspaceQueryRunnerOptions,
|
||||
) => {
|
||||
if (error instanceof QueryFailedError) {
|
||||
if (
|
||||
@ -96,6 +96,7 @@ export const workspaceQueryRunnerGraphqlApiExceptionHandler = (
|
||||
case GraphqlQueryRunnerExceptionCode.UNSUPPORTED_OPERATOR:
|
||||
case GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT:
|
||||
case GraphqlQueryRunnerExceptionCode.FIELD_NOT_FOUND:
|
||||
case GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT:
|
||||
throw new UserInputError(error.message);
|
||||
case GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND:
|
||||
throw new NotFoundError(error.message);
|
||||
|
@ -2,7 +2,6 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { WorkspaceQueryBuilderModule } from 'src/engine/api/graphql/workspace-query-builder/workspace-query-builder.module';
|
||||
import { RecordPositionBackfillCommand } from 'src/engine/api/graphql/workspace-query-runner/commands/0-20-record-position-backfill.command';
|
||||
import { workspaceQueryRunnerFactories } from 'src/engine/api/graphql/workspace-query-runner/factories';
|
||||
import { TelemetryListener } from 'src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener';
|
||||
import { WorkspaceQueryHookModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module';
|
||||
@ -35,7 +34,6 @@ import { EntityEventsToDbListener } from './listeners/entity-events-to-db.listen
|
||||
...workspaceQueryRunnerFactories,
|
||||
EntityEventsToDbListener,
|
||||
TelemetryListener,
|
||||
RecordPositionBackfillCommand,
|
||||
],
|
||||
exports: [...workspaceQueryRunnerFactories],
|
||||
})
|
||||
|
@ -9,7 +9,6 @@ import {
|
||||
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service';
|
||||
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
|
||||
|
||||
@Injectable()
|
||||
export class CreateManyResolverFactory
|
||||
@ -27,23 +26,19 @@ export class CreateManyResolverFactory
|
||||
const internalContext = context;
|
||||
|
||||
return async (_source, args, _context, info) => {
|
||||
try {
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
objectMetadataMaps: internalContext.objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps:
|
||||
internalContext.objectMetadataItemWithFieldMaps,
|
||||
};
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
objectMetadataMaps: internalContext.objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps:
|
||||
internalContext.objectMetadataItemWithFieldMaps,
|
||||
};
|
||||
|
||||
return await this.graphqlQueryRunnerService.execute(
|
||||
args,
|
||||
options,
|
||||
CreateManyResolverFactory.methodName,
|
||||
);
|
||||
} catch (error) {
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error, context);
|
||||
}
|
||||
return await this.graphqlQueryRunnerService.execute(
|
||||
args,
|
||||
options,
|
||||
CreateManyResolverFactory.methodName,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import {
|
||||
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { GraphqlQueryCreateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service';
|
||||
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
|
||||
|
||||
@Injectable()
|
||||
export class CreateOneResolverFactory
|
||||
@ -27,23 +26,19 @@ export class CreateOneResolverFactory
|
||||
const internalContext = context;
|
||||
|
||||
return async (_source, args, _context, info) => {
|
||||
try {
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
objectMetadataMaps: internalContext.objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps:
|
||||
internalContext.objectMetadataItemWithFieldMaps,
|
||||
};
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
objectMetadataMaps: internalContext.objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps:
|
||||
internalContext.objectMetadataItemWithFieldMaps,
|
||||
};
|
||||
|
||||
return await this.graphqlQueryRunnerService.execute(
|
||||
args,
|
||||
options,
|
||||
CreateOneResolverFactory.methodName,
|
||||
);
|
||||
} catch (error) {
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
|
||||
}
|
||||
return await this.graphqlQueryRunnerService.execute(
|
||||
args,
|
||||
options,
|
||||
CreateOneResolverFactory.methodName,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import {
|
||||
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { GraphqlQueryDeleteManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service';
|
||||
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteManyResolverFactory
|
||||
@ -27,23 +26,19 @@ export class DeleteManyResolverFactory
|
||||
const internalContext = context;
|
||||
|
||||
return async (_source, args, context, info) => {
|
||||
try {
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
objectMetadataMaps: internalContext.objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps:
|
||||
internalContext.objectMetadataItemWithFieldMaps,
|
||||
};
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
objectMetadataMaps: internalContext.objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps:
|
||||
internalContext.objectMetadataItemWithFieldMaps,
|
||||
};
|
||||
|
||||
return await this.graphqlQueryRunnerService.execute(
|
||||
args,
|
||||
options,
|
||||
DeleteManyResolverFactory.methodName,
|
||||
);
|
||||
} catch (error) {
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
|
||||
}
|
||||
return await this.graphqlQueryRunnerService.execute(
|
||||
args,
|
||||
options,
|
||||
DeleteManyResolverFactory.methodName,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import {
|
||||
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { GraphqlQueryDeleteOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service';
|
||||
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteOneResolverFactory
|
||||
@ -27,23 +26,19 @@ export class DeleteOneResolverFactory
|
||||
const internalContext = context;
|
||||
|
||||
return async (_source, args, context, info) => {
|
||||
try {
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
objectMetadataMaps: internalContext.objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps:
|
||||
internalContext.objectMetadataItemWithFieldMaps,
|
||||
};
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
objectMetadataMaps: internalContext.objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps:
|
||||
internalContext.objectMetadataItemWithFieldMaps,
|
||||
};
|
||||
|
||||
return await this.graphqlQueryRunnerService.execute(
|
||||
args,
|
||||
options,
|
||||
DeleteOneResolverFactory.methodName,
|
||||
);
|
||||
} catch (error) {
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
|
||||
}
|
||||
return await this.graphqlQueryRunnerService.execute(
|
||||
args,
|
||||
options,
|
||||
DeleteOneResolverFactory.methodName,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import {
|
||||
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { GraphqlQueryDestroyManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service';
|
||||
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
|
||||
|
||||
@Injectable()
|
||||
export class DestroyManyResolverFactory
|
||||
@ -27,23 +26,19 @@ export class DestroyManyResolverFactory
|
||||
const internalContext = context;
|
||||
|
||||
return async (_source, args, context, info) => {
|
||||
try {
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
objectMetadataMaps: internalContext.objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps:
|
||||
internalContext.objectMetadataItemWithFieldMaps,
|
||||
};
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
objectMetadataMaps: internalContext.objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps:
|
||||
internalContext.objectMetadataItemWithFieldMaps,
|
||||
};
|
||||
|
||||
return await this.graphqlQueryRunnerService.execute(
|
||||
args,
|
||||
options,
|
||||
DestroyManyResolverFactory.methodName,
|
||||
);
|
||||
} catch (error) {
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
|
||||
}
|
||||
return await this.graphqlQueryRunnerService.execute(
|
||||
args,
|
||||
options,
|
||||
DestroyManyResolverFactory.methodName,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import {
|
||||
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service';
|
||||
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
|
||||
|
||||
@Injectable()
|
||||
export class DestroyOneResolverFactory
|
||||
@ -27,23 +26,19 @@ export class DestroyOneResolverFactory
|
||||
const internalContext = context;
|
||||
|
||||
return async (_source, args, context, info) => {
|
||||
try {
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
objectMetadataMaps: internalContext.objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps:
|
||||
internalContext.objectMetadataItemWithFieldMaps,
|
||||
};
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
objectMetadataMaps: internalContext.objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps:
|
||||
internalContext.objectMetadataItemWithFieldMaps,
|
||||
};
|
||||
|
||||
return await this.graphQLQueryRunnerService.execute(
|
||||
args,
|
||||
options,
|
||||
DestroyOneResolverFactory.methodName,
|
||||
);
|
||||
} catch (error) {
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
|
||||
}
|
||||
return await this.graphQLQueryRunnerService.execute(
|
||||
args,
|
||||
options,
|
||||
DestroyOneResolverFactory.methodName,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import {
|
||||
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service';
|
||||
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
|
||||
|
||||
@Injectable()
|
||||
export class FindDuplicatesResolverFactory
|
||||
@ -27,23 +26,19 @@ export class FindDuplicatesResolverFactory
|
||||
const internalContext = context;
|
||||
|
||||
return async (_source, args, context, info) => {
|
||||
try {
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
objectMetadataMaps: internalContext.objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps:
|
||||
internalContext.objectMetadataItemWithFieldMaps,
|
||||
};
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
objectMetadataMaps: internalContext.objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps:
|
||||
internalContext.objectMetadataItemWithFieldMaps,
|
||||
};
|
||||
|
||||
return await this.graphqlQueryRunnerService.execute(
|
||||
args,
|
||||
options,
|
||||
FindDuplicatesResolverFactory.methodName,
|
||||
);
|
||||
} catch (error) {
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
|
||||
}
|
||||
return await this.graphqlQueryRunnerService.execute(
|
||||
args,
|
||||
options,
|
||||
FindDuplicatesResolverFactory.methodName,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import {
|
||||
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service';
|
||||
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
|
||||
|
||||
@Injectable()
|
||||
export class FindManyResolverFactory
|
||||
@ -27,23 +26,19 @@ export class FindManyResolverFactory
|
||||
const internalContext = context;
|
||||
|
||||
return async (_source, args, _context, info) => {
|
||||
try {
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
objectMetadataMaps: internalContext.objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps:
|
||||
internalContext.objectMetadataItemWithFieldMaps,
|
||||
};
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
objectMetadataMaps: internalContext.objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps:
|
||||
internalContext.objectMetadataItemWithFieldMaps,
|
||||
};
|
||||
|
||||
return await this.graphqlQueryRunnerService.execute(
|
||||
args,
|
||||
options,
|
||||
FindManyResolverFactory.methodName,
|
||||
);
|
||||
} catch (error) {
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
|
||||
}
|
||||
return await this.graphqlQueryRunnerService.execute(
|
||||
args,
|
||||
options,
|
||||
FindManyResolverFactory.methodName,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user