Merge branch 'n8n-io:master' into master

This commit is contained in:
Tristan Robert 2025-01-08 17:14:13 +01:00 committed by GitHub
commit 3cc6048501
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 115 additions and 23 deletions

View file

@ -185,12 +185,9 @@ onMounted(async () => {
});
}
await nextTick(() => {
uiStore.sidebarMenuCollapsed = window.innerWidth < 900;
fullyExpanded.value = !isCollapsed.value;
});
becomeTemplateCreatorStore.startMonitoringCta();
await nextTick(onResizeEnd);
});
onBeforeUnmount(() => {
@ -273,22 +270,21 @@ const handleSelect = (key: string) => {
}
};
const onResize = (event: UIEvent) => {
void callDebounced(onResizeEnd, { debounceTime: 100 }, event);
};
function onResize() {
void callDebounced(onResizeEnd, { debounceTime: 250 });
}
const onResizeEnd = async (event: UIEvent) => {
const browserWidth = (event.target as Window).outerWidth;
await checkWidthAndAdjustSidebar(browserWidth);
};
const checkWidthAndAdjustSidebar = async (width: number) => {
if (width < 900) {
async function onResizeEnd() {
if (window.outerWidth < 900) {
uiStore.sidebarMenuCollapsed = true;
await nextTick();
fullyExpanded.value = !isCollapsed.value;
} else {
uiStore.sidebarMenuCollapsed = uiStore.sidebarMenuCollapsedPreference;
}
};
void nextTick(() => {
fullyExpanded.value = !isCollapsed.value;
});
}
const {
menu,

View file

@ -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<typeof mockedStore<typeof useWorkflowsStore>>;
let mockCredentialsStore: ReturnType<typeof mockedStore<typeof useCredentialsStore>>;
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,
}),
);
});
});

View file

@ -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,
});
}
},
);
</script>
<template>

View file

@ -2848,5 +2848,7 @@
"freeAi.credits.callout.success.title.part2": "gpt-4o-mini, text-embedding-3-small, dall-e-3, tts-1, whisper-1, and text-moderation-latest",
"freeAi.credits.credentials.edit": "This is a managed credential and cannot be edited.",
"freeAi.credits.showError.claim.title": "Free AI credits",
"freeAi.credits.showError.claim.message": "Enable to claim credits"
"freeAi.credits.showError.claim.message": "Enable to claim credits",
"freeAi.credits.showWarning.workflow.activation.title": "You're using free OpenAI API credits",
"freeAi.credits.showWarning.workflow.activation.description": "To make sure your workflow runs smoothly in the future, replace the free OpenAI API credits with your own API key."
}

View file

@ -65,6 +65,7 @@ import {
} from './ui.utils';
import { computed, ref } from 'vue';
import type { Connection } from '@vue-flow/core';
import { useLocalStorage } from '@vueuse/core';
let savedTheme: ThemeOption = 'system';
@ -151,7 +152,8 @@ export const useUIStore = defineStore(STORES.UI, () => {
});
const modalStack = ref<string[]>([]);
const sidebarMenuCollapsed = ref<boolean>(true);
const sidebarMenuCollapsedPreference = useLocalStorage<boolean>('sidebar.collapsed', false);
const sidebarMenuCollapsed = ref<boolean>(sidebarMenuCollapsedPreference.value);
const currentView = ref<string>('');
const draggable = ref<Draggable>({
isDragging: false,
@ -502,7 +504,9 @@ export const useUIStore = defineStore(STORES.UI, () => {
};
const toggleSidebarMenuCollapse = () => {
sidebarMenuCollapsed.value = !sidebarMenuCollapsed.value;
const newCollapsedState = !sidebarMenuCollapsed.value;
sidebarMenuCollapsedPreference.value = newCollapsedState;
sidebarMenuCollapsed.value = newCollapsedState;
};
const getCurlToJson = async (curlCommand: string) => {
@ -592,6 +596,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
nodeViewInitialized,
addFirstStepOnLoad,
sidebarMenuCollapsed,
sidebarMenuCollapsedPreference,
bannerStack,
theme,
modalsById,