diff --git a/packages/react-ui/src/app/features/builder/flow-canvas/nodes/step-node.tsx b/packages/react-ui/src/app/features/builder/flow-canvas/nodes/step-node.tsx
index a6abdf69e7..89d0f90579 100644
--- a/packages/react-ui/src/app/features/builder/flow-canvas/nodes/step-node.tsx
+++ b/packages/react-ui/src/app/features/builder/flow-canvas/nodes/step-node.tsx
@@ -35,6 +35,8 @@ import {
TriggerType,
} from '@openops/shared';
+import { flowsHooks } from '@/app/features/flows/lib/flows-hooks';
+import { ShieldHalf } from 'lucide-react';
import { CanvasContextMenu } from '../context-menu/canvas-context-menu';
import { CollapsibleButton } from './collapsible-button';
import { StackedNodeLayers } from './stacked-node-layer';
@@ -86,6 +88,11 @@ const WorkflowStepNode = React.memo(
step: data.step!,
});
+ const { metadata: actionsMetadata } = blocksHooks.useAllStepsMetadata({
+ searchQuery: '',
+ type: 'action',
+ });
+
const stepIndex = useMemo(() => {
const steps = flowHelper.getAllSteps(flowVersion.trigger);
return steps.findIndex((step) => step.name === data.step!.name) + 1;
@@ -122,6 +129,12 @@ const WorkflowStepNode = React.memo(
return getStepStatus(data.step?.name, run, loopIndexes, flowVersion);
}, [data.step?.name, run, loopIndexes, flowVersion]);
+ const isRiskyStep = flowsHooks.useIsRiskyAction(
+ actionsMetadata,
+ data.step?.settings.blockName,
+ data.step?.settings.actionName,
+ );
+
const showRunningIcon =
isNil(stepOutputStatus) && run?.status === FlowRunStatus.RUNNING;
const statusInfo = isNil(stepOutputStatus)
@@ -247,15 +260,49 @@ const WorkflowStepNode = React.memo(
- {!readonly && (
-
- )}
+
+
+ {!data.step?.valid && (
+
+
+
+
+
+ {t('Incomplete settings')}
+
+
+ )}
+
+ {isRiskyStep && (
+
+
+
+
+
+
+
+ {t(
+ 'This step may make changes to your environment',
+ )}
+
+
+ )}
+
+
+ {!readonly && (
+
+ )}
+
@@ -276,22 +323,6 @@ const WorkflowStepNode = React.memo(
{showRunningIcon && (
)}
- {!data.step?.valid && (
-
-
-
-
-
-
-
- {t('Incomplete settings')}
-
-
- )}
diff --git a/packages/react-ui/src/app/features/flows/components/execute-risky-flow-dialog/utils.ts b/packages/react-ui/src/app/features/flows/components/execute-risky-flow-dialog/utils.ts
index 19fe322267..38256e7b57 100644
--- a/packages/react-ui/src/app/features/flows/components/execute-risky-flow-dialog/utils.ts
+++ b/packages/react-ui/src/app/features/flows/components/execute-risky-flow-dialog/utils.ts
@@ -1,29 +1,9 @@
-import { ActionBase } from '@openops/blocks-framework';
-import {
- BlockStepMetadataWithSuggestions,
- StepMetadataWithSuggestions,
-} from '@openops/components/ui';
+import { flowsUtils } from '@/app/features/flows/lib/flows-utils';
+import { StepMetadataWithSuggestions } from '@openops/components/ui';
import { Action, ActionType, RiskLevel, Trigger } from '@openops/shared';
type ActionOrTriggerWithIndex = (Action | Trigger) & { index: number };
-const getActionMetadata = (
- metadata: StepMetadataWithSuggestions[] | undefined,
- blockName: string,
- actionName: string | undefined,
-): ActionBase | undefined => {
- const blockStepMetadata = metadata?.find(
- (stepMetadata: StepMetadataWithSuggestions) =>
- stepMetadata.type === ActionType.BLOCK &&
- (stepMetadata as BlockStepMetadataWithSuggestions).blockName ===
- blockName,
- ) as BlockStepMetadataWithSuggestions | undefined;
-
- return blockStepMetadata?.suggestedActions?.find(
- (suggestedAction) => suggestedAction.name === actionName,
- );
-};
-
export const getRiskyActionFormattedNames = (
allSteps: (Action | Trigger)[],
metadata: StepMetadataWithSuggestions[] | undefined,
@@ -35,7 +15,7 @@ export const getRiskyActionFormattedNames = (
.map((action) => {
return {
action,
- metadata: getActionMetadata(
+ metadata: flowsUtils.getActionMetadata(
metadata,
action.settings.blockName,
action.settings.actionName,
diff --git a/packages/react-ui/src/app/features/flows/lib/flows-hooks.ts b/packages/react-ui/src/app/features/flows/lib/flows-hooks.ts
index b9d18b3545..3301a81215 100644
--- a/packages/react-ui/src/app/features/flows/lib/flows-hooks.ts
+++ b/packages/react-ui/src/app/features/flows/lib/flows-hooks.ts
@@ -1,10 +1,15 @@
-import { INTERNAL_ERROR_TOAST, toast } from '@openops/components/ui';
-import { ListFlowsRequest, PopulatedFlow } from '@openops/shared';
+import {
+ INTERNAL_ERROR_TOAST,
+ StepMetadataWithSuggestions,
+ toast,
+} from '@openops/components/ui';
+import { ListFlowsRequest, PopulatedFlow, RiskLevel } from '@openops/shared';
import { useMutation, useQuery } from '@tanstack/react-query';
import { t } from 'i18next';
-import { useState } from 'react';
+import { useMemo, useState } from 'react';
import { NavigateFunction } from 'react-router-dom';
+import { flowsUtils } from '@/app/features/flows/lib/flows-utils';
import { flowsApi } from './flows-api';
import { userSettingsHooks } from '@/app/common/hooks/user-settings-hooks';
@@ -65,6 +70,22 @@ export const flowsHooks = {
setSearchTerm,
};
},
+ useIsRiskyAction: (
+ metadata: StepMetadataWithSuggestions[] | undefined,
+
+ blockName: string | undefined,
+ actionName: string | undefined,
+ ) => {
+ return useMemo(() => {
+ if (!metadata || !blockName || !actionName) return false;
+ const actionMetadata = flowsUtils.getActionMetadata(
+ metadata,
+ blockName,
+ actionName,
+ );
+ return actionMetadata?.riskLevel === RiskLevel.HIGH;
+ }, [metadata, blockName, actionName]);
+ },
useCreateFlow: (navigate: NavigateFunction) => {
const { updateHomePageOperationalViewFlag } =
userSettingsHooks.useHomePageOperationalView();
diff --git a/packages/react-ui/src/app/features/flows/lib/flows-utils.tsx b/packages/react-ui/src/app/features/flows/lib/flows-utils.tsx
index d0d694d18e..765b645457 100644
--- a/packages/react-ui/src/app/features/flows/lib/flows-utils.tsx
+++ b/packages/react-ui/src/app/features/flows/lib/flows-utils.tsx
@@ -1,8 +1,13 @@
+import { ActionBase } from '@openops/blocks-framework';
+import {
+ BlockStepMetadataWithSuggestions,
+ StepMetadataWithSuggestions,
+} from '@openops/components/ui';
import cronstrue from 'cronstrue/i18n';
import { t } from 'i18next';
import { TimerReset, TriangleAlert, Zap } from 'lucide-react';
-import { Flow, FlowVersion, TriggerType } from '@openops/shared';
+import { ActionType, Flow, FlowVersion, TriggerType } from '@openops/shared';
import { flowsApi } from './flows-api';
@@ -29,8 +34,26 @@ const downloadFlow = async (flowId: string, versionId: string) => {
downloadFile(JSON.stringify(template, null, 2), template.name, 'json');
};
+const getActionMetadata = (
+ metadata: StepMetadataWithSuggestions[] | undefined,
+ blockName: string,
+ actionName: string | undefined,
+): ActionBase | undefined => {
+ const blockStepMetadata = metadata?.find(
+ (stepMetadata: StepMetadataWithSuggestions) =>
+ stepMetadata.type === ActionType.BLOCK &&
+ (stepMetadata as BlockStepMetadataWithSuggestions).blockName ===
+ blockName,
+ ) as BlockStepMetadataWithSuggestions | undefined;
+
+ return blockStepMetadata?.suggestedActions?.find(
+ (suggestedAction) => suggestedAction.name === actionName,
+ );
+};
+
export const flowsUtils = {
downloadFlow,
+ getActionMetadata,
flowStatusToolTipRenderer: (flow: Flow, version: FlowVersion) => {
const trigger = version.trigger;
switch (trigger.type) {