Fix Side Panel v2 bugs (#10865)

Fixing:
- Export as PDF on empty note
- Command O (sub commands) not using the right contextStore
- BelongsToOne Field input triggering an error on open if no existing
relation record is pre-selected
This commit is contained in:
Charles Bochet
2025-03-13 19:18:34 +01:00
committed by GitHub
parent cb96f019d6
commit 3901ff3207
8 changed files with 34 additions and 201 deletions

View File

@ -2,7 +2,7 @@ import { RegisterRecordActionEffect } from '@/action-menu/actions/record-actions
import { WorkflowRunRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/workflow-run-record-actions/components/WorkflowRunRecordActionMenuEntrySetter';
import { getActionConfig } from '@/action-menu/actions/utils/getActionConfig';
import { getActionViewType } from '@/action-menu/actions/utils/getActionViewType';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
@ -14,18 +14,31 @@ import { isDefined } from 'twenty-shared';
import { FeatureFlagKey } from '~/generated/graphql';
export const RecordActionMenuEntriesSetter = () => {
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(
const localContextStoreCurrentObjectMetadataItemId =
useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
);
const mainContextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
COMMAND_MENU_COMPONENT_INSTANCE_ID,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const objectMetadataItem = objectMetadataItems.find(
const localContextStoreObjectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id === contextStoreCurrentObjectMetadataItemId,
objectMetadataItem.id === localContextStoreCurrentObjectMetadataItemId,
);
const mainContextStoreObjectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id === mainContextStoreCurrentObjectMetadataItemId,
);
const objectMetadataItem =
localContextStoreObjectMetadataItem ?? mainContextStoreObjectMetadataItem;
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
);

View File

@ -39,7 +39,7 @@ import {
IconTrashX,
} from 'twenty-ui';
export const DEFAULT_ACTIONS_CONFIG_V2: Record<
export const DEFAULT_ACTIONS_CONFIG: Record<
string,
ActionMenuEntry & {
useAction: ActionHook;

View File

@ -1,188 +0,0 @@
import { useDeleteMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction';
import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction';
import { useRestoreMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useRestoreMultipleRecordsAction';
import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys';
import { useImportRecordsNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useImportRecordsNoSelectionRecordAction';
import { useSeeDeletedRecordsNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useSeeDeletedRecordsNoSelectionRecordAction';
import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKey';
import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction';
import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction';
import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction';
import { useRestoreSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRestoreSingleRecordAction';
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
import { ActionHook } from '@/action-menu/actions/types/ActionHook';
import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import {
ActionMenuEntry,
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
import { msg } from '@lingui/core/macro';
import {
IconDatabaseExport,
IconFileImport,
IconHeart,
IconHeartOff,
IconRefresh,
IconRotate2,
IconTrash,
} from 'twenty-ui';
export const DEFAULT_ACTIONS_CONFIG_V1: Record<
string,
ActionMenuEntry & {
useAction: ActionHook;
}
> = {
addToFavoritesSingleRecord: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: SingleRecordActionKeys.ADD_TO_FAVORITES,
label: msg`Add to favorites`,
position: 0,
Icon: IconHeart,
availableOn: [
ActionViewType.SHOW_PAGE,
ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
useAction: useAddToFavoritesSingleRecordAction,
},
removeFromFavoritesSingleRecord: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: SingleRecordActionKeys.REMOVE_FROM_FAVORITES,
label: msg`Remove from favorites`,
position: 1,
Icon: IconHeartOff,
availableOn: [
ActionViewType.SHOW_PAGE,
ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
useAction: useRemoveFromFavoritesSingleRecordAction,
},
deleteSingleRecord: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: SingleRecordActionKeys.DELETE,
label: msg`Delete`,
position: 2,
Icon: IconTrash,
accent: 'danger',
isPinned: true,
availableOn: [
ActionViewType.SHOW_PAGE,
ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
useAction: useDeleteSingleRecordAction,
},
deleteMultipleRecords: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: MultipleRecordsActionKeys.DELETE,
label: msg`Delete records`,
shortLabel: msg`Delete`,
position: 3,
Icon: IconTrash,
accent: 'danger',
isPinned: true,
availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION],
useAction: useDeleteMultipleRecordsAction,
},
exportSingleRecord: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: SingleRecordActionKeys.EXPORT,
label: msg`Export record`,
shortLabel: msg`Export`,
position: 4,
Icon: IconDatabaseExport,
accent: 'default',
isPinned: false,
availableOn: [
ActionViewType.SHOW_PAGE,
ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
useAction: useExportMultipleRecordsAction,
},
exportMultipleRecords: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: MultipleRecordsActionKeys.EXPORT,
label: msg`Export records`,
shortLabel: msg`Export`,
position: 5,
Icon: IconDatabaseExport,
accent: 'default',
isPinned: false,
availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION],
useAction: useExportMultipleRecordsAction,
},
exportView: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.Object,
key: NoSelectionRecordActionKeys.EXPORT_VIEW,
label: msg`Export view`,
shortLabel: msg`Export`,
position: 6,
Icon: IconDatabaseExport,
accent: 'default',
isPinned: false,
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
useAction: useExportMultipleRecordsAction,
},
restoreSingleRecord: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: SingleRecordActionKeys.RESTORE,
label: msg`Restore record`,
shortLabel: msg`Restore`,
position: 7,
Icon: IconRefresh,
accent: 'default',
isPinned: true,
availableOn: [
ActionViewType.SHOW_PAGE,
ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
useAction: useRestoreSingleRecordAction,
},
restoreMultipleRecords: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: MultipleRecordsActionKeys.RESTORE,
label: msg`Restore records`,
shortLabel: msg`Restore`,
position: 8,
Icon: IconRefresh,
accent: 'default',
isPinned: true,
availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION],
useAction: useRestoreMultipleRecordsAction,
},
seeDeletedRecords: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.Object,
key: NoSelectionRecordActionKeys.SEE_DELETED_RECORDS,
label: msg`See deleted records`,
shortLabel: msg`Deleted records`,
position: 9,
Icon: IconRotate2,
accent: 'default',
isPinned: false,
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
useAction: useSeeDeletedRecordsNoSelectionRecordAction,
},
importRecords: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.Object,
key: NoSelectionRecordActionKeys.IMPORT_RECORDS,
label: msg`Import records`,
shortLabel: msg`Import`,
position: 10,
Icon: IconFileImport,
accent: 'default',
isPinned: false,
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
useAction: useImportRecordsNoSelectionRecordAction,
},
};

