Migrate output schema to V2 (#14311)

Previous refacto was creating output schema V2 which has more specific
schemas based on the step type. Before we were using one common schema,
which was too complex when searching for variable informations.

This PR migrate the deprecated schemas and remove the old code:
- mark previous `BaseOutputSchema` as deprecated
- remove other previous schemas
- use V2 everywhere
- icon should not be stored in schema. Instead it should be generated
based on the fieldmetadata or the item type
This commit is contained in:
Thomas Trompette
2025-09-09 18:37:00 +02:00
committed by GitHub
parent e787dadde8
commit 2b59198a1b
57 changed files with 589 additions and 1256 deletions
@@ -4,13 +4,12 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { type InputSchemaPropertyType } from '@/workflow/types/InputSchema';
import { WorkflowVariablesDropdownAllItems } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownAllItems';
import { WorkflowVariablesDropdownFieldItems } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownFieldItems';
import { WorkflowVariablesDropdownWorkflowStepItems } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownWorkflowStepItems';
import { WorkflowVariablesDropdownStepItems } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownStepItems';
import { WorkflowVariablesDropdownSteps } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownSteps';
import { SEARCH_VARIABLES_DROPDOWN_ID } from '@/workflow/workflow-variables/constants/SearchVariablesDropdownId';
import { useAvailableVariablesInWorkflowStep } from '@/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep';
import { type StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema';
import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useState } from 'react';
@@ -73,7 +72,7 @@ export const WorkflowVariablesDropdown = ({
: undefined;
const [selectedStep, setSelectedStep] = useState<
StepOutputSchema | undefined
StepOutputSchemaV2 | undefined
>(initialStep);
const handleStepSelect = (stepId: string) => {
@@ -122,22 +121,17 @@ export const WorkflowVariablesDropdown = ({
}
dropdownComponents={
!isDefined(selectedStep) ? (
<WorkflowVariablesDropdownWorkflowStepItems
<WorkflowVariablesDropdownSteps
dropdownId={dropdownId}
steps={availableVariablesInWorkflowStep}
onSelect={handleStepSelect}
/>
) : shouldDisplayRecordObjects ? (
<WorkflowVariablesDropdownAllItems
step={selectedStep}
onSelect={handleSubItemSelect}
onBack={handleBack}
/>
) : (
<WorkflowVariablesDropdownFieldItems
<WorkflowVariablesDropdownStepItems
step={selectedStep}
onSelect={handleSubItemSelect}
onBack={handleBack}
shouldDisplayRecordObjects={shouldDisplayRecordObjects}
/>
)
}
@@ -1,82 +0,0 @@
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { type StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { getStepHeaderLabel } from '@/workflow/workflow-variables/utils/getStepHeaderLabel';
import {
IconChevronLeft,
OverflowingTextWithTooltip,
useIcons,
} from 'twenty-ui/display';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { useVariableDropdown } from '../hooks/useVariableDropdown';
type WorkflowVariablesDropdownFieldItemsProps = {
step: StepOutputSchema;
onSelect: (value: string) => void;
onBack: () => void;
};
export const WorkflowVariablesDropdownFieldItems = ({
step,
onSelect,
onBack,
}: WorkflowVariablesDropdownFieldItemsProps) => {
const { getIcon } = useIcons();
const {
searchInputValue,
setSearchInputValue,
handleSelectField,
goBack,
filteredOptions,
currentPath,
} = useVariableDropdown({
step,
onSelect,
onBack,
});
return (
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<DropdownMenuHeader
StartComponent={
<DropdownMenuHeaderLeftComponent
onClick={goBack}
Icon={IconChevronLeft}
/>
}
>
<OverflowingTextWithTooltip
text={getStepHeaderLabel(step, currentPath)}
/>
</DropdownMenuHeader>
<DropdownMenuSearchInput
autoFocus
value={searchInputValue}
onChange={(event) => setSearchInputValue(event.target.value)}
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
{filteredOptions.map(([key, option]) => (
<MenuItemSelect
key={key}
selected={false}
focused={false}
onClick={() => handleSelectField(key)}
text={option.label || key}
hasSubMenu={!option.isLeaf}
LeftIcon={option.icon ? getIcon(option.icon) : undefined}
contextualText={
option.isLeaf ? option?.value?.toString() : undefined
}
/>
))}
</DropdownMenuItemsContainer>
</DropdownContent>
);
};
@@ -2,16 +2,19 @@ import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenu
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { type StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { isRecordOutputSchemaV2 } from '@/workflow/workflow-variables/types/guards/isRecordOutputSchemaV2';
import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2';
import { getCurrentSubStepFromPath } from '@/workflow/workflow-variables/utils/getCurrentSubStepFromPath';
import { getStepHeaderLabel } from '@/workflow/workflow-variables/utils/getStepHeaderLabel';
import { getStepItemIcon } from '@/workflow/workflow-variables/utils/getStepItemIcon';
import { getVariableTemplateFromPath } from '@/workflow/workflow-variables/utils/getVariableTemplateFromPath';
import { isRecordOutputSchema } from '@/workflow/workflow-variables/utils/isRecordOutputSchema';
import { useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared/utils';
import {
IconChevronLeft,
OverflowingTextWithTooltip,
@@ -20,17 +23,19 @@ import {
import { MenuItemSelect } from 'twenty-ui/navigation';
import { useVariableDropdown } from '../hooks/useVariableDropdown';
type WorkflowVariablesDropdownAllItemsProps = {
step: StepOutputSchema;
type WorkflowVariablesDropdownStepItemsProps = {
step: StepOutputSchemaV2;
onSelect: (value: string) => void;
onBack: () => void;
shouldDisplayRecordObjects: boolean;
};
export const WorkflowVariablesDropdownAllItems = ({
export const WorkflowVariablesDropdownStepItems = ({
step,
onSelect,
onBack,
}: WorkflowVariablesDropdownAllItemsProps) => {
shouldDisplayRecordObjects,
}: WorkflowVariablesDropdownStepItemsProps) => {
const { t } = useLingui();
const { getIcon } = useIcons();
const {
@@ -46,10 +51,12 @@ export const WorkflowVariablesDropdownAllItems = ({
onBack,
});
const { objectMetadataItems } = useObjectMetadataItems();
const getDisplayedSubStepObject = () => {
const currentSubStep = getCurrentSubStepFromPath(step, currentPath);
if (!isRecordOutputSchema(currentSubStep)) {
if (!isRecordOutputSchemaV2(currentSubStep)) {
return;
}
@@ -59,30 +66,37 @@ export const WorkflowVariablesDropdownAllItems = ({
const handleSelectObject = () => {
const currentSubStep = getCurrentSubStepFromPath(step, currentPath);
if (!isRecordOutputSchema(currentSubStep)) {
if (!isRecordOutputSchemaV2(currentSubStep)) {
return;
}
onSelect(
getVariableTemplateFromPath({
stepId: step.id,
path: [...currentPath, currentSubStep.object.fieldIdName],
path: [...currentPath, 'id'],
}),
);
};
const displayedSubStepObject = getDisplayedSubStepObject();
const shouldDisplaySubStepObject = searchInputValue
? displayedSubStepObject?.label &&
displayedSubStepObject.label
const displayedSubStepObjectMetadata = isDefined(displayedSubStepObject)
? objectMetadataItems.find(
(item) => item.id === displayedSubStepObject?.objectMetadataId,
)
: undefined;
const isObjectFoundThroughSearch = isDefined(searchInputValue)
? isDefined(displayedSubStepObject?.label) &&
displayedSubStepObject?.label
.toLowerCase()
.includes(searchInputValue.toLowerCase())
: true;
const shouldDisplayObject =
shouldDisplaySubStepObject && displayedSubStepObject?.label;
const nameSingular = displayedSubStepObject?.nameSingular;
const shouldDisplaySubStepObject =
shouldDisplayRecordObjects && isObjectFoundThroughSearch;
const objectLabel = displayedSubStepObjectMetadata?.labelSingular;
return (
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
@@ -105,22 +119,22 @@ export const WorkflowVariablesDropdownAllItems = ({
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
{shouldDisplayObject && (
{shouldDisplaySubStepObject && (
<MenuItemSelect
selected={false}
focused={false}
onClick={handleSelectObject}
text={displayedSubStepObject?.label || ''}
text={objectLabel || ''}
hasSubMenu={false}
LeftIcon={
displayedSubStepObject.icon
? getIcon(displayedSubStepObject.icon)
displayedSubStepObjectMetadata?.icon
? getIcon(displayedSubStepObjectMetadata.icon)
: undefined
}
contextualText={t`Pick a ${nameSingular} record`}
contextualText={t`Pick a ${objectLabel} record`}
/>
)}
{filteredOptions.length > 0 && shouldDisplayObject && (
{filteredOptions.length > 0 && shouldDisplaySubStepObject && (
<DropdownMenuSeparator />
)}
{filteredOptions.map(([key, subStep]) => (
@@ -131,7 +145,15 @@ export const WorkflowVariablesDropdownAllItems = ({
onClick={() => handleSelectField(key)}
text={subStep.label || key}
hasSubMenu={!subStep.isLeaf}
LeftIcon={subStep.icon ? getIcon(subStep.icon) : undefined}
LeftIcon={
subStep.icon
? getIcon(subStep.icon)
: getIcon(
getStepItemIcon({
itemType: subStep.type,
}),
)
}
contextualText={
subStep.isLeaf ? subStep?.value?.toString() : undefined
}
@@ -6,22 +6,22 @@ import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/Dropdow
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { type StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema';
import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2';
import { useState } from 'react';
import { IconX, OverflowingTextWithTooltip, useIcons } from 'twenty-ui/display';
import { MenuItem, MenuItemSelect } from 'twenty-ui/navigation';
type WorkflowVariablesDropdownWorkflowStepItemsProps = {
type WorkflowVariablesDropdownStepsProps = {
dropdownId: string;
steps: StepOutputSchema[];
steps: StepOutputSchemaV2[];
onSelect: (value: string) => void;
};
export const WorkflowVariablesDropdownWorkflowStepItems = ({
export const WorkflowVariablesDropdownSteps = ({
dropdownId,
steps,
onSelect,
}: WorkflowVariablesDropdownWorkflowStepItemsProps) => {
}: WorkflowVariablesDropdownStepsProps) => {
const { getIcon } = useIcons();
const [searchInputValue, setSearchInputValue] = useState('');