Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CommandMenuWorkflowRunStepContentComponentInstanceContext } from '@/command-menu/pages/workflow/step/view-run/states/contexts/CommandMenuWorkflowRunStepContentComponentInstanceContext';
import { getIsInputTabDisabled } from '@/command-menu/pages/workflow/step/view-run/utils/getIsInputTabDisabled';
import { getIsOutputTabDisabled } from '@/command-menu/pages/workflow/step/view-run/utils/getIsOutputTabDisabled';
import { getShouldFocusNodeTab } from '@/command-menu/pages/workflow/step/view-run/utils/getShouldFocusNodeTab';
Expand All @@ -12,6 +13,7 @@ import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { WorkflowIteratorSubStepSwitcher } from '@/workflow/workflow-steps/components/WorkflowIteratorSubStepSwitcher';
import { WorkflowRunStepInputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepInputDetail';
import { WorkflowRunStepNodeDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepNodeDetail';
import { WorkflowRunStepOutputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepOutputDetail';
Expand Down Expand Up @@ -112,46 +114,54 @@ export const CommandMenuWorkflowRunViewStepContent = () => {
];

return (
<StyledContainer>
{shouldFocusNodeTab ? (
<WorkflowRunStepNodeDetail
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
stepExecutionStatus={stepExecutionStatus}
/>
) : (
<>
<StyledTabList
tabs={tabs}
behaveAsLinks={false}
componentInstanceId={commandMenuPageComponentInstance.instanceId}
<CommandMenuWorkflowRunStepContentComponentInstanceContext.Provider
value={{
instanceId: `${workflowRunId}_${workflowSelectedNode}`,
}}
>
<StyledContainer>
{shouldFocusNodeTab ? (
<WorkflowRunStepNodeDetail
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
stepExecutionStatus={stepExecutionStatus}
/>

{activeTabId === WorkflowRunTabId.OUTPUT ? (
<WorkflowRunStepOutputDetail
key={workflowSelectedNode}
stepId={workflowSelectedNode}
/>
) : null}

{activeTabId === WorkflowRunTabId.NODE ? (
<WorkflowRunStepNodeDetail
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
stepExecutionStatus={stepExecutionStatus}
) : (
<>
<StyledTabList
tabs={tabs}
behaveAsLinks={false}
componentInstanceId={commandMenuPageComponentInstance.instanceId}
/>
) : null}

{activeTabId === WorkflowRunTabId.INPUT ? (
<WorkflowRunStepInputDetail
key={workflowSelectedNode}
stepId={workflowSelectedNode}
/>
) : null}
</>
)}
</StyledContainer>
{activeTabId === WorkflowRunTabId.OUTPUT ? (
<WorkflowRunStepOutputDetail
key={workflowSelectedNode}
stepId={workflowSelectedNode}
/>
) : null}

{activeTabId === WorkflowRunTabId.NODE ? (
<WorkflowRunStepNodeDetail
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
stepExecutionStatus={stepExecutionStatus}
/>
) : null}

{activeTabId === WorkflowRunTabId.INPUT ? (
<WorkflowRunStepInputDetail
key={workflowSelectedNode}
stepId={workflowSelectedNode}
/>
) : null}

<WorkflowIteratorSubStepSwitcher stepId={workflowSelectedNode} />
</>
)}
</StyledContainer>
</CommandMenuWorkflowRunStepContentComponentInstanceContext.Provider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';

export const CommandMenuWorkflowRunStepContentComponentInstanceContext =
createComponentInstanceContext({
instanceId: '',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { CommandMenuWorkflowRunStepContentComponentInstanceContext } from '@/command-menu/pages/workflow/step/view-run/states/contexts/CommandMenuWorkflowRunStepContentComponentInstanceContext';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';

export const workflowRunIteratorSubStepIterationIndexComponentState =
createComponentState<number>({
key: 'workflowRunIteratorSubStepIterationIndexComponentState',
defaultValue: 0,
componentInstanceContext:
CommandMenuWorkflowRunStepContentComponentInstanceContext,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { type WorkflowStep } from '@/workflow/types/Workflow';
import { isLastStepOfLoop } from '@/workflow/workflow-diagram/utils/isLastStepOfLoop';

export const isParentStep = ({
currentStep,
potentialParentStep,
steps,
}: {
currentStep: WorkflowStep;
potentialParentStep: WorkflowStep;
steps: WorkflowStep[];
}): boolean => {
if (potentialParentStep.type === 'ITERATOR') {
return !!(
potentialParentStep.settings.input.initialLoopStepIds?.includes(
currentStep.id,
) || potentialParentStep.nextStepIds?.includes(currentStep.id)
);
}

if (currentStep.type === 'ITERATOR') {
return !!(
potentialParentStep.nextStepIds?.includes(currentStep.id) &&
!isLastStepOfLoop({
iterator: currentStep,
stepId: potentialParentStep.id,
steps,
})
);
}

return !!potentialParentStep.nextStepIds?.includes(currentStep.id);
};
Original file line number Diff line number Diff line change
Expand Up @@ -30,58 +30,53 @@ export const WorkflowActionFooter = ({
const workflowId = useCommandMenuWorkflowIdOrThrow();
const { openWorkflowEditStepTypeInCommandMenu } = useWorkflowCommandMenu();

const OptionsDropdown = () => {
return (
<Dropdown
dropdownId={dropdownId}
data-select-disable
clickableComponent={
<Button title="Options" hotkeys={[getOsControlSymbol(), 'O']} />
}
dropdownPlacement="top-end"
dropdownOffset={{ y: parseInt(theme.spacing(2), 10) }}
globalHotkeysConfig={{
enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: false,
}}
dropdownComponents={
<DropdownContent>
<DropdownMenuItemsContainer>
<SelectableList
selectableListInstanceId={dropdownId}
focusId={dropdownId}
selectableItemIdArray={['change-node-type', 'duplicate']}
>
<MenuItem
onClick={() => {
closeDropdown(dropdownId);
openWorkflowEditStepTypeInCommandMenu(workflowId);
}}
text={t`Change node type`}
LeftIcon={IconPencil}
/>
<MenuItem
onClick={() => {
closeDropdown(dropdownId);
duplicateStep({ stepId });
}}
text={t`Duplicate node`}
LeftIcon={IconCopyPlus}
/>
</SelectableList>
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
);
};
const OptionsDropdown = (
<Dropdown
dropdownId={dropdownId}
data-select-disable
clickableComponent={
<Button title="Options" hotkeys={[getOsControlSymbol(), 'O']} />
}
dropdownPlacement="top-end"
dropdownOffset={{ y: parseInt(theme.spacing(2), 10) }}
globalHotkeysConfig={{
enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: false,
}}
dropdownComponents={
<DropdownContent>
<DropdownMenuItemsContainer>
<SelectableList
selectableListInstanceId={dropdownId}
focusId={dropdownId}
selectableItemIdArray={['change-node-type', 'duplicate']}
>
<MenuItem
onClick={() => {
closeDropdown(dropdownId);
openWorkflowEditStepTypeInCommandMenu(workflowId);
}}
text={t`Change node type`}
LeftIcon={IconPencil}
/>
<MenuItem
onClick={() => {
closeDropdown(dropdownId);
duplicateStep({ stepId });
}}
text={t`Duplicate node`}
LeftIcon={IconCopyPlus}
/>
</SelectableList>
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
);

return (
<RightDrawerFooter
actions={[
<OptionsDropdown key="options" />,
...(additionalActions ?? []),
]}
actions={[OptionsDropdown, ...(additionalActions ?? [])]}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { workflowRunIteratorSubStepIterationIndexComponentState } from '@/command-menu/pages/workflow/step/view-run/states/workflowRunIteratorSubStepIterationIndexComponentState';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
import { getIsDescendantOfIterator } from '@/workflow/workflow-steps/utils/getIsDescendantOfIterator';
import { getWorkflowRunAllStepInfoHistory } from '@/workflow/workflow-steps/utils/getWorkflowRunAllStepInfoHistory';
import styled from '@emotion/styled';
import { plural } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { IconChevronLeft, IconChevronRight } from 'twenty-ui/display';
import { IconButton } from 'twenty-ui/input';

const StyledContainer = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
padding-block: ${({ theme }) => theme.spacing(2)};
padding-inline: ${({ theme }) => theme.spacing(3)};
`;

const StyledCounter = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
font-weight: ${({ theme }) => theme.font.weight.medium};
`;

export const WorkflowIteratorSubStepSwitcher = ({
stepId,
}: {
stepId: string;
}) => {
const flow = useFlowOrThrow();
const workflowRunId = useWorkflowRunIdOrThrow();
const workflowRun = useWorkflowRun({ workflowRunId });

const [
workflowRunIteratorSubStepIterationIndex,
setWorkflowRunIteratorSubStepIterationIndex,
] = useRecoilComponentState(
workflowRunIteratorSubStepIterationIndexComponentState,
);

const stepDefinition = getStepDefinitionOrThrow({
stepId,
trigger: flow.trigger,
steps: flow.steps,
});

const stepInfo = workflowRun?.state?.stepInfos[stepId];

if (
!isDefined(stepInfo) ||
!isDefined(workflowRun?.state) ||
!isDefined(flow.steps) ||
stepDefinition?.type !== 'action'
) {
return null;
}

const allStepInfos = getWorkflowRunAllStepInfoHistory({
stepInfo,
});

const isDescendantOfIterator = getIsDescendantOfIterator({
stepId,
steps: flow.steps,
});

const workflowRunIteratorSubStepIterationsCount = allStepInfos.length;

const canGoToPreviousIndex = workflowRunIteratorSubStepIterationIndex > 0;
const canGoToNextIndex =
workflowRunIteratorSubStepIterationIndex <
workflowRunIteratorSubStepIterationsCount - 1;

const handleDecrementIndex = () => {
if (!canGoToPreviousIndex) {
return;
}

setWorkflowRunIteratorSubStepIterationIndex(
workflowRunIteratorSubStepIterationIndex - 1,
);
};

const handleIncrementIndex = () => {
if (!canGoToNextIndex) {
return;
}

setWorkflowRunIteratorSubStepIterationIndex(
workflowRunIteratorSubStepIterationIndex + 1,
);
};

if (!isDescendantOfIterator) {
return null;
}

return (
<StyledContainer>
<IconButton
Icon={IconChevronLeft}
size="small"
disabled={!canGoToPreviousIndex}
onClick={handleDecrementIndex}
/>

<StyledCounter>
{workflowRunIteratorSubStepIterationIndex + 1}/
{plural(workflowRunIteratorSubStepIterationsCount, {
one: '# item',
other: '# items',
})}
</StyledCounter>

<IconButton
Icon={IconChevronRight}
size="small"
disabled={!canGoToNextIndex}
onClick={handleIncrementIndex}
/>
</StyledContainer>
);
};
Loading