mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-19 11:01:15 +00:00
fix(core): Add optional context parameter to track creation source for workflows, credentials, and projects (#18736)
Co-authored-by: r00gm <raul00gm@gmail.com>
This commit is contained in:
@@ -52,6 +52,7 @@ import {
|
||||
import { isCredentialModalState, isValidCredentialResponse } from '@/utils/typeGuards';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useElementSize } from '@vueuse/core';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
type Props = {
|
||||
modalName: string;
|
||||
@@ -75,6 +76,7 @@ const toast = useToast();
|
||||
const message = useMessage();
|
||||
const i18n = useI18n();
|
||||
const telemetry = useTelemetry();
|
||||
const router = useRouter();
|
||||
|
||||
const activeTab = ref('connection');
|
||||
const authError = ref('');
|
||||
@@ -801,7 +803,16 @@ async function createCredential(
|
||||
let credential;
|
||||
|
||||
try {
|
||||
credential = await credentialsStore.createNewCredential(credentialDetails, project?.id);
|
||||
credential = await credentialsStore.createNewCredential(
|
||||
credentialDetails,
|
||||
project?.id,
|
||||
router.currentRoute.value.query.uiContext?.toString(),
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { uiContext, ...rest } = router.currentRoute.value.query;
|
||||
void router.replace({ query: rest });
|
||||
|
||||
hasUnsavedChanges.value = false;
|
||||
|
||||
const { title, message } = createToastMessagingForNewCredentials(project);
|
||||
|
||||
@@ -266,13 +266,15 @@ describe('ProjectHeader', () => {
|
||||
|
||||
await userEvent.click(getByTestId('action-credential'));
|
||||
|
||||
expect(mockPush).toHaveBeenCalledWith({
|
||||
name: VIEWS.PROJECTS_CREDENTIALS,
|
||||
params: {
|
||||
projectId: project.id,
|
||||
credentialId: 'create',
|
||||
},
|
||||
});
|
||||
expect(mockPush).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: VIEWS.PROJECTS_CREDENTIALS,
|
||||
params: {
|
||||
projectId: project.id,
|
||||
credentialId: 'create',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -162,6 +162,37 @@ const showProjectIcon = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
function isCredentialsListView(routeName: string) {
|
||||
const CREDENTIAL_VIEWS: string[] = [
|
||||
VIEWS.PROJECTS_CREDENTIALS,
|
||||
VIEWS.CREDENTIALS,
|
||||
VIEWS.SHARED_CREDENTIALS,
|
||||
];
|
||||
|
||||
return CREDENTIAL_VIEWS.includes(routeName);
|
||||
}
|
||||
|
||||
function isWorkflowListView(routeName: string) {
|
||||
const WORKFLOWS_VIEWS: string[] = [
|
||||
VIEWS.PROJECTS_WORKFLOWS,
|
||||
VIEWS.WORKFLOWS,
|
||||
VIEWS.SHARED_WORKFLOWS,
|
||||
VIEWS.PROJECTS_FOLDERS,
|
||||
];
|
||||
|
||||
return WORKFLOWS_VIEWS.includes(routeName);
|
||||
}
|
||||
|
||||
function getUIContext(routeName: string) {
|
||||
if (isCredentialsListView(routeName)) {
|
||||
return 'credentials_list';
|
||||
} else if (isWorkflowListView(routeName)) {
|
||||
return 'workflow_list';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const actions: Record<ActionTypes, (projectId: string) => void> = {
|
||||
[ACTION_TYPES.WORKFLOW]: (projectId: string) => {
|
||||
void router.push({
|
||||
@@ -169,6 +200,7 @@ const actions: Record<ActionTypes, (projectId: string) => void> = {
|
||||
query: {
|
||||
projectId,
|
||||
parentFolderId: route.params.folderId as string,
|
||||
uiContext: getUIContext(route.name?.toString() ?? ''),
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -179,6 +211,9 @@ const actions: Record<ActionTypes, (projectId: string) => void> = {
|
||||
projectId,
|
||||
credentialId: 'create',
|
||||
},
|
||||
query: {
|
||||
uiContext: getUIContext(route.name?.toString() ?? ''),
|
||||
},
|
||||
});
|
||||
},
|
||||
[ACTION_TYPES.FOLDER]: () => {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, onBeforeMount } from 'vue';
|
||||
import type { IMenuItem } from '@n8n/design-system/types';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useGlobalEntityCreation } from '@/composables/useGlobalEntityCreation';
|
||||
import { VIEWS } from '@/constants';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import type { ProjectListItem } from '@/types/projects.types';
|
||||
import { useGlobalEntityCreation } from '@/composables/useGlobalEntityCreation';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
import type { ProjectListItem } from '@/types/projects.types';
|
||||
import type { IMenuItem } from '@n8n/design-system/types';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { ElMenu } from 'element-plus';
|
||||
import { computed, onBeforeMount } from 'vue';
|
||||
|
||||
type Props = {
|
||||
collapsed: boolean;
|
||||
@@ -28,7 +28,7 @@ const isCreatingProject = computed(() => globalEntityCreation.isCreatingProject.
|
||||
const displayProjects = computed(() => globalEntityCreation.displayProjects.value);
|
||||
const isFoldersFeatureEnabled = computed(() => settingsStore.isFoldersFeatureEnabled);
|
||||
const hasMultipleVerifiedUsers = computed(
|
||||
() => usersStore.allUsers.filter((user) => user.isPendingUser === false).length > 1,
|
||||
() => usersStore.allUsers.filter((user) => !user.isPendingUser).length > 1,
|
||||
);
|
||||
|
||||
const home = computed<IMenuItem>(() => ({
|
||||
@@ -140,7 +140,7 @@ onBeforeMount(async () => {
|
||||
data-test-id="project-plus-button"
|
||||
:disabled="isCreatingProject || !projectsStore.hasPermissionToCreateProjects"
|
||||
:class="$style.plusBtn"
|
||||
@click="globalEntityCreation.createProject"
|
||||
@click="globalEntityCreation.createProject('add_icon')"
|
||||
/>
|
||||
</N8nTooltip>
|
||||
</N8nText>
|
||||
|
||||
@@ -184,13 +184,14 @@ export const useGlobalEntityCreation = () => {
|
||||
] satisfies Item[];
|
||||
});
|
||||
|
||||
const createProject = async () => {
|
||||
const createProject = async (uiContext?: string) => {
|
||||
isCreatingProject.value = true;
|
||||
|
||||
try {
|
||||
const newProject = await projectsStore.createProject({
|
||||
name: i18n.baseText('projects.settings.newProjectName'),
|
||||
icon: { type: 'icon', value: DEFAULT_ICON },
|
||||
uiContext,
|
||||
});
|
||||
await router.push({ name: VIEWS.PROJECT_SETTINGS, params: { projectId: newProject.id } });
|
||||
toast.showMessage({
|
||||
@@ -210,7 +211,7 @@ export const useGlobalEntityCreation = () => {
|
||||
if (id !== CREATE_PROJECT_ID) return;
|
||||
|
||||
if (projectsStore.canCreateProjects && projectsStore.hasPermissionToCreateProjects) {
|
||||
void createProject();
|
||||
void createProject('universal_button');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import type { NavigationGuardNext, useRouter } from 'vue-router';
|
||||
import type { LocationQuery, NavigationGuardNext, useRouter } from 'vue-router';
|
||||
import { useMessage } from './useMessage';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import {
|
||||
@@ -156,6 +156,13 @@ export function useWorkflowSaving({ router }: { router: ReturnType<typeof useRou
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getQueryParam(query: LocationQuery, key: string): string | undefined {
|
||||
const value = query[key];
|
||||
if (Array.isArray(value)) return value[0] ?? undefined;
|
||||
if (value === null) return undefined;
|
||||
return value;
|
||||
}
|
||||
|
||||
async function saveCurrentWorkflow(
|
||||
{ id, name, tags }: { id?: string; name?: string; tags?: string[] } = {},
|
||||
redirect = true,
|
||||
@@ -167,11 +174,12 @@ export function useWorkflowSaving({ router }: { router: ReturnType<typeof useRou
|
||||
}
|
||||
|
||||
const isLoading = useCanvasStore().isLoading;
|
||||
const currentWorkflow = id || (router.currentRoute.value.params.name as string);
|
||||
const parentFolderId = router.currentRoute.value.query.parentFolderId as string;
|
||||
const currentWorkflow = id ?? getQueryParam(router.currentRoute.value.params, 'name');
|
||||
const parentFolderId = getQueryParam(router.currentRoute.value.query, 'parentFolderId');
|
||||
const uiContext = getQueryParam(router.currentRoute.value.query, 'uiContext');
|
||||
|
||||
if (!currentWorkflow || ['new', PLACEHOLDER_EMPTY_WORKFLOW_ID].includes(currentWorkflow)) {
|
||||
return !!(await saveAsNewWorkflow({ name, tags, parentFolderId }, redirect));
|
||||
return !!(await saveAsNewWorkflow({ name, tags, parentFolderId, uiContext }, redirect));
|
||||
}
|
||||
|
||||
// Workflow exists already so update it
|
||||
@@ -292,6 +300,7 @@ export function useWorkflowSaving({ router }: { router: ReturnType<typeof useRou
|
||||
resetNodeIds,
|
||||
openInNewWindow,
|
||||
parentFolderId,
|
||||
uiContext,
|
||||
data,
|
||||
}: {
|
||||
name?: string;
|
||||
@@ -300,6 +309,7 @@ export function useWorkflowSaving({ router }: { router: ReturnType<typeof useRou
|
||||
openInNewWindow?: boolean;
|
||||
resetNodeIds?: boolean;
|
||||
parentFolderId?: string;
|
||||
uiContext?: string;
|
||||
data?: WorkflowDataCreate;
|
||||
} = {},
|
||||
redirect = true,
|
||||
@@ -340,6 +350,11 @@ export function useWorkflowSaving({ router }: { router: ReturnType<typeof useRou
|
||||
if (parentFolderId) {
|
||||
workflowDataRequest.parentFolderId = parentFolderId;
|
||||
}
|
||||
|
||||
if (uiContext) {
|
||||
workflowDataRequest.uiContext = uiContext;
|
||||
}
|
||||
|
||||
const workflowData = await workflowsStore.createNewWorkflow(workflowDataRequest);
|
||||
|
||||
workflowsStore.addWorkflow(workflowData);
|
||||
|
||||
@@ -317,6 +317,7 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
||||
const createNewCredential = async (
|
||||
data: ICredentialsDecrypted,
|
||||
projectId?: string,
|
||||
uiContext?: string,
|
||||
): Promise<ICredentialsResponse> => {
|
||||
const settingsStore = useSettingsStore();
|
||||
const credential = await credentialsApi.createNewCredential(rootStore.restApiContext, {
|
||||
@@ -324,6 +325,7 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
||||
type: data.type,
|
||||
data: data.data ?? {},
|
||||
projectId,
|
||||
uiContext,
|
||||
});
|
||||
|
||||
if (data?.homeProject && !credential.homeProject) {
|
||||
|
||||
Reference in New Issue
Block a user