[COMMAND MENU ITEMS] Remove deprecated code (#19199)
This PR is the first one of a cleanup after upgrading command menu items to V2.
This commit is contained in:
@@ -1720,12 +1720,12 @@ enum FeatureFlagKey {
|
||||
IS_UNIQUE_INDEXES_ENABLED
|
||||
IS_JSON_FILTER_ENABLED
|
||||
IS_AI_ENABLED
|
||||
IS_COMMAND_MENU_ITEM_ENABLED
|
||||
IS_MARKETPLACE_ENABLED
|
||||
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED
|
||||
IS_PUBLIC_DOMAIN_ENABLED
|
||||
IS_EMAILING_DOMAIN_ENABLED
|
||||
IS_JUNCTION_RELATIONS_ENABLED
|
||||
IS_COMMAND_MENU_ITEM_ENABLED
|
||||
IS_DRAFT_EMAIL_ENABLED
|
||||
IS_USAGE_ANALYTICS_ENABLED
|
||||
IS_RICH_TEXT_V1_MIGRATED
|
||||
|
||||
@@ -1422,7 +1422,7 @@ export interface PublicFeatureFlag {
|
||||
__typename: 'PublicFeatureFlag'
|
||||
}
|
||||
|
||||
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_DIRECT_GRAPHQL_EXECUTION_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_GRAPHQL_QUERY_TIMING_ENABLED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED'
|
||||
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_DIRECT_GRAPHQL_EXECUTION_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_GRAPHQL_QUERY_TIMING_ENABLED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED'
|
||||
|
||||
export interface ClientConfig {
|
||||
appVersion?: Scalars['String']
|
||||
@@ -8949,12 +8949,12 @@ export const enumFeatureFlagKey = {
|
||||
IS_UNIQUE_INDEXES_ENABLED: 'IS_UNIQUE_INDEXES_ENABLED' as const,
|
||||
IS_JSON_FILTER_ENABLED: 'IS_JSON_FILTER_ENABLED' as const,
|
||||
IS_AI_ENABLED: 'IS_AI_ENABLED' as const,
|
||||
IS_COMMAND_MENU_ITEM_ENABLED: 'IS_COMMAND_MENU_ITEM_ENABLED' as const,
|
||||
IS_MARKETPLACE_ENABLED: 'IS_MARKETPLACE_ENABLED' as const,
|
||||
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' as const,
|
||||
IS_PUBLIC_DOMAIN_ENABLED: 'IS_PUBLIC_DOMAIN_ENABLED' as const,
|
||||
IS_EMAILING_DOMAIN_ENABLED: 'IS_EMAILING_DOMAIN_ENABLED' as const,
|
||||
IS_JUNCTION_RELATIONS_ENABLED: 'IS_JUNCTION_RELATIONS_ENABLED' as const,
|
||||
IS_COMMAND_MENU_ITEM_ENABLED: 'IS_COMMAND_MENU_ITEM_ENABLED' as const,
|
||||
IS_DRAFT_EMAIL_ENABLED: 'IS_DRAFT_EMAIL_ENABLED' as const,
|
||||
IS_USAGE_ANALYTICS_ENABLED: 'IS_USAGE_ANALYTICS_ENABLED' as const,
|
||||
IS_RICH_TEXT_V1_MIGRATED: 'IS_RICH_TEXT_V1_MIGRATED' as const,
|
||||
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
import { CommandMenuItemComponent } from '@/command-menu-item/display/components/CommandMenuItemComponent';
|
||||
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { styled } from '@linaria/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useContext } from 'react';
|
||||
import { ThemeContext } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledActionContainer = styled(motion.div)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
export const PageHeaderCommandMenuButtons = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const { commandMenuItems } = useContext(CommandMenuContext);
|
||||
const pinnedActions = commandMenuItems
|
||||
.filter((entry) => entry.isPinned)
|
||||
.toReversed();
|
||||
|
||||
const actionsWithPositionForAnimation = pinnedActions.map(
|
||||
(action, index) => ({
|
||||
action,
|
||||
position: pinnedActions.length - index - 1,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{actionsWithPositionForAnimation.map(({ action, position }) => (
|
||||
<StyledActionContainer
|
||||
key={position}
|
||||
layout
|
||||
initial={{ width: 0, opacity: 0 }}
|
||||
animate={{ width: 'unset', opacity: 1 }}
|
||||
exit={{ width: 0, opacity: 0 }}
|
||||
transition={{
|
||||
duration: theme.animation.duration.instant,
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
>
|
||||
<CommandMenuItemComponent action={action} />
|
||||
</StyledActionContainer>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+2
-14
@@ -1,4 +1,3 @@
|
||||
import { PageHeaderCommandMenuButtons } from '@/command-menu-item/components/PageHeaderCommandMenuButtons';
|
||||
import { PinnedCommandMenuItemButtons } from '@/command-menu-item/server-items/display/components/PinnedCommandMenuItemButtons';
|
||||
import { RecordIndexCommandMenuDropdown } from '@/command-menu-item/components/RecordIndexCommandMenuDropdown';
|
||||
import { CommandMenuContextProvider } from '@/command-menu-item/contexts/CommandMenuContextProvider';
|
||||
@@ -9,8 +8,6 @@ import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context
|
||||
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
|
||||
export const RecordIndexCommandMenu = () => {
|
||||
@@ -20,15 +17,11 @@ export const RecordIndexCommandMenu = () => {
|
||||
);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
const isCommandMenuItemEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
);
|
||||
const isLayoutCustomizationModeEnabled = useAtomStateValue(
|
||||
isLayoutCustomizationModeEnabledState,
|
||||
);
|
||||
|
||||
const showEditModePinnedButtons =
|
||||
isCommandMenuItemEnabled && isLayoutCustomizationModeEnabled;
|
||||
const showEditModePinnedButtons = isLayoutCustomizationModeEnabled;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -42,12 +35,7 @@ export const RecordIndexCommandMenu = () => {
|
||||
displayType="button"
|
||||
containerType="index-page-header"
|
||||
>
|
||||
{!isMobile &&
|
||||
(isCommandMenuItemEnabled ? (
|
||||
<PinnedCommandMenuItemButtons />
|
||||
) : (
|
||||
<PageHeaderCommandMenuButtons />
|
||||
))}
|
||||
{!isMobile && <PinnedCommandMenuItemButtons />}
|
||||
</CommandMenuContextProvider>
|
||||
)}
|
||||
<CommandMenuContextProvider
|
||||
|
||||
+2
-13
@@ -1,13 +1,10 @@
|
||||
import { PageHeaderCommandMenuButtons } from '@/command-menu-item/components/PageHeaderCommandMenuButtons';
|
||||
import { PinnedCommandMenuItemButtons } from '@/command-menu-item/server-items/display/components/PinnedCommandMenuItemButtons';
|
||||
import { CommandMenuContextProvider } from '@/command-menu-item/contexts/CommandMenuContextProvider';
|
||||
import { PinnedCommandMenuItemButtons } from '@/command-menu-item/server-items/display/components/PinnedCommandMenuItemButtons';
|
||||
import { CommandMenuItemEditButton } from '@/command-menu-item/server-items/edit/components/CommandMenuItemEditButton';
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
|
||||
export const RecordShowCommandMenu = () => {
|
||||
@@ -26,9 +23,6 @@ export const RecordShowCommandMenu = () => {
|
||||
contextStoreTargetedRecordsRule.selectedRecordIds.length === 1;
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
const isCommandMenuItemEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -39,12 +33,7 @@ export const RecordShowCommandMenu = () => {
|
||||
displayType="button"
|
||||
containerType="show-page-header"
|
||||
>
|
||||
{!isMobile &&
|
||||
(isCommandMenuItemEnabled ? (
|
||||
<PinnedCommandMenuItemButtons />
|
||||
) : (
|
||||
<PageHeaderCommandMenuButtons />
|
||||
))}
|
||||
{!isMobile && <PinnedCommandMenuItemButtons />}
|
||||
</CommandMenuContextProvider>
|
||||
<CommandMenuItemEditButton />
|
||||
</>
|
||||
|
||||
+2
-25
@@ -1,44 +1,21 @@
|
||||
import { type CommandMenuContextType } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { CommandMenuContextProviderLegacy } from '@/command-menu-item/contexts/CommandMenuContextProviderLegacy';
|
||||
import { CommandMenuContextProviderServerItems } from '@/command-menu-item/server-items/common/contexts/CommandMenuContextProviderServerItems';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
export const CommandMenuContextProvider = ({
|
||||
children,
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
objectMetadataItemOverride,
|
||||
}: Omit<CommandMenuContextType, 'commandMenuItems'> & {
|
||||
children: React.ReactNode;
|
||||
objectMetadataItemOverride?: EnrichedObjectMetadataItem;
|
||||
}) => {
|
||||
const isCommandMenuItemEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
);
|
||||
|
||||
if (isCommandMenuItemEnabled) {
|
||||
return (
|
||||
<CommandMenuContextProviderServerItems
|
||||
isInSidePanel={isInSidePanel}
|
||||
displayType={displayType}
|
||||
containerType={containerType}
|
||||
>
|
||||
{children}
|
||||
</CommandMenuContextProviderServerItems>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CommandMenuContextProviderLegacy
|
||||
<CommandMenuContextProviderServerItems
|
||||
isInSidePanel={isInSidePanel}
|
||||
displayType={displayType}
|
||||
containerType={containerType}
|
||||
objectMetadataItemOverride={objectMetadataItemOverride}
|
||||
>
|
||||
{children}
|
||||
</CommandMenuContextProviderLegacy>
|
||||
</CommandMenuContextProviderServerItems>
|
||||
);
|
||||
};
|
||||
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
import {
|
||||
CommandMenuContext,
|
||||
type CommandMenuContextType,
|
||||
} from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { useRegisteredCommandMenuItems } from '@/command-menu-item/hooks/useRegisteredCommandMenuItems';
|
||||
import { useShouldCommandMenuItemBeRegisteredParams } from '@/command-menu-item/hooks/useShouldCommandMenuItemBeRegisteredParams';
|
||||
import { useRunWorkflowRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands';
|
||||
import { useRunWorkflowRecordCommands } from '@/command-menu-item/record/workflow/hooks/useRunWorkflowRecordCommands';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
|
||||
export const CommandMenuContextProviderDefault = ({
|
||||
objectMetadataItem,
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
children,
|
||||
}: {
|
||||
objectMetadataItem: EnrichedObjectMetadataItem;
|
||||
isInSidePanel: CommandMenuContextType['isInSidePanel'];
|
||||
displayType: CommandMenuContextType['displayType'];
|
||||
containerType: CommandMenuContextType['containerType'];
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const shouldBeRegisteredParams = useShouldCommandMenuItemBeRegisteredParams({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const commandMenuItems = useRegisteredCommandMenuItems(
|
||||
shouldBeRegisteredParams,
|
||||
);
|
||||
|
||||
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
const isRecordSelection =
|
||||
contextStoreTargetedRecordsRule.mode === 'selection' &&
|
||||
contextStoreTargetedRecordsRule.selectedRecordIds.length > 0;
|
||||
|
||||
const runWorkflowRecordCommands = useRunWorkflowRecordCommands({
|
||||
objectMetadataItem,
|
||||
skip: !isRecordSelection,
|
||||
});
|
||||
|
||||
const runWorkflowRecordAgnosticCommands =
|
||||
useRunWorkflowRecordAgnosticCommands();
|
||||
|
||||
return (
|
||||
<CommandMenuContext.Provider
|
||||
value={{
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
commandMenuItems: [
|
||||
...commandMenuItems,
|
||||
...runWorkflowRecordCommands,
|
||||
...runWorkflowRecordAgnosticCommands,
|
||||
],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</CommandMenuContext.Provider>
|
||||
);
|
||||
};
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
import { type CommandMenuContextType } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { CommandMenuContextProviderDefault } from '@/command-menu-item/contexts/CommandMenuContextProviderDefault';
|
||||
import { CommandMenuContextProviderWorkflowObjects } from '@/command-menu-item/contexts/CommandMenuContextProviderWorkflowObjects';
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const CommandMenuContextProviderLegacy = ({
|
||||
children,
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
objectMetadataItemOverride,
|
||||
}: Omit<CommandMenuContextType, 'commandMenuItems'> & {
|
||||
children: React.ReactNode;
|
||||
objectMetadataItemOverride?: EnrichedObjectMetadataItem;
|
||||
}) => {
|
||||
const contextStoreCurrentObjectMetadataItemId = useAtomComponentStateValue(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||
);
|
||||
|
||||
const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector);
|
||||
|
||||
const objectMetadataItem =
|
||||
objectMetadataItemOverride ??
|
||||
objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.id === contextStoreCurrentObjectMetadataItemId,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isWorkflowObject =
|
||||
objectMetadataItem.nameSingular === CoreObjectNameSingular.Workflow;
|
||||
|
||||
if (isWorkflowObject) {
|
||||
return (
|
||||
<CommandMenuContextProviderWorkflowObjects
|
||||
isInSidePanel={isInSidePanel}
|
||||
displayType={displayType}
|
||||
containerType={containerType}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
>
|
||||
{children}
|
||||
</CommandMenuContextProviderWorkflowObjects>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CommandMenuContextProviderDefault
|
||||
isInSidePanel={isInSidePanel}
|
||||
displayType={displayType}
|
||||
containerType={containerType}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
>
|
||||
{children}
|
||||
</CommandMenuContextProviderDefault>
|
||||
);
|
||||
};
|
||||
-159
@@ -1,159 +0,0 @@
|
||||
import {
|
||||
CommandMenuContext,
|
||||
type CommandMenuContextType,
|
||||
} from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { useRegisteredCommandMenuItems } from '@/command-menu-item/hooks/useRegisteredCommandMenuItems';
|
||||
import { useShouldCommandMenuItemBeRegisteredParams } from '@/command-menu-item/hooks/useShouldCommandMenuItemBeRegisteredParams';
|
||||
import { useRunWorkflowRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { type WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type CommandMenuContextProviderWorkflowObjectsProps = {
|
||||
objectMetadataItem: EnrichedObjectMetadataItem;
|
||||
isInSidePanel: CommandMenuContextType['isInSidePanel'];
|
||||
displayType: CommandMenuContextType['displayType'];
|
||||
containerType: CommandMenuContextType['containerType'];
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const CommandMenuContextProviderWorkflowObjectsContent = ({
|
||||
objectMetadataItem,
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
children,
|
||||
selectedRecordId,
|
||||
}: CommandMenuContextProviderWorkflowObjectsProps & {
|
||||
selectedRecordId: string;
|
||||
}) => {
|
||||
const params = useShouldCommandMenuItemBeRegisteredParams({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const workflowWithCurrentVersion =
|
||||
useWorkflowWithCurrentVersion(selectedRecordId);
|
||||
|
||||
const shouldBeRegisteredParams = {
|
||||
...params,
|
||||
workflowWithCurrentVersion,
|
||||
};
|
||||
|
||||
const commandMenuItems = useRegisteredCommandMenuItems(
|
||||
shouldBeRegisteredParams,
|
||||
);
|
||||
|
||||
const runWorkflowRecordAgnosticCommands =
|
||||
useRunWorkflowRecordAgnosticCommands();
|
||||
|
||||
return (
|
||||
<CommandMenuContext.Provider
|
||||
value={{
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
commandMenuItems: [
|
||||
...commandMenuItems,
|
||||
...runWorkflowRecordAgnosticCommands,
|
||||
],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</CommandMenuContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const CommandMenuContextProviderWorkflowObjectsWithoutWorkflow = ({
|
||||
objectMetadataItem,
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
children,
|
||||
}: CommandMenuContextProviderWorkflowObjectsProps & {
|
||||
workflowWithCurrentVersion: WorkflowWithCurrentVersion | undefined;
|
||||
}) => {
|
||||
const params = useShouldCommandMenuItemBeRegisteredParams({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const shouldBeRegisteredParams = {
|
||||
...params,
|
||||
workflowWithCurrentVersion: undefined,
|
||||
};
|
||||
|
||||
const commandMenuItems = useRegisteredCommandMenuItems(
|
||||
shouldBeRegisteredParams,
|
||||
);
|
||||
|
||||
const runWorkflowRecordAgnosticCommands =
|
||||
useRunWorkflowRecordAgnosticCommands();
|
||||
|
||||
return (
|
||||
<CommandMenuContext.Provider
|
||||
value={{
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
commandMenuItems: [
|
||||
...commandMenuItems,
|
||||
...runWorkflowRecordAgnosticCommands,
|
||||
],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</CommandMenuContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const CommandMenuContextProviderWorkflowObjects = ({
|
||||
objectMetadataItem,
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
children,
|
||||
}: CommandMenuContextProviderWorkflowObjectsProps) => {
|
||||
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
const recordId =
|
||||
contextStoreTargetedRecordsRule.mode === 'selection' &&
|
||||
contextStoreTargetedRecordsRule.selectedRecordIds.length === 1
|
||||
? contextStoreTargetedRecordsRule.selectedRecordIds[0]
|
||||
: undefined;
|
||||
|
||||
const selectedRecord =
|
||||
useAtomFamilyStateValue(recordStoreFamilyState, recordId ?? '') ||
|
||||
undefined;
|
||||
|
||||
if (isDefined(selectedRecord?.id)) {
|
||||
return (
|
||||
<CommandMenuContextProviderWorkflowObjectsContent
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
isInSidePanel={isInSidePanel}
|
||||
displayType={displayType}
|
||||
containerType={containerType}
|
||||
selectedRecordId={selectedRecord.id}
|
||||
>
|
||||
{children}
|
||||
</CommandMenuContextProviderWorkflowObjectsContent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CommandMenuContextProviderWorkflowObjectsWithoutWorkflow
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
isInSidePanel={isInSidePanel}
|
||||
displayType={displayType}
|
||||
containerType={containerType}
|
||||
workflowWithCurrentVersion={undefined}
|
||||
>
|
||||
{children}
|
||||
</CommandMenuContextProviderWorkflowObjectsWithoutWorkflow>
|
||||
);
|
||||
};
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
|
||||
import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
|
||||
import { useNavigateSidePanel } from '@/side-panel/hooks/useNavigateSidePanel';
|
||||
import { sidePanelSearchState } from '@/side-panel/states/sidePanelSearchState';
|
||||
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
||||
import { type MessageDescriptor } from '@lingui/core';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useContext } from 'react';
|
||||
import { type SidePanelPages } from 'twenty-shared/types';
|
||||
import { type IconComponent } from 'twenty-ui/display';
|
||||
|
||||
export const CommandMenuItemOpenSidePanelPage = ({
|
||||
page,
|
||||
pageTitle,
|
||||
pageIcon,
|
||||
onClick,
|
||||
shouldResetSearchState = false,
|
||||
}: {
|
||||
page: SidePanelPages;
|
||||
pageTitle: MessageDescriptor;
|
||||
pageIcon: IconComponent;
|
||||
onClick?: () => void;
|
||||
shouldResetSearchState?: boolean;
|
||||
}) => {
|
||||
const actionConfig = useContext(CommandConfigContext);
|
||||
|
||||
const { navigateSidePanel } = useNavigateSidePanel();
|
||||
|
||||
const setSidePanelSearch = useSetAtomState(sidePanelSearchState);
|
||||
|
||||
if (!actionConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
onClick?.();
|
||||
|
||||
navigateSidePanel({
|
||||
page,
|
||||
pageTitle: t(pageTitle),
|
||||
pageIcon,
|
||||
});
|
||||
|
||||
if (shouldResetSearchState) {
|
||||
setSidePanelSearch('');
|
||||
}
|
||||
};
|
||||
|
||||
return <CommandMenuItemDisplay onClick={handleClick} />;
|
||||
};
|
||||
+7
-7
@@ -1,12 +1,12 @@
|
||||
import { CommandMenuItemButton } from '@/command-menu-item/display/components/CommandMenuItemButton';
|
||||
import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
|
||||
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
|
||||
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
|
||||
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
|
||||
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
||||
import { expect, fn, userEvent, within } from 'storybook/test';
|
||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
|
||||
|
||||
import { CommandMenuItemButton } from '@/command-menu-item/display/components/CommandMenuItemButton';
|
||||
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
|
||||
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
|
||||
import { EngineComponentKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const meta: Meta<typeof CommandMenuItemButton> = {
|
||||
title: 'Modules/CommandMenuItem/Display/CommandMenuItemButton',
|
||||
component: CommandMenuItemButton,
|
||||
@@ -26,11 +26,11 @@ const mockActions = createMockCommandMenuItems({
|
||||
});
|
||||
|
||||
const addToFavoritesCommandMenuItem = mockActions.find(
|
||||
(action) => action.key === SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
(action) => action.key === EngineComponentKey.ADD_TO_FAVORITES,
|
||||
);
|
||||
|
||||
const goToPeopleCommandMenuItem = mockActions.find(
|
||||
(action) => action.key === NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
|
||||
(action) => action.key === EngineComponentKey.GO_TO_PEOPLE,
|
||||
);
|
||||
|
||||
export const Default: Story = {
|
||||
|
||||
+8
-8
@@ -1,18 +1,18 @@
|
||||
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
||||
import { expect, within } from 'storybook/test';
|
||||
|
||||
import { CommandMenuItemComponent } from '@/command-menu-item/display/components/CommandMenuItemComponent';
|
||||
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
|
||||
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
|
||||
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
|
||||
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
|
||||
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { CommandMenuItemComponent } from '@/command-menu-item/display/components/CommandMenuItemComponent';
|
||||
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
|
||||
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
|
||||
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
|
||||
import { EngineComponentKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const mockActions = createMockCommandMenuItems({});
|
||||
|
||||
const addToFavoritesCommandMenuItem = mockActions.find(
|
||||
(action) => action.key === SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
(action) => action.key === EngineComponentKey.ADD_TO_FAVORITES,
|
||||
);
|
||||
|
||||
if (!addToFavoritesCommandMenuItem) {
|
||||
|
||||
+9
-8
@@ -1,14 +1,15 @@
|
||||
import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
|
||||
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
|
||||
import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
|
||||
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
|
||||
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
|
||||
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
|
||||
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
||||
import { expect, fn, userEvent, within } from 'storybook/test';
|
||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
|
||||
|
||||
import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
|
||||
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
|
||||
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
|
||||
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
|
||||
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
|
||||
import { EngineComponentKey } from '~/generated-metadata/graphql';
|
||||
|
||||
type Story = StoryObj<typeof CommandMenuItemDisplay>;
|
||||
|
||||
const deleteMock = fn();
|
||||
@@ -20,7 +21,7 @@ const mockActions = createMockCommandMenuItems({
|
||||
});
|
||||
|
||||
const addToFavoritesCommandMenuItem = mockActions.find(
|
||||
(action) => action.key === SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
(action) => action.key === EngineComponentKey.ADD_TO_FAVORITES,
|
||||
);
|
||||
|
||||
if (!addToFavoritesCommandMenuItem) {
|
||||
|
||||
+8
-8
@@ -1,13 +1,13 @@
|
||||
import { CommandDropdownItem } from '@/command-menu-item/display/components/CommandDropdownItem';
|
||||
import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
|
||||
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
|
||||
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
|
||||
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
|
||||
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
|
||||
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
||||
import { expect, fn, userEvent, within } from 'storybook/test';
|
||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
|
||||
|
||||
import { CommandDropdownItem } from '@/command-menu-item/display/components/CommandDropdownItem';
|
||||
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
|
||||
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
|
||||
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
|
||||
import { EngineComponentKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const meta: Meta<typeof CommandDropdownItem> = {
|
||||
title: 'Modules/CommandMenuItem/Display/CommandDropdownItem',
|
||||
component: CommandDropdownItem,
|
||||
@@ -37,11 +37,11 @@ const mockActions = createMockCommandMenuItems({
|
||||
});
|
||||
|
||||
const addToFavoritesCommandMenuItem = mockActions.find(
|
||||
(action) => action.key === SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
(action) => action.key === EngineComponentKey.ADD_TO_FAVORITES,
|
||||
);
|
||||
|
||||
const goToPeopleCommandMenuItem = mockActions.find(
|
||||
(action) => action.key === NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
|
||||
(action) => action.key === EngineComponentKey.GO_TO_PEOPLE,
|
||||
);
|
||||
|
||||
export const Default: Story = {
|
||||
|
||||
+8
-8
@@ -1,13 +1,13 @@
|
||||
import { CommandListItem } from '@/command-menu-item/display/components/CommandListItem';
|
||||
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
|
||||
import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
|
||||
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
|
||||
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
|
||||
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
|
||||
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
||||
import { expect, fn, userEvent, within } from 'storybook/test';
|
||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
|
||||
|
||||
import { CommandListItem } from '@/command-menu-item/display/components/CommandListItem';
|
||||
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
|
||||
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
|
||||
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
|
||||
import { EngineComponentKey } from '~/generated-metadata/graphql';
|
||||
|
||||
type Story = StoryObj<typeof CommandListItem>;
|
||||
|
||||
const deleteMock = fn();
|
||||
@@ -19,11 +19,11 @@ const mockActions = createMockCommandMenuItems({
|
||||
});
|
||||
|
||||
const addToFavoritesCommandMenuItem = mockActions.find(
|
||||
(action) => action.key === SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
(action) => action.key === EngineComponentKey.ADD_TO_FAVORITES,
|
||||
);
|
||||
|
||||
const goToPeopleCommandMenuItem = mockActions.find(
|
||||
(action) => action.key === NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
|
||||
(action) => action.key === EngineComponentKey.GO_TO_PEOPLE,
|
||||
);
|
||||
|
||||
const meta: Meta<typeof CommandListItem> = {
|
||||
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
import { type HeadlessEngineCommandContextApi } from '@/command-menu-item/engine-command/types/HeadlessCommandContextApi';
|
||||
import { useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation } from '@/command-menu-item/engine-command/hooks/useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { createStore, Provider as JotaiProvider } from 'jotai';
|
||||
import { type ReactNode } from 'react';
|
||||
import {
|
||||
CommandMenuItemAvailabilityType,
|
||||
EngineComponentKey,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
const mockFindOneWorkflowVersion = jest.fn();
|
||||
const mockEnqueueWarningSnackBar = jest.fn();
|
||||
const mockBuildTriggerWorkflowVersionPayloads = jest.fn();
|
||||
|
||||
jest.mock('@/object-record/hooks/useLazyFindOneRecord', () => ({
|
||||
useLazyFindOneRecord: () => ({
|
||||
findOneRecord: mockFindOneWorkflowVersion,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar', () => ({
|
||||
useSnackBar: () => ({
|
||||
enqueueWarningSnackBar: mockEnqueueWarningSnackBar,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
'@/command-menu-item/engine-command/utils/buildTriggerWorkflowVersionPayloads',
|
||||
() => ({
|
||||
buildTriggerWorkflowVersionPayloads: (...args: unknown[]) =>
|
||||
mockBuildTriggerWorkflowVersionPayloads(...args),
|
||||
}),
|
||||
);
|
||||
|
||||
const getWrapper =
|
||||
(store = createStore()) =>
|
||||
({ children }: { children: ReactNode }) => (
|
||||
<JotaiProvider store={store}>{children}</JotaiProvider>
|
||||
);
|
||||
|
||||
const buildBaseContextApi = (
|
||||
overrides: Partial<HeadlessEngineCommandContextApi> = {},
|
||||
): HeadlessEngineCommandContextApi => ({
|
||||
engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
|
||||
contextStoreInstanceId: 'ctx-1',
|
||||
objectMetadataItem: null,
|
||||
currentViewId: null,
|
||||
recordIndexId: null,
|
||||
targetedRecordsRule: { mode: 'selection', selectedRecordIds: [] },
|
||||
selectedRecords: [],
|
||||
graphqlFilter: null,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return enriched context API with workflow info and payloads', async () => {
|
||||
const store = createStore();
|
||||
const wrapper = getWrapper(store);
|
||||
|
||||
const workflowVersionRecord = {
|
||||
id: 'wf-version-1',
|
||||
workflowId: 'workflow-1',
|
||||
trigger: { type: 'MANUAL' },
|
||||
__typename: 'WorkflowVersion' as const,
|
||||
};
|
||||
|
||||
mockFindOneWorkflowVersion.mockImplementation(
|
||||
async ({ onCompleted }: { onCompleted: (data: unknown) => void }) => {
|
||||
onCompleted(workflowVersionRecord);
|
||||
},
|
||||
);
|
||||
|
||||
const expectedPayloads = [{ recordId: 'rec-1' }];
|
||||
mockBuildTriggerWorkflowVersionPayloads.mockReturnValue(expectedPayloads);
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
const headlessEngineCommandContextApi = buildBaseContextApi();
|
||||
|
||||
let enrichedResult: unknown;
|
||||
|
||||
await act(async () => {
|
||||
enrichedResult =
|
||||
await result.current.enrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(
|
||||
{
|
||||
headlessEngineCommandContextApi,
|
||||
workflowVersionId: 'wf-version-1',
|
||||
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
expect(enrichedResult).toEqual({
|
||||
...headlessEngineCommandContextApi,
|
||||
workflowId: 'workflow-1',
|
||||
workflowVersionId: 'wf-version-1',
|
||||
payloads: expectedPayloads,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined when workflow version is not found', async () => {
|
||||
const store = createStore();
|
||||
const wrapper = getWrapper(store);
|
||||
|
||||
mockFindOneWorkflowVersion.mockImplementation(async () => {});
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
let enrichedResult: unknown;
|
||||
|
||||
await act(async () => {
|
||||
enrichedResult =
|
||||
await result.current.enrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(
|
||||
{
|
||||
headlessEngineCommandContextApi: buildBaseContextApi(),
|
||||
workflowVersionId: 'nonexistent',
|
||||
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
expect(enrichedResult).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for RECORD_SELECTION type when payloads are empty', async () => {
|
||||
const store = createStore();
|
||||
const wrapper = getWrapper(store);
|
||||
|
||||
const workflowVersionRecord = {
|
||||
id: 'wf-version-1',
|
||||
workflowId: 'workflow-1',
|
||||
trigger: { type: 'MANUAL' },
|
||||
__typename: 'WorkflowVersion' as const,
|
||||
};
|
||||
|
||||
mockFindOneWorkflowVersion.mockImplementation(
|
||||
async ({ onCompleted }: { onCompleted: (data: unknown) => void }) => {
|
||||
onCompleted(workflowVersionRecord);
|
||||
},
|
||||
);
|
||||
|
||||
mockBuildTriggerWorkflowVersionPayloads.mockReturnValue([]);
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
let enrichedResult: unknown;
|
||||
|
||||
await act(async () => {
|
||||
enrichedResult =
|
||||
await result.current.enrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(
|
||||
{
|
||||
headlessEngineCommandContextApi: buildBaseContextApi(),
|
||||
workflowVersionId: 'wf-version-1',
|
||||
availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
expect(enrichedResult).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should show warning snackbar when selected records exceed QUERY_MAX_RECORDS', async () => {
|
||||
const store = createStore();
|
||||
const wrapper = getWrapper(store);
|
||||
|
||||
const workflowVersionRecord = {
|
||||
id: 'wf-version-1',
|
||||
workflowId: 'workflow-1',
|
||||
trigger: { type: 'MANUAL' },
|
||||
__typename: 'WorkflowVersion' as const,
|
||||
};
|
||||
|
||||
mockFindOneWorkflowVersion.mockImplementation(
|
||||
async ({ onCompleted }: { onCompleted: (data: unknown) => void }) => {
|
||||
onCompleted(workflowVersionRecord);
|
||||
},
|
||||
);
|
||||
|
||||
mockBuildTriggerWorkflowVersionPayloads.mockReturnValue([
|
||||
{ recordId: 'rec-1' },
|
||||
]);
|
||||
|
||||
const selectedRecordIds = Array.from({ length: 201 }, (_, index) =>
|
||||
String(index),
|
||||
);
|
||||
|
||||
const headlessEngineCommandContextApi = buildBaseContextApi({
|
||||
targetedRecordsRule: { mode: 'selection', selectedRecordIds },
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.enrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(
|
||||
{
|
||||
headlessEngineCommandContextApi,
|
||||
workflowVersionId: 'wf-version-1',
|
||||
availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockEnqueueWarningSnackBar).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
options: {
|
||||
dedupeKey: 'workflow-manual-trigger-selection-limit',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
import { headlessCommandContextApisState } from '@/command-menu-item/engine-command/states/headlessCommandContextApisState';
|
||||
import { type HeadlessEngineCommandContextApi } from '@/command-menu-item/engine-command/types/HeadlessCommandContextApi';
|
||||
import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { createStore, Provider as JotaiProvider } from 'jotai';
|
||||
import { type ReactNode } from 'react';
|
||||
import { EngineComponentKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const TEST_ENGINE_COMMAND_ID = 'test-engine-cmd-1';
|
||||
|
||||
jest.mock(
|
||||
'@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow',
|
||||
() => ({
|
||||
useAvailableComponentInstanceIdOrThrow: () => TEST_ENGINE_COMMAND_ID,
|
||||
}),
|
||||
);
|
||||
|
||||
const getWrapper =
|
||||
(store = createStore()) =>
|
||||
({ children }: { children: ReactNode }) => (
|
||||
<JotaiProvider store={store}>{children}</JotaiProvider>
|
||||
);
|
||||
|
||||
const buildHeadlessContextApi = (
|
||||
overrides: Partial<HeadlessEngineCommandContextApi> = {},
|
||||
): HeadlessEngineCommandContextApi => ({
|
||||
engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
|
||||
contextStoreInstanceId: 'ctx-1',
|
||||
objectMetadataItem: null,
|
||||
currentViewId: null,
|
||||
recordIndexId: null,
|
||||
targetedRecordsRule: { mode: 'selection', selectedRecordIds: [] },
|
||||
selectedRecords: [],
|
||||
graphqlFilter: null,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('useHeadlessCommandContextApi', () => {
|
||||
it('should return the HeadlessCommandContextApi for the current instance id', () => {
|
||||
const store = createStore();
|
||||
const wrapper = getWrapper(store);
|
||||
|
||||
const contextApi = buildHeadlessContextApi();
|
||||
store.set(
|
||||
headlessCommandContextApisState.atom,
|
||||
new Map([[TEST_ENGINE_COMMAND_ID, contextApi]]),
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useHeadlessCommandContextApi(), {
|
||||
wrapper,
|
||||
});
|
||||
|
||||
expect(result.current).toEqual(contextApi);
|
||||
});
|
||||
|
||||
it('should throw when no entry exists for the instance id', () => {
|
||||
const store = createStore();
|
||||
const wrapper = getWrapper(store);
|
||||
|
||||
store.set(headlessCommandContextApisState.atom, new Map());
|
||||
|
||||
expect(() =>
|
||||
renderHook(() => useHeadlessCommandContextApi(), { wrapper }),
|
||||
).toThrow(
|
||||
'Headless command context API not found. Make sure the command was mounted via the command mount flow.',
|
||||
);
|
||||
});
|
||||
});
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { useIsHeadlessEngineCommandEffectInitialized } from '@/command-menu-item/engine-command/hooks/useIsHeadlessEngineCommandEffectInitialized';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
|
||||
describe('useIsHeadlessEngineCommandEffectInitialized', () => {
|
||||
it('should return isInitializedRef as false initially', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useIsHeadlessEngineCommandEffectInitialized(),
|
||||
);
|
||||
|
||||
expect(result.current.isInitializedRef.current).toBe(false);
|
||||
});
|
||||
|
||||
it('should update isInitializedRef to true after calling setIsInitialized(true)', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useIsHeadlessEngineCommandEffectInitialized(),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.setIsInitialized(true);
|
||||
});
|
||||
|
||||
expect(result.current.isInitializedRef.current).toBe(true);
|
||||
});
|
||||
|
||||
it('should toggle back to false after calling setIsInitialized(false)', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useIsHeadlessEngineCommandEffectInitialized(),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.setIsInitialized(true);
|
||||
});
|
||||
|
||||
expect(result.current.isInitializedRef.current).toBe(true);
|
||||
|
||||
act(() => {
|
||||
result.current.setIsInitialized(false);
|
||||
});
|
||||
|
||||
expect(result.current.isInitializedRef.current).toBe(false);
|
||||
});
|
||||
});
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
import { headlessCommandContextApisState } from '@/command-menu-item/engine-command/states/headlessCommandContextApisState';
|
||||
import { type HeadlessEngineCommandContextApi } from '@/command-menu-item/engine-command/types/HeadlessCommandContextApi';
|
||||
import { useMountCommand } from '@/command-menu-item/engine-command/hooks/useMountCommand';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { createStore, Provider as JotaiProvider } from 'jotai';
|
||||
import { type ReactNode } from 'react';
|
||||
import {
|
||||
CommandMenuItemAvailabilityType,
|
||||
EngineComponentKey,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
const mockEnrichFn = jest.fn();
|
||||
|
||||
jest.mock(
|
||||
'@/command-menu-item/engine-command/hooks/useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation',
|
||||
() => ({
|
||||
useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation:
|
||||
() => ({
|
||||
enrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation:
|
||||
mockEnrichFn,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const baseContextApi: HeadlessEngineCommandContextApi = {
|
||||
engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
|
||||
contextStoreInstanceId: 'ctx-1',
|
||||
objectMetadataItem: null,
|
||||
currentViewId: null,
|
||||
recordIndexId: null,
|
||||
targetedRecordsRule: { mode: 'selection', selectedRecordIds: [] },
|
||||
selectedRecords: [],
|
||||
graphqlFilter: null,
|
||||
};
|
||||
|
||||
jest.mock(
|
||||
'@/command-menu-item/engine-command/utils/buildHeadlessCommandContextApi',
|
||||
() => ({
|
||||
buildHeadlessCommandContextApi: () => baseContextApi,
|
||||
}),
|
||||
);
|
||||
|
||||
const getWrapper =
|
||||
(store = createStore()) =>
|
||||
({ children }: { children: ReactNode }) => (
|
||||
<JotaiProvider store={store}>{children}</JotaiProvider>
|
||||
);
|
||||
|
||||
describe('useMountCommand', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should mount with frontComponentId when provided', async () => {
|
||||
const store = createStore();
|
||||
const wrapper = getWrapper(store);
|
||||
|
||||
const { result } = renderHook(() => useMountCommand(), { wrapper });
|
||||
|
||||
await act(async () => {
|
||||
await result.current({
|
||||
engineCommandId: 'cmd-1',
|
||||
contextStoreInstanceId: 'ctx-1',
|
||||
engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
|
||||
frontComponentId: 'front-comp-1',
|
||||
});
|
||||
});
|
||||
|
||||
const map = store.get(headlessCommandContextApisState.atom);
|
||||
|
||||
expect(map.get('cmd-1')).toEqual({
|
||||
...baseContextApi,
|
||||
frontComponentId: 'front-comp-1',
|
||||
});
|
||||
expect(mockEnrichFn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should mount with workflow enrichment when workflowVersionId and availabilityType are provided', async () => {
|
||||
const store = createStore();
|
||||
const wrapper = getWrapper(store);
|
||||
|
||||
const enrichedState = {
|
||||
...baseContextApi,
|
||||
workflowId: 'workflow-1',
|
||||
workflowVersionId: 'wf-version-1',
|
||||
payloads: [{ recordId: 'rec-1' }],
|
||||
};
|
||||
mockEnrichFn.mockResolvedValue(enrichedState);
|
||||
|
||||
const { result } = renderHook(() => useMountCommand(), { wrapper });
|
||||
|
||||
await act(async () => {
|
||||
await result.current({
|
||||
engineCommandId: 'cmd-1',
|
||||
contextStoreInstanceId: 'ctx-1',
|
||||
engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
|
||||
workflowVersionId: 'wf-version-1',
|
||||
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
|
||||
});
|
||||
});
|
||||
|
||||
const map = store.get(headlessCommandContextApisState.atom);
|
||||
|
||||
expect(map.get('cmd-1')).toEqual(enrichedState);
|
||||
expect(mockEnrichFn).toHaveBeenCalledWith({
|
||||
headlessEngineCommandContextApi: baseContextApi,
|
||||
workflowVersionId: 'wf-version-1',
|
||||
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
|
||||
availabilityObjectMetadataId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should mount with base headless context API when neither frontComponentId nor workflow params are provided', async () => {
|
||||
const store = createStore();
|
||||
const wrapper = getWrapper(store);
|
||||
|
||||
const { result } = renderHook(() => useMountCommand(), { wrapper });
|
||||
|
||||
await act(async () => {
|
||||
await result.current({
|
||||
engineCommandId: 'cmd-1',
|
||||
contextStoreInstanceId: 'ctx-1',
|
||||
engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
|
||||
});
|
||||
});
|
||||
|
||||
const map = store.get(headlessCommandContextApisState.atom);
|
||||
|
||||
expect(map.get('cmd-1')).toEqual(baseContextApi);
|
||||
expect(mockEnrichFn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not set state when workflow enrichment returns undefined', async () => {
|
||||
const store = createStore();
|
||||
const wrapper = getWrapper(store);
|
||||
|
||||
mockEnrichFn.mockResolvedValue(undefined);
|
||||
|
||||
const { result } = renderHook(() => useMountCommand(), { wrapper });
|
||||
|
||||
await act(async () => {
|
||||
await result.current({
|
||||
engineCommandId: 'cmd-1',
|
||||
contextStoreInstanceId: 'ctx-1',
|
||||
engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
|
||||
workflowVersionId: 'wf-version-1',
|
||||
availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
|
||||
});
|
||||
});
|
||||
|
||||
const map = store.get(headlessCommandContextApisState.atom);
|
||||
|
||||
expect(map.has('cmd-1')).toBe(false);
|
||||
});
|
||||
});
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
import { headlessCommandContextApisState } from '@/command-menu-item/engine-command/states/headlessCommandContextApisState';
|
||||
import { type HeadlessEngineCommandContextApi } from '@/command-menu-item/engine-command/types/HeadlessCommandContextApi';
|
||||
import { useUnmountCommand } from '@/command-menu-item/engine-command/hooks/useUnmountEngineCommand';
|
||||
import { commandMenuItemProgressFamilyState } from '@/command-menu-item/states/commandMenuItemProgressFamilyState';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { createStore, Provider as JotaiProvider } from 'jotai';
|
||||
import { type ReactNode } from 'react';
|
||||
import { EngineComponentKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const getWrapper =
|
||||
(store = createStore()) =>
|
||||
({ children }: { children: ReactNode }) => (
|
||||
<JotaiProvider store={store}>{children}</JotaiProvider>
|
||||
);
|
||||
|
||||
const buildHeadlessContextApi = (
|
||||
overrides: Partial<HeadlessEngineCommandContextApi> = {},
|
||||
): HeadlessEngineCommandContextApi => ({
|
||||
engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
|
||||
contextStoreInstanceId: 'ctx-1',
|
||||
objectMetadataItem: null,
|
||||
currentViewId: null,
|
||||
recordIndexId: null,
|
||||
targetedRecordsRule: { mode: 'selection', selectedRecordIds: [] },
|
||||
selectedRecords: [],
|
||||
graphqlFilter: null,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('useUnmountCommand', () => {
|
||||
it('should remove entry from headlessCommandContextApisState map', () => {
|
||||
const store = createStore();
|
||||
const wrapper = getWrapper(store);
|
||||
|
||||
const contextApi = buildHeadlessContextApi();
|
||||
store.set(
|
||||
headlessCommandContextApisState.atom,
|
||||
new Map([['cmd-1', contextApi]]),
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useUnmountCommand(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current('cmd-1');
|
||||
});
|
||||
|
||||
const map = store.get(headlessCommandContextApisState.atom);
|
||||
|
||||
expect(map.has('cmd-1')).toBe(false);
|
||||
expect(map.size).toBe(0);
|
||||
});
|
||||
|
||||
it('should reset commandMenuItemProgressFamilyState for the given id to undefined', () => {
|
||||
const store = createStore();
|
||||
const wrapper = getWrapper(store);
|
||||
|
||||
store.set(commandMenuItemProgressFamilyState.atomFamily('cmd-1'), 50);
|
||||
|
||||
const { result } = renderHook(() => useUnmountCommand(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current('cmd-1');
|
||||
});
|
||||
|
||||
const progress = store.get(
|
||||
commandMenuItemProgressFamilyState.atomFamily('cmd-1'),
|
||||
);
|
||||
|
||||
expect(progress).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not affect other entries in the map', () => {
|
||||
const store = createStore();
|
||||
const wrapper = getWrapper(store);
|
||||
|
||||
const contextApi1 = buildHeadlessContextApi({
|
||||
contextStoreInstanceId: 'ctx-1',
|
||||
});
|
||||
const contextApi2 = buildHeadlessContextApi({
|
||||
contextStoreInstanceId: 'ctx-2',
|
||||
});
|
||||
|
||||
store.set(
|
||||
headlessCommandContextApisState.atom,
|
||||
new Map([
|
||||
['cmd-1', contextApi1],
|
||||
['cmd-2', contextApi2],
|
||||
]),
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useUnmountCommand(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current('cmd-1');
|
||||
});
|
||||
|
||||
const map = store.get(headlessCommandContextApisState.atom);
|
||||
|
||||
expect(map.has('cmd-1')).toBe(false);
|
||||
expect(map.has('cmd-2')).toBe(true);
|
||||
expect(map.get('cmd-2')).toEqual(contextApi2);
|
||||
});
|
||||
});
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect';
|
||||
import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useRecordPageLayoutIdFromRecordStoreOrThrow } from '@/page-layout/hooks/useRecordPageLayoutIdFromRecordStoreOrThrow';
|
||||
import { useResetDraftPageLayoutToPersistedPageLayout } from '@/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout';
|
||||
import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
|
||||
import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord';
|
||||
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { PageLayoutType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const CancelRecordPageLayoutSingleRecordCommand = () => {
|
||||
const { objectMetadataItem } = useHeadlessCommandContextApi();
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
throw new Error(
|
||||
'Object metadata item is required to cancel record page layout',
|
||||
);
|
||||
}
|
||||
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
|
||||
const { pageLayoutId } = useRecordPageLayoutIdFromRecordStoreOrThrow({
|
||||
targetObjectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
const tabListInstanceId = getTabListInstanceIdFromPageLayoutAndRecord({
|
||||
pageLayoutId,
|
||||
layoutType: PageLayoutType.RECORD_PAGE,
|
||||
targetRecordIdentifier: {
|
||||
id: recordId,
|
||||
targetObjectNameSingular: objectMetadataItem.nameSingular,
|
||||
},
|
||||
});
|
||||
|
||||
const { closeSidePanelMenu } = useSidePanelMenu();
|
||||
|
||||
const { setIsPageLayoutInEditMode } =
|
||||
useSetIsPageLayoutInEditMode(pageLayoutId);
|
||||
|
||||
const { resetDraftPageLayoutToPersistedPageLayout } =
|
||||
useResetDraftPageLayoutToPersistedPageLayout({
|
||||
pageLayoutId,
|
||||
tabListInstanceId,
|
||||
});
|
||||
|
||||
const handleExecute = () => {
|
||||
closeSidePanelMenu();
|
||||
|
||||
resetDraftPageLayoutToPersistedPageLayout();
|
||||
setIsPageLayoutInEditMode(false);
|
||||
};
|
||||
|
||||
return <HeadlessEngineCommandWrapperEffect execute={handleExecute} />;
|
||||
};
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect';
|
||||
import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
|
||||
import { useRecordPageLayoutIdFromRecordStoreOrThrow } from '@/page-layout/hooks/useRecordPageLayoutIdFromRecordStoreOrThrow';
|
||||
import { useSavePageLayout } from '@/page-layout/hooks/useSavePageLayout';
|
||||
import { useSavePageLayoutWidgetsData } from '@/page-layout/hooks/useSavePageLayoutWidgetsData';
|
||||
import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
|
||||
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const SaveRecordPageLayoutSingleRecordCommand = () => {
|
||||
const { objectMetadataItem } = useHeadlessCommandContextApi();
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
throw new Error(
|
||||
'Object metadata item is required to save record page layout',
|
||||
);
|
||||
}
|
||||
|
||||
const { pageLayoutId } = useRecordPageLayoutIdFromRecordStoreOrThrow({
|
||||
targetObjectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
const { savePageLayout } = useSavePageLayout(pageLayoutId);
|
||||
|
||||
const { savePageLayoutWidgetsData } = useSavePageLayoutWidgetsData();
|
||||
|
||||
const { setIsPageLayoutInEditMode } =
|
||||
useSetIsPageLayoutInEditMode(pageLayoutId);
|
||||
|
||||
const { closeSidePanelMenu } = useSidePanelMenu();
|
||||
|
||||
const handleExecute = async () => {
|
||||
const result = await savePageLayout();
|
||||
|
||||
if (result.status === 'successful') {
|
||||
await savePageLayoutWidgetsData(pageLayoutId);
|
||||
|
||||
closeSidePanelMenu();
|
||||
setIsPageLayoutInEditMode(false);
|
||||
}
|
||||
};
|
||||
|
||||
return <HeadlessEngineCommandWrapperEffect execute={handleExecute} />;
|
||||
};
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import { type HeadlessCommandContextApi } from '@/command-menu-item/engine-command/types/HeadlessCommandContextApi';
|
||||
import { isHeadlessTriggerWorkflowVersionCommandContextApi } from '@/command-menu-item/engine-command/utils/isHeadlessTriggerWorkflowVersionCommandContextApi';
|
||||
import { EngineComponentKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const baseContextApi: HeadlessCommandContextApi = {
|
||||
engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
|
||||
contextStoreInstanceId: 'ctx-1',
|
||||
objectMetadataItem: null,
|
||||
currentViewId: null,
|
||||
recordIndexId: null,
|
||||
targetedRecordsRule: { mode: 'selection', selectedRecordIds: [] },
|
||||
selectedRecords: [],
|
||||
graphqlFilter: null,
|
||||
};
|
||||
|
||||
describe('isHeadlessTriggerWorkflowVersionCommandContextApi', () => {
|
||||
it('should return true when state has workflowId', () => {
|
||||
const triggerWorkflowContext: HeadlessCommandContextApi = {
|
||||
...baseContextApi,
|
||||
workflowId: 'wf-1',
|
||||
workflowVersionId: 'wfv-1',
|
||||
payloads: [],
|
||||
};
|
||||
|
||||
expect(
|
||||
isHeadlessTriggerWorkflowVersionCommandContextApi(triggerWorkflowContext),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for plain HeadlessEngineCommandContextApi', () => {
|
||||
expect(
|
||||
isHeadlessTriggerWorkflowVersionCommandContextApi(baseContextApi),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for HeadlessFrontComponentCommandContextApi', () => {
|
||||
const frontComponentContext: HeadlessCommandContextApi = {
|
||||
...baseContextApi,
|
||||
frontComponentId: 'fc-1',
|
||||
};
|
||||
|
||||
expect(
|
||||
isHeadlessTriggerWorkflowVersionCommandContextApi(frontComponentContext),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { useCloseCommandMenu } from '@/command-menu-item/hooks/useCloseCommandMenu';
|
||||
import { type CommandMenuItemContainerType } from '@/command-menu-item/types/CommandMenuItemContainerType';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
const TEST_COMMAND_MENU_ID = 'test-cmd-menu-1';
|
||||
|
||||
const mockCloseSidePanelMenu = jest.fn();
|
||||
const mockCloseDropdown = jest.fn();
|
||||
|
||||
jest.mock(
|
||||
'@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow',
|
||||
() => ({
|
||||
useAvailableComponentInstanceIdOrThrow: () => TEST_COMMAND_MENU_ID,
|
||||
}),
|
||||
);
|
||||
|
||||
jest.mock('@/side-panel/hooks/useSidePanelMenu', () => ({
|
||||
useSidePanelMenu: () => ({
|
||||
closeSidePanelMenu: mockCloseSidePanelMenu,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('@/ui/layout/dropdown/hooks/useCloseDropdown', () => ({
|
||||
useCloseDropdown: () => ({
|
||||
closeDropdown: mockCloseDropdown,
|
||||
}),
|
||||
}));
|
||||
|
||||
const getWrapper =
|
||||
({
|
||||
containerType,
|
||||
isInSidePanel = false,
|
||||
}: {
|
||||
containerType: CommandMenuItemContainerType;
|
||||
isInSidePanel?: boolean;
|
||||
}) =>
|
||||
({ children }: { children: ReactNode }) => (
|
||||
<CommandMenuContext.Provider
|
||||
value={{
|
||||
containerType,
|
||||
isInSidePanel,
|
||||
displayType: 'button',
|
||||
commandMenuItems: [],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</CommandMenuContext.Provider>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('useCloseCommandMenu', () => {
|
||||
describe('when containerType is command-menu-list', () => {
|
||||
it('should call closeSidePanelMenu by default', () => {
|
||||
const wrapper = getWrapper({ containerType: 'command-menu-list' });
|
||||
|
||||
const { result } = renderHook(() => useCloseCommandMenu(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.closeCommandMenu();
|
||||
});
|
||||
|
||||
expect(mockCloseSidePanelMenu).toHaveBeenCalledTimes(1);
|
||||
expect(mockCloseDropdown).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call closeSidePanelMenu when closeSidePanelOnCommandMenuListExecution is false', () => {
|
||||
const wrapper = getWrapper({ containerType: 'command-menu-list' });
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useCloseCommandMenu({
|
||||
closeSidePanelOnCommandMenuListExecution: false,
|
||||
}),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.closeCommandMenu();
|
||||
});
|
||||
|
||||
expect(mockCloseSidePanelMenu).not.toHaveBeenCalled();
|
||||
expect(mockCloseDropdown).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call closeDropdown', () => {
|
||||
const wrapper = getWrapper({ containerType: 'command-menu-list' });
|
||||
|
||||
const { result } = renderHook(() => useCloseCommandMenu(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.closeCommandMenu();
|
||||
});
|
||||
|
||||
expect(mockCloseDropdown).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when containerType is index-page-dropdown', () => {
|
||||
it('should call closeDropdown with the correct dropdown id', () => {
|
||||
const wrapper = getWrapper({ containerType: 'index-page-dropdown' });
|
||||
|
||||
const { result } = renderHook(() => useCloseCommandMenu(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.closeCommandMenu();
|
||||
});
|
||||
|
||||
expect(mockCloseDropdown).toHaveBeenCalledWith(
|
||||
`command-menu-dropdown-${TEST_COMMAND_MENU_ID}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not call closeSidePanelMenu', () => {
|
||||
const wrapper = getWrapper({ containerType: 'index-page-dropdown' });
|
||||
|
||||
const { result } = renderHook(() => useCloseCommandMenu(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.closeCommandMenu();
|
||||
});
|
||||
|
||||
expect(mockCloseSidePanelMenu).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when containerType is command-menu-show-page-dropdown', () => {
|
||||
it('should call closeDropdown with the correct dropdown id', () => {
|
||||
const wrapper = getWrapper({
|
||||
containerType: 'command-menu-show-page-dropdown',
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useCloseCommandMenu(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.closeCommandMenu();
|
||||
});
|
||||
|
||||
expect(mockCloseDropdown).toHaveBeenCalledWith(
|
||||
`command-menu-dropdown-${TEST_COMMAND_MENU_ID}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not call closeSidePanelMenu by default', () => {
|
||||
const wrapper = getWrapper({
|
||||
containerType: 'command-menu-show-page-dropdown',
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useCloseCommandMenu(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.closeCommandMenu();
|
||||
});
|
||||
|
||||
expect(mockCloseSidePanelMenu).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call closeSidePanelMenu when closeSidePanelOnShowPageOptionsExecution is true', () => {
|
||||
const wrapper = getWrapper({
|
||||
containerType: 'command-menu-show-page-dropdown',
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useCloseCommandMenu({
|
||||
closeSidePanelOnShowPageOptionsExecution: true,
|
||||
}),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.closeCommandMenu();
|
||||
});
|
||||
|
||||
expect(mockCloseSidePanelMenu).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when isInSidePanel is true', () => {
|
||||
it('should use side panel dropdown id for closeDropdown', () => {
|
||||
const wrapper = getWrapper({
|
||||
containerType: 'index-page-dropdown',
|
||||
isInSidePanel: true,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useCloseCommandMenu(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.closeCommandMenu();
|
||||
});
|
||||
|
||||
expect(mockCloseDropdown).toHaveBeenCalledWith(
|
||||
`side-panel-command-menu-dropdown-${TEST_COMMAND_MENU_ID}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
-186
@@ -1,186 +0,0 @@
|
||||
import { useRegisteredCommandMenuItems } from '@/command-menu-item/hooks/useRegisteredCommandMenuItems';
|
||||
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
|
||||
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { createStore, Provider as JotaiProvider } from 'jotai';
|
||||
import { type ReactNode } from 'react';
|
||||
import { CommandMenuItemViewType } from 'twenty-shared/types';
|
||||
import { Icon123 } from 'twenty-ui/display';
|
||||
|
||||
jest.mock('@/command-menu-item/utils/getCommandMenuItemConfig', () => ({
|
||||
getCommandMenuItemConfig: () => ({}),
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
'@/command-menu-item/record-agnostic/hooks/useRelatedRecordCommands',
|
||||
() => ({
|
||||
useRelatedRecordCommands: () => ({}),
|
||||
}),
|
||||
);
|
||||
|
||||
jest.mock('@/settings/roles/hooks/usePermissionFlagMap', () => ({
|
||||
usePermissionFlagMap: () => ({}),
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
'@/command-menu-item/record-agnostic/hooks/useRecordAgnosticCommands',
|
||||
() => ({
|
||||
useRecordAgnosticCommands: () => ({
|
||||
pageEditItem: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: 'page-edit-item',
|
||||
label: 'Page Edit Item',
|
||||
position: 0,
|
||||
Icon: Icon123,
|
||||
availableOn: [CommandMenuItemViewType.PAGE_EDIT_MODE],
|
||||
shouldBeRegistered: () => true,
|
||||
component: null,
|
||||
},
|
||||
showItem: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: 'show-item',
|
||||
label: 'Show Item',
|
||||
position: 1,
|
||||
Icon: Icon123,
|
||||
availableOn: [CommandMenuItemViewType.SHOW_PAGE],
|
||||
shouldBeRegistered: () => true,
|
||||
component: null,
|
||||
},
|
||||
globalItem: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: 'global-item',
|
||||
label: 'Global Item',
|
||||
position: 2,
|
||||
Icon: Icon123,
|
||||
availableOn: [CommandMenuItemViewType.GLOBAL],
|
||||
shouldBeRegistered: () => true,
|
||||
component: null,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const CONTEXT_STORE_INSTANCE_ID = 'test-context-store-instance-id';
|
||||
|
||||
const getWrapper = (store = createStore()) => {
|
||||
return ({ children }: { children: ReactNode }) => (
|
||||
<JotaiProvider store={store}>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{ instanceId: CONTEXT_STORE_INSTANCE_ID }}
|
||||
>
|
||||
{children}
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</JotaiProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const shouldBeRegisteredParams = {
|
||||
objectPermissions: {
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: true,
|
||||
canDestroyObjectRecords: true,
|
||||
restrictedFields: {},
|
||||
objectMetadataId: '',
|
||||
rowLevelPermissionPredicates: [],
|
||||
rowLevelPermissionPredicateGroups: [],
|
||||
},
|
||||
getTargetObjectReadPermission: () => true,
|
||||
getTargetObjectWritePermission: () => true,
|
||||
isFeatureFlagEnabled: () => true,
|
||||
};
|
||||
|
||||
describe('useRegisteredCommandMenuItems', () => {
|
||||
it('should register SHOW_PAGE and GLOBAL commands when page is not in edit mode', () => {
|
||||
const store = createStore();
|
||||
const wrapper = getWrapper(store);
|
||||
|
||||
store.set(
|
||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||
instanceId: CONTEXT_STORE_INSTANCE_ID,
|
||||
}),
|
||||
ContextStoreViewType.ShowPage,
|
||||
);
|
||||
store.set(
|
||||
contextStoreTargetedRecordsRuleComponentState.atomFamily({
|
||||
instanceId: CONTEXT_STORE_INSTANCE_ID,
|
||||
}),
|
||||
{ mode: 'selection', selectedRecordIds: [] },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
store.set(
|
||||
contextStoreIsPageInEditModeComponentState.atomFamily({
|
||||
instanceId: CONTEXT_STORE_INSTANCE_ID,
|
||||
}),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useRegisteredCommandMenuItems(
|
||||
shouldBeRegisteredParams as Parameters<
|
||||
typeof useRegisteredCommandMenuItems
|
||||
>[0],
|
||||
),
|
||||
{
|
||||
wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.map((item) => item.key)).toEqual([
|
||||
'show-item',
|
||||
'global-item',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should register PAGE_EDIT_MODE commands when page is in edit mode', () => {
|
||||
const store = createStore();
|
||||
const wrapper = getWrapper(store);
|
||||
|
||||
store.set(
|
||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||
instanceId: CONTEXT_STORE_INSTANCE_ID,
|
||||
}),
|
||||
ContextStoreViewType.ShowPage,
|
||||
);
|
||||
store.set(
|
||||
contextStoreTargetedRecordsRuleComponentState.atomFamily({
|
||||
instanceId: CONTEXT_STORE_INSTANCE_ID,
|
||||
}),
|
||||
{ mode: 'selection', selectedRecordIds: [] },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
store.set(
|
||||
contextStoreIsPageInEditModeComponentState.atomFamily({
|
||||
instanceId: CONTEXT_STORE_INSTANCE_ID,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useRegisteredCommandMenuItems(
|
||||
shouldBeRegisteredParams as Parameters<
|
||||
typeof useRegisteredCommandMenuItems
|
||||
>[0],
|
||||
),
|
||||
{
|
||||
wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.map((item) => item.key)).toEqual(['page-edit-item']);
|
||||
});
|
||||
});
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
import { useRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/hooks/useRecordAgnosticCommands';
|
||||
import { useRelatedRecordCommands } from '@/command-menu-item/record-agnostic/hooks/useRelatedRecordCommands';
|
||||
import { CommandMenuItemViewType } from 'twenty-shared/types';
|
||||
import { type ShouldBeRegisteredFunctionParams } from '@/command-menu-item/types/ShouldBeRegisteredFunctionParams';
|
||||
import { getCommandMenuItemConfig } from '@/command-menu-item/utils/getCommandMenuItemConfig';
|
||||
import { getCommandMenuItemViewType } from '@/command-menu-item/utils/getCommandMenuItemViewType';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
|
||||
export const useRegisteredCommandMenuItems = (
|
||||
shouldBeRegisteredParams: ShouldBeRegisteredFunctionParams,
|
||||
) => {
|
||||
const { objectMetadataItem } = shouldBeRegisteredParams;
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
const contextStoreCurrentViewType = useAtomComponentStateValue(
|
||||
contextStoreCurrentViewTypeComponentState,
|
||||
);
|
||||
|
||||
const contextStoreIsPageInEditMode = useAtomComponentStateValue(
|
||||
contextStoreIsPageInEditModeComponentState,
|
||||
);
|
||||
|
||||
const viewType = getCommandMenuItemViewType(
|
||||
contextStoreCurrentViewType,
|
||||
contextStoreTargetedRecordsRule,
|
||||
);
|
||||
|
||||
const recordCommandMenuItemsConfig = getCommandMenuItemConfig({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const relatedRecordCommandMenuItemsConfig = useRelatedRecordCommands({
|
||||
sourceObjectMetadataItem: objectMetadataItem,
|
||||
getIcon,
|
||||
startPosition: Object.keys(recordCommandMenuItemsConfig).length + 1,
|
||||
});
|
||||
|
||||
const recordAgnosticCommandMenuItemsConfig = useRecordAgnosticCommands();
|
||||
|
||||
const commandMenuItemsConfig = {
|
||||
...recordCommandMenuItemsConfig,
|
||||
...relatedRecordCommandMenuItemsConfig,
|
||||
...recordAgnosticCommandMenuItemsConfig,
|
||||
};
|
||||
|
||||
const permissionMap = usePermissionFlagMap();
|
||||
|
||||
const commandMenuItemsToRegister = Object.values(
|
||||
commandMenuItemsConfig,
|
||||
).filter((commandMenuItem) => {
|
||||
if (contextStoreIsPageInEditMode) {
|
||||
return (
|
||||
isDefined(commandMenuItem.availableOn) &&
|
||||
commandMenuItem.availableOn.includes(
|
||||
CommandMenuItemViewType.PAGE_EDIT_MODE,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (isDefined(viewType)) {
|
||||
return (
|
||||
commandMenuItem.availableOn?.includes(viewType) ||
|
||||
commandMenuItem.availableOn?.includes(CommandMenuItemViewType.GLOBAL)
|
||||
);
|
||||
}
|
||||
|
||||
return commandMenuItem.availableOn?.includes(
|
||||
CommandMenuItemViewType.GLOBAL,
|
||||
);
|
||||
});
|
||||
|
||||
const commandMenuItems = commandMenuItemsToRegister
|
||||
.filter((commandMenuItem) => {
|
||||
if (
|
||||
isDefined(commandMenuItem.requiredPermissionFlag) &&
|
||||
!permissionMap[commandMenuItem.requiredPermissionFlag]
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return commandMenuItem.shouldBeRegistered(shouldBeRegisteredParams);
|
||||
})
|
||||
.sort((a, b) => a.position - b.position);
|
||||
|
||||
return commandMenuItems;
|
||||
};
|
||||
-144
@@ -1,144 +0,0 @@
|
||||
import { type ShouldBeRegisteredFunctionParams } from '@/command-menu-item/types/ShouldBeRegisteredFunctionParams';
|
||||
import { getCommandMenuItemViewType } from '@/command-menu-item/utils/getCommandMenuItemViewType';
|
||||
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { objectPermissionsFamilySelector } from '@/auth/states/objectPermissionsFamilySelector';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
|
||||
import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/record-filter/states/hasAnySoftDeleteFilterOnView';
|
||||
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback, useContext, useMemo } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import type { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useShouldCommandMenuItemBeRegisteredParams = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem?: EnrichedObjectMetadataItem;
|
||||
}): ShouldBeRegisteredFunctionParams => {
|
||||
const store = useStore();
|
||||
const { navigationMenuItems } = useNavigationMenuItemsData();
|
||||
|
||||
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
const recordId =
|
||||
contextStoreTargetedRecordsRule.mode === 'selection'
|
||||
? contextStoreTargetedRecordsRule.selectedRecordIds[0]
|
||||
: undefined;
|
||||
|
||||
const isFavorite = useMemo(() => {
|
||||
if (!isDefined(recordId) || !isDefined(objectMetadataItem)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const foundNavigationMenuItem = navigationMenuItems?.find(
|
||||
(item) =>
|
||||
item.targetRecordId === recordId &&
|
||||
item.targetObjectMetadataId === objectMetadataItem.id,
|
||||
);
|
||||
return !!foundNavigationMenuItem;
|
||||
}, [recordId, objectMetadataItem, navigationMenuItems]);
|
||||
|
||||
const selectedRecord =
|
||||
useAtomFamilyStateValue(recordStoreFamilyState, recordId ?? '') ||
|
||||
undefined;
|
||||
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem?.id ?? '',
|
||||
);
|
||||
|
||||
const isNoteOrTask =
|
||||
objectMetadataItem?.nameSingular === CoreObjectNameSingular.Note ||
|
||||
objectMetadataItem?.nameSingular === CoreObjectNameSingular.Task;
|
||||
|
||||
const { isInSidePanel } = useContext(CommandMenuContext);
|
||||
|
||||
const { recordIndexId } = useRecordIndexIdFromCurrentContextStore();
|
||||
|
||||
const hasAnySoftDeleteFilterOnView = useAtomComponentSelectorValue(
|
||||
hasAnySoftDeleteFilterOnViewComponentSelector,
|
||||
recordIndexId,
|
||||
);
|
||||
|
||||
const isShowPage =
|
||||
useAtomComponentStateValue(contextStoreCurrentViewTypeComponentState) ===
|
||||
ContextStoreViewType.ShowPage;
|
||||
|
||||
const contextStoreNumberOfSelectedRecords = useAtomComponentStateValue(
|
||||
contextStoreNumberOfSelectedRecordsComponentState,
|
||||
);
|
||||
|
||||
const contextStoreCurrentViewType = useAtomComponentStateValue(
|
||||
contextStoreCurrentViewTypeComponentState,
|
||||
);
|
||||
|
||||
const viewType = getCommandMenuItemViewType(
|
||||
contextStoreCurrentViewType,
|
||||
contextStoreTargetedRecordsRule,
|
||||
);
|
||||
|
||||
const isSelectAll = contextStoreTargetedRecordsRule.mode === 'exclusion';
|
||||
|
||||
const getObjectReadPermission = useCallback(
|
||||
(objectMetadataNameSingular: string) => {
|
||||
return store.get(
|
||||
objectPermissionsFamilySelector.selectorFamily({
|
||||
objectNameSingular: objectMetadataNameSingular,
|
||||
}),
|
||||
).canRead;
|
||||
},
|
||||
[store],
|
||||
);
|
||||
|
||||
const getObjectWritePermission = useCallback(
|
||||
(objectMetadataNameSingular: string) => {
|
||||
return store.get(
|
||||
objectPermissionsFamilySelector.selectorFamily({
|
||||
objectNameSingular: objectMetadataNameSingular,
|
||||
}),
|
||||
).canUpdate;
|
||||
},
|
||||
[store],
|
||||
);
|
||||
|
||||
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
|
||||
|
||||
const isFeatureFlagEnabled = (featureFlagKey: FeatureFlagKey) => {
|
||||
const featureFlag = currentWorkspace?.featureFlags?.find(
|
||||
(flag) => flag.key === featureFlagKey,
|
||||
);
|
||||
|
||||
return featureFlag?.value === true;
|
||||
};
|
||||
|
||||
return {
|
||||
objectMetadataItem,
|
||||
isFavorite,
|
||||
objectPermissions,
|
||||
isNoteOrTask,
|
||||
isInSidePanel,
|
||||
hasAnySoftDeleteFilterOnView,
|
||||
isShowPage,
|
||||
isSelectAll,
|
||||
selectedRecord,
|
||||
numberOfSelectedRecords: contextStoreNumberOfSelectedRecords,
|
||||
viewType: viewType ?? undefined,
|
||||
getTargetObjectReadPermission: getObjectReadPermission,
|
||||
getTargetObjectWritePermission: getObjectWritePermission,
|
||||
isFeatureFlagEnabled,
|
||||
};
|
||||
};
|
||||
+14
-20
@@ -1,17 +1,5 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
|
||||
import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
|
||||
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
|
||||
import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
|
||||
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
|
||||
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
|
||||
import {
|
||||
CommandMenuItemViewType,
|
||||
CoreObjectNameSingular,
|
||||
AppPath,
|
||||
} from 'twenty-shared/types';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { CommandMenuItemViewType, AppPath } from 'twenty-shared/types';
|
||||
import {
|
||||
IconFileExport,
|
||||
IconHeart,
|
||||
@@ -19,6 +7,14 @@ import {
|
||||
IconUser,
|
||||
} from 'twenty-ui/display';
|
||||
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
|
||||
import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
|
||||
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
|
||||
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { EngineComponentKey } from '~/generated-metadata/graphql';
|
||||
|
||||
export const createMockCommandMenuItems = ({
|
||||
deleteMock = () => {},
|
||||
addToFavoritesMock = () => {},
|
||||
@@ -31,7 +27,7 @@ export const createMockCommandMenuItems = ({
|
||||
{
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
key: EngineComponentKey.ADD_TO_FAVORITES,
|
||||
label: msg`Add to favorites`,
|
||||
shortLabel: msg`Add to favorites`,
|
||||
position: 2,
|
||||
@@ -47,7 +43,7 @@ export const createMockCommandMenuItems = ({
|
||||
{
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX,
|
||||
key: EngineComponentKey.EXPORT_FROM_RECORD_INDEX,
|
||||
label: msg`Export`,
|
||||
shortLabel: msg`Export`,
|
||||
position: 4,
|
||||
@@ -61,7 +57,7 @@ export const createMockCommandMenuItems = ({
|
||||
{
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: SingleRecordCommandKeys.DELETE,
|
||||
key: EngineComponentKey.DELETE_SINGLE_RECORD,
|
||||
label: msg`Delete`,
|
||||
shortLabel: msg`Delete`,
|
||||
position: 7,
|
||||
@@ -78,7 +74,7 @@ export const createMockCommandMenuItems = ({
|
||||
{
|
||||
type: CommandMenuItemType.Navigation,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
|
||||
key: EngineComponentKey.GO_TO_PEOPLE,
|
||||
label: msg`Go to People`,
|
||||
shortLabel: msg`People`,
|
||||
position: 19,
|
||||
@@ -90,9 +86,7 @@ export const createMockCommandMenuItems = ({
|
||||
CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
],
|
||||
shouldBeRegistered: ({ objectMetadataItem, viewType }) =>
|
||||
objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Person ||
|
||||
viewType === CommandMenuItemViewType.SHOW_PAGE,
|
||||
shouldBeRegistered: () => true,
|
||||
component: (
|
||||
<CommandLink
|
||||
to={AppPath.RecordIndexPage}
|
||||
|
||||
-113
@@ -1,113 +0,0 @@
|
||||
import { CommandMenuItemOpenSidePanelPage } from '@/command-menu-item/display/components/CommandMenuItemOpenSidePanelPage';
|
||||
import { RecordAgnosticCommandKeys } from '@/command-menu-item/record-agnostic/types/RecordAgnosticCommandKeys';
|
||||
import { EditNavigationSidebarNoSelectionRecordCommand } from '@/command-menu-item/record/no-selection/components/EditNavigationSidebarNoSelectionRecordCommand';
|
||||
import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
|
||||
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
|
||||
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
|
||||
import { CommandMenuItemViewType, SidePanelPages } from 'twenty-shared/types';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import {
|
||||
IconHistory,
|
||||
IconLayout,
|
||||
IconSearch,
|
||||
IconSparkles,
|
||||
} from 'twenty-ui/display';
|
||||
|
||||
export const RECORD_AGNOSTIC_COMMAND_MENU_ITEMS_CONFIG: Record<
|
||||
string,
|
||||
CommandMenuItemConfig
|
||||
> = {
|
||||
[RecordAgnosticCommandKeys.SEARCH_RECORDS]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: RecordAgnosticCommandKeys.SEARCH_RECORDS,
|
||||
label: msg`Search records`,
|
||||
shortLabel: msg`Search`,
|
||||
position: 0,
|
||||
isPinned: false,
|
||||
Icon: IconSearch,
|
||||
availableOn: [CommandMenuItemViewType.GLOBAL],
|
||||
component: (
|
||||
<CommandMenuItemOpenSidePanelPage
|
||||
page={SidePanelPages.SearchRecords}
|
||||
pageTitle={msg`Search`}
|
||||
pageIcon={IconSearch}
|
||||
shouldResetSearchState={true}
|
||||
/>
|
||||
),
|
||||
hotKeys: ['/'],
|
||||
shouldBeRegistered: () => true,
|
||||
},
|
||||
[RecordAgnosticCommandKeys.SEARCH_RECORDS_FALLBACK]: {
|
||||
type: CommandMenuItemType.Fallback,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: RecordAgnosticCommandKeys.SEARCH_RECORDS_FALLBACK,
|
||||
label: msg`Search records`,
|
||||
shortLabel: msg`Search`,
|
||||
position: 1,
|
||||
isPinned: false,
|
||||
Icon: IconSearch,
|
||||
availableOn: [CommandMenuItemViewType.GLOBAL],
|
||||
component: (
|
||||
<CommandMenuItemOpenSidePanelPage
|
||||
page={SidePanelPages.SearchRecords}
|
||||
pageTitle={msg`Search`}
|
||||
pageIcon={IconSearch}
|
||||
/>
|
||||
),
|
||||
hotKeys: ['/'],
|
||||
shouldBeRegistered: () => true,
|
||||
},
|
||||
[RecordAgnosticCommandKeys.ASK_AI]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: RecordAgnosticCommandKeys.ASK_AI,
|
||||
label: msg`Ask AI`,
|
||||
shortLabel: msg`Ask AI`,
|
||||
position: 2,
|
||||
isPinned: false,
|
||||
Icon: IconSparkles,
|
||||
availableOn: [CommandMenuItemViewType.GLOBAL],
|
||||
component: (
|
||||
<CommandMenuItemOpenSidePanelPage
|
||||
page={SidePanelPages.AskAI}
|
||||
pageTitle={msg`Ask AI`}
|
||||
pageIcon={IconSparkles}
|
||||
/>
|
||||
),
|
||||
hotKeys: ['@'],
|
||||
shouldBeRegistered: () => true,
|
||||
},
|
||||
[RecordAgnosticCommandKeys.VIEW_PREVIOUS_AI_CHATS]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: RecordAgnosticCommandKeys.VIEW_PREVIOUS_AI_CHATS,
|
||||
label: msg`View Previous AI Chats`,
|
||||
shortLabel: msg`Previous AI Chats`,
|
||||
position: 3,
|
||||
isPinned: false,
|
||||
Icon: IconHistory,
|
||||
availableOn: [CommandMenuItemViewType.GLOBAL],
|
||||
component: (
|
||||
<CommandMenuItemOpenSidePanelPage
|
||||
page={SidePanelPages.ViewPreviousAIChats}
|
||||
pageTitle={msg`View Previous AI Chats`}
|
||||
pageIcon={IconSparkles}
|
||||
/>
|
||||
),
|
||||
shouldBeRegistered: () => true,
|
||||
},
|
||||
[RecordAgnosticCommandKeys.EDIT_NAVIGATION_SIDEBAR]: {
|
||||
type: CommandMenuItemType.Navigation,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: RecordAgnosticCommandKeys.EDIT_NAVIGATION_SIDEBAR,
|
||||
label: msg`Edit navigation sidebar`,
|
||||
shortLabel: msg`Edit sidebar`,
|
||||
position: 4,
|
||||
Icon: IconLayout,
|
||||
isPinned: false,
|
||||
availableOn: [CommandMenuItemViewType.GLOBAL],
|
||||
shouldBeRegistered: () => true,
|
||||
component: <EditNavigationSidebarNoSelectionRecordCommand />,
|
||||
},
|
||||
};
|
||||
-252
@@ -1,252 +0,0 @@
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useRelatedRecordCommands } from '@/command-menu-item/record-agnostic/hooks/useRelatedRecordCommands';
|
||||
|
||||
jest.mock('@/object-metadata/hooks/useObjectMetadataItems', () => ({
|
||||
useObjectMetadataItems: () => ({
|
||||
objectMetadataItems: [
|
||||
{
|
||||
id: 'person-id',
|
||||
nameSingular: CoreObjectNameSingular.Person,
|
||||
namePlural: 'People',
|
||||
labelSingular: 'Person',
|
||||
},
|
||||
{
|
||||
id: 'company-id',
|
||||
nameSingular: CoreObjectNameSingular.Company,
|
||||
namePlural: 'Companies',
|
||||
labelSingular: 'Company',
|
||||
},
|
||||
],
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('useRelatedRecordCommands', () => {
|
||||
const mockGetIcon = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
mockGetIcon.mockClear();
|
||||
});
|
||||
|
||||
it('should return empty object when objectMetadataItem has no fields', () => {
|
||||
const objectMetadataItem = {
|
||||
fields: [],
|
||||
readableFields: [],
|
||||
updatableFields: [],
|
||||
} as unknown as EnrichedObjectMetadataItem;
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useRelatedRecordCommands({
|
||||
sourceObjectMetadataItem: objectMetadataItem,
|
||||
getIcon: mockGetIcon,
|
||||
startPosition: 18,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current).toEqual({});
|
||||
});
|
||||
|
||||
it('should return empty object when objectMetadataItem is undefined', () => {
|
||||
const objectMetadataItem =
|
||||
undefined as unknown as EnrichedObjectMetadataItem;
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useRelatedRecordCommands({
|
||||
sourceObjectMetadataItem: objectMetadataItem,
|
||||
getIcon: mockGetIcon,
|
||||
startPosition: 18,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current).toEqual({});
|
||||
});
|
||||
|
||||
it('should generate actions for one-to-many relations', () => {
|
||||
const fields = [
|
||||
{
|
||||
type: 'RELATION',
|
||||
relation: {
|
||||
type: 'ONE_TO_MANY',
|
||||
targetObjectMetadata: {
|
||||
nameSingular: CoreObjectNameSingular.Person,
|
||||
namePlural: 'People',
|
||||
},
|
||||
},
|
||||
label: 'person',
|
||||
isSystem: false,
|
||||
},
|
||||
{
|
||||
type: 'RELATION',
|
||||
relation: {
|
||||
type: 'ONE_TO_MANY',
|
||||
targetObjectMetadata: {
|
||||
nameSingular: CoreObjectNameSingular.Company,
|
||||
namePlural: 'Companies',
|
||||
},
|
||||
},
|
||||
label: 'company',
|
||||
isSystem: false,
|
||||
},
|
||||
];
|
||||
const objectMetadataItem = {
|
||||
fields,
|
||||
readableFields: fields,
|
||||
updatableFields: fields,
|
||||
} as unknown as EnrichedObjectMetadataItem;
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useRelatedRecordCommands({
|
||||
sourceObjectMetadataItem: objectMetadataItem,
|
||||
getIcon: mockGetIcon,
|
||||
startPosition: 18,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(Object.keys(result.current)).toHaveLength(2);
|
||||
expect(result.current['create-related-person']).toBeDefined();
|
||||
expect(result.current['create-related-company']).toBeDefined();
|
||||
});
|
||||
|
||||
it('should filter out non-one-to-many relations', () => {
|
||||
const fields = [
|
||||
{
|
||||
type: 'RELATION',
|
||||
relation: {
|
||||
type: 'MANY_TO_ONE',
|
||||
targetObjectMetadata: {
|
||||
nameSingular: CoreObjectNameSingular.Person,
|
||||
namePlural: 'People',
|
||||
},
|
||||
},
|
||||
label: 'person',
|
||||
isSystem: false,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
type: 'RELATION',
|
||||
relation: {
|
||||
type: 'ONE_TO_MANY',
|
||||
targetObjectMetadata: {
|
||||
nameSingular: CoreObjectNameSingular.Company,
|
||||
namePlural: 'Companies',
|
||||
},
|
||||
},
|
||||
label: 'company',
|
||||
isSystem: false,
|
||||
},
|
||||
];
|
||||
|
||||
const objectMetadataItem = {
|
||||
fields,
|
||||
readableFields: fields,
|
||||
updatableFields: fields,
|
||||
} as unknown as EnrichedObjectMetadataItem;
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useRelatedRecordCommands({
|
||||
sourceObjectMetadataItem: objectMetadataItem,
|
||||
getIcon: mockGetIcon,
|
||||
startPosition: 18,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(Object.keys(result.current)).toHaveLength(1);
|
||||
expect(result.current['create-related-company']).toBeDefined();
|
||||
expect(result.current['create-related-person']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should assign correct positions to each action', () => {
|
||||
const fields = [
|
||||
{
|
||||
type: 'RELATION',
|
||||
relation: {
|
||||
type: 'ONE_TO_MANY',
|
||||
targetObjectMetadata: {
|
||||
nameSingular: CoreObjectNameSingular.Person,
|
||||
namePlural: 'People',
|
||||
},
|
||||
},
|
||||
label: 'person',
|
||||
isSystem: false,
|
||||
},
|
||||
{
|
||||
type: 'RELATION',
|
||||
relation: {
|
||||
type: 'ONE_TO_MANY',
|
||||
targetObjectMetadata: {
|
||||
nameSingular: CoreObjectNameSingular.Company,
|
||||
namePlural: 'Companies',
|
||||
},
|
||||
},
|
||||
label: 'company',
|
||||
isSystem: false,
|
||||
},
|
||||
];
|
||||
const objectMetadataItem = {
|
||||
fields,
|
||||
readableFields: fields,
|
||||
updatableFields: fields,
|
||||
} as unknown as EnrichedObjectMetadataItem;
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useRelatedRecordCommands({
|
||||
sourceObjectMetadataItem: objectMetadataItem,
|
||||
getIcon: mockGetIcon,
|
||||
startPosition: 18,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current['create-related-person'].position).toBe(18);
|
||||
expect(result.current['create-related-company'].position).toBe(19);
|
||||
});
|
||||
|
||||
it('should filter out hidden system fields', () => {
|
||||
const fields = [
|
||||
{
|
||||
type: 'RELATION',
|
||||
name: 'position',
|
||||
relation: {
|
||||
type: 'ONE_TO_MANY',
|
||||
targetObjectMetadata: {
|
||||
nameSingular: CoreObjectNameSingular.Person,
|
||||
namePlural: 'People',
|
||||
},
|
||||
},
|
||||
label: 'person',
|
||||
isSystem: true,
|
||||
},
|
||||
{
|
||||
type: 'RELATION',
|
||||
relation: {
|
||||
type: 'ONE_TO_MANY',
|
||||
targetObjectMetadata: {
|
||||
nameSingular: CoreObjectNameSingular.Company,
|
||||
namePlural: 'Companies',
|
||||
},
|
||||
},
|
||||
label: 'company',
|
||||
isSystem: false,
|
||||
},
|
||||
];
|
||||
const objectMetadataItem = {
|
||||
fields,
|
||||
readableFields: fields,
|
||||
updatableFields: fields,
|
||||
} as unknown as EnrichedObjectMetadataItem;
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useRelatedRecordCommands({
|
||||
sourceObjectMetadataItem: objectMetadataItem,
|
||||
getIcon: mockGetIcon,
|
||||
startPosition: 18,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(Object.keys(result.current)).toHaveLength(1);
|
||||
expect(result.current['create-related-company']).toBeDefined();
|
||||
expect(result.current['create-related-person']).toBeUndefined();
|
||||
});
|
||||
});
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
import { RECORD_AGNOSTIC_COMMAND_MENU_ITEMS_CONFIG } from '@/command-menu-item/record-agnostic/constants/RecordAgnosticCommandMenuItemsConfig';
|
||||
import { RecordAgnosticCommandKeys } from '@/command-menu-item/record-agnostic/types/RecordAgnosticCommandKeys';
|
||||
import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useRecordAgnosticCommands = () => {
|
||||
const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
|
||||
|
||||
const commandMenuItems: Record<string, CommandMenuItemConfig> = {
|
||||
[RecordAgnosticCommandKeys.SEARCH_RECORDS]:
|
||||
RECORD_AGNOSTIC_COMMAND_MENU_ITEMS_CONFIG[
|
||||
RecordAgnosticCommandKeys.SEARCH_RECORDS
|
||||
],
|
||||
[RecordAgnosticCommandKeys.SEARCH_RECORDS_FALLBACK]:
|
||||
RECORD_AGNOSTIC_COMMAND_MENU_ITEMS_CONFIG[
|
||||
RecordAgnosticCommandKeys.SEARCH_RECORDS_FALLBACK
|
||||
],
|
||||
[RecordAgnosticCommandKeys.EDIT_NAVIGATION_SIDEBAR]:
|
||||
RECORD_AGNOSTIC_COMMAND_MENU_ITEMS_CONFIG[
|
||||
RecordAgnosticCommandKeys.EDIT_NAVIGATION_SIDEBAR
|
||||
],
|
||||
};
|
||||
|
||||
if (isAiEnabled) {
|
||||
commandMenuItems[RecordAgnosticCommandKeys.ASK_AI] =
|
||||
RECORD_AGNOSTIC_COMMAND_MENU_ITEMS_CONFIG[
|
||||
RecordAgnosticCommandKeys.ASK_AI
|
||||
];
|
||||
commandMenuItems[RecordAgnosticCommandKeys.VIEW_PREVIOUS_AI_CHATS] =
|
||||
RECORD_AGNOSTIC_COMMAND_MENU_ITEMS_CONFIG[
|
||||
RecordAgnosticCommandKeys.VIEW_PREVIOUS_AI_CHATS
|
||||
];
|
||||
}
|
||||
|
||||
return commandMenuItems;
|
||||
};
|
||||
-124
@@ -1,124 +0,0 @@
|
||||
import { CreateRelatedRecordCommand } from '@/command-menu-item/record/single-record/components/CreateRelatedRecordCommand';
|
||||
import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
|
||||
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
|
||||
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
|
||||
import {
|
||||
CommandMenuItemViewType,
|
||||
CoreObjectNameSingular,
|
||||
} from 'twenty-shared/types';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
|
||||
import { isRecordReadOnly } from '@/object-record/read-only/utils/isRecordReadOnly';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import React from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type IconComponent, IconPlus } from 'twenty-ui/display';
|
||||
|
||||
interface GenerateRelatedRecordCommandsParams {
|
||||
sourceObjectMetadataItem?: EnrichedObjectMetadataItem;
|
||||
getIcon: (iconKey: string) => IconComponent;
|
||||
startPosition: number;
|
||||
}
|
||||
|
||||
export const useRelatedRecordCommands = ({
|
||||
sourceObjectMetadataItem,
|
||||
getIcon,
|
||||
startPosition,
|
||||
}: GenerateRelatedRecordCommandsParams): Record<
|
||||
string,
|
||||
CommandMenuItemConfig
|
||||
> => {
|
||||
const relatedCommands: Record<string, CommandMenuItemConfig> = {};
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
if (!sourceObjectMetadataItem?.fields) {
|
||||
return relatedCommands;
|
||||
}
|
||||
|
||||
const oneToManyFields = sourceObjectMetadataItem.readableFields.filter(
|
||||
(field) =>
|
||||
field.type === 'RELATION' &&
|
||||
field.relation?.type === 'ONE_TO_MANY' &&
|
||||
!isHiddenSystemField(field),
|
||||
);
|
||||
|
||||
let currentPosition = startPosition;
|
||||
|
||||
for (const field of oneToManyFields) {
|
||||
if (!isDefined(field.relation)) {
|
||||
throw new Error(`Field relation is undefined for field: ${field.id}`);
|
||||
}
|
||||
|
||||
const targetObjectName = field.relation.targetObjectMetadata.nameSingular;
|
||||
|
||||
const targetObjectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.nameSingular === targetObjectName,
|
||||
);
|
||||
|
||||
if (!isDefined(targetObjectMetadataItem)) {
|
||||
throw new Error(
|
||||
`Target object metadata item is undefined for field: ${field.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
const targetObjectNameSingular = targetObjectMetadataItem.nameSingular;
|
||||
const targetObjectLabelSingular =
|
||||
targetObjectNameSingular === CoreObjectNameSingular.TaskTarget
|
||||
? 'task'
|
||||
: targetObjectNameSingular === CoreObjectNameSingular.NoteTarget
|
||||
? 'note'
|
||||
: targetObjectMetadataItem.labelSingular.toLowerCase();
|
||||
|
||||
const actionKey = `create-related-${targetObjectLabelSingular}`;
|
||||
|
||||
relatedCommands[actionKey] = {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.CreateRelatedRecord,
|
||||
key: actionKey,
|
||||
label: msg`Create ${targetObjectLabelSingular}`,
|
||||
shortLabel: msg`Create ${targetObjectLabelSingular}`,
|
||||
position: currentPosition,
|
||||
Icon: field.icon ? getIcon(field.icon) : IconPlus,
|
||||
accent: 'default',
|
||||
isPinned: false,
|
||||
shouldBeRegistered: ({
|
||||
selectedRecord,
|
||||
objectPermissions,
|
||||
getTargetObjectWritePermission,
|
||||
objectMetadataItem,
|
||||
}) =>
|
||||
(isDefined(selectedRecord) &&
|
||||
isDefined(objectMetadataItem) &&
|
||||
isRecordReadOnly({
|
||||
objectPermissions: {
|
||||
canUpdateObjectRecords: objectPermissions.canUpdateObjectRecords,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
},
|
||||
objectMetadataItem,
|
||||
isRecordDeleted: isDefined(selectedRecord.deletedAt),
|
||||
}) &&
|
||||
objectPermissions.canUpdateObjectRecords &&
|
||||
getTargetObjectWritePermission(
|
||||
targetObjectNameSingular === CoreObjectNameSingular.TaskTarget
|
||||
? CoreObjectNameSingular.Task
|
||||
: targetObjectNameSingular === CoreObjectNameSingular.NoteTarget
|
||||
? CoreObjectNameSingular.Note
|
||||
: targetObjectNameSingular,
|
||||
)) ??
|
||||
false,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
component: React.createElement(CreateRelatedRecordCommand, {
|
||||
targetFieldMetadataItemRelation: field.relation,
|
||||
}),
|
||||
};
|
||||
|
||||
currentPosition++;
|
||||
}
|
||||
|
||||
return relatedCommands;
|
||||
};
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
export enum RecordAgnosticCommandKeys {
|
||||
SEARCH_RECORDS = 'search-records',
|
||||
SEARCH_RECORDS_FALLBACK = 'search-records-fallback',
|
||||
ASK_AI = 'ask-ai',
|
||||
VIEW_PREVIOUS_AI_CHATS = 'view-previous-ai-chats',
|
||||
EDIT_NAVIGATION_SIDEBAR = 'edit-navigation-sidebar',
|
||||
}
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
|
||||
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
|
||||
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useActiveWorkflowVersionsWithManualTrigger } from '@/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger';
|
||||
import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion';
|
||||
import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon';
|
||||
import { useContext } from 'react';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
|
||||
export const useRunWorkflowRecordAgnosticCommands = () => {
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const contextStoreIsPageInEditMode = useAtomComponentStateValue(
|
||||
contextStoreIsPageInEditModeComponentState,
|
||||
);
|
||||
|
||||
const { containerType } = useContext(CommandMenuContext);
|
||||
|
||||
const { records: activeWorkflowVersions } =
|
||||
useActiveWorkflowVersionsWithManualTrigger({
|
||||
skip:
|
||||
containerType !== 'command-menu-list' &&
|
||||
containerType !== 'command-menu-show-page-dropdown',
|
||||
});
|
||||
|
||||
const { runWorkflowVersion } = useRunWorkflowVersion();
|
||||
|
||||
return activeWorkflowVersions
|
||||
.map((activeWorkflowVersion, index) => {
|
||||
if (!isDefined(activeWorkflowVersion.workflow)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const name = capitalize(activeWorkflowVersion.workflow.name);
|
||||
|
||||
const Icon = getIcon(
|
||||
activeWorkflowVersion.trigger?.settings.icon,
|
||||
COMMAND_MENU_DEFAULT_ICON,
|
||||
);
|
||||
|
||||
return {
|
||||
type: CommandMenuItemType.WorkflowRun,
|
||||
key: `workflow-run-${activeWorkflowVersion.id}`,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
label: name,
|
||||
shortLabel: name,
|
||||
position: index,
|
||||
isPinned:
|
||||
!contextStoreIsPageInEditMode &&
|
||||
activeWorkflowVersion.trigger?.settings?.isPinned,
|
||||
Icon,
|
||||
shouldBeRegistered: () => true,
|
||||
component: (
|
||||
<Command
|
||||
onClick={() => {
|
||||
runWorkflowVersion({
|
||||
workflowVersionId: activeWorkflowVersion.id,
|
||||
workflowId: activeWorkflowVersion.workflowId,
|
||||
});
|
||||
}}
|
||||
closeSidePanelOnCommandMenuListExecution={false}
|
||||
/>
|
||||
),
|
||||
};
|
||||
})
|
||||
.filter(isDefined);
|
||||
};
|
||||
-204
@@ -1,204 +0,0 @@
|
||||
import { MultipleRecordsCommandKeys } from '@/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys';
|
||||
import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
|
||||
import { CancelDashboardSingleRecordCommand } from '@/command-menu-item/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand';
|
||||
import { DuplicateDashboardSingleRecordCommand } from '@/command-menu-item/record/single-record/dashboard/components/DuplicateDashboardSingleRecordCommand';
|
||||
import { EditDashboardSingleRecordCommand } from '@/command-menu-item/record/single-record/dashboard/components/EditDashboardSingleRecordCommand';
|
||||
import { SaveDashboardSingleRecordCommand } from '@/command-menu-item/record/single-record/dashboard/components/SaveDashboardSingleRecordCommand';
|
||||
import { DashboardSingleRecordCommandKeys } from '@/command-menu-item/record/single-record/dashboard/types/DashboardSingleRecordCommandKeys';
|
||||
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
|
||||
import { inheritCommandMenuItemsFromDefaultConfig } from '@/command-menu-item/record/utils/inheritCommandMenuItemsFromDefaultConfig';
|
||||
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
|
||||
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
|
||||
import { CommandMenuItemViewType } from 'twenty-shared/types';
|
||||
import { PageLayoutSingleRecordActionKeys } from '@/page-layout/actions/PageLayoutSingleRecordActionKeys';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
IconCancel,
|
||||
IconCopyPlus,
|
||||
IconDeviceFloppy,
|
||||
IconPencil,
|
||||
} from 'twenty-ui/display';
|
||||
|
||||
export const DASHBOARD_COMMAND_MENU_ITEMS_CONFIG =
|
||||
inheritCommandMenuItemsFromDefaultConfig({
|
||||
config: {
|
||||
[PageLayoutSingleRecordActionKeys.EDIT_LAYOUT]: {
|
||||
key: PageLayoutSingleRecordActionKeys.EDIT_LAYOUT,
|
||||
label: msg`Edit Dashboard`,
|
||||
shortLabel: msg`Edit`,
|
||||
isPinned: true,
|
||||
position: 3,
|
||||
Icon: IconPencil,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
shouldBeRegistered: ({ selectedRecord, objectPermissions }) =>
|
||||
isDefined(selectedRecord) &&
|
||||
!selectedRecord?.isRemote &&
|
||||
!isDefined(selectedRecord?.deletedAt) &&
|
||||
isDefined(selectedRecord?.pageLayoutId) &&
|
||||
objectPermissions.canUpdateObjectRecords,
|
||||
availableOn: [CommandMenuItemViewType.SHOW_PAGE],
|
||||
component: <EditDashboardSingleRecordCommand />,
|
||||
},
|
||||
[PageLayoutSingleRecordActionKeys.SAVE_LAYOUT]: {
|
||||
key: PageLayoutSingleRecordActionKeys.SAVE_LAYOUT,
|
||||
label: msg`Save Dashboard`,
|
||||
shortLabel: msg`Save`,
|
||||
isPinned: true,
|
||||
isPrimaryCTA: true,
|
||||
position: 4,
|
||||
Icon: IconDeviceFloppy,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
shouldBeRegistered: ({ selectedRecord, objectPermissions }) =>
|
||||
isDefined(selectedRecord) &&
|
||||
!selectedRecord?.isRemote &&
|
||||
!isDefined(selectedRecord?.deletedAt) &&
|
||||
isDefined(selectedRecord?.pageLayoutId) &&
|
||||
objectPermissions.canUpdateObjectRecords,
|
||||
availableOn: [CommandMenuItemViewType.PAGE_EDIT_MODE],
|
||||
component: <SaveDashboardSingleRecordCommand />,
|
||||
},
|
||||
[PageLayoutSingleRecordActionKeys.CANCEL_LAYOUT_EDITION]: {
|
||||
key: PageLayoutSingleRecordActionKeys.CANCEL_LAYOUT_EDITION,
|
||||
label: msg`Cancel Edition`,
|
||||
shortLabel: msg`Cancel`,
|
||||
isPinned: true,
|
||||
position: 5,
|
||||
Icon: IconCancel,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
shouldBeRegistered: ({ selectedRecord, objectPermissions }) =>
|
||||
isDefined(selectedRecord) &&
|
||||
!selectedRecord?.isRemote &&
|
||||
!isDefined(selectedRecord?.deletedAt) &&
|
||||
isDefined(selectedRecord?.pageLayoutId) &&
|
||||
objectPermissions.canUpdateObjectRecords,
|
||||
availableOn: [CommandMenuItemViewType.PAGE_EDIT_MODE],
|
||||
component: <CancelDashboardSingleRecordCommand />,
|
||||
},
|
||||
[DashboardSingleRecordCommandKeys.DUPLICATE_DASHBOARD]: {
|
||||
key: DashboardSingleRecordCommandKeys.DUPLICATE_DASHBOARD,
|
||||
label: msg`Duplicate Dashboard`,
|
||||
shortLabel: msg`Duplicate`,
|
||||
isPinned: false,
|
||||
position: 6,
|
||||
Icon: IconCopyPlus,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
shouldBeRegistered: ({ selectedRecord, objectPermissions }) =>
|
||||
isDefined(selectedRecord) &&
|
||||
!selectedRecord?.isRemote &&
|
||||
!isDefined(selectedRecord?.deletedAt) &&
|
||||
objectPermissions.canUpdateObjectRecords,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
],
|
||||
component: <DuplicateDashboardSingleRecordCommand />,
|
||||
},
|
||||
},
|
||||
commandKeys: [
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
|
||||
SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
SingleRecordCommandKeys.REMOVE_FROM_FAVORITES,
|
||||
SingleRecordCommandKeys.DELETE,
|
||||
SingleRecordCommandKeys.DESTROY,
|
||||
SingleRecordCommandKeys.RESTORE,
|
||||
MultipleRecordsCommandKeys.DELETE,
|
||||
MultipleRecordsCommandKeys.DESTROY,
|
||||
MultipleRecordsCommandKeys.RESTORE,
|
||||
NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS,
|
||||
NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS,
|
||||
SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW,
|
||||
SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD,
|
||||
SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD,
|
||||
NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
|
||||
NoSelectionRecordCommandKeys.GO_TO_COMPANIES,
|
||||
NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES,
|
||||
NoSelectionRecordCommandKeys.GO_TO_SETTINGS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_TASKS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_NOTES,
|
||||
],
|
||||
propertiesToOverwrite: {
|
||||
[SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD]: {
|
||||
position: 0,
|
||||
label: msg`Navigate to next dashboard`,
|
||||
},
|
||||
[SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD]: {
|
||||
position: 1,
|
||||
label: msg`Navigate to previous dashboard`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {
|
||||
position: 2,
|
||||
label: msg`Create new dashboard`,
|
||||
},
|
||||
[SingleRecordCommandKeys.DELETE]: {
|
||||
position: 7,
|
||||
label: msg`Delete dashboard`,
|
||||
},
|
||||
[MultipleRecordsCommandKeys.DELETE]: {
|
||||
position: 12,
|
||||
label: msg`Delete dashboards`,
|
||||
},
|
||||
[SingleRecordCommandKeys.ADD_TO_FAVORITES]: {
|
||||
position: 8,
|
||||
isPinned: true,
|
||||
},
|
||||
[SingleRecordCommandKeys.REMOVE_FROM_FAVORITES]: {
|
||||
position: 9,
|
||||
isPinned: true,
|
||||
},
|
||||
[SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW]: {
|
||||
position: 10,
|
||||
label: msg`Export dashboard`,
|
||||
},
|
||||
[SingleRecordCommandKeys.DESTROY]: {
|
||||
position: 11,
|
||||
label: msg`Permanently destroy dashboard`,
|
||||
},
|
||||
[MultipleRecordsCommandKeys.DESTROY]: {
|
||||
position: 13,
|
||||
label: msg`Permanently destroy dashboards`,
|
||||
},
|
||||
[SingleRecordCommandKeys.RESTORE]: {
|
||||
position: 14,
|
||||
label: msg`Restore dashboard`,
|
||||
},
|
||||
[MultipleRecordsCommandKeys.RESTORE]: {
|
||||
position: 15,
|
||||
label: msg`Restore dashboards`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS]: {
|
||||
position: 22,
|
||||
label: msg`See deleted dashboards`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS]: {
|
||||
position: 23,
|
||||
label: msg`Hide deleted dashboards`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS]: {
|
||||
position: 24,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_PEOPLE]: {
|
||||
position: 25,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_COMPANIES]: {
|
||||
position: 26,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES]: {
|
||||
position: 27,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_SETTINGS]: {
|
||||
position: 28,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_TASKS]: {
|
||||
position: 29,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_NOTES]: {
|
||||
position: 30,
|
||||
},
|
||||
},
|
||||
});
|
||||
-813
@@ -1,813 +0,0 @@
|
||||
import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
|
||||
import { DeleteMultipleRecordsCommand } from '@/command-menu-item/record/multiple-records/components/DeleteMultipleRecordsCommand';
|
||||
import { DestroyMultipleRecordsCommand } from '@/command-menu-item/record/multiple-records/components/DestroyMultipleRecordsCommand';
|
||||
import { ExportMultipleRecordsCommand } from '@/command-menu-item/record/multiple-records/components/ExportMultipleRecordsCommand';
|
||||
import { MergeMultipleRecordsCommand } from '@/command-menu-item/record/multiple-records/components/MergeMultipleRecordsCommand';
|
||||
import { RestoreMultipleRecordsCommand } from '@/command-menu-item/record/multiple-records/components/RestoreMultipleRecordsCommand';
|
||||
import { UpdateMultipleRecordsCommand } from '@/command-menu-item/record/multiple-records/components/UpdateMultipleRecordsCommand';
|
||||
import { MultipleRecordsCommandKeys } from '@/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys';
|
||||
import { CreateNewIndexRecordNoSelectionRecordCommand } from '@/command-menu-item/record/no-selection/components/CreateNewIndexRecordNoSelectionRecordCommand';
|
||||
import { CreateNewViewNoSelectionRecordCommand } from '@/command-menu-item/record/no-selection/components/CreateNewViewNoSelectionRecordCommand';
|
||||
import { HideDeletedRecordsNoSelectionRecordCommand } from '@/command-menu-item/record/no-selection/components/HideDeletedRecordsNoSelectionRecordCommand';
|
||||
import { ImportRecordsNoSelectionRecordCommand } from '@/command-menu-item/record/no-selection/components/ImportRecordsNoSelectionRecordCommand';
|
||||
import { SeeDeletedRecordsNoSelectionRecordCommand } from '@/command-menu-item/record/no-selection/components/SeeDeletedRecordsNoSelectionRecordCommand';
|
||||
import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
|
||||
import { AddToFavoritesSingleRecordCommand } from '@/command-menu-item/record/single-record/components/AddToFavoritesSingleRecordCommand';
|
||||
import { DeleteSingleRecordCommand } from '@/command-menu-item/record/single-record/components/DeleteSingleRecordCommand';
|
||||
import { DestroySingleRecordCommand } from '@/command-menu-item/record/single-record/components/DestroySingleRecordCommand';
|
||||
import { ExportNoteSingleRecordCommand } from '@/command-menu-item/record/single-record/components/ExportNoteSingleRecordCommand';
|
||||
import { ExportSingleRecordCommand } from '@/command-menu-item/record/single-record/components/ExportSingleRecordCommand';
|
||||
import { NavigateToNextRecordSingleRecordCommand } from '@/command-menu-item/record/single-record/components/NavigateToNextRecordSingleRecordCommand';
|
||||
import { NavigateToPreviousRecordSingleRecordCommand } from '@/command-menu-item/record/single-record/components/NavigateToPreviousRecordSingleRecordCommand';
|
||||
import { RemoveFromFavoritesSingleRecordCommand } from '@/command-menu-item/record/single-record/components/RemoveFromFavoritesSingleRecordCommand';
|
||||
import { RestoreSingleRecordCommand } from '@/command-menu-item/record/single-record/components/RestoreSingleRecordCommand';
|
||||
import { EditRecordPageLayoutSingleRecordCommand } from '@/command-menu-item/record/single-record/record-page-layout/components/EditRecordPageLayoutSingleRecordCommand';
|
||||
import { RecordPageLayoutSingleRecordCommandKeys } from '@/command-menu-item/record/single-record/record-page-layout/types/RecordPageLayoutSingleRecordCommandKeys';
|
||||
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
|
||||
import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
|
||||
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
|
||||
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import {
|
||||
BACKEND_BATCH_REQUEST_MAX_COUNT,
|
||||
MUTATION_MAX_MERGE_RECORDS,
|
||||
} from 'twenty-shared/constants';
|
||||
import {
|
||||
AppPath,
|
||||
CommandMenuItemViewType,
|
||||
CoreObjectNameSingular,
|
||||
SettingsPath,
|
||||
} from 'twenty-shared/types';
|
||||
import {
|
||||
IconArrowMerge,
|
||||
IconBuildingSkyscraper,
|
||||
IconCheckbox,
|
||||
IconChevronDown,
|
||||
IconChevronUp,
|
||||
IconEdit,
|
||||
IconEyeOff,
|
||||
IconFileExport,
|
||||
IconFileImport,
|
||||
IconHeart,
|
||||
IconHeartOff,
|
||||
IconLayout,
|
||||
IconLayoutDashboard,
|
||||
IconPencil,
|
||||
IconPlus,
|
||||
IconRefresh,
|
||||
IconRotate2,
|
||||
IconSettings,
|
||||
IconSettingsAutomation,
|
||||
IconTargetArrow,
|
||||
IconTrash,
|
||||
IconTrashX,
|
||||
IconUser,
|
||||
} from 'twenty-ui/display';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
FeatureFlagKey,
|
||||
PermissionFlagType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG: Record<
|
||||
| NoSelectionRecordCommandKeys
|
||||
| SingleRecordCommandKeys
|
||||
| MultipleRecordsCommandKeys
|
||||
| RecordPageLayoutSingleRecordCommandKeys,
|
||||
CommandMenuItemConfig
|
||||
> = {
|
||||
[SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD,
|
||||
label: msg`Navigate to next record`,
|
||||
position: 0,
|
||||
isPinned: true,
|
||||
Icon: IconChevronDown,
|
||||
shouldBeRegistered: ({ isInSidePanel }) => !isInSidePanel,
|
||||
availableOn: [CommandMenuItemViewType.SHOW_PAGE],
|
||||
component: <NavigateToNextRecordSingleRecordCommand />,
|
||||
},
|
||||
[SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD,
|
||||
label: msg`Navigate to previous record`,
|
||||
position: 1,
|
||||
isPinned: true,
|
||||
Icon: IconChevronUp,
|
||||
shouldBeRegistered: ({ isInSidePanel }) => !isInSidePanel,
|
||||
availableOn: [CommandMenuItemViewType.SHOW_PAGE],
|
||||
component: <NavigateToPreviousRecordSingleRecordCommand />,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.Object,
|
||||
key: NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
|
||||
label: msg`Create new record`,
|
||||
shortLabel: msg`New record`,
|
||||
position: 2,
|
||||
isPinned: true,
|
||||
Icon: IconPlus,
|
||||
shouldBeRegistered: ({
|
||||
objectMetadataItem,
|
||||
objectPermissions,
|
||||
hasAnySoftDeleteFilterOnView,
|
||||
}) =>
|
||||
(!objectMetadataItem?.isSystem &&
|
||||
objectPermissions.canUpdateObjectRecords &&
|
||||
!hasAnySoftDeleteFilterOnView) ??
|
||||
false,
|
||||
availableOn: [CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION],
|
||||
component: <CreateNewIndexRecordNoSelectionRecordCommand />,
|
||||
},
|
||||
[SingleRecordCommandKeys.DELETE]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: SingleRecordCommandKeys.DELETE,
|
||||
label: msg`Delete`,
|
||||
shortLabel: msg`Delete`,
|
||||
position: 3,
|
||||
Icon: IconTrash,
|
||||
accent: 'default',
|
||||
isPinned: true,
|
||||
shouldBeRegistered: ({
|
||||
selectedRecord,
|
||||
hasAnySoftDeleteFilterOnView,
|
||||
objectPermissions,
|
||||
objectMetadataItem,
|
||||
}) =>
|
||||
(!objectMetadataItem?.isSystem &&
|
||||
isDefined(selectedRecord) &&
|
||||
!selectedRecord.isRemote &&
|
||||
!hasAnySoftDeleteFilterOnView &&
|
||||
objectPermissions.canSoftDeleteObjectRecords &&
|
||||
!isDefined(selectedRecord?.deletedAt)) ??
|
||||
false,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
],
|
||||
component: <DeleteSingleRecordCommand />,
|
||||
},
|
||||
[MultipleRecordsCommandKeys.DELETE]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: MultipleRecordsCommandKeys.DELETE,
|
||||
label: msg`Delete records`,
|
||||
shortLabel: msg`Delete`,
|
||||
position: 4,
|
||||
Icon: IconTrash,
|
||||
accent: 'default',
|
||||
isPinned: true,
|
||||
shouldBeRegistered: ({
|
||||
objectPermissions,
|
||||
isRemote,
|
||||
hasAnySoftDeleteFilterOnView,
|
||||
numberOfSelectedRecords,
|
||||
objectMetadataItem,
|
||||
}) =>
|
||||
(!objectMetadataItem?.isSystem &&
|
||||
objectPermissions.canSoftDeleteObjectRecords &&
|
||||
!isRemote &&
|
||||
!hasAnySoftDeleteFilterOnView &&
|
||||
isDefined(numberOfSelectedRecords) &&
|
||||
numberOfSelectedRecords < BACKEND_BATCH_REQUEST_MAX_COUNT) ??
|
||||
false,
|
||||
availableOn: [CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION],
|
||||
component: <DeleteMultipleRecordsCommand />,
|
||||
},
|
||||
[SingleRecordCommandKeys.RESTORE]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: SingleRecordCommandKeys.RESTORE,
|
||||
label: msg`Restore record`,
|
||||
shortLabel: msg`Restore`,
|
||||
position: 5,
|
||||
Icon: IconRefresh,
|
||||
accent: 'default',
|
||||
isPinned: true,
|
||||
shouldBeRegistered: ({
|
||||
selectedRecord,
|
||||
objectPermissions,
|
||||
isRemote,
|
||||
isShowPage,
|
||||
hasAnySoftDeleteFilterOnView,
|
||||
objectMetadataItem,
|
||||
}) =>
|
||||
(!objectMetadataItem?.isSystem &&
|
||||
!isRemote &&
|
||||
isDefined(selectedRecord?.deletedAt) &&
|
||||
objectPermissions.canSoftDeleteObjectRecords &&
|
||||
((isDefined(isShowPage) && isShowPage) ||
|
||||
(isDefined(hasAnySoftDeleteFilterOnView) &&
|
||||
hasAnySoftDeleteFilterOnView))) ??
|
||||
false,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
component: <RestoreSingleRecordCommand />,
|
||||
},
|
||||
[MultipleRecordsCommandKeys.RESTORE]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: MultipleRecordsCommandKeys.RESTORE,
|
||||
label: msg`Restore records`,
|
||||
shortLabel: msg`Restore`,
|
||||
position: 6,
|
||||
Icon: IconRefresh,
|
||||
accent: 'default',
|
||||
isPinned: true,
|
||||
shouldBeRegistered: ({
|
||||
objectPermissions,
|
||||
isRemote,
|
||||
hasAnySoftDeleteFilterOnView,
|
||||
numberOfSelectedRecords,
|
||||
objectMetadataItem,
|
||||
}) =>
|
||||
(!objectMetadataItem?.isSystem &&
|
||||
objectPermissions.canSoftDeleteObjectRecords &&
|
||||
!isRemote &&
|
||||
isDefined(hasAnySoftDeleteFilterOnView) &&
|
||||
hasAnySoftDeleteFilterOnView &&
|
||||
isDefined(numberOfSelectedRecords) &&
|
||||
numberOfSelectedRecords < BACKEND_BATCH_REQUEST_MAX_COUNT) ??
|
||||
false,
|
||||
availableOn: [CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION],
|
||||
component: <RestoreMultipleRecordsCommand />,
|
||||
},
|
||||
[SingleRecordCommandKeys.DESTROY]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: SingleRecordCommandKeys.DESTROY,
|
||||
label: msg`Permanently destroy record`,
|
||||
shortLabel: msg`Destroy`,
|
||||
position: 7,
|
||||
Icon: IconTrashX,
|
||||
accent: 'danger',
|
||||
isPinned: true,
|
||||
shouldBeRegistered: ({
|
||||
selectedRecord,
|
||||
objectPermissions,
|
||||
isRemote,
|
||||
objectMetadataItem,
|
||||
}) =>
|
||||
(!objectMetadataItem?.isSystem &&
|
||||
objectPermissions.canDestroyObjectRecords &&
|
||||
!isRemote &&
|
||||
isDefined(selectedRecord?.deletedAt)) ??
|
||||
false,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
],
|
||||
component: <DestroySingleRecordCommand />,
|
||||
},
|
||||
[MultipleRecordsCommandKeys.DESTROY]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: MultipleRecordsCommandKeys.DESTROY,
|
||||
label: msg`Permanently destroy records`,
|
||||
shortLabel: msg`Destroy`,
|
||||
position: 8,
|
||||
Icon: IconTrashX,
|
||||
accent: 'danger',
|
||||
isPinned: true,
|
||||
shouldBeRegistered: ({
|
||||
objectPermissions,
|
||||
isRemote,
|
||||
hasAnySoftDeleteFilterOnView,
|
||||
numberOfSelectedRecords,
|
||||
objectMetadataItem,
|
||||
}) =>
|
||||
(!objectMetadataItem?.isSystem &&
|
||||
objectPermissions.canDestroyObjectRecords &&
|
||||
!isRemote &&
|
||||
isDefined(hasAnySoftDeleteFilterOnView) &&
|
||||
hasAnySoftDeleteFilterOnView &&
|
||||
isDefined(numberOfSelectedRecords) &&
|
||||
numberOfSelectedRecords < BACKEND_BATCH_REQUEST_MAX_COUNT) ??
|
||||
false,
|
||||
availableOn: [CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION],
|
||||
component: <DestroyMultipleRecordsCommand />,
|
||||
},
|
||||
|
||||
[SingleRecordCommandKeys.ADD_TO_FAVORITES]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
label: msg`Add to favorites`,
|
||||
shortLabel: msg`Add to favorites`,
|
||||
position: 9,
|
||||
isPinned: true,
|
||||
Icon: IconHeart,
|
||||
shouldBeRegistered: ({
|
||||
selectedRecord,
|
||||
isFavorite,
|
||||
hasAnySoftDeleteFilterOnView,
|
||||
objectMetadataItem,
|
||||
}) =>
|
||||
!objectMetadataItem?.isSystem &&
|
||||
!selectedRecord?.isRemote &&
|
||||
!isFavorite &&
|
||||
!isDefined(selectedRecord?.deletedAt) &&
|
||||
!hasAnySoftDeleteFilterOnView,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
],
|
||||
component: <AddToFavoritesSingleRecordCommand />,
|
||||
},
|
||||
[SingleRecordCommandKeys.REMOVE_FROM_FAVORITES]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: SingleRecordCommandKeys.REMOVE_FROM_FAVORITES,
|
||||
label: msg`Remove from favorites`,
|
||||
shortLabel: msg`Remove from favorites`,
|
||||
isPinned: true,
|
||||
position: 10,
|
||||
Icon: IconHeartOff,
|
||||
shouldBeRegistered: ({
|
||||
selectedRecord,
|
||||
isFavorite,
|
||||
hasAnySoftDeleteFilterOnView,
|
||||
objectMetadataItem,
|
||||
}) =>
|
||||
!objectMetadataItem?.isSystem &&
|
||||
isDefined(selectedRecord) &&
|
||||
!selectedRecord?.isRemote &&
|
||||
isDefined(isFavorite) &&
|
||||
isFavorite &&
|
||||
!isDefined(selectedRecord?.deletedAt) &&
|
||||
!hasAnySoftDeleteFilterOnView,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
],
|
||||
component: <RemoveFromFavoritesSingleRecordCommand />,
|
||||
},
|
||||
[SingleRecordCommandKeys.EXPORT_NOTE_TO_PDF]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: SingleRecordCommandKeys.EXPORT_NOTE_TO_PDF,
|
||||
label: msg`Export to PDF`,
|
||||
shortLabel: msg`Export`,
|
||||
position: 11,
|
||||
isPinned: false,
|
||||
Icon: IconFileExport,
|
||||
shouldBeRegistered: ({ selectedRecord, isNoteOrTask }) =>
|
||||
isDefined(isNoteOrTask) &&
|
||||
isNoteOrTask &&
|
||||
isNonEmptyString(selectedRecord?.bodyV2?.blocknote),
|
||||
availableOn: [CommandMenuItemViewType.SHOW_PAGE],
|
||||
component: <ExportNoteSingleRecordCommand />,
|
||||
},
|
||||
[SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX,
|
||||
label: msg`Export`,
|
||||
shortLabel: msg`Export`,
|
||||
position: 12,
|
||||
Icon: IconFileExport,
|
||||
accent: 'default',
|
||||
isPinned: false,
|
||||
shouldBeRegistered: ({ selectedRecord }) =>
|
||||
isDefined(selectedRecord) && !selectedRecord.isRemote,
|
||||
availableOn: [CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION],
|
||||
component: <ExportMultipleRecordsCommand />,
|
||||
requiredPermissionFlag: PermissionFlagType.EXPORT_CSV,
|
||||
},
|
||||
[SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW,
|
||||
label: msg`Export`,
|
||||
shortLabel: msg`Export`,
|
||||
position: 13,
|
||||
Icon: IconFileExport,
|
||||
accent: 'default',
|
||||
isPinned: false,
|
||||
shouldBeRegistered: ({ selectedRecord }) =>
|
||||
isDefined(selectedRecord) && !selectedRecord.isRemote,
|
||||
availableOn: [CommandMenuItemViewType.SHOW_PAGE],
|
||||
component: <ExportSingleRecordCommand />,
|
||||
requiredPermissionFlag: PermissionFlagType.EXPORT_CSV,
|
||||
},
|
||||
[MultipleRecordsCommandKeys.UPDATE]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: MultipleRecordsCommandKeys.UPDATE,
|
||||
label: msg`Update records`,
|
||||
shortLabel: msg`Update`,
|
||||
position: 14,
|
||||
Icon: IconEdit,
|
||||
accent: 'default',
|
||||
isPinned: true,
|
||||
shouldBeRegistered: ({ objectPermissions, isRemote, objectMetadataItem }) =>
|
||||
!objectMetadataItem?.isSystem &&
|
||||
objectPermissions.canUpdateObjectRecords &&
|
||||
!isRemote,
|
||||
availableOn: [CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION],
|
||||
component: <UpdateMultipleRecordsCommand />,
|
||||
},
|
||||
[MultipleRecordsCommandKeys.MERGE]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: MultipleRecordsCommandKeys.MERGE,
|
||||
label: msg`Merge records`,
|
||||
shortLabel: msg`Merge`,
|
||||
position: 15,
|
||||
Icon: IconArrowMerge,
|
||||
accent: 'default',
|
||||
isPinned: false,
|
||||
shouldBeRegistered: ({
|
||||
objectMetadataItem,
|
||||
numberOfSelectedRecords,
|
||||
objectPermissions,
|
||||
}) =>
|
||||
isDefined(objectMetadataItem?.duplicateCriteria) &&
|
||||
isDefined(numberOfSelectedRecords) &&
|
||||
Boolean(objectPermissions.canUpdateObjectRecords) &&
|
||||
Boolean(objectPermissions.canDestroyObjectRecords) &&
|
||||
numberOfSelectedRecords <= MUTATION_MAX_MERGE_RECORDS,
|
||||
availableOn: [CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION],
|
||||
component: <MergeMultipleRecordsCommand />,
|
||||
},
|
||||
[MultipleRecordsCommandKeys.EXPORT]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: MultipleRecordsCommandKeys.EXPORT,
|
||||
label: msg`Export records`,
|
||||
shortLabel: msg`Export`,
|
||||
position: 16,
|
||||
Icon: IconFileExport,
|
||||
accent: 'default',
|
||||
isPinned: false,
|
||||
shouldBeRegistered: () => true,
|
||||
availableOn: [CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION],
|
||||
component: <ExportMultipleRecordsCommand />,
|
||||
requiredPermissionFlag: PermissionFlagType.EXPORT_CSV,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.IMPORT_RECORDS]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.Object,
|
||||
key: NoSelectionRecordCommandKeys.IMPORT_RECORDS,
|
||||
label: msg`Import records`,
|
||||
shortLabel: msg`Import`,
|
||||
position: 17,
|
||||
Icon: IconFileImport,
|
||||
accent: 'default',
|
||||
isPinned: false,
|
||||
shouldBeRegistered: ({
|
||||
objectMetadataItem,
|
||||
hasAnySoftDeleteFilterOnView,
|
||||
}) => !objectMetadataItem?.isSystem && !hasAnySoftDeleteFilterOnView,
|
||||
availableOn: [CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION],
|
||||
component: <ImportRecordsNoSelectionRecordCommand />,
|
||||
requiredPermissionFlag: PermissionFlagType.IMPORT_CSV,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.EXPORT_VIEW]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.Object,
|
||||
key: NoSelectionRecordCommandKeys.EXPORT_VIEW,
|
||||
label: msg`Export view`,
|
||||
shortLabel: msg`Export`,
|
||||
position: 18,
|
||||
Icon: IconFileExport,
|
||||
accent: 'default',
|
||||
isPinned: false,
|
||||
shouldBeRegistered: () => true,
|
||||
availableOn: [CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION],
|
||||
component: <ExportMultipleRecordsCommand />,
|
||||
requiredPermissionFlag: PermissionFlagType.EXPORT_CSV,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.Object,
|
||||
key: NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS,
|
||||
label: msg`See deleted records`,
|
||||
shortLabel: msg`Deleted records`,
|
||||
position: 19,
|
||||
Icon: IconRotate2,
|
||||
accent: 'default',
|
||||
isPinned: false,
|
||||
shouldBeRegistered: ({ hasAnySoftDeleteFilterOnView }) =>
|
||||
!hasAnySoftDeleteFilterOnView,
|
||||
availableOn: [CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION],
|
||||
component: <SeeDeletedRecordsNoSelectionRecordCommand />,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.CREATE_NEW_VIEW]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.Object,
|
||||
key: NoSelectionRecordCommandKeys.CREATE_NEW_VIEW,
|
||||
label: msg`Create View`,
|
||||
shortLabel: msg`Create View`,
|
||||
position: 20,
|
||||
Icon: IconLayout,
|
||||
accent: 'default',
|
||||
isPinned: false,
|
||||
shouldBeRegistered: ({ hasAnySoftDeleteFilterOnView }) =>
|
||||
!hasAnySoftDeleteFilterOnView,
|
||||
availableOn: [CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION],
|
||||
component: <CreateNewViewNoSelectionRecordCommand />,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.Object,
|
||||
key: NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS,
|
||||
label: msg`Hide deleted records`,
|
||||
shortLabel: msg`Hide deleted`,
|
||||
position: 21,
|
||||
Icon: IconEyeOff,
|
||||
accent: 'default',
|
||||
isPinned: false,
|
||||
shouldBeRegistered: ({ hasAnySoftDeleteFilterOnView }) =>
|
||||
isDefined(hasAnySoftDeleteFilterOnView) && hasAnySoftDeleteFilterOnView,
|
||||
availableOn: [CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION],
|
||||
component: <HideDeletedRecordsNoSelectionRecordCommand />,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS]: {
|
||||
type: CommandMenuItemType.Navigation,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS,
|
||||
label: msg`Go to Workflows`,
|
||||
shortLabel: msg`See Workflows`,
|
||||
position: 22,
|
||||
Icon: IconSettingsAutomation,
|
||||
accent: 'default',
|
||||
isPinned: false,
|
||||
shouldBeRegistered: ({
|
||||
objectMetadataItem,
|
||||
viewType,
|
||||
getTargetObjectReadPermission,
|
||||
}) =>
|
||||
getTargetObjectReadPermission(CoreObjectNameSingular.Workflow) &&
|
||||
(objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Workflow ||
|
||||
viewType === CommandMenuItemViewType.SHOW_PAGE),
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.PAGE_EDIT_MODE,
|
||||
],
|
||||
component: (
|
||||
<CommandLink
|
||||
to={AppPath.RecordIndexPage}
|
||||
params={{ objectNamePlural: CoreObjectNamePlural.Workflow }}
|
||||
/>
|
||||
),
|
||||
hotKeys: ['G', 'W'],
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_PEOPLE]: {
|
||||
type: CommandMenuItemType.Navigation,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
|
||||
label: msg`Go to People`,
|
||||
shortLabel: msg`People`,
|
||||
position: 23,
|
||||
Icon: IconUser,
|
||||
isPinned: false,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.PAGE_EDIT_MODE,
|
||||
],
|
||||
shouldBeRegistered: ({
|
||||
objectMetadataItem,
|
||||
viewType,
|
||||
getTargetObjectReadPermission,
|
||||
}) =>
|
||||
getTargetObjectReadPermission(CoreObjectNameSingular.Person) &&
|
||||
(objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Person ||
|
||||
viewType === CommandMenuItemViewType.SHOW_PAGE),
|
||||
component: (
|
||||
<CommandLink
|
||||
to={AppPath.RecordIndexPage}
|
||||
params={{ objectNamePlural: CoreObjectNamePlural.Person }}
|
||||
/>
|
||||
),
|
||||
hotKeys: ['G', 'P'],
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_COMPANIES]: {
|
||||
type: CommandMenuItemType.Navigation,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: NoSelectionRecordCommandKeys.GO_TO_COMPANIES,
|
||||
label: msg`Go to Companies`,
|
||||
shortLabel: msg`Companies`,
|
||||
position: 24,
|
||||
Icon: IconBuildingSkyscraper,
|
||||
isPinned: false,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.PAGE_EDIT_MODE,
|
||||
],
|
||||
shouldBeRegistered: ({
|
||||
objectMetadataItem,
|
||||
viewType,
|
||||
getTargetObjectReadPermission,
|
||||
}) =>
|
||||
getTargetObjectReadPermission(CoreObjectNameSingular.Company) &&
|
||||
(objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Company ||
|
||||
viewType === CommandMenuItemViewType.SHOW_PAGE),
|
||||
component: (
|
||||
<CommandLink
|
||||
to={AppPath.RecordIndexPage}
|
||||
params={{ objectNamePlural: CoreObjectNamePlural.Company }}
|
||||
/>
|
||||
),
|
||||
hotKeys: ['G', 'C'],
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS]: {
|
||||
type: CommandMenuItemType.Navigation,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS,
|
||||
label: msg`Go to Dashboards`,
|
||||
shortLabel: msg`Dashboards`,
|
||||
position: 25,
|
||||
Icon: IconLayoutDashboard,
|
||||
isPinned: false,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
],
|
||||
shouldBeRegistered: ({
|
||||
objectMetadataItem,
|
||||
viewType,
|
||||
getTargetObjectReadPermission,
|
||||
}) =>
|
||||
getTargetObjectReadPermission(CoreObjectNameSingular.Dashboard) &&
|
||||
(objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Dashboard ||
|
||||
viewType === CommandMenuItemViewType.SHOW_PAGE),
|
||||
component: (
|
||||
<CommandLink
|
||||
to={AppPath.RecordIndexPage}
|
||||
params={{ objectNamePlural: CoreObjectNamePlural.Dashboard }}
|
||||
/>
|
||||
),
|
||||
hotKeys: ['G', 'D'],
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES]: {
|
||||
type: CommandMenuItemType.Navigation,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES,
|
||||
label: msg`Go to Opportunities`,
|
||||
shortLabel: msg`Opportunities`,
|
||||
position: 26,
|
||||
Icon: IconTargetArrow,
|
||||
isPinned: false,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.PAGE_EDIT_MODE,
|
||||
],
|
||||
shouldBeRegistered: ({
|
||||
objectMetadataItem,
|
||||
viewType,
|
||||
getTargetObjectReadPermission,
|
||||
}) =>
|
||||
getTargetObjectReadPermission(CoreObjectNameSingular.Opportunity) &&
|
||||
(objectMetadataItem?.nameSingular !==
|
||||
CoreObjectNameSingular.Opportunity ||
|
||||
viewType === CommandMenuItemViewType.SHOW_PAGE),
|
||||
component: (
|
||||
<CommandLink
|
||||
to={AppPath.RecordIndexPage}
|
||||
params={{ objectNamePlural: CoreObjectNamePlural.Opportunity }}
|
||||
/>
|
||||
),
|
||||
hotKeys: ['G', 'O'],
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_SETTINGS]: {
|
||||
type: CommandMenuItemType.Navigation,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: NoSelectionRecordCommandKeys.GO_TO_SETTINGS,
|
||||
label: msg`Go to Settings`,
|
||||
shortLabel: msg`Settings`,
|
||||
position: 27,
|
||||
Icon: IconSettings,
|
||||
isPinned: false,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
],
|
||||
shouldBeRegistered: () => true,
|
||||
component: (
|
||||
<CommandLink
|
||||
to={AppPath.SettingsCatchAll}
|
||||
params={{
|
||||
'*': SettingsPath.ProfilePage,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
hotKeys: ['G', 'S'],
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_TASKS]: {
|
||||
type: CommandMenuItemType.Navigation,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: NoSelectionRecordCommandKeys.GO_TO_TASKS,
|
||||
label: msg`Go to Tasks`,
|
||||
shortLabel: msg`Tasks`,
|
||||
position: 28,
|
||||
Icon: IconCheckbox,
|
||||
isPinned: false,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.PAGE_EDIT_MODE,
|
||||
],
|
||||
shouldBeRegistered: ({
|
||||
objectMetadataItem,
|
||||
viewType,
|
||||
getTargetObjectReadPermission,
|
||||
}) =>
|
||||
getTargetObjectReadPermission(CoreObjectNameSingular.Task) &&
|
||||
(objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Task ||
|
||||
viewType === CommandMenuItemViewType.SHOW_PAGE),
|
||||
component: (
|
||||
<CommandLink
|
||||
to={AppPath.RecordIndexPage}
|
||||
params={{ objectNamePlural: CoreObjectNamePlural.Task }}
|
||||
/>
|
||||
),
|
||||
hotKeys: ['G', 'T'],
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_NOTES]: {
|
||||
type: CommandMenuItemType.Navigation,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: NoSelectionRecordCommandKeys.GO_TO_NOTES,
|
||||
label: msg`Go to Notes`,
|
||||
shortLabel: msg`Notes`,
|
||||
position: 29,
|
||||
Icon: IconCheckbox,
|
||||
isPinned: false,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.PAGE_EDIT_MODE,
|
||||
],
|
||||
shouldBeRegistered: ({
|
||||
objectMetadataItem,
|
||||
viewType,
|
||||
getTargetObjectReadPermission,
|
||||
}) =>
|
||||
getTargetObjectReadPermission(CoreObjectNameSingular.Note) &&
|
||||
(objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Note ||
|
||||
viewType === CommandMenuItemViewType.SHOW_PAGE),
|
||||
component: (
|
||||
<CommandLink
|
||||
to={AppPath.RecordIndexPage}
|
||||
params={{ objectNamePlural: CoreObjectNamePlural.Note }}
|
||||
/>
|
||||
),
|
||||
hotKeys: ['G', 'N'],
|
||||
},
|
||||
|
||||
[RecordPageLayoutSingleRecordCommandKeys.EDIT_RECORD_PAGE_LAYOUT]: {
|
||||
key: RecordPageLayoutSingleRecordCommandKeys.EDIT_RECORD_PAGE_LAYOUT,
|
||||
label: msg`Edit Layout`,
|
||||
shortLabel: msg`Edit Layout`,
|
||||
isPinned: false,
|
||||
position: 30,
|
||||
Icon: IconPencil,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
requiredPermissionFlag: PermissionFlagType.LAYOUTS,
|
||||
shouldBeRegistered: ({
|
||||
selectedRecord,
|
||||
objectPermissions,
|
||||
objectMetadataItem,
|
||||
isFeatureFlagEnabled,
|
||||
}) =>
|
||||
isFeatureFlagEnabled(
|
||||
FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED,
|
||||
) &&
|
||||
isDefined(selectedRecord) &&
|
||||
!selectedRecord?.isRemote &&
|
||||
!isDefined(selectedRecord?.deletedAt) &&
|
||||
objectPermissions.canUpdateObjectRecords &&
|
||||
objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Dashboard,
|
||||
availableOn: [CommandMenuItemViewType.SHOW_PAGE],
|
||||
component: <EditRecordPageLayoutSingleRecordCommand />,
|
||||
},
|
||||
};
|
||||
-390
@@ -1,390 +0,0 @@
|
||||
import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
|
||||
import { MultipleRecordsCommandKeys } from '@/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys';
|
||||
import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
|
||||
import { NoSelectionWorkflowRecordCommandKeys } from '@/command-menu-item/record/no-selection/workflow/types/NoSelectionWorkflowRecordCommandKeys';
|
||||
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
|
||||
import { ActivateWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/ActivateWorkflowSingleRecordCommand';
|
||||
import { AddNodeWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/AddNodeWorkflowSingleRecordCommand';
|
||||
import { DeactivateWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/DeactivateWorkflowSingleRecordCommand';
|
||||
import { DiscardDraftWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/DiscardDraftWorkflowSingleRecordCommand';
|
||||
import { DuplicateWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/DuplicateWorkflowSingleRecordCommand';
|
||||
import { SeeActiveVersionWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/SeeActiveVersionWorkflowSingleRecordCommand';
|
||||
import { SeeRunsWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/SeeRunsWorkflowSingleRecordCommand';
|
||||
import { SeeVersionsWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/SeeVersionsWorkflowSingleRecordCommand';
|
||||
import { TestWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/TestWorkflowSingleRecordCommand';
|
||||
import { TidyUpWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/TidyUpWorkflowSingleRecordCommand';
|
||||
import { WorkflowSingleRecordCommandKeys } from '@/command-menu-item/record/single-record/workflow/types/WorkflowSingleRecordCommandKeys';
|
||||
import { inheritCommandMenuItemsFromDefaultConfig } from '@/command-menu-item/record/utils/inheritCommandMenuItemsFromDefaultConfig';
|
||||
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
|
||||
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
|
||||
import { CommandMenuItemViewType, AppPath } from 'twenty-shared/types';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import {
|
||||
type WorkflowStep,
|
||||
type WorkflowTrigger,
|
||||
type WorkflowWithCurrentVersion,
|
||||
} from '@/workflow/types/Workflow';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
IconCopy,
|
||||
IconHistoryToggle,
|
||||
IconNoteOff,
|
||||
IconPlayerPause,
|
||||
IconPlayerPlay,
|
||||
IconPlus,
|
||||
IconPower,
|
||||
IconReorder,
|
||||
IconVersions,
|
||||
} from 'twenty-ui/display';
|
||||
|
||||
const areWorkflowTriggerAndStepsDefined = (
|
||||
workflowWithCurrentVersion: WorkflowWithCurrentVersion | undefined,
|
||||
): workflowWithCurrentVersion is WorkflowWithCurrentVersion & {
|
||||
currentVersion: {
|
||||
trigger: WorkflowTrigger;
|
||||
steps: Array<WorkflowStep>;
|
||||
};
|
||||
} => {
|
||||
return (
|
||||
isDefined(workflowWithCurrentVersion?.currentVersion?.trigger) &&
|
||||
isDefined(workflowWithCurrentVersion.currentVersion?.steps) &&
|
||||
workflowWithCurrentVersion.currentVersion.steps.length > 0
|
||||
);
|
||||
};
|
||||
|
||||
export const WORKFLOW_COMMAND_MENU_ITEMS_CONFIG =
|
||||
inheritCommandMenuItemsFromDefaultConfig({
|
||||
config: {
|
||||
[WorkflowSingleRecordCommandKeys.ACTIVATE]: {
|
||||
key: WorkflowSingleRecordCommandKeys.ACTIVATE,
|
||||
label: msg`Activate Workflow`,
|
||||
shortLabel: msg`Activate`,
|
||||
isPinned: true,
|
||||
isPrimaryCTA: true,
|
||||
position: 3,
|
||||
Icon: IconPower,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
|
||||
areWorkflowTriggerAndStepsDefined(workflowWithCurrentVersion) &&
|
||||
(workflowWithCurrentVersion.currentVersion.status === 'DRAFT' ||
|
||||
!workflowWithCurrentVersion.versions?.some(
|
||||
(version) => version.status === 'ACTIVE',
|
||||
)) &&
|
||||
!isDefined(selectedRecord?.deletedAt),
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
component: <ActivateWorkflowSingleRecordCommand />,
|
||||
},
|
||||
[WorkflowSingleRecordCommandKeys.DEACTIVATE]: {
|
||||
key: WorkflowSingleRecordCommandKeys.DEACTIVATE,
|
||||
label: msg`Deactivate Workflow`,
|
||||
shortLabel: msg`Deactivate`,
|
||||
isPinned: true,
|
||||
position: 4,
|
||||
Icon: IconPlayerPause,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
|
||||
isDefined(workflowWithCurrentVersion) &&
|
||||
workflowWithCurrentVersion.currentVersion.status === 'ACTIVE' &&
|
||||
!isDefined(selectedRecord?.deletedAt),
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
component: <DeactivateWorkflowSingleRecordCommand />,
|
||||
},
|
||||
[WorkflowSingleRecordCommandKeys.DISCARD_DRAFT]: {
|
||||
key: WorkflowSingleRecordCommandKeys.DISCARD_DRAFT,
|
||||
label: msg`Discard Draft`,
|
||||
shortLabel: msg`Discard Draft`,
|
||||
isPinned: true,
|
||||
position: 5,
|
||||
Icon: IconNoteOff,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
|
||||
isDefined(workflowWithCurrentVersion) &&
|
||||
workflowWithCurrentVersion.versions.length > 1 &&
|
||||
workflowWithCurrentVersion.currentVersion.status === 'DRAFT' &&
|
||||
!isDefined(selectedRecord?.deletedAt),
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
component: <DiscardDraftWorkflowSingleRecordCommand />,
|
||||
},
|
||||
[WorkflowSingleRecordCommandKeys.TEST]: {
|
||||
key: WorkflowSingleRecordCommandKeys.TEST,
|
||||
label: msg`Test Workflow`,
|
||||
shortLabel: msg`Test`,
|
||||
isPinned: true,
|
||||
position: 6,
|
||||
Icon: IconPlayerPlay,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
|
||||
areWorkflowTriggerAndStepsDefined(workflowWithCurrentVersion) &&
|
||||
((workflowWithCurrentVersion.currentVersion.trigger.type ===
|
||||
'MANUAL' &&
|
||||
!isDefined(
|
||||
workflowWithCurrentVersion.currentVersion.trigger.settings
|
||||
.objectType,
|
||||
)) ||
|
||||
workflowWithCurrentVersion.currentVersion.trigger.type ===
|
||||
'WEBHOOK' ||
|
||||
workflowWithCurrentVersion.currentVersion.trigger.type ===
|
||||
'CRON') &&
|
||||
!isDefined(selectedRecord?.deletedAt),
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
component: <TestWorkflowSingleRecordCommand />,
|
||||
},
|
||||
|
||||
[WorkflowSingleRecordCommandKeys.SEE_ACTIVE_VERSION]: {
|
||||
key: WorkflowSingleRecordCommandKeys.SEE_ACTIVE_VERSION,
|
||||
label: msg`See active version`,
|
||||
shortLabel: msg`See active version`,
|
||||
isPinned: false,
|
||||
position: 7,
|
||||
Icon: IconVersions,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
shouldBeRegistered: ({ workflowWithCurrentVersion, selectedRecord }) =>
|
||||
(workflowWithCurrentVersion?.statuses?.includes('ACTIVE') || false) &&
|
||||
(workflowWithCurrentVersion?.statuses?.includes('DRAFT') || false) &&
|
||||
!isDefined(selectedRecord?.deletedAt),
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
component: <SeeActiveVersionWorkflowSingleRecordCommand />,
|
||||
},
|
||||
[WorkflowSingleRecordCommandKeys.SEE_RUNS]: {
|
||||
key: WorkflowSingleRecordCommandKeys.SEE_RUNS,
|
||||
label: msg`See runs`,
|
||||
shortLabel: msg`See runs`,
|
||||
isPinned: true,
|
||||
position: 8,
|
||||
Icon: IconHistoryToggle,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
|
||||
isDefined(workflowWithCurrentVersion) &&
|
||||
!isDefined(selectedRecord?.deletedAt),
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
component: <SeeRunsWorkflowSingleRecordCommand />,
|
||||
},
|
||||
[WorkflowSingleRecordCommandKeys.SEE_VERSIONS]: {
|
||||
key: WorkflowSingleRecordCommandKeys.SEE_VERSIONS,
|
||||
label: msg`See versions history`,
|
||||
shortLabel: msg`See versions`,
|
||||
isPinned: false,
|
||||
position: 9,
|
||||
Icon: IconVersions,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
|
||||
isDefined(workflowWithCurrentVersion) &&
|
||||
!isDefined(selectedRecord?.deletedAt),
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
component: <SeeVersionsWorkflowSingleRecordCommand />,
|
||||
},
|
||||
|
||||
[WorkflowSingleRecordCommandKeys.ADD_NODE]: {
|
||||
key: WorkflowSingleRecordCommandKeys.ADD_NODE,
|
||||
label: msg`Add a node`,
|
||||
shortLabel: msg`Add a node`,
|
||||
isPinned: true,
|
||||
position: 10,
|
||||
Icon: IconPlus,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
|
||||
areWorkflowTriggerAndStepsDefined(workflowWithCurrentVersion) &&
|
||||
!isDefined(selectedRecord?.deletedAt),
|
||||
availableOn: [CommandMenuItemViewType.SHOW_PAGE],
|
||||
component: <AddNodeWorkflowSingleRecordCommand />,
|
||||
},
|
||||
[WorkflowSingleRecordCommandKeys.TIDY_UP]: {
|
||||
key: WorkflowSingleRecordCommandKeys.TIDY_UP,
|
||||
label: msg`Tidy up workflow`,
|
||||
shortLabel: msg`Tidy up`,
|
||||
isPinned: false,
|
||||
position: 11,
|
||||
Icon: IconReorder,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
|
||||
areWorkflowTriggerAndStepsDefined(workflowWithCurrentVersion) &&
|
||||
!isDefined(selectedRecord?.deletedAt),
|
||||
availableOn: [CommandMenuItemViewType.SHOW_PAGE],
|
||||
component: <TidyUpWorkflowSingleRecordCommand />,
|
||||
},
|
||||
[WorkflowSingleRecordCommandKeys.DUPLICATE_WORKFLOW]: {
|
||||
key: WorkflowSingleRecordCommandKeys.DUPLICATE_WORKFLOW,
|
||||
label: msg`Duplicate Workflow`,
|
||||
shortLabel: msg`Duplicate`,
|
||||
isPinned: false,
|
||||
position: 12,
|
||||
Icon: IconCopy,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
|
||||
isDefined(workflowWithCurrentVersion) &&
|
||||
isDefined(workflowWithCurrentVersion.currentVersion) &&
|
||||
!isDefined(selectedRecord?.deletedAt),
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
component: <DuplicateWorkflowSingleRecordCommand />,
|
||||
},
|
||||
[NoSelectionWorkflowRecordCommandKeys.GO_TO_RUNS]: {
|
||||
type: CommandMenuItemType.Navigation,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: NoSelectionWorkflowRecordCommandKeys.GO_TO_RUNS,
|
||||
label: msg`Go to runs`,
|
||||
shortLabel: msg`See runs`,
|
||||
position: 22,
|
||||
Icon: IconHistoryToggle,
|
||||
accent: 'default',
|
||||
isPinned: true,
|
||||
shouldBeRegistered: ({ hasAnySoftDeleteFilterOnView }) =>
|
||||
!hasAnySoftDeleteFilterOnView,
|
||||
availableOn: [CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION],
|
||||
component: (
|
||||
<CommandLink
|
||||
to={AppPath.RecordIndexPage}
|
||||
params={{ objectNamePlural: CoreObjectNamePlural.WorkflowRun }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
commandKeys: [
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
|
||||
SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD,
|
||||
SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD,
|
||||
SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
SingleRecordCommandKeys.REMOVE_FROM_FAVORITES,
|
||||
SingleRecordCommandKeys.DELETE,
|
||||
SingleRecordCommandKeys.DESTROY,
|
||||
SingleRecordCommandKeys.RESTORE,
|
||||
SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX,
|
||||
SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW,
|
||||
MultipleRecordsCommandKeys.DELETE,
|
||||
MultipleRecordsCommandKeys.DESTROY,
|
||||
MultipleRecordsCommandKeys.RESTORE,
|
||||
MultipleRecordsCommandKeys.EXPORT,
|
||||
NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS,
|
||||
NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
|
||||
NoSelectionRecordCommandKeys.GO_TO_COMPANIES,
|
||||
NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES,
|
||||
NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_SETTINGS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_TASKS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_NOTES,
|
||||
],
|
||||
propertiesToOverwrite: {
|
||||
[SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD]: {
|
||||
position: 0,
|
||||
label: msg`Navigate to next workflow`,
|
||||
},
|
||||
[SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD]: {
|
||||
position: 1,
|
||||
label: msg`Navigate to previous workflow`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {
|
||||
position: 2,
|
||||
label: msg`Create new workflow`,
|
||||
},
|
||||
[SingleRecordCommandKeys.DELETE]: {
|
||||
position: 12,
|
||||
label: msg`Delete workflow`,
|
||||
},
|
||||
[MultipleRecordsCommandKeys.DELETE]: {
|
||||
position: 13,
|
||||
label: msg`Delete workflows`,
|
||||
},
|
||||
[SingleRecordCommandKeys.ADD_TO_FAVORITES]: {
|
||||
position: 14,
|
||||
isPinned: false,
|
||||
},
|
||||
[SingleRecordCommandKeys.REMOVE_FROM_FAVORITES]: {
|
||||
position: 15,
|
||||
isPinned: false,
|
||||
},
|
||||
[SingleRecordCommandKeys.DESTROY]: {
|
||||
position: 16,
|
||||
label: msg`Permanently destroy workflow`,
|
||||
},
|
||||
[SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX]: {
|
||||
position: 17,
|
||||
label: msg`Export workflow`,
|
||||
shouldBeRegistered: ({ selectedRecord }) =>
|
||||
!isDefined(selectedRecord?.deletedAt),
|
||||
},
|
||||
[SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW]: {
|
||||
position: 18,
|
||||
label: msg`Export workflow`,
|
||||
},
|
||||
[MultipleRecordsCommandKeys.EXPORT]: {
|
||||
position: 19,
|
||||
label: msg`Export workflows`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.EXPORT_VIEW]: {
|
||||
position: 20,
|
||||
label: msg`Export view`,
|
||||
},
|
||||
[MultipleRecordsCommandKeys.DESTROY]: {
|
||||
position: 21,
|
||||
label: msg`Permanently destroy workflows`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS]: {
|
||||
position: 22,
|
||||
label: msg`See deleted workflows`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS]: {
|
||||
position: 23,
|
||||
label: msg`Hide deleted workflows`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.IMPORT_RECORDS]: {
|
||||
position: 24,
|
||||
label: msg`Import workflows`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_PEOPLE]: {
|
||||
position: 25,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_COMPANIES]: {
|
||||
position: 26,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES]: {
|
||||
position: 27,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS]: {
|
||||
position: 28,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_SETTINGS]: {
|
||||
position: 29,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_TASKS]: {
|
||||
position: 30,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_NOTES]: {
|
||||
position: 31,
|
||||
},
|
||||
},
|
||||
});
|
||||
-165
@@ -1,165 +0,0 @@
|
||||
import { MultipleRecordsCommandKeys } from '@/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys';
|
||||
import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
|
||||
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
|
||||
import { SeeVersionWorkflowRunSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow-runs/components/SeeVersionWorkflowRunSingleRecordCommand';
|
||||
import { SeeWorkflowWorkflowRunSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow-runs/components/SeeWorkflowWorkflowRunSingleRecordCommand';
|
||||
import { StopWorkflowRunSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow-runs/components/StopWorkflowRunSingleRecordCommand';
|
||||
import { WorkflowRunSingleRecordCommandKeys } from '@/command-menu-item/record/single-record/workflow-runs/types/WorkflowRunSingleRecordCommandKeys';
|
||||
import { inheritCommandMenuItemsFromDefaultConfig } from '@/command-menu-item/record/utils/inheritCommandMenuItemsFromDefaultConfig';
|
||||
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
|
||||
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
|
||||
import { CommandMenuItemViewType } from 'twenty-shared/types';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import {
|
||||
IconPlayerStop,
|
||||
IconSettingsAutomation,
|
||||
IconVersions,
|
||||
} from 'twenty-ui/display';
|
||||
|
||||
export const WORKFLOW_RUNS_COMMAND_MENU_ITEMS_CONFIG =
|
||||
inheritCommandMenuItemsFromDefaultConfig({
|
||||
config: {
|
||||
[WorkflowRunSingleRecordCommandKeys.SEE_VERSION]: {
|
||||
key: WorkflowRunSingleRecordCommandKeys.SEE_VERSION,
|
||||
label: msg`See version`,
|
||||
shortLabel: msg`See version`,
|
||||
position: 0,
|
||||
isPinned: true,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
Icon: IconVersions,
|
||||
shouldBeRegistered: () => true,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
component: <SeeVersionWorkflowRunSingleRecordCommand />,
|
||||
},
|
||||
|
||||
[WorkflowRunSingleRecordCommandKeys.SEE_WORKFLOW]: {
|
||||
key: WorkflowRunSingleRecordCommandKeys.SEE_WORKFLOW,
|
||||
label: msg`See workflow`,
|
||||
shortLabel: msg`See workflow`,
|
||||
position: 1,
|
||||
isPinned: true,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
Icon: IconSettingsAutomation,
|
||||
shouldBeRegistered: () => true,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
component: <SeeWorkflowWorkflowRunSingleRecordCommand />,
|
||||
},
|
||||
[WorkflowRunSingleRecordCommandKeys.STOP]: {
|
||||
key: WorkflowRunSingleRecordCommandKeys.STOP,
|
||||
label: msg`Stop`,
|
||||
shortLabel: msg`Stop`,
|
||||
position: 2,
|
||||
isPinned: true,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
Icon: IconPlayerStop,
|
||||
shouldBeRegistered: ({ selectedRecord, isSelectAll }) => {
|
||||
if (isSelectAll === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const stoppableStatuses = ['NOT_STARTED', 'ENQUEUED', 'RUNNING'];
|
||||
return stoppableStatuses.includes(selectedRecord?.status);
|
||||
},
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
|
||||
],
|
||||
component: <StopWorkflowRunSingleRecordCommand />,
|
||||
},
|
||||
},
|
||||
commandKeys: [
|
||||
SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
SingleRecordCommandKeys.REMOVE_FROM_FAVORITES,
|
||||
SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD,
|
||||
SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD,
|
||||
SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX,
|
||||
SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW,
|
||||
MultipleRecordsCommandKeys.EXPORT,
|
||||
NoSelectionRecordCommandKeys.EXPORT_VIEW,
|
||||
NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS,
|
||||
NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
|
||||
NoSelectionRecordCommandKeys.GO_TO_COMPANIES,
|
||||
NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES,
|
||||
NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_SETTINGS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_TASKS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_NOTES,
|
||||
],
|
||||
propertiesToOverwrite: {
|
||||
[SingleRecordCommandKeys.ADD_TO_FAVORITES]: {
|
||||
isPinned: false,
|
||||
position: 3,
|
||||
},
|
||||
[SingleRecordCommandKeys.REMOVE_FROM_FAVORITES]: {
|
||||
isPinned: false,
|
||||
position: 4,
|
||||
},
|
||||
[SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX]: {
|
||||
position: 5,
|
||||
label: msg`Export run`,
|
||||
},
|
||||
[SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW]: {
|
||||
position: 5,
|
||||
label: msg`Export run`,
|
||||
},
|
||||
[MultipleRecordsCommandKeys.EXPORT]: {
|
||||
position: 6,
|
||||
label: msg`Export runs`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.EXPORT_VIEW]: {
|
||||
position: 7,
|
||||
label: msg`Export view`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS]: {
|
||||
position: 8,
|
||||
label: msg`See deleted runs`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS]: {
|
||||
position: 9,
|
||||
label: msg`Hide deleted runs`,
|
||||
},
|
||||
[SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD]: {
|
||||
position: 10,
|
||||
},
|
||||
[SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD]: {
|
||||
position: 11,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS]: {
|
||||
position: 12,
|
||||
isPinned: true,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_PEOPLE]: {
|
||||
position: 13,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_COMPANIES]: {
|
||||
position: 14,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES]: {
|
||||
position: 15,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS]: {
|
||||
position: 16,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_SETTINGS]: {
|
||||
position: 17,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_TASKS]: {
|
||||
position: 18,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_NOTES]: {
|
||||
position: 19,
|
||||
},
|
||||
},
|
||||
});
|
||||
-199
@@ -1,199 +0,0 @@
|
||||
import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
|
||||
import { MultipleRecordsCommandKeys } from '@/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys';
|
||||
import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
|
||||
import { NoSelectionWorkflowRecordCommandKeys } from '@/command-menu-item/record/no-selection/workflow/types/NoSelectionWorkflowRecordCommandKeys';
|
||||
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
|
||||
import { SeeRunsWorkflowVersionSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow-versions/components/SeeRunsWorkflowVersionSingleRecordCommand';
|
||||
import { SeeVersionsWorkflowVersionSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow-versions/components/SeeVersionsWorkflowVersionSingleRecordCommand';
|
||||
import { SeeWorkflowWorkflowVersionSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow-versions/components/SeeWorkflowWorkflowVersionSingleRecordCommand';
|
||||
import { UseAsDraftWorkflowVersionSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow-versions/components/UseAsDraftWorkflowVersionSingleRecordCommand';
|
||||
import { WorkflowVersionSingleRecordCommandKeys } from '@/command-menu-item/record/single-record/workflow-versions/types/WorkflowVersionSingleRecordCommandKeys';
|
||||
import { inheritCommandMenuItemsFromDefaultConfig } from '@/command-menu-item/record/utils/inheritCommandMenuItemsFromDefaultConfig';
|
||||
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
|
||||
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
|
||||
import { CommandMenuItemViewType, AppPath } from 'twenty-shared/types';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
IconHistoryToggle,
|
||||
IconPencil,
|
||||
IconSettingsAutomation,
|
||||
IconVersions,
|
||||
} from 'twenty-ui/display';
|
||||
|
||||
export const WORKFLOW_VERSIONS_COMMAND_MENU_ITEMS_CONFIG =
|
||||
inheritCommandMenuItemsFromDefaultConfig({
|
||||
config: {
|
||||
[WorkflowVersionSingleRecordCommandKeys.SEE_RUNS]: {
|
||||
key: WorkflowVersionSingleRecordCommandKeys.SEE_RUNS,
|
||||
label: msg`See runs`,
|
||||
shortLabel: msg`See runs`,
|
||||
position: 1,
|
||||
isPinned: true,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
Icon: IconHistoryToggle,
|
||||
shouldBeRegistered: ({ workflowWithCurrentVersion }) =>
|
||||
isDefined(workflowWithCurrentVersion),
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
component: <SeeRunsWorkflowVersionSingleRecordCommand />,
|
||||
},
|
||||
[WorkflowVersionSingleRecordCommandKeys.SEE_WORKFLOW]: {
|
||||
key: WorkflowVersionSingleRecordCommandKeys.SEE_WORKFLOW,
|
||||
label: msg`See workflow`,
|
||||
shortLabel: msg`See workflow`,
|
||||
position: 2,
|
||||
isPinned: true,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
Icon: IconSettingsAutomation,
|
||||
shouldBeRegistered: ({ selectedRecord }) =>
|
||||
isDefined(selectedRecord?.workflow?.id),
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
component: <SeeWorkflowWorkflowVersionSingleRecordCommand />,
|
||||
},
|
||||
[WorkflowVersionSingleRecordCommandKeys.USE_AS_DRAFT]: {
|
||||
key: WorkflowVersionSingleRecordCommandKeys.USE_AS_DRAFT,
|
||||
label: msg`Use as draft`,
|
||||
shortLabel: msg`Use as draft`,
|
||||
position: 3,
|
||||
isPinned: true,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
Icon: IconPencil,
|
||||
shouldBeRegistered: ({ selectedRecord }) =>
|
||||
isDefined(selectedRecord) && selectedRecord.status !== 'DRAFT',
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
component: <UseAsDraftWorkflowVersionSingleRecordCommand />,
|
||||
},
|
||||
[WorkflowVersionSingleRecordCommandKeys.SEE_VERSIONS]: {
|
||||
key: WorkflowVersionSingleRecordCommandKeys.SEE_VERSIONS,
|
||||
label: msg`See versions history`,
|
||||
shortLabel: msg`See versions`,
|
||||
position: 4,
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
Icon: IconVersions,
|
||||
shouldBeRegistered: ({ workflowWithCurrentVersion }) =>
|
||||
isDefined(workflowWithCurrentVersion),
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
component: <SeeVersionsWorkflowVersionSingleRecordCommand />,
|
||||
},
|
||||
[NoSelectionWorkflowRecordCommandKeys.GO_TO_RUNS]: {
|
||||
type: CommandMenuItemType.Navigation,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: NoSelectionWorkflowRecordCommandKeys.GO_TO_RUNS,
|
||||
label: msg`Go to runs`,
|
||||
shortLabel: msg`See runs`,
|
||||
position: 14,
|
||||
Icon: IconHistoryToggle,
|
||||
accent: 'default',
|
||||
isPinned: true,
|
||||
shouldBeRegistered: () => true,
|
||||
availableOn: [CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION],
|
||||
component: (
|
||||
<CommandLink
|
||||
to={AppPath.RecordIndexPage}
|
||||
params={{ objectNamePlural: CoreObjectNamePlural.WorkflowRun }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
commandKeys: [
|
||||
SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD,
|
||||
SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD,
|
||||
SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
SingleRecordCommandKeys.REMOVE_FROM_FAVORITES,
|
||||
SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX,
|
||||
SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW,
|
||||
MultipleRecordsCommandKeys.EXPORT,
|
||||
NoSelectionRecordCommandKeys.EXPORT_VIEW,
|
||||
NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS,
|
||||
NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
|
||||
NoSelectionRecordCommandKeys.GO_TO_COMPANIES,
|
||||
NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES,
|
||||
NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_SETTINGS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_TASKS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_NOTES,
|
||||
],
|
||||
propertiesToOverwrite: {
|
||||
[SingleRecordCommandKeys.ADD_TO_FAVORITES]: {
|
||||
position: 5,
|
||||
isPinned: false,
|
||||
},
|
||||
[SingleRecordCommandKeys.REMOVE_FROM_FAVORITES]: {
|
||||
position: 6,
|
||||
isPinned: false,
|
||||
},
|
||||
[SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX]: {
|
||||
position: 7,
|
||||
label: msg`Export version`,
|
||||
},
|
||||
[SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW]: {
|
||||
position: 7,
|
||||
label: msg`Export version`,
|
||||
},
|
||||
[MultipleRecordsCommandKeys.EXPORT]: {
|
||||
position: 8,
|
||||
label: msg`Export versions`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.EXPORT_VIEW]: {
|
||||
position: 9,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS]: {
|
||||
position: 10,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS]: {
|
||||
position: 11,
|
||||
},
|
||||
[SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD]: {
|
||||
position: 12,
|
||||
label: msg`Navigate to previous version`,
|
||||
},
|
||||
[SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD]: {
|
||||
position: 13,
|
||||
label: msg`Navigate to next version`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS]: {
|
||||
position: 15,
|
||||
isPinned: true,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_PEOPLE]: {
|
||||
position: 16,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_COMPANIES]: {
|
||||
position: 17,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES]: {
|
||||
position: 18,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS]: {
|
||||
position: 19,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_SETTINGS]: {
|
||||
position: 20,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_TASKS]: {
|
||||
position: 21,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_NOTES]: {
|
||||
position: 22,
|
||||
},
|
||||
},
|
||||
});
|
||||
-126
@@ -1,126 +0,0 @@
|
||||
import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
|
||||
import { MultipleRecordsCommandKeys } from '@/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys';
|
||||
import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
|
||||
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
|
||||
import { inheritCommandMenuItemsFromDefaultConfig } from '@/command-menu-item/record/utils/inheritCommandMenuItemsFromDefaultConfig';
|
||||
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
|
||||
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
|
||||
import {
|
||||
CommandMenuItemViewType,
|
||||
AppPath,
|
||||
SettingsPath,
|
||||
} from 'twenty-shared/types';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { IconSettings } from 'twenty-ui/display';
|
||||
|
||||
export const WORKSPACE_MEMBERS_COMMAND_MENU_ITEMS_CONFIG =
|
||||
inheritCommandMenuItemsFromDefaultConfig({
|
||||
config: {
|
||||
[NoSelectionRecordCommandKeys.GO_TO_SETTINGS]: {
|
||||
type: CommandMenuItemType.Navigation,
|
||||
scope: CommandMenuItemScope.Global,
|
||||
key: NoSelectionRecordCommandKeys.GO_TO_SETTINGS,
|
||||
label: msg`Manage members in settings`,
|
||||
shortLabel: msg`Manage in settings`,
|
||||
position: 14,
|
||||
Icon: IconSettings,
|
||||
isPinned: true,
|
||||
availableOn: [
|
||||
CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
|
||||
CommandMenuItemViewType.SHOW_PAGE,
|
||||
],
|
||||
shouldBeRegistered: () => true,
|
||||
component: (
|
||||
<CommandLink
|
||||
to={AppPath.SettingsCatchAll}
|
||||
params={{
|
||||
'*': SettingsPath.WorkspaceMembersPage,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
hotKeys: ['G', 'S'],
|
||||
},
|
||||
},
|
||||
commandKeys: [
|
||||
SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
SingleRecordCommandKeys.REMOVE_FROM_FAVORITES,
|
||||
SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD,
|
||||
SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD,
|
||||
SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX,
|
||||
SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW,
|
||||
MultipleRecordsCommandKeys.EXPORT,
|
||||
NoSelectionRecordCommandKeys.EXPORT_VIEW,
|
||||
NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS,
|
||||
NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
|
||||
NoSelectionRecordCommandKeys.GO_TO_COMPANIES,
|
||||
NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES,
|
||||
NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_TASKS,
|
||||
NoSelectionRecordCommandKeys.GO_TO_NOTES,
|
||||
],
|
||||
propertiesToOverwrite: {
|
||||
[SingleRecordCommandKeys.ADD_TO_FAVORITES]: {
|
||||
isPinned: false,
|
||||
position: 0,
|
||||
},
|
||||
[SingleRecordCommandKeys.REMOVE_FROM_FAVORITES]: {
|
||||
isPinned: false,
|
||||
position: 1,
|
||||
},
|
||||
[SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX]: {
|
||||
position: 2,
|
||||
label: msg`Export member`,
|
||||
},
|
||||
[SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW]: {
|
||||
position: 2,
|
||||
label: msg`Export member`,
|
||||
},
|
||||
[MultipleRecordsCommandKeys.EXPORT]: {
|
||||
position: 3,
|
||||
label: msg`Export members`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.EXPORT_VIEW]: {
|
||||
position: 4,
|
||||
label: msg`Export view`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS]: {
|
||||
position: 5,
|
||||
label: msg`See deleted members`,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS]: {
|
||||
position: 6,
|
||||
label: msg`Hide deleted members`,
|
||||
},
|
||||
[SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD]: {
|
||||
position: 7,
|
||||
},
|
||||
[SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD]: {
|
||||
position: 8,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS]: {
|
||||
position: 9,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_PEOPLE]: {
|
||||
position: 10,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_COMPANIES]: {
|
||||
position: 11,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES]: {
|
||||
position: 12,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS]: {
|
||||
position: 13,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_TASKS]: {
|
||||
position: 15,
|
||||
},
|
||||
[NoSelectionRecordCommandKeys.GO_TO_NOTES]: {
|
||||
position: 16,
|
||||
},
|
||||
},
|
||||
});
|
||||
-102
@@ -1,102 +0,0 @@
|
||||
import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { computeProgressText } from '@/command-menu-item/utils/computeProgressText';
|
||||
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
|
||||
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreFilterGroupsComponentState } from '@/context-store/states/contextStoreFilterGroupsComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||
import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize';
|
||||
import { useIncrementalDeleteManyRecords } from '@/object-record/hooks/useIncrementalDeleteManyRecords';
|
||||
import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
|
||||
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useContext } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const DeleteMultipleRecordsCommand = () => {
|
||||
const { recordIndexId, objectMetadataItem } =
|
||||
useRecordIndexIdFromCurrentContextStore();
|
||||
|
||||
const contextStoreCurrentViewId = useAtomComponentStateValue(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
|
||||
if (!contextStoreCurrentViewId) {
|
||||
throw new Error('Current view ID is not defined');
|
||||
}
|
||||
|
||||
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
|
||||
|
||||
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
const contextStoreFilters = useAtomComponentStateValue(
|
||||
contextStoreFiltersComponentState,
|
||||
);
|
||||
|
||||
const contextStoreFilterGroups = useAtomComponentStateValue(
|
||||
contextStoreFilterGroupsComponentState,
|
||||
);
|
||||
|
||||
const contextStoreAnyFieldFilterValue = useAtomComponentStateValue(
|
||||
contextStoreAnyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { removeSelectedRecordsFromRecordBoard } =
|
||||
useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const graphqlFilter = computeContextStoreFilters({
|
||||
contextStoreTargetedRecordsRule,
|
||||
contextStoreFilters,
|
||||
contextStoreFilterGroups,
|
||||
objectMetadataItem,
|
||||
filterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue,
|
||||
});
|
||||
|
||||
const { incrementalDeleteManyRecords, progress } =
|
||||
useIncrementalDeleteManyRecords({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
filter: graphqlFilter,
|
||||
pageSize: DEFAULT_QUERY_PAGE_SIZE,
|
||||
delayInMsBetweenMutations: 50,
|
||||
});
|
||||
|
||||
const actionConfig = useContext(CommandConfigContext);
|
||||
|
||||
if (!isDefined(actionConfig)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const originalLabel = getCommandMenuItemLabel(actionConfig.label);
|
||||
|
||||
const originalShortLabel = getCommandMenuItemLabel(actionConfig.shortLabel);
|
||||
|
||||
const progressText = computeProgressText(progress);
|
||||
|
||||
const actionConfigWithProgress = {
|
||||
...actionConfig,
|
||||
label: `${originalLabel}${progressText}`,
|
||||
shortLabel: `${originalShortLabel}${progressText}`,
|
||||
};
|
||||
|
||||
const handleDeleteClick = async () => {
|
||||
removeSelectedRecordsFromRecordBoard();
|
||||
resetTableRowSelection();
|
||||
await incrementalDeleteManyRecords();
|
||||
};
|
||||
|
||||
return (
|
||||
<CommandConfigContext.Provider value={actionConfigWithProgress}>
|
||||
<Command onClick={handleDeleteClick} />
|
||||
</CommandConfigContext.Provider>
|
||||
);
|
||||
};
|
||||
-114
@@ -1,114 +0,0 @@
|
||||
import { CommandModal } from '@/command-menu-item/display/components/CommandModal';
|
||||
import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
|
||||
import { computeProgressText } from '@/command-menu-item/utils/computeProgressText';
|
||||
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
|
||||
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreFilterGroupsComponentState } from '@/context-store/states/contextStoreFilterGroupsComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||
import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize';
|
||||
import { useIncrementalDestroyManyRecords } from '@/object-record/hooks/useIncrementalDestroyManyRecords';
|
||||
import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
|
||||
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useContext } from 'react';
|
||||
import { type RecordGqlOperationFilter } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const DestroyMultipleRecordsCommand = () => {
|
||||
const { recordIndexId, objectMetadataItem } =
|
||||
useRecordIndexIdFromCurrentContextStore();
|
||||
|
||||
const contextStoreCurrentViewId = useAtomComponentStateValue(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
|
||||
if (!contextStoreCurrentViewId) {
|
||||
throw new Error('Current view ID is not defined');
|
||||
}
|
||||
|
||||
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
|
||||
const { removeSelectedRecordsFromRecordBoard } =
|
||||
useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
|
||||
|
||||
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
const contextStoreFilters = useAtomComponentStateValue(
|
||||
contextStoreFiltersComponentState,
|
||||
);
|
||||
|
||||
const contextStoreFilterGroups = useAtomComponentStateValue(
|
||||
contextStoreFilterGroupsComponentState,
|
||||
);
|
||||
|
||||
const contextStoreAnyFieldFilterValue = useAtomComponentStateValue(
|
||||
contextStoreAnyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const deletedAtFilter: RecordGqlOperationFilter = {
|
||||
deletedAt: { is: 'NOT_NULL' },
|
||||
};
|
||||
const graphqlFilter = {
|
||||
...computeContextStoreFilters({
|
||||
contextStoreTargetedRecordsRule,
|
||||
contextStoreFilters,
|
||||
contextStoreFilterGroups,
|
||||
objectMetadataItem,
|
||||
filterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue,
|
||||
}),
|
||||
...deletedAtFilter,
|
||||
};
|
||||
|
||||
const { incrementalDestroyManyRecords, progress } =
|
||||
useIncrementalDestroyManyRecords({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
filter: graphqlFilter,
|
||||
pageSize: DEFAULT_QUERY_PAGE_SIZE,
|
||||
delayInMsBetweenMutations: 50,
|
||||
});
|
||||
|
||||
const actionConfig = useContext(CommandConfigContext);
|
||||
|
||||
if (!isDefined(actionConfig)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const originalLabel = getCommandMenuItemLabel(actionConfig.label);
|
||||
|
||||
const originalShortLabel = getCommandMenuItemLabel(actionConfig.shortLabel);
|
||||
|
||||
const progressText = computeProgressText(progress);
|
||||
|
||||
const actionConfigWithProgress = {
|
||||
...actionConfig,
|
||||
label: `${originalLabel}${progressText}`,
|
||||
shortLabel: `${originalShortLabel}${progressText}`,
|
||||
};
|
||||
|
||||
const handleDestroyClick = async () => {
|
||||
removeSelectedRecordsFromRecordBoard();
|
||||
resetTableRowSelection();
|
||||
await incrementalDestroyManyRecords();
|
||||
};
|
||||
|
||||
return (
|
||||
<CommandConfigContext.Provider value={actionConfigWithProgress}>
|
||||
<CommandModal
|
||||
title={t`Permanently Destroy Records`}
|
||||
subtitle={t`Are you sure you want to destroy these records? They won't be recoverable anymore.`}
|
||||
onConfirmClick={handleDestroyClick}
|
||||
confirmButtonText={t`Destroy Records`}
|
||||
/>
|
||||
</CommandConfigContext.Provider>
|
||||
);
|
||||
};
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
|
||||
import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
|
||||
import { useCloseCommandMenu } from '@/command-menu-item/hooks/useCloseCommandMenu';
|
||||
import { computeProgressText } from '@/command-menu-item/utils/computeProgressText';
|
||||
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { useRecordIndexExportRecords } from '@/object-record/record-index/export/hooks/useRecordIndexExportRecords';
|
||||
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useContext } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const ExportMultipleRecordsCommand = () => {
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const contextStoreCurrentViewId = useAtomComponentStateValue(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
|
||||
if (!contextStoreCurrentViewId) {
|
||||
throw new Error('Current view ID is not defined');
|
||||
}
|
||||
|
||||
const { download, progress } = useRecordIndexExportRecords({
|
||||
delayMs: 100,
|
||||
objectMetadataItem,
|
||||
recordIndexId: getRecordIndexIdFromObjectNamePluralAndViewId(
|
||||
objectMetadataItem.namePlural,
|
||||
contextStoreCurrentViewId,
|
||||
),
|
||||
filename: `${objectMetadataItem.nameSingular}.csv`,
|
||||
});
|
||||
|
||||
const { closeCommandMenu } = useCloseCommandMenu({});
|
||||
|
||||
const exportProgress = isDefined(progress)
|
||||
? {
|
||||
processedRecordCount: progress.processedRecordCount,
|
||||
totalRecordCount: progress.totalRecordCount,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const actionConfig = useContext(CommandConfigContext);
|
||||
|
||||
if (!isDefined(actionConfig)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const originalLabel = getCommandMenuItemLabel(actionConfig.label);
|
||||
|
||||
const originalShortLabel = getCommandMenuItemLabel(actionConfig.shortLabel);
|
||||
|
||||
const progressText = computeProgressText(exportProgress);
|
||||
|
||||
const actionConfigWithProgress = {
|
||||
...actionConfig,
|
||||
label: `${originalLabel}${progressText}`,
|
||||
shortLabel: `${originalShortLabel}${progressText}`,
|
||||
};
|
||||
|
||||
const handleClick = async () => {
|
||||
try {
|
||||
await download();
|
||||
closeCommandMenu();
|
||||
} catch (error) {
|
||||
closeCommandMenu();
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<CommandConfigContext.Provider value={actionConfigWithProgress}>
|
||||
<CommandMenuItemDisplay onClick={handleClick} />
|
||||
</CommandConfigContext.Provider>
|
||||
);
|
||||
};
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
|
||||
import { useSelectedRecordIds } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIds';
|
||||
import { useOpenMergeRecordsPageInSidePanel } from '@/side-panel/hooks/useOpenMergeRecordsPageInSidePanel';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
|
||||
export const MergeMultipleRecordsCommand = () => {
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const contextStoreCurrentViewId = useAtomComponentStateValue(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
|
||||
if (!contextStoreCurrentViewId) {
|
||||
throw new Error('Current view ID is not defined');
|
||||
}
|
||||
const selectedRecordIds = useSelectedRecordIds();
|
||||
|
||||
const { openMergeRecordsPageInSidePanel } =
|
||||
useOpenMergeRecordsPageInSidePanel({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
objectRecordIds: selectedRecordIds,
|
||||
});
|
||||
|
||||
const handleClick = () => {
|
||||
openMergeRecordsPageInSidePanel();
|
||||
};
|
||||
|
||||
return <CommandMenuItemDisplay onClick={handleClick} />;
|
||||
};
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
import { CommandModal } from '@/command-menu-item/display/components/CommandModal';
|
||||
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreFilterGroupsComponentState } from '@/context-store/states/contextStoreFilterGroupsComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||
import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize';
|
||||
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
|
||||
import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords';
|
||||
import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
|
||||
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { type RecordGqlOperationFilter } from 'twenty-shared/types';
|
||||
|
||||
export const RestoreMultipleRecordsCommand = () => {
|
||||
const { recordIndexId, objectMetadataItem } =
|
||||
useRecordIndexIdFromCurrentContextStore();
|
||||
|
||||
const contextStoreCurrentViewId = useAtomComponentStateValue(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
|
||||
if (!contextStoreCurrentViewId) {
|
||||
throw new Error('Current view ID is not defined');
|
||||
}
|
||||
|
||||
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
|
||||
const { removeSelectedRecordsFromRecordBoard } =
|
||||
useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
|
||||
|
||||
const { restoreManyRecords } = useRestoreManyRecords({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
const contextStoreFilters = useAtomComponentStateValue(
|
||||
contextStoreFiltersComponentState,
|
||||
);
|
||||
|
||||
const contextStoreFilterGroups = useAtomComponentStateValue(
|
||||
contextStoreFilterGroupsComponentState,
|
||||
);
|
||||
|
||||
const contextStoreAnyFieldFilterValue = useAtomComponentStateValue(
|
||||
contextStoreAnyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const deletedAtFilter: RecordGqlOperationFilter = {
|
||||
deletedAt: { is: 'NOT_NULL' },
|
||||
};
|
||||
|
||||
const graphqlFilter = {
|
||||
...computeContextStoreFilters({
|
||||
contextStoreTargetedRecordsRule,
|
||||
contextStoreFilters,
|
||||
contextStoreFilterGroups,
|
||||
objectMetadataItem,
|
||||
filterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue,
|
||||
}),
|
||||
...deletedAtFilter,
|
||||
};
|
||||
|
||||
const { fetchAllRecords: fetchAllRecordIds } = useLazyFetchAllRecords({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
filter: graphqlFilter,
|
||||
limit: DEFAULT_QUERY_PAGE_SIZE,
|
||||
recordGqlFields: { id: true },
|
||||
});
|
||||
|
||||
const handleRestoreClick = async () => {
|
||||
removeSelectedRecordsFromRecordBoard();
|
||||
const recordsToRestore = await fetchAllRecordIds();
|
||||
const recordIdsToRestore = recordsToRestore.map((record) => record.id);
|
||||
|
||||
resetTableRowSelection();
|
||||
|
||||
await restoreManyRecords({
|
||||
idsToRestore: recordIdsToRestore,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<CommandModal
|
||||
title={t`Restore Records`}
|
||||
subtitle={t`Are you sure you want to restore these records?`}
|
||||
onConfirmClick={handleRestoreClick}
|
||||
confirmButtonText={t`Restore Records`}
|
||||
confirmButtonAccent="default"
|
||||
/>
|
||||
);
|
||||
};
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
|
||||
import { useOpenUpdateMultipleRecordsPageInSidePanel } from '@/side-panel/hooks/useOpenUpdateMultipleRecordsPageInSidePanel';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
|
||||
export const UpdateMultipleRecordsCommand = () => {
|
||||
const contextStoreInstanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
ContextStoreComponentInstanceContext,
|
||||
);
|
||||
|
||||
const { openUpdateMultipleRecordsPageInSidePanel } =
|
||||
useOpenUpdateMultipleRecordsPageInSidePanel({
|
||||
contextStoreInstanceId,
|
||||
});
|
||||
|
||||
const handleClick = () => {
|
||||
openUpdateMultipleRecordsPageInSidePanel();
|
||||
};
|
||||
|
||||
return <CommandMenuItemDisplay onClick={handleClick} />;
|
||||
};
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
export enum MultipleRecordsCommandKeys {
|
||||
UPDATE = 'update-multiple-records',
|
||||
DELETE = 'delete-multiple-records',
|
||||
EXPORT = 'export-multiple-records',
|
||||
MERGE = 'merge-multiple-records',
|
||||
DESTROY = 'destroy-multiple-records',
|
||||
RESTORE = 'restore-multiple-records',
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||
|
||||
export const CreateNewIndexRecordNoSelectionRecordCommand = () => {
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
return (
|
||||
<Command
|
||||
onClick={() => createNewIndexRecord({ position: 'first' })}
|
||||
closeSidePanelOnCommandMenuListExecution={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
|
||||
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
||||
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
|
||||
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
|
||||
import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
|
||||
|
||||
export const CreateNewViewNoSelectionRecordCommand = () => {
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
const { openDropdown } = useOpenDropdown();
|
||||
|
||||
const contextStoreCurrentViewId = useAtomComponentStateValue(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
|
||||
if (!contextStoreCurrentViewId) {
|
||||
throw new Error('Current view ID is not defined');
|
||||
}
|
||||
|
||||
const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
|
||||
objectMetadataItem.namePlural,
|
||||
contextStoreCurrentViewId,
|
||||
);
|
||||
|
||||
const setViewPickerReferenceViewId = useSetAtomComponentState(
|
||||
viewPickerReferenceViewIdComponentState,
|
||||
recordIndexId,
|
||||
);
|
||||
|
||||
const { setViewPickerMode } = useViewPickerMode(recordIndexId);
|
||||
|
||||
const handleAddViewButtonClick = () => {
|
||||
setViewPickerReferenceViewId(contextStoreCurrentViewId);
|
||||
setViewPickerMode('create-empty');
|
||||
openDropdown({
|
||||
dropdownComponentInstanceIdFromProps: VIEW_PICKER_DROPDOWN_ID,
|
||||
});
|
||||
};
|
||||
|
||||
return <Command onClick={handleAddViewButtonClick} />;
|
||||
};
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
import { useEnterLayoutCustomizationMode } from '@/layout-customization/hooks/useEnterLayoutCustomizationMode';
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
|
||||
export const EditNavigationSidebarNoSelectionRecordCommand = () => {
|
||||
const { enterLayoutCustomizationMode } = useEnterLayoutCustomizationMode();
|
||||
|
||||
return (
|
||||
<Command
|
||||
onClick={() => enterLayoutCustomizationMode()}
|
||||
closeSidePanelOnCommandMenuListExecution
|
||||
/>
|
||||
);
|
||||
};
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { useCheckIsSoftDeleteFilter } from '@/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter';
|
||||
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
|
||||
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const HideDeletedRecordsNoSelectionRecordCommand = () => {
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const contextStoreCurrentViewId = useAtomComponentStateValue(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
|
||||
if (!contextStoreCurrentViewId) {
|
||||
throw new Error('Current view ID is not defined');
|
||||
}
|
||||
|
||||
const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
|
||||
objectMetadataItem.namePlural,
|
||||
contextStoreCurrentViewId,
|
||||
);
|
||||
|
||||
const { toggleSoftDeleteFilterState } = useHandleToggleTrashColumnFilter({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
viewBarId: recordIndexId,
|
||||
});
|
||||
|
||||
const { isRecordFilterAboutSoftDelete } = useCheckIsSoftDeleteFilter();
|
||||
|
||||
const currentRecordFilters = useAtomComponentStateValue(
|
||||
currentRecordFiltersComponentState,
|
||||
recordIndexId,
|
||||
);
|
||||
|
||||
const deletedFilter = currentRecordFilters.find(
|
||||
isRecordFilterAboutSoftDelete,
|
||||
);
|
||||
|
||||
const { removeRecordFilter } = useRemoveRecordFilter();
|
||||
|
||||
const handleClick = () => {
|
||||
if (!isDefined(deletedFilter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeRecordFilter({ recordFilterId: deletedFilter.id });
|
||||
toggleSoftDeleteFilterState(false);
|
||||
};
|
||||
|
||||
return <Command onClick={handleClick} />;
|
||||
};
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { useOpenObjectRecordsSpreadsheetImportDialog } from '@/object-record/spreadsheet-import/hooks/useOpenObjectRecordsSpreadsheetImportDialog';
|
||||
|
||||
export const ImportRecordsNoSelectionRecordCommand = () => {
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const { openObjectRecordsSpreadsheetImportDialog } =
|
||||
useOpenObjectRecordsSpreadsheetImportDialog(
|
||||
objectMetadataItem.nameSingular,
|
||||
);
|
||||
|
||||
return <Command onClick={openObjectRecordsSpreadsheetImportDialog} />;
|
||||
};
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
|
||||
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
|
||||
export const SeeDeletedRecordsNoSelectionRecordCommand = () => {
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const contextStoreCurrentViewId = useAtomComponentStateValue(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
|
||||
if (!contextStoreCurrentViewId) {
|
||||
throw new Error('Current view ID is not defined');
|
||||
}
|
||||
|
||||
const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
|
||||
objectMetadataItem.namePlural,
|
||||
contextStoreCurrentViewId,
|
||||
);
|
||||
|
||||
const { handleToggleTrashColumnFilter, toggleSoftDeleteFilterState } =
|
||||
useHandleToggleTrashColumnFilter({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
viewBarId: recordIndexId,
|
||||
});
|
||||
|
||||
return (
|
||||
<Command
|
||||
onClick={() => {
|
||||
handleToggleTrashColumnFilter();
|
||||
toggleSoftDeleteFilterState(true);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
export enum NoSelectionRecordCommandKeys {
|
||||
EXPORT_VIEW = 'export-view',
|
||||
CREATE_NEW_RECORD = 'create-new-record',
|
||||
SEE_DELETED_RECORDS = 'see-deleted-records',
|
||||
HIDE_DELETED_RECORDS = 'hide-deleted-records',
|
||||
IMPORT_RECORDS = 'import-records',
|
||||
GO_TO_WORKFLOWS = 'go-to-workflows',
|
||||
GO_TO_PEOPLE = 'go-to-people',
|
||||
GO_TO_COMPANIES = 'go-to-companies',
|
||||
GO_TO_DASHBOARDS = 'go-to-dashboards',
|
||||
GO_TO_OPPORTUNITIES = 'go-to-opportunities',
|
||||
GO_TO_SETTINGS = 'go-to-settings',
|
||||
GO_TO_TASKS = 'go-to-tasks',
|
||||
GO_TO_NOTES = 'go-to-notes',
|
||||
CREATE_NEW_VIEW = 'create-view',
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
export enum NoSelectionWorkflowRecordCommandKeys {
|
||||
GO_TO_RUNS = 'go-to-runs',
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { useCreateManyNavigationMenuItems } from '@/navigation-menu-item/common/hooks/useCreateManyNavigationMenuItems';
|
||||
import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
|
||||
|
||||
export const AddToFavoritesSingleRecordCommand = () => {
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
|
||||
const { createManyNavigationMenuItems } = useCreateManyNavigationMenuItems();
|
||||
const { navigationMenuItems, currentWorkspaceMemberId } =
|
||||
useNavigationMenuItemsData();
|
||||
|
||||
const handleClick = () => {
|
||||
const relevantItems = navigationMenuItems.filter(
|
||||
(item) => !isDefined(item.folderId) && isDefined(item.userWorkspaceId),
|
||||
);
|
||||
|
||||
const maxPosition = Math.max(
|
||||
...relevantItems.map((item) => item.position),
|
||||
0,
|
||||
);
|
||||
|
||||
createManyNavigationMenuItems([
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: NavigationMenuItemType.RECORD,
|
||||
targetRecordId: recordId,
|
||||
targetObjectMetadataId: objectMetadataItem.id,
|
||||
userWorkspaceId: currentWorkspaceMemberId,
|
||||
position: maxPosition + 1,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
return <Command onClick={handleClick} />;
|
||||
};
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useOpenRecordInSidePanel } from '@/side-panel/hooks/useOpenRecordInSidePanel';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { type FieldMetadataItemRelation } from '@/object-metadata/types/FieldMetadataItemRelation';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { getForeignKeyNameFromRelationFieldName } from '@/object-record/utils/getForeignKeyNameFromRelationFieldName';
|
||||
|
||||
interface CreateRelatedRecordCommandProps {
|
||||
targetFieldMetadataItemRelation: FieldMetadataItemRelation;
|
||||
}
|
||||
|
||||
export const CreateRelatedRecordCommand = ({
|
||||
targetFieldMetadataItemRelation,
|
||||
}: CreateRelatedRecordCommandProps) => {
|
||||
const sourceRecordId = useSelectedRecordIdOrThrow();
|
||||
|
||||
const { objectMetadataItem: targetObjectMetadataItem } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular:
|
||||
targetFieldMetadataItemRelation.targetObjectMetadata.nameSingular,
|
||||
});
|
||||
|
||||
const { objectMetadataItem: taskObjectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.Task,
|
||||
});
|
||||
|
||||
const { objectMetadataItem: noteObjectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.Note,
|
||||
});
|
||||
|
||||
const { openRecordInSidePanel } = useOpenRecordInSidePanel();
|
||||
|
||||
const { createOneRecord: createOneTaskTarget } = useCreateOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.TaskTarget,
|
||||
});
|
||||
|
||||
const { createOneRecord: createOneNoteTarget } = useCreateOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.NoteTarget,
|
||||
});
|
||||
|
||||
const targetObject =
|
||||
targetObjectMetadataItem.nameSingular === CoreObjectNameSingular.TaskTarget
|
||||
? taskObjectMetadataItem
|
||||
: targetObjectMetadataItem.nameSingular ===
|
||||
CoreObjectNameSingular.NoteTarget
|
||||
? noteObjectMetadataItem
|
||||
: targetObjectMetadataItem;
|
||||
|
||||
const { createOneRecord } = useCreateOneRecord({
|
||||
objectNameSingular: targetObject.nameSingular,
|
||||
});
|
||||
|
||||
const handleCreateRelatedRecord = async () => {
|
||||
const foreignKeyFieldName =
|
||||
targetFieldMetadataItemRelation.targetFieldMetadata.name;
|
||||
const foreignKeyIdFieldName =
|
||||
getForeignKeyNameFromRelationFieldName(foreignKeyFieldName);
|
||||
|
||||
let createdRecord: ObjectRecord;
|
||||
|
||||
switch (targetObjectMetadataItem.nameSingular) {
|
||||
case CoreObjectNameSingular.TaskTarget: {
|
||||
createdRecord = await createOneRecord({});
|
||||
|
||||
await createOneTaskTarget({
|
||||
taskId: createdRecord.id,
|
||||
[foreignKeyIdFieldName]: sourceRecordId,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case CoreObjectNameSingular.NoteTarget: {
|
||||
createdRecord = await createOneRecord({});
|
||||
|
||||
await createOneNoteTarget({
|
||||
noteId: createdRecord.id,
|
||||
[foreignKeyIdFieldName]: sourceRecordId,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
createdRecord = await createOneRecord({
|
||||
[foreignKeyIdFieldName]: sourceRecordId,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
openRecordInSidePanel({
|
||||
recordId: createdRecord.id,
|
||||
objectNameSingular: targetObject.nameSingular,
|
||||
isNewRecord: true,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Command
|
||||
onClick={handleCreateRelatedRecord}
|
||||
closeSidePanelOnCommandMenuListExecution={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
|
||||
import { useRemoveNavigationMenuItemByTargetRecordId } from '@/navigation-menu-item/common/hooks/useRemoveNavigationMenuItemByTargetRecordId';
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
|
||||
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
|
||||
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const DeleteSingleRecordCommand = () => {
|
||||
const { recordIndexId, objectMetadataItem } =
|
||||
useRecordIndexIdFromCurrentContextStore();
|
||||
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
|
||||
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
|
||||
|
||||
const { removeSelectedRecordsFromRecordBoard } =
|
||||
useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
|
||||
|
||||
const { deleteOneRecord } = useDeleteOneRecord({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
const { navigationMenuItems, workspaceNavigationMenuItems } =
|
||||
useNavigationMenuItemsData();
|
||||
const { removeNavigationMenuItemsByTargetRecordIds } =
|
||||
useRemoveNavigationMenuItemByTargetRecordId();
|
||||
|
||||
const handleDeleteClick = async () => {
|
||||
removeSelectedRecordsFromRecordBoard();
|
||||
|
||||
resetTableRowSelection();
|
||||
|
||||
const foundNavigationMenuItem = [
|
||||
...navigationMenuItems,
|
||||
...workspaceNavigationMenuItems,
|
||||
].find((item) => item.targetRecordId === recordId);
|
||||
|
||||
if (isDefined(foundNavigationMenuItem)) {
|
||||
removeNavigationMenuItemsByTargetRecordIds([recordId]);
|
||||
}
|
||||
|
||||
await deleteOneRecord(recordId);
|
||||
};
|
||||
|
||||
return <Command onClick={handleDeleteClick} />;
|
||||
};
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
import { CommandModal } from '@/command-menu-item/display/components/CommandModal';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord';
|
||||
import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
|
||||
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
|
||||
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
|
||||
export const DestroySingleRecordCommand = () => {
|
||||
const { recordIndexId, objectMetadataItem } =
|
||||
useRecordIndexIdFromCurrentContextStore();
|
||||
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
|
||||
const navigateApp = useNavigateApp();
|
||||
|
||||
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
|
||||
|
||||
const { removeSelectedRecordsFromRecordBoard } =
|
||||
useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
|
||||
|
||||
const { destroyOneRecord } = useDestroyOneRecord({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
const handleDeleteClick = async () => {
|
||||
removeSelectedRecordsFromRecordBoard();
|
||||
resetTableRowSelection();
|
||||
|
||||
await destroyOneRecord(recordId);
|
||||
navigateApp(AppPath.RecordIndexPage, {
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<CommandModal
|
||||
title={t`Permanently Destroy Record`}
|
||||
subtitle={t`Are you sure you want to destroy this record? It cannot be recovered anymore.`}
|
||||
onConfirmClick={handleDeleteClick}
|
||||
confirmButtonText={t`Permanently Destroy Record`}
|
||||
closeSidePanelOnShowPageOptionsExecution={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const ExportNoteSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
|
||||
const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
|
||||
|
||||
const filename = `${(recordStore?.title || 'Untitled Note').replace(/[<>:"/\\|?*]/g, '-')}`;
|
||||
|
||||
const handleClick = async () => {
|
||||
if (!isDefined(recordStore)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const initialBody = recordStore.bodyV2?.blocknote;
|
||||
|
||||
let parsedBody = [];
|
||||
|
||||
// TODO: Remove this once we have removed the old rich text
|
||||
try {
|
||||
parsedBody = JSON.parse(initialBody);
|
||||
} catch {
|
||||
// oxlint-disable-next-line no-console
|
||||
console.warn(
|
||||
`Failed to parse body for record ${recordId}, for rich text version 'v2'`,
|
||||
);
|
||||
// oxlint-disable-next-line no-console
|
||||
console.warn(initialBody);
|
||||
}
|
||||
|
||||
const { exportBlockNoteEditorToPdf } = await import(
|
||||
'@/command-menu-item/record/single-record/utils/exportBlockNoteEditorToPdf'
|
||||
);
|
||||
|
||||
await exportBlockNoteEditorToPdf(parsedBody, filename);
|
||||
|
||||
// TODO later: implement DOCX export
|
||||
// const { exportBlockNoteEditorToDocx } = await import(
|
||||
// '@/command-menu-item/record/single-record/utils/exportBlockNoteEditorToDocx'
|
||||
// );
|
||||
// await exportBlockNoteEditorToDocx(editor, filename);
|
||||
};
|
||||
|
||||
return <Command onClick={handleClick} />;
|
||||
};
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { useExportSingleRecord } from '@/object-record/record-show/hooks/useExportSingleRecord';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
|
||||
export const ExportSingleRecordCommand = () => {
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const contextStoreCurrentViewId = useAtomComponentStateValue(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
|
||||
if (!contextStoreCurrentViewId) {
|
||||
throw new Error('Current view ID is not defined');
|
||||
}
|
||||
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
|
||||
const filename = `${objectMetadataItem.nameSingular}.csv`;
|
||||
const { download } = useExportSingleRecord({
|
||||
filename,
|
||||
objectMetadataItem,
|
||||
recordId,
|
||||
});
|
||||
|
||||
return <Command onClick={download} />;
|
||||
};
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination';
|
||||
|
||||
export const NavigateToNextRecordSingleRecordCommand = () => {
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
|
||||
const { navigateToNextRecord } = useRecordShowPagePagination(
|
||||
objectMetadataItem.nameSingular,
|
||||
recordId,
|
||||
);
|
||||
|
||||
return <Command onClick={navigateToNextRecord} />;
|
||||
};
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination';
|
||||
|
||||
export const NavigateToPreviousRecordSingleRecordCommand = () => {
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
|
||||
const { navigateToPreviousRecord } = useRecordShowPagePagination(
|
||||
objectMetadataItem.nameSingular,
|
||||
recordId,
|
||||
);
|
||||
|
||||
return <Command onClick={navigateToPreviousRecord} />;
|
||||
};
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { useDeleteManyNavigationMenuItems } from '@/navigation-menu-item/common/hooks/useDeleteManyNavigationMenuItems';
|
||||
import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
|
||||
|
||||
export const RemoveFromFavoritesSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const { navigationMenuItems, workspaceNavigationMenuItems } =
|
||||
useNavigationMenuItemsData();
|
||||
|
||||
const { deleteManyNavigationMenuItems } = useDeleteManyNavigationMenuItems();
|
||||
|
||||
const foundNavigationMenuItem = [
|
||||
...navigationMenuItems,
|
||||
...workspaceNavigationMenuItems,
|
||||
].find(
|
||||
(item) =>
|
||||
item.targetRecordId === recordId &&
|
||||
item.targetObjectMetadataId === objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const handleClick = () => {
|
||||
if (!isDefined(foundNavigationMenuItem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteManyNavigationMenuItems([foundNavigationMenuItem.id]);
|
||||
};
|
||||
|
||||
return <Command onClick={handleClick} />;
|
||||
};
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
import { CommandModal } from '@/command-menu-item/display/components/CommandModal';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords';
|
||||
import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
|
||||
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
|
||||
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
export const RestoreSingleRecordCommand = () => {
|
||||
const { recordIndexId, objectMetadataItem } =
|
||||
useRecordIndexIdFromCurrentContextStore();
|
||||
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
|
||||
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
|
||||
const { removeSelectedRecordsFromRecordBoard } =
|
||||
useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
|
||||
|
||||
const { restoreManyRecords } = useRestoreManyRecords({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
const handleRestoreClick = async () => {
|
||||
removeSelectedRecordsFromRecordBoard();
|
||||
resetTableRowSelection();
|
||||
|
||||
await restoreManyRecords({
|
||||
idsToRestore: [recordId],
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<CommandModal
|
||||
title={t`Restore Record`}
|
||||
subtitle={t`Are you sure you want to restore this record?`}
|
||||
onConfirmClick={handleRestoreClick}
|
||||
confirmButtonText={t`Restore Record`}
|
||||
confirmButtonAccent="default"
|
||||
/>
|
||||
);
|
||||
};
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useResetDraftPageLayoutToPersistedPageLayout } from '@/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout';
|
||||
import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
|
||||
import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord';
|
||||
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { PageLayoutType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const CancelDashboardSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
|
||||
const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
|
||||
|
||||
const pageLayoutId = recordStore?.pageLayoutId;
|
||||
|
||||
if (!isDefined(pageLayoutId)) {
|
||||
throw new Error(
|
||||
'CancelDashboardSingleRecordCommand requires a valid pageLayoutId from the record store.',
|
||||
);
|
||||
}
|
||||
|
||||
const tabListInstanceId = getTabListInstanceIdFromPageLayoutAndRecord({
|
||||
pageLayoutId,
|
||||
layoutType: PageLayoutType.DASHBOARD,
|
||||
targetRecordIdentifier: { id: recordId, targetObjectNameSingular: '' },
|
||||
});
|
||||
|
||||
const { closeSidePanelMenu } = useSidePanelMenu();
|
||||
|
||||
const { setIsPageLayoutInEditMode } =
|
||||
useSetIsPageLayoutInEditMode(pageLayoutId);
|
||||
|
||||
const { resetDraftPageLayoutToPersistedPageLayout } =
|
||||
useResetDraftPageLayoutToPersistedPageLayout({
|
||||
pageLayoutId,
|
||||
tabListInstanceId,
|
||||
});
|
||||
|
||||
const handleClick = () => {
|
||||
closeSidePanelMenu();
|
||||
|
||||
resetDraftPageLayoutToPersistedPageLayout();
|
||||
setIsPageLayoutInEditMode(false);
|
||||
};
|
||||
|
||||
return <Command onClick={handleClick} />;
|
||||
};
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useDuplicateDashboard } from '@/dashboards/hooks/useDuplicateDashboard';
|
||||
import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
|
||||
export const DuplicateDashboardSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const { duplicateDashboard } = useDuplicateDashboard();
|
||||
const navigate = useNavigateApp();
|
||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||
const { t } = useLingui();
|
||||
|
||||
const handleClick = async () => {
|
||||
const result = await duplicateDashboard(recordId);
|
||||
|
||||
if (isDefined(result) && isNonEmptyString(result.id)) {
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Dashboard duplicated successfully`,
|
||||
});
|
||||
navigate(AppPath.RecordShowPage, {
|
||||
objectNameSingular: CoreObjectNameSingular.Dashboard,
|
||||
objectRecordId: result.id,
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to duplicate dashboard`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return <Command onClick={handleClick} />;
|
||||
};
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { useResetLocationHash } from 'twenty-ui/utilities';
|
||||
|
||||
export const EditDashboardSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
|
||||
const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
|
||||
|
||||
const pageLayoutId = recordStore?.pageLayoutId;
|
||||
|
||||
const { setIsPageLayoutInEditMode } =
|
||||
useSetIsPageLayoutInEditMode(pageLayoutId);
|
||||
|
||||
const { resetLocationHash } = useResetLocationHash();
|
||||
|
||||
const handleClick = () => {
|
||||
setIsPageLayoutInEditMode(true);
|
||||
resetLocationHash();
|
||||
};
|
||||
|
||||
return <Command onClick={handleClick} />;
|
||||
};
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useSavePageLayout } from '@/page-layout/hooks/useSavePageLayout';
|
||||
import { useSavePageLayoutWidgetsData } from '@/page-layout/hooks/useSavePageLayoutWidgetsData';
|
||||
import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
|
||||
export const SaveDashboardSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
|
||||
const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
|
||||
|
||||
const pageLayoutId = recordStore?.pageLayoutId;
|
||||
|
||||
const { savePageLayout } = useSavePageLayout(pageLayoutId);
|
||||
|
||||
const { savePageLayoutWidgetsData } = useSavePageLayoutWidgetsData();
|
||||
|
||||
const { setIsPageLayoutInEditMode } =
|
||||
useSetIsPageLayoutInEditMode(pageLayoutId);
|
||||
|
||||
const { closeSidePanelMenu } = useSidePanelMenu();
|
||||
|
||||
const handleClick = async () => {
|
||||
const result = await savePageLayout();
|
||||
|
||||
if (result.status === 'successful') {
|
||||
await savePageLayoutWidgetsData(pageLayoutId);
|
||||
closeSidePanelMenu();
|
||||
setIsPageLayoutInEditMode(false);
|
||||
}
|
||||
};
|
||||
|
||||
return <Command onClick={handleClick} />;
|
||||
};
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
export enum DashboardSingleRecordCommandKeys {
|
||||
DUPLICATE_DASHBOARD = 'duplicate-dashboard-single-record',
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
|
||||
export const useSelectedRecordIdOrThrow = () => {
|
||||
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
if (
|
||||
contextStoreTargetedRecordsRule.mode === 'exclusion' ||
|
||||
(contextStoreTargetedRecordsRule.mode === 'selection' &&
|
||||
contextStoreTargetedRecordsRule.selectedRecordIds.length === 0)
|
||||
) {
|
||||
throw new Error('Selected record ID is required');
|
||||
}
|
||||
|
||||
return contextStoreTargetedRecordsRule.selectedRecordIds[0];
|
||||
};
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
|
||||
export const useSelectedRecordIds = () => {
|
||||
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
if (
|
||||
contextStoreTargetedRecordsRule.mode === 'exclusion' ||
|
||||
(contextStoreTargetedRecordsRule.mode === 'selection' &&
|
||||
contextStoreTargetedRecordsRule.selectedRecordIds.length === 0)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return contextStoreTargetedRecordsRule.selectedRecordIds;
|
||||
};
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
import { useEnterLayoutCustomizationMode } from '@/layout-customization/hooks/useEnterLayoutCustomizationMode';
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useResetLocationHash } from 'twenty-ui/utilities';
|
||||
|
||||
export const EditRecordPageLayoutSingleRecordCommand = () => {
|
||||
const { enterLayoutCustomizationMode } = useEnterLayoutCustomizationMode();
|
||||
|
||||
const { resetLocationHash } = useResetLocationHash();
|
||||
|
||||
const handleClick = () => {
|
||||
enterLayoutCustomizationMode();
|
||||
resetLocationHash();
|
||||
};
|
||||
|
||||
return <Command onClick={handleClick} />;
|
||||
};
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
export enum RecordPageLayoutSingleRecordCommandKeys {
|
||||
EDIT_RECORD_PAGE_LAYOUT = 'edit-record-page-layout-single-record',
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
export enum SingleRecordCommandKeys {
|
||||
DELETE = 'delete-single-record',
|
||||
DESTROY = 'destroy-single-record',
|
||||
ADD_TO_FAVORITES = 'add-to-favorites-single-record',
|
||||
REMOVE_FROM_FAVORITES = 'remove-from-favorites-single-record',
|
||||
NAVIGATE_TO_NEXT_RECORD = 'navigate-to-next-record-single-record',
|
||||
NAVIGATE_TO_PREVIOUS_RECORD = 'navigate-to-previous-record-single-record',
|
||||
EXPORT_NOTE_TO_PDF = 'export-note-to-pdf-single-record',
|
||||
EXPORT_FROM_RECORD_INDEX = 'export-from-record-index-single-record',
|
||||
EXPORT_FROM_RECORD_SHOW = 'export-from-record-show-single-record',
|
||||
RESTORE = 'restore-single-record',
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
import { type BlockNoteEditor } from '@blocknote/core';
|
||||
import {
|
||||
docxDefaultSchemaMappings,
|
||||
DOCXExporter,
|
||||
} from '@blocknote/xl-docx-exporter';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
export const exportBlockNoteEditorToDocx = async (
|
||||
editor: BlockNoteEditor,
|
||||
filename: string,
|
||||
) => {
|
||||
const exporter = new DOCXExporter(editor.schema, docxDefaultSchemaMappings);
|
||||
|
||||
const blob = await exporter.toBlob(editor.document);
|
||||
saveAs(blob, `${filename}.docx`);
|
||||
};
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const SeeVersionWorkflowRunSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
|
||||
|
||||
if (!isDefined(recordStore) || !isDefined(recordStore?.workflowVersion?.id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<CommandLink
|
||||
to={AppPath.RecordShowPage}
|
||||
params={{
|
||||
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
|
||||
objectRecordId: recordStore.workflowVersion.id,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const SeeWorkflowWorkflowRunSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
|
||||
|
||||
if (!isDefined(recordStore) || !isDefined(recordStore?.workflow?.id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<CommandLink
|
||||
to={AppPath.RecordShowPage}
|
||||
params={{
|
||||
objectNameSingular: CoreObjectNameSingular.Workflow,
|
||||
objectRecordId: recordStore.workflow.id,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||
import { contextStoreFilterGroupsComponentState } from '@/context-store/states/contextStoreFilterGroupsComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize';
|
||||
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useStopWorkflowRun } from '@/workflow/hooks/useStopWorkflowRun';
|
||||
|
||||
export const StopWorkflowRunSingleRecordCommand = () => {
|
||||
const { objectMetadataItem } = useRecordIndexIdFromCurrentContextStore();
|
||||
|
||||
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
const contextStoreFilters = useAtomComponentStateValue(
|
||||
contextStoreFiltersComponentState,
|
||||
);
|
||||
|
||||
const contextStoreFilterGroups = useAtomComponentStateValue(
|
||||
contextStoreFilterGroupsComponentState,
|
||||
);
|
||||
|
||||
const contextStoreAnyFieldFilterValue = useAtomComponentStateValue(
|
||||
contextStoreAnyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const graphqlFilter = computeContextStoreFilters({
|
||||
contextStoreTargetedRecordsRule,
|
||||
contextStoreFilters,
|
||||
contextStoreFilterGroups,
|
||||
objectMetadataItem,
|
||||
filterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue,
|
||||
});
|
||||
|
||||
const { fetchAllRecords: fetchAllRecordIds } = useLazyFetchAllRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.WorkflowRun,
|
||||
filter: graphqlFilter,
|
||||
limit: DEFAULT_QUERY_PAGE_SIZE,
|
||||
recordGqlFields: { id: true },
|
||||
});
|
||||
|
||||
const { stopWorkflowRun } = useStopWorkflowRun();
|
||||
|
||||
const handleClick = async () => {
|
||||
if (contextStoreTargetedRecordsRule.mode === 'selection') {
|
||||
for (const selectedRecordId of contextStoreTargetedRecordsRule.selectedRecordIds) {
|
||||
await stopWorkflowRun(selectedRecordId);
|
||||
}
|
||||
} else {
|
||||
const records = await fetchAllRecordIds();
|
||||
|
||||
for (const record of records) {
|
||||
await stopWorkflowRun(record.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return <Command onClick={handleClick} />;
|
||||
};
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
export enum WorkflowRunSingleRecordCommandKeys {
|
||||
STOP = 'stop-workflow-run-single-record',
|
||||
SEE_WORKFLOW = 'see-workflow-workflow-run-single-record',
|
||||
SEE_VERSION = 'see-version-workflow-run-single-record',
|
||||
}
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { AppPath, ViewFilterOperand } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const SeeRunsWorkflowVersionSingleRecordCommandContent = ({
|
||||
workflowId,
|
||||
recordId,
|
||||
}: {
|
||||
workflowId: string;
|
||||
recordId: string;
|
||||
}) => {
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
|
||||
|
||||
return (
|
||||
<CommandLink
|
||||
to={AppPath.RecordIndexPage}
|
||||
params={{ objectNamePlural: CoreObjectNamePlural.WorkflowRun }}
|
||||
queryParams={{
|
||||
filter: {
|
||||
workflow: {
|
||||
[ViewFilterOperand.IS]: {
|
||||
selectedRecordIds: [workflowWithCurrentVersion?.id],
|
||||
},
|
||||
},
|
||||
recordStore: {
|
||||
[ViewFilterOperand.IS]: {
|
||||
selectedRecordIds: [recordId],
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SeeRunsWorkflowVersionSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
|
||||
|
||||
const workflowId = recordStore?.workflow?.id;
|
||||
|
||||
if (!isDefined(workflowId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SeeRunsWorkflowVersionSingleRecordCommandContent
|
||||
workflowId={workflowId}
|
||||
recordId={recordId}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { AppPath, ViewFilterOperand } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const SeeVersionsWorkflowVersionSingleRecordCommandContent = ({
|
||||
workflowId,
|
||||
}: {
|
||||
workflowId: string;
|
||||
}) => {
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
|
||||
|
||||
return (
|
||||
<CommandLink
|
||||
to={AppPath.RecordIndexPage}
|
||||
params={{ objectNamePlural: CoreObjectNamePlural.WorkflowVersion }}
|
||||
queryParams={{
|
||||
filter: {
|
||||
workflow: {
|
||||
[ViewFilterOperand.IS]: {
|
||||
selectedRecordIds: [workflowWithCurrentVersion?.id],
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SeeVersionsWorkflowVersionSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
|
||||
|
||||
if (!isDefined(recordStore) || !isDefined(recordStore.workflowId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SeeVersionsWorkflowVersionSingleRecordCommandContent
|
||||
workflowId={recordStore.workflowId}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
|
||||
export const SeeWorkflowWorkflowVersionSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
|
||||
|
||||
return (
|
||||
<CommandLink
|
||||
to={AppPath.RecordShowPage}
|
||||
params={{
|
||||
objectNameSingular: CoreObjectNameSingular.Workflow,
|
||||
objectRecordId: recordStore?.workflow?.id,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { OverrideWorkflowDraftConfirmationModal } from '@/workflow/components/OverrideWorkflowDraftConfirmationModal';
|
||||
import { OVERRIDE_WORKFLOW_DRAFT_CONFIRMATION_MODAL_ID } from '@/workflow/constants/OverrideWorkflowDraftConfirmationModalId';
|
||||
import { useCreateDraftFromWorkflowVersion } from '@/workflow/hooks/useCreateDraftFromWorkflowVersion';
|
||||
import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
|
||||
const UseAsDraftWorkflowVersionSingleRecordCommandContent = ({
|
||||
workflowId,
|
||||
workflowVersionId,
|
||||
}: {
|
||||
workflowId: string;
|
||||
workflowVersionId: string;
|
||||
}) => {
|
||||
const { openModal } = useModal();
|
||||
const workflow = useWorkflowWithCurrentVersion(workflowId);
|
||||
const { createDraftFromWorkflowVersion } =
|
||||
useCreateDraftFromWorkflowVersion();
|
||||
const navigate = useNavigateApp();
|
||||
const [hasNavigated, setHasNavigated] = useState(false);
|
||||
|
||||
const hasAlreadyDraftVersion =
|
||||
workflow?.versions.some((version) => version.status === 'DRAFT') || false;
|
||||
|
||||
const handleClick = () => {
|
||||
if (!isDefined(workflow) || hasNavigated) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasAlreadyDraftVersion) {
|
||||
openModal(OVERRIDE_WORKFLOW_DRAFT_CONFIRMATION_MODAL_ID);
|
||||
} else {
|
||||
const executeCommandWithoutWaiting = async () => {
|
||||
await createDraftFromWorkflowVersion({
|
||||
workflowId,
|
||||
workflowVersionIdToCopy: workflowVersionId,
|
||||
});
|
||||
|
||||
navigate(AppPath.RecordShowPage, {
|
||||
objectNameSingular: CoreObjectNameSingular.Workflow,
|
||||
objectRecordId: workflowId,
|
||||
});
|
||||
|
||||
setHasNavigated(true);
|
||||
};
|
||||
|
||||
executeCommandWithoutWaiting();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Command onClick={handleClick} />
|
||||
<OverrideWorkflowDraftConfirmationModal
|
||||
workflowId={workflowId}
|
||||
workflowVersionIdToCopy={workflowVersionId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const UseAsDraftWorkflowVersionSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const workflowVersion = useWorkflowVersion(recordId);
|
||||
|
||||
if (!isDefined(workflowVersion?.workflow?.id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<UseAsDraftWorkflowVersionSingleRecordCommandContent
|
||||
workflowId={workflowVersion.workflow.id}
|
||||
workflowVersionId={workflowVersion.id}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
export enum WorkflowVersionSingleRecordCommandKeys {
|
||||
SEE_RUNS = 'see-runs-workflow-version-single-record',
|
||||
SEE_VERSIONS = 'see-versions-workflow-version-single-record',
|
||||
USE_AS_DRAFT = 'use-as-draft-workflow-version-single-record',
|
||||
SEE_WORKFLOW = 'see-workflow-workflow-version-single-record',
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const ActivateWorkflowSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const { activateWorkflowVersion } = useActivateWorkflowVersion();
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
|
||||
|
||||
const onClick = () => {
|
||||
if (!isDefined(workflowWithCurrentVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
activateWorkflowVersion({
|
||||
workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
|
||||
workflowId: workflowWithCurrentVersion.id,
|
||||
});
|
||||
};
|
||||
|
||||
return <Command onClick={onClick} />;
|
||||
};
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useSidePanelWorkflowNavigation } from '@/side-panel/pages/workflow/hooks/useSidePanelWorkflowNavigation';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const AddNodeWorkflowSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
|
||||
const { openWorkflowCreateStepInSidePanel } =
|
||||
useSidePanelWorkflowNavigation();
|
||||
|
||||
const onClick = () => {
|
||||
if (!isDefined(workflowWithCurrentVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
openWorkflowCreateStepInSidePanel(workflowWithCurrentVersion.id);
|
||||
};
|
||||
|
||||
return <Command onClick={onClick} />;
|
||||
};
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useDeactivateWorkflowVersion } from '@/workflow/hooks/useDeactivateWorkflowVersion';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const DeactivateWorkflowSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const { deactivateWorkflowVersion } = useDeactivateWorkflowVersion();
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
|
||||
|
||||
const onClick = () => {
|
||||
if (!isDefined(workflowWithCurrentVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
deactivateWorkflowVersion({
|
||||
workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
|
||||
});
|
||||
};
|
||||
|
||||
return <Command onClick={onClick} />;
|
||||
};
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useDeleteOneWorkflowVersion } from '@/workflow/hooks/useDeleteOneWorkflowVersion';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const DiscardDraftWorkflowSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const { deleteOneWorkflowVersion } = useDeleteOneWorkflowVersion();
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
|
||||
|
||||
const onClick = () => {
|
||||
if (!isDefined(workflowWithCurrentVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteOneWorkflowVersion({
|
||||
workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
|
||||
});
|
||||
};
|
||||
|
||||
return <Command onClick={onClick} />;
|
||||
};
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useDuplicateWorkflow } from '@/workflow/hooks/useDuplicateWorkflow';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
|
||||
export const DuplicateWorkflowSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const workflow = useWorkflowWithCurrentVersion(recordId);
|
||||
const { duplicateWorkflow } = useDuplicateWorkflow();
|
||||
const navigate = useNavigateApp();
|
||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||
const { t } = useLingui();
|
||||
|
||||
const handleClick = async () => {
|
||||
if (!isDefined(workflow) || !isDefined(workflow.currentVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await duplicateWorkflow({
|
||||
workflowIdToDuplicate: workflow.id,
|
||||
workflowVersionIdToCopy: workflow.currentVersion.id,
|
||||
});
|
||||
|
||||
if (isDefined(result) && isNonEmptyString(result.workflowId)) {
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Workflow duplicated successfully`,
|
||||
});
|
||||
navigate(AppPath.RecordShowPage, {
|
||||
objectNameSingular: CoreObjectNameSingular.Workflow,
|
||||
objectRecordId: result.workflowId,
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to duplicate workflow`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return isDefined(workflow) ? <Command onClick={handleClick} /> : null;
|
||||
};
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
|
||||
import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types';
|
||||
import { useActiveWorkflowVersion } from '@/workflow/hooks/useActiveWorkflowVersion';
|
||||
|
||||
export const SeeActiveVersionWorkflowSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
|
||||
const { workflowVersion, loading } = useActiveWorkflowVersion({
|
||||
workflowId: recordId,
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
return <CommandMenuItemDisplay />;
|
||||
}
|
||||
|
||||
return (
|
||||
<CommandLink
|
||||
to={AppPath.RecordShowPage}
|
||||
params={{
|
||||
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
|
||||
objectRecordId: workflowVersion.id,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { AppPath, ViewFilterOperand } from 'twenty-shared/types';
|
||||
|
||||
export const SeeRunsWorkflowSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
|
||||
|
||||
return (
|
||||
<CommandLink
|
||||
to={AppPath.RecordIndexPage}
|
||||
params={{ objectNamePlural: CoreObjectNamePlural.WorkflowRun }}
|
||||
queryParams={{
|
||||
filter: {
|
||||
workflow: {
|
||||
[ViewFilterOperand.IS]: {
|
||||
selectedRecordIds: [workflowWithCurrentVersion?.id],
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { AppPath, ViewFilterOperand } from 'twenty-shared/types';
|
||||
|
||||
export const SeeVersionsWorkflowSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
|
||||
|
||||
return (
|
||||
<CommandLink
|
||||
to={AppPath.RecordIndexPage}
|
||||
params={{ objectNamePlural: CoreObjectNamePlural.WorkflowVersion }}
|
||||
queryParams={{
|
||||
filter: {
|
||||
workflow: {
|
||||
[ViewFilterOperand.IS]: {
|
||||
selectedRecordIds: [workflowWithCurrentVersion?.id],
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const TestWorkflowSingleRecordCommand = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const { runWorkflowVersion } = useRunWorkflowVersion();
|
||||
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
|
||||
|
||||
const onClick = () => {
|
||||
if (!isDefined(workflowWithCurrentVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
runWorkflowVersion({
|
||||
workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
|
||||
workflowId: workflowWithCurrentVersion.id,
|
||||
});
|
||||
};
|
||||
|
||||
return <Command onClick={onClick} />;
|
||||
};
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useGetUpdatableWorkflowVersionOrThrow } from '@/workflow/hooks/useGetUpdatableWorkflowVersionOrThrow';
|
||||
import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId';
|
||||
import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
|
||||
import { useTidyUpWorkflowVersion } from '@/workflow/workflow-version/hooks/useTidyUpWorkflowVersion';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useStore } from 'jotai';
|
||||
|
||||
export const TidyUpWorkflowSingleRecordCommand = () => {
|
||||
const store = useStore();
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const { tidyUpWorkflowVersion } = useTidyUpWorkflowVersion();
|
||||
const instanceId = getWorkflowVisualizerComponentInstanceId({
|
||||
recordId,
|
||||
});
|
||||
const { getUpdatableWorkflowVersion } =
|
||||
useGetUpdatableWorkflowVersionOrThrow(instanceId);
|
||||
|
||||
const onClick = async () => {
|
||||
const workflowDiagramAtom = workflowDiagramComponentState.atomFamily({
|
||||
instanceId,
|
||||
});
|
||||
const workflowDiagram = store.get(workflowDiagramAtom);
|
||||
|
||||
if (!isDefined(workflowDiagram)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const workflowVersionId = await getUpdatableWorkflowVersion();
|
||||
|
||||
await tidyUpWorkflowVersion(workflowVersionId, workflowDiagram);
|
||||
};
|
||||
|
||||
return <Command onClick={onClick} />;
|
||||
};
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
export enum WorkflowSingleRecordCommandKeys {
|
||||
ACTIVATE = 'activate-workflow-single-record',
|
||||
DEACTIVATE = 'deactivate-workflow-single-record',
|
||||
DISCARD_DRAFT = 'discard-draft-workflow-single-record',
|
||||
DUPLICATE_WORKFLOW = 'duplicate-workflow-single-record',
|
||||
SEE_ACTIVE_VERSION = 'see-active-version-workflow-single-record',
|
||||
SEE_RUNS = 'see-runs-workflow-single-record',
|
||||
SEE_VERSIONS = 'see-versions-workflow-single-record',
|
||||
TEST = 'test-workflow-single-record',
|
||||
ADD_NODE = 'add-node-workflow-single-record',
|
||||
TIDY_UP = 'tidy-up-workflow-single-record',
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
import { type MultipleRecordsCommandKeys } from '@/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys';
|
||||
import { type NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
|
||||
import { type SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
|
||||
|
||||
export type DefaultRecordCommandKeys =
|
||||
| NoSelectionRecordCommandKeys
|
||||
| SingleRecordCommandKeys
|
||||
| MultipleRecordsCommandKeys;
|
||||
-330
@@ -1,330 +0,0 @@
|
||||
import { DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG } from '@/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig';
|
||||
import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
|
||||
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
|
||||
import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
|
||||
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
|
||||
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
|
||||
import { type DefaultRecordCommandKeys } from '@/command-menu-item/record/types/DefaultRecordCommandKeys';
|
||||
import { IconHeart, IconPlus } from 'twenty-ui/display';
|
||||
import { inheritCommandMenuItemsFromDefaultConfig } from '@/command-menu-item/record/utils/inheritCommandMenuItemsFromDefaultConfig';
|
||||
|
||||
const MockComponent = <div>Mock Component</div>;
|
||||
|
||||
describe('inheritCommandMenuItemsFromDefaultConfig', () => {
|
||||
it('should return empty object when no action keys are provided', () => {
|
||||
const result = inheritCommandMenuItemsFromDefaultConfig({
|
||||
config: {},
|
||||
commandKeys: [],
|
||||
propertiesToOverwrite: {},
|
||||
});
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('should return only provided config when no default action keys are specified', () => {
|
||||
const customConfig: Record<string, CommandMenuItemConfig> = {
|
||||
'custom-action': {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.Object,
|
||||
key: 'custom-action',
|
||||
label: 'Custom Action',
|
||||
position: 100,
|
||||
Icon: IconPlus,
|
||||
shouldBeRegistered: () => true,
|
||||
component: MockComponent,
|
||||
},
|
||||
};
|
||||
|
||||
const result = inheritCommandMenuItemsFromDefaultConfig({
|
||||
config: customConfig,
|
||||
commandKeys: [],
|
||||
propertiesToOverwrite: {},
|
||||
});
|
||||
|
||||
expect(result).toEqual(customConfig);
|
||||
});
|
||||
|
||||
it('should inherit actions from default config', () => {
|
||||
const commandKeys: DefaultRecordCommandKeys[] = [
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
|
||||
SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
];
|
||||
|
||||
const result = inheritCommandMenuItemsFromDefaultConfig({
|
||||
config: {},
|
||||
commandKeys,
|
||||
propertiesToOverwrite: {},
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]:
|
||||
DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD
|
||||
],
|
||||
[SingleRecordCommandKeys.ADD_TO_FAVORITES]:
|
||||
DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
|
||||
SingleRecordCommandKeys.ADD_TO_FAVORITES
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should overwrite specific properties of inherited actions', () => {
|
||||
const commandKeys: DefaultRecordCommandKeys[] = [
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
|
||||
];
|
||||
|
||||
const propertiesToOverwrite = {
|
||||
[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {
|
||||
label: 'Custom Create Label',
|
||||
position: 999,
|
||||
isPinned: false,
|
||||
},
|
||||
};
|
||||
|
||||
const result = inheritCommandMenuItemsFromDefaultConfig({
|
||||
config: {},
|
||||
commandKeys,
|
||||
propertiesToOverwrite,
|
||||
});
|
||||
|
||||
const expectedCommandMenuItem = {
|
||||
...DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD
|
||||
],
|
||||
label: 'Custom Create Label',
|
||||
position: 999,
|
||||
isPinned: false,
|
||||
};
|
||||
|
||||
expect(result).toEqual({
|
||||
[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: expectedCommandMenuItem,
|
||||
});
|
||||
});
|
||||
|
||||
it('should overwrite properties for multiple actions', () => {
|
||||
const commandKeys: DefaultRecordCommandKeys[] = [
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
|
||||
SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
];
|
||||
|
||||
const propertiesToOverwrite = {
|
||||
[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {
|
||||
position: 10,
|
||||
},
|
||||
[SingleRecordCommandKeys.ADD_TO_FAVORITES]: {
|
||||
label: 'Custom Favorite Label',
|
||||
Icon: IconHeart,
|
||||
},
|
||||
};
|
||||
|
||||
const result = inheritCommandMenuItemsFromDefaultConfig({
|
||||
config: {},
|
||||
commandKeys,
|
||||
propertiesToOverwrite,
|
||||
});
|
||||
|
||||
expect(result[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]).toEqual({
|
||||
...DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD
|
||||
],
|
||||
position: 10,
|
||||
});
|
||||
|
||||
expect(result[SingleRecordCommandKeys.ADD_TO_FAVORITES]).toEqual({
|
||||
...DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
|
||||
SingleRecordCommandKeys.ADD_TO_FAVORITES
|
||||
],
|
||||
label: 'Custom Favorite Label',
|
||||
Icon: IconHeart,
|
||||
});
|
||||
});
|
||||
|
||||
it('should only overwrite properties for specified actions', () => {
|
||||
const commandKeys: DefaultRecordCommandKeys[] = [
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
|
||||
SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
];
|
||||
|
||||
const propertiesToOverwrite = {
|
||||
[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {
|
||||
position: 10,
|
||||
},
|
||||
};
|
||||
|
||||
const result = inheritCommandMenuItemsFromDefaultConfig({
|
||||
config: {},
|
||||
commandKeys,
|
||||
propertiesToOverwrite,
|
||||
});
|
||||
|
||||
expect(result[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]).toEqual({
|
||||
...DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD
|
||||
],
|
||||
position: 10,
|
||||
});
|
||||
|
||||
expect(result[SingleRecordCommandKeys.ADD_TO_FAVORITES]).toEqual(
|
||||
DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
|
||||
SingleRecordCommandKeys.ADD_TO_FAVORITES
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
it('should merge inherited actions with provided config', () => {
|
||||
const customConfig: Record<string, CommandMenuItemConfig> = {
|
||||
'custom-action': {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.Object,
|
||||
key: 'custom-action',
|
||||
label: 'Custom Action',
|
||||
position: 100,
|
||||
Icon: IconPlus,
|
||||
shouldBeRegistered: () => true,
|
||||
component: MockComponent,
|
||||
},
|
||||
};
|
||||
|
||||
const commandKeys: DefaultRecordCommandKeys[] = [
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
|
||||
];
|
||||
|
||||
const result = inheritCommandMenuItemsFromDefaultConfig({
|
||||
config: customConfig,
|
||||
commandKeys,
|
||||
propertiesToOverwrite: {},
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]:
|
||||
DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD
|
||||
],
|
||||
'custom-action': customConfig['custom-action'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should prioritize provided config over inherited actions when keys conflict', () => {
|
||||
const customConfig: Record<string, CommandMenuItemConfig> = {
|
||||
[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.Object,
|
||||
key: NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
|
||||
label: 'Overridden Create Action',
|
||||
position: 999,
|
||||
Icon: IconHeart,
|
||||
shouldBeRegistered: () => false,
|
||||
component: MockComponent,
|
||||
},
|
||||
};
|
||||
|
||||
const commandKeys: DefaultRecordCommandKeys[] = [
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
|
||||
];
|
||||
|
||||
const result = inheritCommandMenuItemsFromDefaultConfig({
|
||||
config: customConfig,
|
||||
commandKeys,
|
||||
propertiesToOverwrite: {},
|
||||
});
|
||||
|
||||
expect(result[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]).toEqual(
|
||||
customConfig[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD],
|
||||
);
|
||||
expect(result[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD].label).toBe(
|
||||
'Overridden Create Action',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle complex scenario with inheritance, overrides, and custom config', () => {
|
||||
const customConfig: Record<string, CommandMenuItemConfig> = {
|
||||
'custom-action-1': {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.Object,
|
||||
key: 'custom-action-1',
|
||||
label: 'Custom Action 1',
|
||||
position: 50,
|
||||
Icon: IconPlus,
|
||||
shouldBeRegistered: () => true,
|
||||
component: MockComponent,
|
||||
},
|
||||
[SingleRecordCommandKeys.ADD_TO_FAVORITES]: {
|
||||
type: CommandMenuItemType.Standard,
|
||||
scope: CommandMenuItemScope.RecordSelection,
|
||||
key: SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
label: 'Completely Custom Favorites',
|
||||
position: 1000,
|
||||
Icon: IconHeart,
|
||||
shouldBeRegistered: () => false,
|
||||
component: MockComponent,
|
||||
},
|
||||
};
|
||||
|
||||
const commandKeys: DefaultRecordCommandKeys[] = [
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
|
||||
SingleRecordCommandKeys.ADD_TO_FAVORITES,
|
||||
SingleRecordCommandKeys.REMOVE_FROM_FAVORITES,
|
||||
];
|
||||
|
||||
const propertiesToOverwrite = {
|
||||
[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {
|
||||
label: 'Modified Create Label',
|
||||
position: 5,
|
||||
},
|
||||
[SingleRecordCommandKeys.REMOVE_FROM_FAVORITES]: {
|
||||
isPinned: false,
|
||||
},
|
||||
};
|
||||
|
||||
const result = inheritCommandMenuItemsFromDefaultConfig({
|
||||
config: customConfig,
|
||||
commandKeys,
|
||||
propertiesToOverwrite,
|
||||
});
|
||||
|
||||
expect(Object.keys(result)).toHaveLength(4);
|
||||
|
||||
expect(result['custom-action-1']).toEqual(customConfig['custom-action-1']);
|
||||
|
||||
expect(result[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]).toEqual({
|
||||
...DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD
|
||||
],
|
||||
label: 'Modified Create Label',
|
||||
position: 5,
|
||||
});
|
||||
|
||||
expect(result[SingleRecordCommandKeys.ADD_TO_FAVORITES]).toEqual(
|
||||
customConfig[SingleRecordCommandKeys.ADD_TO_FAVORITES],
|
||||
);
|
||||
|
||||
expect(result[SingleRecordCommandKeys.REMOVE_FROM_FAVORITES]).toEqual({
|
||||
...DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
|
||||
SingleRecordCommandKeys.REMOVE_FROM_FAVORITES
|
||||
],
|
||||
isPinned: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty overrides gracefully', () => {
|
||||
const commandKeys: DefaultRecordCommandKeys[] = [
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
|
||||
];
|
||||
|
||||
const propertiesToOverwrite = {
|
||||
[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {},
|
||||
};
|
||||
|
||||
const result = inheritCommandMenuItemsFromDefaultConfig({
|
||||
config: {},
|
||||
commandKeys,
|
||||
propertiesToOverwrite,
|
||||
});
|
||||
|
||||
expect(result[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]).toEqual(
|
||||
DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
|
||||
NoSelectionRecordCommandKeys.CREATE_NEW_RECORD
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
import { type WorkflowTrigger } from '@/workflow/types/Workflow';
|
||||
import { isBulkRecordsManualTrigger } from '@/command-menu-item/record/utils/isBulkRecordsManualTrigger';
|
||||
|
||||
describe('isBulkRecordsManualTrigger', () => {
|
||||
it('should return true for a MANUAL trigger with BULK_RECORDS availability', () => {
|
||||
const trigger: WorkflowTrigger = {
|
||||
type: 'MANUAL',
|
||||
settings: {
|
||||
outputSchema: {},
|
||||
availability: {
|
||||
type: 'BULK_RECORDS',
|
||||
objectNameSingular: 'person',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(isBulkRecordsManualTrigger(trigger)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for a MANUAL trigger with SINGLE_RECORD availability', () => {
|
||||
const trigger: WorkflowTrigger = {
|
||||
type: 'MANUAL',
|
||||
settings: {
|
||||
outputSchema: {},
|
||||
availability: {
|
||||
type: 'SINGLE_RECORD',
|
||||
objectNameSingular: 'person',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(isBulkRecordsManualTrigger(trigger)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for a MANUAL trigger with GLOBAL availability', () => {
|
||||
const trigger: WorkflowTrigger = {
|
||||
type: 'MANUAL',
|
||||
settings: {
|
||||
outputSchema: {},
|
||||
availability: {
|
||||
type: 'GLOBAL',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(isBulkRecordsManualTrigger(trigger)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for a MANUAL trigger with no availability', () => {
|
||||
const trigger: WorkflowTrigger = {
|
||||
type: 'MANUAL',
|
||||
settings: {
|
||||
outputSchema: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(isBulkRecordsManualTrigger(trigger)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for a non-MANUAL trigger', () => {
|
||||
const trigger: WorkflowTrigger = {
|
||||
type: 'DATABASE_EVENT',
|
||||
settings: {
|
||||
eventName: 'company.created',
|
||||
outputSchema: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(isBulkRecordsManualTrigger(trigger)).toBe(false);
|
||||
});
|
||||
});
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
import { DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG } from '@/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig';
|
||||
import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
|
||||
import { type DefaultRecordCommandKeys } from '@/command-menu-item/record/types/DefaultRecordCommandKeys';
|
||||
|
||||
export const inheritCommandMenuItemsFromDefaultConfig = ({
|
||||
config,
|
||||
commandKeys,
|
||||
propertiesToOverwrite,
|
||||
}: {
|
||||
config: Record<string, CommandMenuItemConfig>;
|
||||
commandKeys: DefaultRecordCommandKeys[];
|
||||
propertiesToOverwrite: Partial<
|
||||
Record<DefaultRecordCommandKeys, Partial<CommandMenuItemConfig>>
|
||||
>;
|
||||
}): Record<string, CommandMenuItemConfig> => {
|
||||
const commandMenuItemsFromDefaultConfig = commandKeys.reduce(
|
||||
(acc, key) => ({
|
||||
...acc,
|
||||
[key]: {
|
||||
...DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[key],
|
||||
...propertiesToOverwrite[key],
|
||||
},
|
||||
}),
|
||||
{} as Record<string, CommandMenuItemConfig>,
|
||||
);
|
||||
|
||||
return {
|
||||
...commandMenuItemsFromDefaultConfig,
|
||||
...config,
|
||||
};
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user