From dd36bb28bf03a58f1727b11477e58a7fdf157968 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 8 Jan 2025 10:58:54 -0500 Subject: [PATCH] feat(editor): Show warning when user activates workflow with free AI credits credential (no-changelog) (#12510) --- .../src/components/WorkflowActivator.test.ts | 66 ++++++++++++++++++- .../src/components/WorkflowActivator.vue | 27 +++++++- .../src/plugins/i18n/locales/en.json | 4 +- 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/packages/editor-ui/src/components/WorkflowActivator.test.ts b/packages/editor-ui/src/components/WorkflowActivator.test.ts index 62f9481636..506f40b787 100644 --- a/packages/editor-ui/src/components/WorkflowActivator.test.ts +++ b/packages/editor-ui/src/components/WorkflowActivator.test.ts @@ -7,16 +7,31 @@ import { useWorkflowsStore } from '@/stores/workflows.store'; import { createTestingPinia } from '@pinia/testing'; import { createComponentRenderer } from '@/__tests__/render'; import { mockedStore } from '@/__tests__/utils'; -import { EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE } from '@/constants'; +import { EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE, WOOCOMMERCE_TRIGGER_NODE_TYPE } from '@/constants'; +import { useCredentialsStore } from '@/stores/credentials.store'; +import { useToast } from '@/composables/useToast'; const renderComponent = createComponentRenderer(WorkflowActivator); let mockWorkflowsStore: ReturnType>; +let mockCredentialsStore: ReturnType>; + +vi.mock('@/composables/useToast', () => { + const showMessage = vi.fn(); + return { + useToast: () => { + return { + showMessage, + }; + }, + }; +}); describe('WorkflowActivator', () => { beforeEach(() => { createTestingPinia(); mockWorkflowsStore = mockedStore(useWorkflowsStore); + mockCredentialsStore = mockedStore(useCredentialsStore); }); afterEach(() => { @@ -79,4 +94,53 @@ describe('WorkflowActivator', () => { ); expect(getByTestId('workflow-activator-status')).toHaveTextContent('Inactive'); }); + + it('Should show warning toast if the workflow to be activated has free OpenAI credentials', async () => { + const toast = useToast(); + + mockWorkflowsStore.workflow.usedCredentials = [ + { + id: '1', + name: '', + credentialType: '', + currentUserHasAccess: false, + }, + ]; + + mockCredentialsStore.state.credentials = { + '1': { + id: '1', + name: 'OpenAI', + type: 'openAiApi', + data: '', + isManaged: true, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }, + }; + + mockWorkflowsStore.workflowTriggerNodes = [ + { type: WOOCOMMERCE_TRIGGER_NODE_TYPE, disabled: false } as never, + ]; + + const { rerender } = renderComponent({ + props: { + workflowActive: false, + workflowId: '1', + workflowPermissions: { update: true }, + }, + }); + + await rerender({ workflowActive: true }); + + expect(toast.showMessage).toHaveBeenCalledWith( + expect.objectContaining({ + title: "You're using free OpenAI API credits", + message: + 'To make sure your workflow runs smoothly in the future, replace the free OpenAI API credits with your own API key.', + type: 'warning', + duration: 0, + }), + ); + }); }); diff --git a/packages/editor-ui/src/components/WorkflowActivator.vue b/packages/editor-ui/src/components/WorkflowActivator.vue index 8dec6f6620..dc1c123e94 100644 --- a/packages/editor-ui/src/components/WorkflowActivator.vue +++ b/packages/editor-ui/src/components/WorkflowActivator.vue @@ -4,11 +4,13 @@ import { useWorkflowActivate } from '@/composables/useWorkflowActivate'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { getActivatableTriggerNodes } from '@/utils/nodeTypesUtils'; import type { VNode } from 'vue'; -import { computed, h } from 'vue'; +import { computed, h, watch } from 'vue'; import { useI18n } from '@/composables/useI18n'; import type { PermissionsRecord } from '@/permissions'; import { EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE, PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants'; import WorkflowActivationErrorMessage from './WorkflowActivationErrorMessage.vue'; +import { useCredentialsStore } from '@/stores/credentials.store'; +import type { IUsedCredential } from '@/Interface'; const props = defineProps<{ workflowActive: boolean; @@ -20,6 +22,7 @@ const workflowActivate = useWorkflowActivate(); const i18n = useI18n(); const workflowsStore = useWorkflowsStore(); +const credentialsStore = useCredentialsStore(); const isWorkflowActive = computed((): boolean => { const activeWorkflows = workflowsStore.activeWorkflows; @@ -69,6 +72,14 @@ const disabled = computed((): boolean => { return false; }); +const currentWorkflowHasFreeAiCredits = computed((): boolean => { + if (!workflowsStore?.workflow?.usedCredentials) return false; + return workflowsStore.workflow.usedCredentials.some( + (usedCredential: IUsedCredential) => + credentialsStore.state.credentials[usedCredential.id].isManaged, + ); +}); + async function activeChanged(newActiveState: boolean) { return await workflowActivate.updateWorkflowActivation(props.workflowId, newActiveState); } @@ -100,6 +111,20 @@ async function displayActivationError() { duration: 0, }); } + +watch( + () => props.workflowActive, + (workflowActive) => { + if (workflowActive && currentWorkflowHasFreeAiCredits.value) { + showMessage({ + title: i18n.baseText('freeAi.credits.showWarning.workflow.activation.title'), + message: i18n.baseText('freeAi.credits.showWarning.workflow.activation.description'), + type: 'warning', + duration: 0, + }); + } + }, +);