mirror of
https://github.com/n8n-io/n8n
synced 2026-04-21 15:47:20 +00:00
refactor(editor): Normalize sharedWithProjects field in workflow document store (no-changelog) (#28078)
This commit is contained in:
parent
0fc2d90b52
commit
d037fd4647
6 changed files with 120 additions and 27 deletions
|
|
@ -78,7 +78,8 @@ const workflowScopes = computed(
|
|||
workflowsStore.workflow.scopes,
|
||||
);
|
||||
const workflowSharedWithProjects = computed(
|
||||
() => workflowListEntry.value?.sharedWithProjects ?? workflowsStore.workflow.sharedWithProjects,
|
||||
() =>
|
||||
workflowDocumentStore.value?.sharedWithProjects ?? workflowListEntry.value?.sharedWithProjects,
|
||||
);
|
||||
const loading = ref(true);
|
||||
const isDirty = ref(false);
|
||||
|
|
@ -203,6 +204,9 @@ const onSave = async () => {
|
|||
workflowId,
|
||||
sharedWithProjects: sharedWithProjects.value,
|
||||
});
|
||||
useWorkflowDocumentStore(createWorkflowDocumentId(workflowId)).setSharedWithProjects(
|
||||
sharedWithProjects.value,
|
||||
);
|
||||
|
||||
toast.showMessage({
|
||||
title: i18n.baseText('workflows.shareModal.onSave.success.title'),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { createTestingPinia } from '@pinia/testing';
|
|||
import { setActivePinia } from 'pinia';
|
||||
import { useWorkflowsStore } from '@/app/stores/workflows.store';
|
||||
import { useWorkflowsListStore } from '@/app/stores/workflowsList.store';
|
||||
import { useWorkflowsEEStore } from '@/app/stores/workflows.ee.store';
|
||||
import { useTagsStore } from '@/features/shared/tags/tags.store';
|
||||
import { useUIStore } from '@/app/stores/ui.store';
|
||||
import {
|
||||
|
|
@ -51,7 +50,6 @@ describe('useWorkflowHelpers', () => {
|
|||
let workflowsStore: ReturnType<typeof mockedStore<typeof useWorkflowsStore>>;
|
||||
let workflowsListStore: ReturnType<typeof mockedStore<typeof useWorkflowsListStore>>;
|
||||
let workflowState: WorkflowState;
|
||||
let workflowsEEStore: ReturnType<typeof useWorkflowsEEStore>;
|
||||
let tagsStore: ReturnType<typeof useTagsStore>;
|
||||
let uiStore: ReturnType<typeof mockedStore<typeof useUIStore>>;
|
||||
|
||||
|
|
@ -63,7 +61,6 @@ describe('useWorkflowHelpers', () => {
|
|||
workflowState = useWorkflowState();
|
||||
vi.mocked(injectWorkflowState).mockReturnValue(workflowState);
|
||||
|
||||
workflowsEEStore = useWorkflowsEEStore();
|
||||
tagsStore = useTagsStore();
|
||||
uiStore = mockedStore(useUIStore);
|
||||
});
|
||||
|
|
@ -247,14 +244,9 @@ describe('useWorkflowHelpers', () => {
|
|||
});
|
||||
const addWorkflowSpy = vi.spyOn(workflowsListStore, 'addWorkflow');
|
||||
const setWorkflowIdSpy = vi.spyOn(workflowState, 'setWorkflowId');
|
||||
const setWorkflowSharedWithSpy = vi.spyOn(workflowsEEStore, 'setWorkflowSharedWith');
|
||||
const upsertTagsSpy = vi.spyOn(tagsStore, 'upsertTags');
|
||||
|
||||
await initState(workflowData);
|
||||
|
||||
const workflowDocumentStore = useWorkflowDocumentStore(
|
||||
createWorkflowDocumentId(workflowData.id),
|
||||
);
|
||||
const { workflowDocumentStore } = await initState(workflowData);
|
||||
|
||||
expect(addWorkflowSpy).toHaveBeenCalledWith(workflowData);
|
||||
expect(setWorkflowIdSpy).toHaveBeenCalledWith('1');
|
||||
|
|
@ -266,10 +258,8 @@ describe('useWorkflowHelpers', () => {
|
|||
name: null,
|
||||
description: null,
|
||||
});
|
||||
expect(setWorkflowSharedWithSpy).toHaveBeenCalledWith({
|
||||
workflowId: '1',
|
||||
sharedWithProjects: [],
|
||||
});
|
||||
// sharedWithProjects is now managed by workflowDocumentStore
|
||||
expect(workflowDocumentStore.setSharedWithProjects).toHaveBeenCalledWith([]);
|
||||
// Tags are now managed by workflowDocumentStore
|
||||
expect(upsertTagsSpy).toHaveBeenCalledWith([]);
|
||||
});
|
||||
|
|
@ -286,11 +276,11 @@ describe('useWorkflowHelpers', () => {
|
|||
scopes: [],
|
||||
tags: [],
|
||||
});
|
||||
const setWorkflowSharedWithSpy = vi.spyOn(workflowsEEStore, 'setWorkflowSharedWith');
|
||||
|
||||
await initState(workflowData);
|
||||
const { workflowDocumentStore } = await initState(workflowData);
|
||||
|
||||
expect(setWorkflowSharedWithSpy).not.toHaveBeenCalled();
|
||||
// When sharedWithProjects is undefined, it defaults to empty array
|
||||
expect(workflowDocumentStore.setSharedWithProjects).toHaveBeenCalledWith([]);
|
||||
});
|
||||
|
||||
it('should handle missing `tags` gracefully', async () => {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ import { convertWorkflowTagsToIds } from '@/app/utils/workflowUtils';
|
|||
import { useI18n } from '@n8n/i18n';
|
||||
import { useProjectsStore } from '@/features/collaboration/projects/projects.store';
|
||||
import { useTagsStore } from '@/features/shared/tags/tags.store';
|
||||
import { useWorkflowsEEStore } from '@/app/stores/workflows.ee.store';
|
||||
import { findWebhook } from '@n8n/rest-api-client/api/webhooks';
|
||||
import type { ExpressionLocalResolveContext } from '@/app/types/expressions';
|
||||
import { injectWorkflowState, type WorkflowState } from '@/app/composables/useWorkflowState';
|
||||
|
|
@ -505,7 +504,6 @@ export function useWorkflowHelpers() {
|
|||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowsListStore = useWorkflowsListStore();
|
||||
const workflowState = injectWorkflowState();
|
||||
const workflowsEEStore = useWorkflowsEEStore();
|
||||
const uiStore = useUIStore();
|
||||
const nodeHelpers = useNodeHelpers();
|
||||
const projectsStore = useProjectsStore();
|
||||
|
|
@ -938,12 +936,13 @@ export function useWorkflowHelpers() {
|
|||
|
||||
function getWorkflowProjectRole(workflowId: string): 'owner' | 'sharee' | 'member' {
|
||||
const workflow = workflowsListStore.getWorkflowById(workflowId);
|
||||
const workflowDocumentStore = useWorkflowDocumentStore(createWorkflowDocumentId(workflowId));
|
||||
|
||||
// Check if workflow is new (not saved) or belongs to personal project
|
||||
if (workflow?.homeProject?.id === projectsStore.personalProject?.id || !workflow?.id) {
|
||||
return 'owner';
|
||||
} else if (
|
||||
workflow?.sharedWithProjects?.some(
|
||||
workflowDocumentStore.sharedWithProjects?.some(
|
||||
(project) => project.id === projectsStore.personalProject?.id,
|
||||
)
|
||||
) {
|
||||
|
|
@ -989,13 +988,6 @@ export function useWorkflowHelpers() {
|
|||
}
|
||||
}
|
||||
|
||||
if (workflowData.sharedWithProjects) {
|
||||
workflowsEEStore.setWorkflowSharedWith({
|
||||
workflowId: workflowData.id,
|
||||
sharedWithProjects: workflowData.sharedWithProjects,
|
||||
});
|
||||
}
|
||||
|
||||
const tags = (workflowData.tags ?? []) as ITag[];
|
||||
const tagIds = convertWorkflowTagsToIds(tags);
|
||||
|
||||
|
|
@ -1032,6 +1024,7 @@ export function useWorkflowHelpers() {
|
|||
initializedWorkflowDocumentStore.setMeta(workflowData.meta);
|
||||
initializedWorkflowDocumentStore.setParentFolder(workflowData.parentFolder ?? null);
|
||||
initializedWorkflowDocumentStore.setScopes(workflowData.scopes ?? []);
|
||||
initializedWorkflowDocumentStore.setSharedWithProjects(workflowData.sharedWithProjects ?? []);
|
||||
initializedWorkflowDocumentStore.setDescription(workflowData.description);
|
||||
tagsStore.upsertTags(tags);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { inject } from 'vue';
|
|||
import { WorkflowDocumentStoreKey } from '@/app/constants/injectionKeys';
|
||||
import { useWorkflowDocumentActive } from './workflowDocument/useWorkflowDocumentActive';
|
||||
import { useWorkflowDocumentHomeProject } from './workflowDocument/useWorkflowDocumentHomeProject';
|
||||
import { useWorkflowDocumentSharedWithProjects } from './workflowDocument/useWorkflowDocumentSharedWithProjects';
|
||||
import { useWorkflowDocumentChecksum } from './workflowDocument/useWorkflowDocumentChecksum';
|
||||
import { useWorkflowDocumentDescription } from './workflowDocument/useWorkflowDocumentDescription';
|
||||
import { useWorkflowDocumentMeta } from './workflowDocument/useWorkflowDocumentMeta';
|
||||
|
|
@ -115,6 +116,7 @@ export function useWorkflowDocumentStore(id: WorkflowDocumentId) {
|
|||
const workflowDocumentName = useWorkflowDocumentName();
|
||||
const workflowDocumentActive = useWorkflowDocumentActive();
|
||||
const workflowDocumentHomeProject = useWorkflowDocumentHomeProject();
|
||||
const workflowDocumentSharedWithProjects = useWorkflowDocumentSharedWithProjects();
|
||||
const workflowDocumentChecksum = useWorkflowDocumentChecksum();
|
||||
const workflowDocumentDescription = useWorkflowDocumentDescription();
|
||||
const workflowDocumentMeta = useWorkflowDocumentMeta();
|
||||
|
|
@ -161,6 +163,7 @@ export function useWorkflowDocumentStore(id: WorkflowDocumentId) {
|
|||
...workflowDocumentName,
|
||||
...workflowDocumentActive,
|
||||
...workflowDocumentHomeProject,
|
||||
...workflowDocumentSharedWithProjects,
|
||||
...workflowDocumentChecksum,
|
||||
...workflowDocumentDescription,
|
||||
...workflowDocumentIsArchived,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { useWorkflowDocumentSharedWithProjects } from './useWorkflowDocumentSharedWithProjects';
|
||||
import type { ProjectSharingData } from '@/features/collaboration/projects/projects.types';
|
||||
|
||||
function createSharedWithProjects() {
|
||||
return useWorkflowDocumentSharedWithProjects();
|
||||
}
|
||||
|
||||
function createProject(overrides: Partial<ProjectSharingData> = {}): ProjectSharingData {
|
||||
return {
|
||||
id: 'project-1',
|
||||
name: 'Test Project',
|
||||
icon: null,
|
||||
type: 'team',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('useWorkflowDocumentSharedWithProjects', () => {
|
||||
describe('initial state', () => {
|
||||
it('should start with null', () => {
|
||||
const { sharedWithProjects } = createSharedWithProjects();
|
||||
expect(sharedWithProjects.value).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('setSharedWithProjects', () => {
|
||||
it('should set projects and fire event hook', () => {
|
||||
const { sharedWithProjects, setSharedWithProjects, onSharedWithProjectsChange } =
|
||||
createSharedWithProjects();
|
||||
const hookSpy = vi.fn();
|
||||
onSharedWithProjectsChange(hookSpy);
|
||||
|
||||
const projects = [createProject({ id: 'p1' }), createProject({ id: 'p2' })];
|
||||
setSharedWithProjects(projects);
|
||||
|
||||
expect(sharedWithProjects.value).toEqual(projects);
|
||||
expect(hookSpy).toHaveBeenCalledWith({
|
||||
action: 'update',
|
||||
payload: { sharedWithProjects: projects },
|
||||
});
|
||||
});
|
||||
|
||||
it('should replace existing projects entirely', () => {
|
||||
const { sharedWithProjects, setSharedWithProjects } = createSharedWithProjects();
|
||||
setSharedWithProjects([createProject({ id: 'p1' })]);
|
||||
|
||||
const newProjects = [createProject({ id: 'p2' }), createProject({ id: 'p3' })];
|
||||
setSharedWithProjects(newProjects);
|
||||
|
||||
expect(sharedWithProjects.value).toEqual(newProjects);
|
||||
});
|
||||
|
||||
it('should allow setting empty array', () => {
|
||||
const { sharedWithProjects, setSharedWithProjects } = createSharedWithProjects();
|
||||
setSharedWithProjects([createProject({ id: 'p1' })]);
|
||||
|
||||
setSharedWithProjects([]);
|
||||
|
||||
expect(sharedWithProjects.value).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import { ref, readonly } from 'vue';
|
||||
import { createEventHook } from '@vueuse/core';
|
||||
import type { ProjectSharingData } from '@/features/collaboration/projects/projects.types';
|
||||
import { CHANGE_ACTION } from './types';
|
||||
import type { ChangeAction, ChangeEvent } from './types';
|
||||
|
||||
export type SharedWithProjectsPayload = {
|
||||
sharedWithProjects: ProjectSharingData[];
|
||||
};
|
||||
|
||||
export type SharedWithProjectsChangeEvent = ChangeEvent<SharedWithProjectsPayload>;
|
||||
|
||||
export function useWorkflowDocumentSharedWithProjects() {
|
||||
const sharedWithProjects = ref<ProjectSharingData[] | null>(null);
|
||||
|
||||
const onSharedWithProjectsChange = createEventHook<SharedWithProjectsChangeEvent>();
|
||||
|
||||
function applySharedWithProjects(
|
||||
projects: ProjectSharingData[],
|
||||
action: ChangeAction = CHANGE_ACTION.UPDATE,
|
||||
) {
|
||||
sharedWithProjects.value = projects;
|
||||
void onSharedWithProjectsChange.trigger({
|
||||
action,
|
||||
payload: { sharedWithProjects: projects },
|
||||
});
|
||||
}
|
||||
|
||||
function setSharedWithProjects(projects: ProjectSharingData[]) {
|
||||
applySharedWithProjects(projects);
|
||||
}
|
||||
|
||||
return {
|
||||
sharedWithProjects: readonly(sharedWithProjects),
|
||||
setSharedWithProjects,
|
||||
onSharedWithProjectsChange: onSharedWithProjectsChange.on,
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue