From 99d60cd48bc89248c0845c0cc5d9a420f55206bd Mon Sep 17 00:00:00 2001 From: Thomas des Francs Date: Mon, 24 Nov 2025 11:48:55 +0100 Subject: [PATCH] Adding system objects to workflow search node (v2) (#15965) This PR adds support for selecting system objects in the workflow Find Records action. The implementation includes extracting duplicated dropdown code into a reusable component and fixing several code quality issues identified in previous PR feedback. All code quality concerns have been addressed: proper TypeScript types, utility function usage, correct memoization, and improved variable naming. https://github.com/user-attachments/assets/8d0bd052-d3dd-4e3e-8379-35bfcb18babc CleanShot 2025-11-20 at 16 55 14 --------- Co-authored-by: Charles Bochet --- .../WorkflowEditActionFindRecords.tsx | 123 +++++++++++------ .../WorkflowObjectDropdownContent.tsx | 130 ++++++++++++++++++ 2 files changed, 210 insertions(+), 43 deletions(-) create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowObjectDropdownContent.tsx diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords.tsx index ab8a14c760..8bef879f5f 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords.tsx @@ -1,8 +1,15 @@ -import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; -import { Select } from '@/ui/input/components/Select'; -import { type WorkflowFindRecordsAction } from '@/workflow/types/Workflow'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { useLingui } from '@lingui/react/macro'; +import { isNumber } from '@sniptt/guards'; import { useEffect, useState } from 'react'; +import { QUERY_MAX_RECORDS } from 'twenty-shared/constants'; +import { isDefined } from 'twenty-shared/utils'; +import { HorizontalSeparator, useIcons } from 'twenty-ui/display'; +import { type JsonValue } from 'type-fest'; +import { useDebouncedCallback } from 'use-debounce'; +import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy'; import { FormNumberFieldInput } from '@/object-record/record-field/ui/form-types/components/FormNumberFieldInput'; @@ -14,19 +21,30 @@ import { RecordIndexContextProvider } from '@/object-record/record-index/context import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates'; import { type RecordSort } from '@/object-record/record-sort/types/RecordSort'; import { InputLabel } from '@/ui/input/components/InputLabel'; +import { SelectControl } from '@/ui/input/components/SelectControl'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; +import { type WorkflowFindRecordsAction } from '@/workflow/types/Workflow'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter'; import { WorkflowFindRecordsFilters } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsFilters'; import { WorkflowFindRecordsFiltersEffect } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsFiltersEffect'; import { WorkflowFindRecordsSorts } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts'; -import { useLingui } from '@lingui/react/macro'; -import { isNumber } from '@sniptt/guards'; -import { QUERY_MAX_RECORDS } from 'twenty-shared/constants'; -import { isDefined } from 'twenty-shared/utils'; -import { HorizontalSeparator, useIcons } from 'twenty-ui/display'; -import { type SelectOption } from 'twenty-ui/input'; -import { type JsonValue } from 'type-fest'; -import { useDebouncedCallback } from 'use-debounce'; +import { WorkflowObjectDropdownContent } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowObjectDropdownContent'; + +const StyledLabel = styled.span` + color: ${({ theme }) => theme.font.color.light}; + display: block; + font-size: ${({ theme }) => theme.font.size.xs}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + margin-bottom: ${({ theme }) => theme.spacing(1)}; +`; + +const StyledRecordTypeSelectContainer = styled.div<{ fullWidth?: boolean }>` + width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')}; +`; + +const DEFAULT_SELECTED_OPTION = { label: 'Select an option', value: '' }; type WorkflowEditActionFindRecordsProps = { action: WorkflowFindRecordsAction; @@ -62,19 +80,15 @@ export const WorkflowEditActionFindRecords = ({ action, actionOptions, }: WorkflowEditActionFindRecordsProps) => { - const { getIcon } = useIcons(); + const theme = useTheme(); const { t } = useLingui(); const maxRecordsFormatted = QUERY_MAX_RECORDS.toLocaleString(); - const { activeNonSystemObjectMetadataItems } = - useFilteredObjectMetadataItems(); + const dropdownId = 'workflow-edit-action-record-find-records-object-name'; - const availableMetadata: Array> = - activeNonSystemObjectMetadataItems.map((item) => ({ - Icon: getIcon(item.icon), - label: item.labelPlural, - value: item.nameSingular, - })); + const { closeDropdown } = useCloseDropdown(); + + const { objectMetadataItems } = useFilteredObjectMetadataItems(); const [formData, setFormData] = useState(() => ({ objectNameSingular: action.settings.input.objectName, @@ -86,16 +100,23 @@ export const WorkflowEditActionFindRecords = ({ filter: action.settings.input.filter as FindRecordsActionFilter, orderBy: action.settings.input.orderBy as FindRecordsActionOrderBy, })); + const [limitError, setLimitError] = useState(undefined); const isFormDisabled = actionOptions.readonly ?? false; const instanceId = `workflow-edit-action-record-find-records-${action.id}-${formData.objectNameSingular}`; - const selectedObjectMetadataItem = activeNonSystemObjectMetadataItems.find( + const selectedObjectMetadataItem = objectMetadataItems.find( (item) => item.nameSingular === formData.objectNameSingular, ); + const { getIcon } = useIcons(); - const selectedObjectMetadataItemNameSingular = - selectedObjectMetadataItem?.nameSingular ?? ''; + const selectedOption = selectedObjectMetadataItem + ? { + Icon: getIcon(selectedObjectMetadataItem?.icon), + label: selectedObjectMetadataItem?.labelPlural, + value: selectedObjectMetadataItem?.nameSingular, + } + : DEFAULT_SELECTED_OPTION; const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); @@ -144,29 +165,45 @@ export const WorkflowEditActionFindRecords = ({ }; }, [saveAction]); + const handleOptionClick = (value: string) => { + if (actionOptions.readonly === true) { + return; + } + + const newFormData: FindRecordsFormData = { + objectNameSingular: value, + limit: 1, + }; + + setFormData(newFormData); + saveAction(newFormData); + closeDropdown(dropdownId); + }; + return ( <> -