feat(editor): Add template Id to workflow metadata (#8088)

## Summary
Adding a link between the workflow and the template it originated from
by saving `templateId` in the workflow metadata

## Related tickets and issues
ADO-1537

## Review / Merge checklist
- [x] PR title and summary are descriptive. **Remember, the title
automatically goes into the changelog. Use `(no-changelog)` otherwise.**
([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md))
- [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up
ticket created.
- [x] Tests included.
> A bug is not considered fixed, unless a test is added to prevent it
from happening again.
   > A feature is not complete without tests.
This commit is contained in:
Milorad FIlipović 2023-12-22 15:07:05 +01:00 committed by GitHub
parent c83d9f45ba
commit 517b050d0a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 48 additions and 5 deletions

View file

@ -35,6 +35,29 @@ describe('Templates', () => {
workflowPage.actions.shouldHaveWorkflowName(OnboardingWorkflow.name); workflowPage.actions.shouldHaveWorkflowName(OnboardingWorkflow.name);
}); });
it('should save template id with the workflow', () => {
cy.visit(templatesPage.url);
templatesPage.getters.firstTemplateCard().click();
cy.url().should('include', '/templates/');
cy.url().then(($url) => {
const templateId = $url.split('/').pop();
templatesPage.getters.useTemplateButton().click();
cy.url().should('include', '/workflow/new');
workflowPage.actions.saveWorkflowOnButtonClick();
workflowPage.actions.selectAll();
workflowPage.actions.hitCopy();
cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite');
// Check workflow JSON by copying it to clipboard
cy.readClipboard().then((workflowJSON) => {
expect(workflowJSON).to.contain(`"templateId": "${templateId}"`);
});
});
});
it('can open template with images and hides workflow screenshots', () => { it('can open template with images and hides workflow screenshots', () => {
templateWorkflowPage.actions.openTemplate(WorkflowTemplate); templateWorkflowPage.actions.openTemplate(WorkflowTemplate);

View file

@ -4,7 +4,9 @@ export class TemplatesPage extends BasePage {
url = '/templates'; url = '/templates';
getters = { getters = {
useTemplateButton: () => cy.get('[data-testid="use-template-button"]'), useTemplateButton: () => cy.getByTestId('use-template-button'),
templateCards: () => cy.getByTestId('template-card'),
firstTemplateCard: () => this.getters.templateCards().first(),
}; };
actions = { actions = {

View file

@ -227,6 +227,7 @@ export interface IWorkflowData {
tags?: string[]; tags?: string[];
pinData?: IPinData; pinData?: IPinData;
versionId?: string; versionId?: string;
meta?: WorkflowMetadata;
} }
export interface IWorkflowDataUpdate { export interface IWorkflowDataUpdate {
@ -243,9 +244,7 @@ export interface IWorkflowDataUpdate {
} }
export interface IWorkflowToShare extends IWorkflowDataUpdate { export interface IWorkflowToShare extends IWorkflowDataUpdate {
meta?: { meta?: WorkflowMetadata;
instanceId: string;
};
} }
export interface IWorkflowTemplateNode export interface IWorkflowTemplateNode
@ -273,6 +272,8 @@ export interface INewWorkflowData {
export interface WorkflowMetadata { export interface WorkflowMetadata {
onboardingId?: string; onboardingId?: string;
templateId?: string;
instanceId?: string;
} }
// Almost identical to cli.Interfaces.ts // Almost identical to cli.Interfaces.ts

View file

@ -537,6 +537,7 @@ export default defineComponent({
const exportData: IWorkflowToShare = { const exportData: IWorkflowToShare = {
...data, ...data,
meta: { meta: {
...(this.workflowsStore.workflow.meta || {}),
instanceId: this.rootStore.instanceId, instanceId: this.rootStore.instanceId,
}, },
tags: (tags || []).map((tagId) => { tags: (tags || []).map((tagId) => {

View file

@ -656,6 +656,7 @@ export const workflowHelpers = defineComponent({
settings: this.workflowsStore.workflow.settings, settings: this.workflowsStore.workflow.settings,
tags: this.workflowsStore.workflowTags, tags: this.workflowsStore.workflowTags,
versionId: this.workflowsStore.workflow.versionId, versionId: this.workflowsStore.workflow.versionId,
meta: this.workflowsStore.workflow.meta,
}; };
const workflowId = this.workflowsStore.workflowId; const workflowId = this.workflowsStore.workflowId;

View file

@ -32,6 +32,7 @@ import type {
IWorkflowsMap, IWorkflowsMap,
WorkflowsState, WorkflowsState,
NodeMetadataMap, NodeMetadataMap,
WorkflowMetadata,
} from '@/Interface'; } from '@/Interface';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import type { import type {
@ -656,6 +657,17 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
}; };
}, },
setWorkflowMetadata(metadata: WorkflowMetadata | undefined): void {
this.workflow.meta = metadata;
},
addToWorkflowMetadata(data: Partial<WorkflowMetadata>): void {
this.workflow.meta = {
...this.workflow.meta,
...data,
};
},
setWorkflow(workflow: IWorkflowDb): void { setWorkflow(workflow: IWorkflowDb): void {
this.workflow = workflow; this.workflow = workflow;
this.workflow = { this.workflow = {

View file

@ -1064,6 +1064,7 @@ export default defineComponent({
await this.addNodes(data.workflow.nodes, data.workflow.connections); await this.addNodes(data.workflow.nodes, data.workflow.connections);
this.workflowData = (await this.workflowsStore.getNewWorkflowData(data.name)) || {}; this.workflowData = (await this.workflowsStore.getNewWorkflowData(data.name)) || {};
this.workflowsStore.addToWorkflowMetadata({ templateId });
await this.$nextTick(); await this.$nextTick();
this.canvasStore.zoomToFit(); this.canvasStore.zoomToFit();
this.uiStore.stateIsDirty = true; this.uiStore.stateIsDirty = true;
@ -1089,6 +1090,7 @@ export default defineComponent({
this.workflowsStore.setWorkflowSettings(workflow.settings || {}); this.workflowsStore.setWorkflowSettings(workflow.settings || {});
this.workflowsStore.setWorkflowPinData(workflow.pinData || {}); this.workflowsStore.setWorkflowPinData(workflow.pinData || {});
this.workflowsStore.setWorkflowVersionId(workflow.versionId); this.workflowsStore.setWorkflowVersionId(workflow.versionId);
this.workflowsStore.setWorkflowMetadata(workflow.meta);
if (workflow.ownedBy) { if (workflow.ownedBy) {
this.workflowsEEStore.setWorkflowOwnedBy({ this.workflowsEEStore.setWorkflowOwnedBy({
@ -1585,6 +1587,7 @@ export default defineComponent({
void this.getNodesToSave(nodes).then((data) => { void this.getNodesToSave(nodes).then((data) => {
const workflowToCopy: IWorkflowToShare = { const workflowToCopy: IWorkflowToShare = {
meta: { meta: {
...(this.workflowsStore.workflow.meta ?? {}),
instanceId: this.rootStore.instanceId, instanceId: this.rootStore.instanceId,
}, },
...data, ...data,