View File

@ -3,6 +3,7 @@ import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/Ac
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { BlockNoteEditor } from '@blocknote/core';
import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
@ -20,10 +21,13 @@ export const useExportNoteAction: ActionHookWithObjectMetadataItem = ({
objectMetadataItem?.nameSingular === CoreObjectNameSingular.Task;
const shouldBeRegistered =
isDefined(objectMetadataItem) && isDefined(selectedRecord) && isNoteOrTask;
isDefined(objectMetadataItem) &&
isDefined(selectedRecord) &&
isNoteOrTask &&
isNonEmptyString(selectedRecord.bodyV2?.blocknote);
const onClick = async () => {
if (!shouldBeRegistered || !selectedRecord?.body) {
if (!shouldBeRegistered || !selectedRecord.bodyV2.blocknote) {
return;
}

View File

@ -1,4 +1,4 @@
import { DEFAULT_ACTIONS_CONFIG_V2 } from '@/action-menu/actions/record-actions/constants/DefaultActionsConfigV2';
import { DEFAULT_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/DefaultActionsConfig';
import { WORKFLOW_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowActionsConfig';
import { WORKFLOW_RUNS_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig';
import { WORKFLOW_VERSIONS_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig';
@ -14,6 +14,6 @@ export const getActionConfig = (objectMetadataItem: ObjectMetadataItem) => {
case CoreObjectNameSingular.WorkflowRun:
return WORKFLOW_RUNS_ACTIONS_CONFIG;
default:
return DEFAULT_ACTIONS_CONFIG_V2;
return DEFAULT_ACTIONS_CONFIG;
}
};

View File

@ -140,7 +140,7 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
))}
</DropdownMenuItemsContainer>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<DropdownMenuItemsContainer scrollable={false}>
<UndecoratedLink
to={newSelectFieldSettingsUrl}
onClick={() => {

View File

@ -197,7 +197,9 @@ export const RecordDetailRelationSection = ({
const handleOpenRelationPickerDropdown = () => {
if (isToOneObject) {
setSingleRecordPickerSearchFilter('');
setSingleRecordPickerSelectedId(relationRecords[0].id);
if (relationRecords.length > 0) {
setSingleRecordPickerSelectedId(relationRecords[0].id);
}
}
if (isToManyObjects) {

View File

@ -65,6 +65,8 @@ export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await sleep(100);
await canvas.findByText('Code your function');
await canvas.findByText('Code your function', undefined, {
timeout: 3000,
});
},
};