Wrap all vizualizers into component context (#10755)

Steps are broken when a variable is set.
This is because component instance is not set for version and run
visualizers.
Each step viewer should be wrapped by the instance context.
Each diagram visualizer should be responsible for populating the output
schema.

Also fixing a billing error when running workflow.
This commit is contained in:
Thomas Trompette
2025-03-10 15:56:14 +01:00
committed by GitHub
parent 3b79018609
commit dc55fac1d5
17 changed files with 80 additions and 31 deletions

View File

@ -4,6 +4,7 @@ import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
import { WorkflowVersionComponentInstanceContext } from '@/workflow/states/context/WorkflowVersionComponentInstanceContext';
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
import { WorkflowRunStepInputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepInputDetail';
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
@ -59,7 +60,9 @@ export const CommandMenuWorkflowRunViewStep = () => {
}
return (
<>
<WorkflowVersionComponentInstanceContext.Provider
value={{ instanceId: workflowRun.workflowVersionId }}
>
<StyledTabListContainer>
<TabList
tabListInstanceId={WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID}
@ -80,6 +83,6 @@ export const CommandMenuWorkflowRunViewStep = () => {
{activeTabId === 'input' ? (
<WorkflowRunStepInputDetail stepId={workflowSelectedNode} />
) : null}
</>
</WorkflowVersionComponentInstanceContext.Provider>
);
};

View File

@ -1,17 +1,21 @@
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
import { WorkflowVersionComponentInstanceContext } from '@/workflow/states/context/WorkflowVersionComponentInstanceContext';
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
export const CommandMenuWorkflowViewStep = () => {
const flow = useFlowOrThrow();
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
return (
<WorkflowStepDetail
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
readonly
/>
<WorkflowVersionComponentInstanceContext.Provider
value={{ instanceId: flow.workflowVersionId }}
>
<WorkflowStepDetail
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
readonly
/>
</WorkflowVersionComponentInstanceContext.Provider>
);
};

View File

@ -83,10 +83,6 @@ export const VariableChip = ({
const stepOutputSchema = getStepsOutputSchema([stepId])?.[0];
if (!isDefined(stepOutputSchema)) {
return null;
}
const { variableLabel, variablePathLabel } =
searchVariableThroughOutputSchema({
stepOutputSchema,

View File

@ -8,8 +8,8 @@ import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableE
import { FieldsCard } from '@/object-record/record-show/components/FieldsCard';
import { CardType } from '@/object-record/record-show/types/CardType';
import { ShowPageActivityContainer } from '@/ui/layout/show-page/components/ShowPageActivityContainer';
import { WorkflowRunOutputVisualizer } from '@/workflow/components/WorkflowRunOutputVisualizer';
import { WorkflowRunVisualizer } from '@/workflow/components/WorkflowRunVisualizer';
import { WorkflowRunOutputVisualizer } from '@/workflow/workflow-diagram/components/WorkflowRunOutputVisualizer';
import { WorkflowRunVisualizer } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizer';
import { WorkflowRunVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect';
import { WorkflowVersionVisualizer } from '@/workflow/workflow-diagram/components/WorkflowVersionVisualizer';
import { WorkflowVersionVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowVersionVisualizerEffect';

View File

@ -2,7 +2,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { Workflow, WorkflowVersion } from '@/workflow/types/Workflow';
export const useWorkflowVersion = (workflowVersionId: string) => {
export const useWorkflowVersion = (workflowVersionId?: string) => {
const { record: workflowVersion } = useFindOneRecord<
WorkflowVersion & {
workflow: Omit<Workflow, 'versions'> & {
@ -30,6 +30,7 @@ export const useWorkflowVersion = (workflowVersionId: string) => {
},
},
},
skip: !workflowVersionId,
});
return workflowVersion;

View File

@ -3,6 +3,7 @@ import { createState } from '@ui/utilities/state/utils/createState';
export const flowState = createState<
| {
workflowVersionId: string;
trigger: WorkflowTrigger | null;
steps: WorkflowAction[] | null;
}

View File

@ -76,6 +76,7 @@ export const WorkflowDiagramEffect = ({
}
setFlow({
workflowVersionId: currentVersion.id,
trigger: currentVersion.trigger,
steps: currentVersion.steps,
});

View File

@ -1,9 +1,11 @@
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion';
import { WorkflowRunDiagramCanvas } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramCanvas';
import { WorkflowVersionOutputSchemaEffect } from '@/workflow/workflow-diagram/components/WorkflowVersionOutputSchemaEffect';
import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared';
const StyledSourceCodeContainer = styled.div`
const StyledContainer = styled.div`
height: 100%;
`;
@ -13,13 +15,16 @@ export const WorkflowRunVisualizer = ({
workflowRunId: string;
}) => {
const workflowRun = useWorkflowRun({ workflowRunId });
if (!isDefined(workflowRun)) {
const workflowVersion = useWorkflowVersion(workflowRun?.workflowVersionId);
if (!isDefined(workflowRun) || !isDefined(workflowVersion)) {
return null;
}
return (
<StyledSourceCodeContainer>
<StyledContainer>
<WorkflowVersionOutputSchemaEffect workflowVersion={workflowVersion} />
<WorkflowRunDiagramCanvas workflowRunStatus={workflowRun.status} />
</StyledSourceCodeContainer>
</StyledContainer>
);
};

View File

@ -31,6 +31,7 @@ export const WorkflowRunVisualizerEffect = ({
}
setFlow({
workflowVersionId: workflowRun.workflowVersionId,
trigger: workflowRun.output.flow.trigger,
steps: workflowRun.output.flow.steps,
});
@ -42,7 +43,12 @@ export const WorkflowRunVisualizerEffect = ({
});
setWorkflowDiagram(nextWorkflowDiagram);
}, [setFlow, setWorkflowDiagram, workflowRun?.output]);
}, [
setFlow,
setWorkflowDiagram,
workflowRun?.output,
workflowRun?.workflowVersionId,
]);
return null;
};

View File

@ -1,5 +1,6 @@
import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion';
import { WorkflowDiagramCanvasReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonly';
import { WorkflowVersionOutputSchemaEffect } from '@/workflow/workflow-diagram/components/WorkflowVersionOutputSchemaEffect';
import '@xyflow/react/dist/style.css';
import { isDefined } from 'twenty-shared';
@ -11,6 +12,9 @@ export const WorkflowVersionVisualizer = ({
const workflowVersion = useWorkflowVersion(workflowVersionId);
return isDefined(workflowVersion) ? (
<WorkflowDiagramCanvasReadonly versionStatus={workflowVersion.status} />
<>
<WorkflowVersionOutputSchemaEffect workflowVersion={workflowVersion} />
<WorkflowDiagramCanvasReadonly versionStatus={workflowVersion.status} />
</>
) : null;
};

View File

@ -25,6 +25,7 @@ export const WorkflowVersionVisualizerEffect = ({
}
setFlow({
workflowVersionId: workflowVersion.id,
trigger: workflowVersion.trigger,
steps: workflowVersion.steps,
});

View File

@ -4,6 +4,7 @@ import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
import { WorkflowVersionComponentInstanceContext } from '@/workflow/states/context/WorkflowVersionComponentInstanceContext';
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
import { WorkflowRunStepInputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepInputDetail';
import { WorkflowRunStepOutputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepOutputDetail';
@ -65,7 +66,9 @@ export const RightDrawerWorkflowRunViewStep = () => {
}
return (
<>
<WorkflowVersionComponentInstanceContext.Provider
value={{ instanceId: flow.workflowVersionId }}
>
<StyledTabListContainer>
<TabList
tabListInstanceId={WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID}
@ -90,6 +93,6 @@ export const RightDrawerWorkflowRunViewStep = () => {
{activeTabId === 'output' ? (
<WorkflowRunStepOutputDetail stepId={workflowSelectedNode} />
) : null}
</>
</WorkflowVersionComponentInstanceContext.Provider>
);
};

View File

@ -1,4 +1,5 @@
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
import { WorkflowVersionComponentInstanceContext } from '@/workflow/states/context/WorkflowVersionComponentInstanceContext';
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
@ -7,11 +8,15 @@ export const RightDrawerWorkflowViewStep = () => {
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
return (
<WorkflowStepDetail
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
readonly
/>
<WorkflowVersionComponentInstanceContext.Provider
value={{ instanceId: flow.workflowVersionId }}
>
<WorkflowStepDetail
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
readonly
/>
</WorkflowVersionComponentInstanceContext.Provider>
);
};

View File

@ -40,7 +40,13 @@ const meta: Meta<typeof RightDrawerWorkflowRunViewStep> = {
);
const setWorkflowRunId = useSetRecoilState(workflowRunIdState);
setFlow(oneFailedWorkflowRunQueryResult.workflowRun.output.flow);
setFlow({
workflowVersionId:
oneFailedWorkflowRunQueryResult.workflowRun.workflowVersionId,
trigger:
oneFailedWorkflowRunQueryResult.workflowRun.output.flow.trigger,
steps: oneFailedWorkflowRunQueryResult.workflowRun.output.flow.steps,
});
setWorkflowSelectedNode(
oneFailedWorkflowRunQueryResult.workflowRun.output.flow.steps[0].id,
);

View File

@ -93,6 +93,13 @@ export const searchVariableThroughOutputSchema = ({
rawVariableName: string;
isFullRecord?: boolean;
}) => {
if (!isDefined(stepOutputSchema)) {
return {
variableLabel: undefined,
variablePathLabel: undefined,
};
}
const variableWithoutBrackets = rawVariableName.replace(
CAPTURE_ALL_VARIABLE_TAG_INNER_REGEX,
(_, variableName) => {

View File

@ -13,6 +13,7 @@ import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billin
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
import { StripeBillingMeterEventService } from 'src/engine/core-modules/billing/stripe/services/stripe-billing-meter-event.service';
import { BillingUsageEvent } from 'src/engine/core-modules/billing/types/billing-usage-event.type';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
@Injectable()
export class BillingUsageService {
@ -22,9 +23,14 @@ export class BillingUsageService {
private readonly billingCustomerRepository: Repository<BillingCustomer>,
private readonly billingSubscriptionService: BillingSubscriptionService,
private readonly stripeBillingMeterEventService: StripeBillingMeterEventService,
private readonly environmentService: EnvironmentService,
) {}
async canFeatureBeUsed(workspaceId: string): Promise<boolean> {
if (!this.environmentService.get('IS_BILLING_ENABLED')) {
return true;
}
const billingSubscription =
await this.billingSubscriptionService.getCurrentBillingSubscriptionOrThrow(
{