mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -08:00
feat(editor): Use website as the main templates repository (#8591)
This commit is contained in:
parent
5ab34fe335
commit
79b09fdf84
|
@ -1,139 +1,44 @@
|
|||
import { TemplatesPage } from '../pages/templates';
|
||||
import { WorkflowPage } from '../pages/workflow';
|
||||
|
||||
import OnboardingWorkflow from '../fixtures/Onboarding_workflow.json';
|
||||
import WorkflowTemplate from '../fixtures/Workflow_template_write_http_query.json';
|
||||
import { TemplateWorkflowPage } from '../pages/template-workflow';
|
||||
import { WorkflowsPage } from '../pages/workflows';
|
||||
import { MainSidebar } from '../pages/sidebar/main-sidebar';
|
||||
|
||||
const templatesPage = new TemplatesPage();
|
||||
const workflowPage = new WorkflowPage();
|
||||
const templateWorkflowPage = new TemplateWorkflowPage();
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
const mainSidebar = new MainSidebar();
|
||||
|
||||
describe('Templates', () => {
|
||||
describe('Workflow templates', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept('GET', '**/api/templates/search?page=1&rows=20&category=&search=', { fixture: 'templates_search/all_templates_search_response.json' }).as('searchRequest');
|
||||
cy.intercept('GET', '**/api/templates/search?page=1&rows=20&category=Sales*', { fixture: 'templates_search/sales_templates_search_response.json' }).as('categorySearchRequest');
|
||||
cy.intercept('GET', '**/api/templates/workflows/*', { fixture: 'templates_search/test_template_preview.json' }).as('singleTemplateRequest');
|
||||
cy.intercept('GET', '**/api/workflows/templates/*', { fixture: 'templates_search/test_template_import.json' }).as('singleTemplateRequest');
|
||||
cy.intercept('GET', '**/rest/settings', (req) => {
|
||||
// Disable cache
|
||||
delete req.headers['if-none-match']
|
||||
req.reply((res) => {
|
||||
if (res.body.data) {
|
||||
// Disable custom templates host if it has been overridden by another intercept
|
||||
res.body.data.templates = { enabled: true, host: 'https://api.n8n.io/api/' };
|
||||
}
|
||||
});
|
||||
}).as('settingsRequest');
|
||||
});
|
||||
|
||||
it('can open onboarding flow', () => {
|
||||
templatesPage.actions.openOnboardingFlow(1234, OnboardingWorkflow.name, OnboardingWorkflow);
|
||||
cy.url().then(($url) => {
|
||||
expect($url).to.match(/.*\/workflow\/.*?onboardingId=1234$/);
|
||||
it('Opens website when clicking templates sidebar link', () => {
|
||||
cy.visit(workflowsPage.url);
|
||||
mainSidebar.getters.menuItem('Templates').should('be.visible');
|
||||
// Templates should be a link to the website
|
||||
mainSidebar.getters.templates().parent('a').should('have.attr', 'href').and('include', 'https://n8n.io/workflows');
|
||||
mainSidebar.getters.templates().parent('a').should('have.attr', 'target', '_blank');
|
||||
});
|
||||
|
||||
it('Redirects to website when visiting templates page directly', () => {
|
||||
cy.visit(templatesPage.url);
|
||||
cy.origin('https://n8n.io', () => {
|
||||
cy.url().should('include', 'https://n8n.io/workflows');
|
||||
})
|
||||
|
||||
workflowPage.actions.shouldHaveWorkflowName(`Demo: ${name}`);
|
||||
|
||||
workflowPage.getters.canvasNodes().should('have.length', 4);
|
||||
workflowPage.getters.stickies().should('have.length', 1);
|
||||
workflowPage.getters.canvasNodes().first().should('have.descendants', '.node-pin-data-icon');
|
||||
});
|
||||
|
||||
it('can import template', () => {
|
||||
templatesPage.actions.importTemplate(1234, OnboardingWorkflow.name, OnboardingWorkflow);
|
||||
|
||||
cy.url().then(($url) => {
|
||||
expect($url).to.include('/workflow/new?templateId=1234');
|
||||
});
|
||||
|
||||
workflowPage.getters.canvasNodes().should('have.length', 4);
|
||||
workflowPage.getters.stickies().should('have.length', 1);
|
||||
workflowPage.actions.shouldHaveWorkflowName(OnboardingWorkflow.name);
|
||||
});
|
||||
|
||||
it('should save template id with the workflow', () => {
|
||||
cy.visit(templatesPage.url);
|
||||
cy.get('.el-skeleton.n8n-loading').should('not.exist');
|
||||
templatesPage.getters.firstTemplateCard().should('exist');
|
||||
templatesPage.getters.templatesLoadingContainer().should('not.exist');
|
||||
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', () => {
|
||||
templateWorkflowPage.actions.openTemplate(WorkflowTemplate);
|
||||
|
||||
templateWorkflowPage.getters.description().find('img').should('have.length', 1);
|
||||
});
|
||||
|
||||
|
||||
it('renders search elements correctly', () => {
|
||||
cy.visit(templatesPage.url);
|
||||
templatesPage.getters.searchInput().should('exist');
|
||||
templatesPage.getters.allCategoriesFilter().should('exist');
|
||||
templatesPage.getters.categoryFilters().should('have.length.greaterThan', 1);
|
||||
templatesPage.getters.templateCards().should('have.length.greaterThan', 0);
|
||||
});
|
||||
|
||||
it('can filter templates by category', () => {
|
||||
cy.visit(templatesPage.url);
|
||||
templatesPage.getters.templatesLoadingContainer().should('not.exist');
|
||||
templatesPage.getters.expandCategoriesButton().click();
|
||||
templatesPage.getters.categoryFilter('sales').should('exist');
|
||||
let initialTemplateCount = 0;
|
||||
let initialCollectionCount = 0;
|
||||
|
||||
templatesPage.getters.templateCountLabel().then(($el) => {
|
||||
initialTemplateCount = parseInt($el.text().replace(/\D/g, ''), 10);
|
||||
templatesPage.getters.collectionCountLabel().then(($el) => {
|
||||
initialCollectionCount = parseInt($el.text().replace(/\D/g, ''), 10);
|
||||
|
||||
templatesPage.getters.categoryFilter('sales').click();
|
||||
templatesPage.getters.templatesLoadingContainer().should('not.exist');
|
||||
|
||||
// Should have less templates and collections after selecting a category
|
||||
templatesPage.getters.templateCountLabel().should(($el) => {
|
||||
expect(parseInt($el.text().replace(/\D/g, ''), 10)).to.be.lessThan(initialTemplateCount);
|
||||
});
|
||||
templatesPage.getters.collectionCountLabel().should(($el) => {
|
||||
expect(parseInt($el.text().replace(/\D/g, ''), 10)).to.be.lessThan(initialCollectionCount);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve search query in URL', () => {
|
||||
cy.visit(templatesPage.url);
|
||||
templatesPage.getters.templatesLoadingContainer().should('not.exist');
|
||||
templatesPage.getters.expandCategoriesButton().click();
|
||||
templatesPage.getters.categoryFilter('sales').should('exist');
|
||||
templatesPage.getters.categoryFilter('sales').click();
|
||||
templatesPage.getters.searchInput().type('auto');
|
||||
|
||||
cy.url().should('include', '?categories=');
|
||||
cy.url().should('include', '&search=');
|
||||
|
||||
cy.reload();
|
||||
|
||||
// Should preserve search query in URL
|
||||
cy.url().should('include', '?categories=');
|
||||
cy.url().should('include', '&search=');
|
||||
|
||||
// Sales category should still be selected
|
||||
templatesPage.getters.categoryFilter('sales').find('label').should('have.class', 'is-checked');
|
||||
// Search input should still have the search query
|
||||
templatesPage.getters.searchInput().should('have.value', 'auto');
|
||||
// Sales checkbox should be pushed to the top
|
||||
templatesPage.getters.categoryFilters().eq(1).then(($el) => {
|
||||
expect($el.text()).to.equal('Sales');
|
||||
});
|
||||
it('Redirects to website when visiting template by id page directly', () => {
|
||||
cy.visit(`${templatesPage.url}/1`);
|
||||
cy.origin('https://n8n.io', () => {
|
||||
cy.url().should('include', 'https://n8n.io/workflows/1');
|
||||
})
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,13 +4,11 @@ import {
|
|||
testData,
|
||||
} from '../pages/template-collection';
|
||||
import * as templateCredentialsSetupPage from '../pages/template-credential-setup';
|
||||
import { TemplateWorkflowPage } from '../pages/template-workflow';
|
||||
import { WorkflowPage } from '../pages/workflow';
|
||||
import * as formStep from '../composables/setup-template-form-step';
|
||||
import { getSetupWorkflowCredentialsButton } from '../composables/setup-workflow-credentials-button';
|
||||
import * as setupCredsModal from '../composables/modals/workflow-credential-setup-modal';
|
||||
|
||||
const templateWorkflowPage = new TemplateWorkflowPage();
|
||||
const workflowPage = new WorkflowPage();
|
||||
|
||||
const testTemplate = templateCredentialsSetupPage.testData.simpleTemplate;
|
||||
|
@ -34,18 +32,16 @@ describe('Template credentials setup', () => {
|
|||
cy.intercept('GET', `https://api.n8n.io/api/templates/workflows/${testTemplate.id}`, {
|
||||
fixture: testTemplate.fixture,
|
||||
});
|
||||
});
|
||||
|
||||
it('can be opened from template workflow page', () => {
|
||||
templateWorkflowPage.actions.visit(testTemplate.id);
|
||||
templateCredentialsSetupPage.enableTemplateCredentialSetupFeatureFlag();
|
||||
templateWorkflowPage.getters.useTemplateButton().should('be.visible');
|
||||
templateCredentialsSetupPage.enableTemplateCredentialSetupFeatureFlag();
|
||||
templateWorkflowPage.actions.clickUseThisWorkflowButton();
|
||||
|
||||
templateCredentialsSetupPage.getters
|
||||
.title(`Set up 'Promote new Shopify products on Twitter and Telegram' template`)
|
||||
.should('be.visible');
|
||||
cy.intercept('GET', '**/rest/settings', (req) => {
|
||||
// Disable cache
|
||||
delete req.headers['if-none-match']
|
||||
req.reply((res) => {
|
||||
if (res.body.data) {
|
||||
// Disable custom templates host if it has been overridden by another intercept
|
||||
res.body.data.templates = { enabled: true, host: 'https://api.n8n.io/api/' };
|
||||
}
|
||||
});
|
||||
}).as('settingsRequest');
|
||||
});
|
||||
|
||||
it('can be opened from template collection page', () => {
|
||||
|
|
147
cypress/e2e/38-custom-template-repository.cy.ts
Normal file
147
cypress/e2e/38-custom-template-repository.cy.ts
Normal file
|
@ -0,0 +1,147 @@
|
|||
import { TemplatesPage } from '../pages/templates';
|
||||
import { WorkflowPage } from '../pages/workflow';
|
||||
import { TemplateWorkflowPage } from '../pages/template-workflow';
|
||||
import OnboardingWorkflow from '../fixtures/Onboarding_workflow.json';
|
||||
import WorkflowTemplate from '../fixtures/Workflow_template_write_http_query.json';
|
||||
|
||||
const templatesPage = new TemplatesPage();
|
||||
const workflowPage = new WorkflowPage();
|
||||
const templateWorkflowPage = new TemplateWorkflowPage();
|
||||
|
||||
|
||||
describe('In-app templates repository', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept('GET', '**/api/templates/search?page=1&rows=20&category=&search=', { fixture: 'templates_search/all_templates_search_response.json' }).as('searchRequest');
|
||||
cy.intercept('GET', '**/api/templates/search?page=1&rows=20&category=Sales*', { fixture: 'templates_search/sales_templates_search_response.json' }).as('categorySearchRequest');
|
||||
cy.intercept('GET', '**/api/templates/workflows/*', { fixture: 'templates_search/test_template_preview.json' }).as('singleTemplateRequest');
|
||||
cy.intercept('GET', '**/api/workflows/templates/*', { fixture: 'templates_search/test_template_import.json' }).as('singleTemplateRequest');
|
||||
cy.intercept('GET', '**/rest/settings', (req) => {
|
||||
// Disable cache
|
||||
delete req.headers['if-none-match']
|
||||
req.reply((res) => {
|
||||
if (res.body.data) {
|
||||
// Enable in-app templates by setting a custom host
|
||||
res.body.data.templates = { enabled: true, host: 'https://api-staging.n8n.io/api/' };
|
||||
}
|
||||
});
|
||||
}).as('settingsRequest');
|
||||
});
|
||||
|
||||
it('can open onboarding flow', () => {
|
||||
templatesPage.actions.openOnboardingFlow(1, OnboardingWorkflow.name, OnboardingWorkflow, 'https://api-staging.n8n.io');
|
||||
cy.url().then(($url) => {
|
||||
expect($url).to.match(/.*\/workflow\/.*?onboardingId=1$/);
|
||||
})
|
||||
|
||||
workflowPage.actions.shouldHaveWorkflowName(`Demo: ${name}`);
|
||||
|
||||
workflowPage.getters.canvasNodes().should('have.length', 4);
|
||||
workflowPage.getters.stickies().should('have.length', 1);
|
||||
workflowPage.getters.canvasNodes().first().should('have.descendants', '.node-pin-data-icon');
|
||||
});
|
||||
|
||||
it('can import template', () => {
|
||||
templatesPage.actions.importTemplate(1, OnboardingWorkflow.name, OnboardingWorkflow, 'https://api-staging.n8n.io');
|
||||
|
||||
cy.url().then(($url) => {
|
||||
expect($url).to.include('/workflow/new?templateId=1');
|
||||
});
|
||||
|
||||
workflowPage.getters.canvasNodes().should('have.length', 4);
|
||||
workflowPage.getters.stickies().should('have.length', 1);
|
||||
workflowPage.actions.shouldHaveWorkflowName(OnboardingWorkflow.name);
|
||||
});
|
||||
|
||||
it('should save template id with the workflow', () => {
|
||||
cy.visit(templatesPage.url);
|
||||
cy.get('.el-skeleton.n8n-loading').should('not.exist');
|
||||
templatesPage.getters.firstTemplateCard().should('exist');
|
||||
templatesPage.getters.templatesLoadingContainer().should('not.exist');
|
||||
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', () => {
|
||||
templateWorkflowPage.actions.openTemplate(WorkflowTemplate, 'https://api-staging.n8n.io');
|
||||
|
||||
templateWorkflowPage.getters.description().find('img').should('have.length', 1);
|
||||
});
|
||||
|
||||
|
||||
it('renders search elements correctly', () => {
|
||||
cy.visit(templatesPage.url);
|
||||
templatesPage.getters.searchInput().should('exist');
|
||||
templatesPage.getters.allCategoriesFilter().should('exist');
|
||||
templatesPage.getters.categoryFilters().should('have.length.greaterThan', 1);
|
||||
templatesPage.getters.templateCards().should('have.length.greaterThan', 0);
|
||||
});
|
||||
|
||||
it('can filter templates by category', () => {
|
||||
cy.visit(templatesPage.url);
|
||||
templatesPage.getters.templatesLoadingContainer().should('not.exist');
|
||||
templatesPage.getters.categoryFilter('sales').should('exist');
|
||||
let initialTemplateCount = 0;
|
||||
let initialCollectionCount = 0;
|
||||
|
||||
templatesPage.getters.templateCountLabel().then(($el) => {
|
||||
initialTemplateCount = parseInt($el.text().replace(/\D/g, ''), 10);
|
||||
templatesPage.getters.collectionCountLabel().then(($el) => {
|
||||
initialCollectionCount = parseInt($el.text().replace(/\D/g, ''), 10);
|
||||
|
||||
templatesPage.getters.categoryFilter('sales').click();
|
||||
templatesPage.getters.templatesLoadingContainer().should('not.exist');
|
||||
|
||||
// Should have less templates and collections after selecting a category
|
||||
templatesPage.getters.templateCountLabel().should(($el) => {
|
||||
expect(parseInt($el.text().replace(/\D/g, ''), 10)).to.be.lessThan(initialTemplateCount);
|
||||
});
|
||||
templatesPage.getters.collectionCountLabel().should(($el) => {
|
||||
expect(parseInt($el.text().replace(/\D/g, ''), 10)).to.be.lessThan(initialCollectionCount);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve search query in URL', () => {
|
||||
cy.visit(templatesPage.url);
|
||||
templatesPage.getters.templatesLoadingContainer().should('not.exist');
|
||||
templatesPage.getters.categoryFilter('sales').should('exist');
|
||||
templatesPage.getters.categoryFilter('sales').click();
|
||||
templatesPage.getters.searchInput().type('auto');
|
||||
|
||||
cy.url().should('include', '?categories=');
|
||||
cy.url().should('include', '&search=');
|
||||
|
||||
cy.reload();
|
||||
|
||||
// Should preserve search query in URL
|
||||
cy.url().should('include', '?categories=');
|
||||
cy.url().should('include', '&search=');
|
||||
|
||||
// Sales category should still be selected
|
||||
templatesPage.getters.categoryFilter('sales').find('label').should('have.class', 'is-checked');
|
||||
// Search input should still have the search query
|
||||
templatesPage.getters.searchInput().should('have.value', 'auto');
|
||||
// Sales checkbox should be pushed to the top
|
||||
templatesPage.getters.categoryFilters().eq(1).then(($el) => {
|
||||
expect($el.text()).to.equal('Sales');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"workflow": {
|
||||
"id": 3,
|
||||
"id": 1,
|
||||
"name": "Write HTTP query string on image",
|
||||
"views": 116,
|
||||
"recentViews": 9766,
|
||||
|
@ -185,4 +185,4 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,8 +25,8 @@ export class TemplateWorkflowPage extends BasePage {
|
|||
user: { username: string };
|
||||
image: { id: number; url: string }[];
|
||||
};
|
||||
}) => {
|
||||
cy.intercept('GET', `https://api.n8n.io/api/templates/workflows/${template.workflow.id}`, {
|
||||
}, templateHost: string) => {
|
||||
cy.intercept('GET', `${templateHost}/api/templates/workflows/${template.workflow.id}`, {
|
||||
statusCode: 200,
|
||||
body: template,
|
||||
}).as('getTemplate');
|
||||
|
|
|
@ -23,14 +23,14 @@ export class TemplatesPage extends BasePage {
|
|||
cy.waitForLoad();
|
||||
},
|
||||
|
||||
openOnboardingFlow: (id: number, name: string, workflow: object) => {
|
||||
openOnboardingFlow: (id: number, name: string, workflow: object, templatesHost: string) => {
|
||||
const apiResponse = {
|
||||
id,
|
||||
name,
|
||||
workflow,
|
||||
};
|
||||
cy.intercept('POST', '/rest/workflows').as('createWorkflow');
|
||||
cy.intercept('GET', `https://api.n8n.io/api/workflows/templates/${id}`, {
|
||||
cy.intercept('GET', `${templatesHost}/api/workflows/templates/${id}`, {
|
||||
statusCode: 200,
|
||||
body: apiResponse,
|
||||
}).as('getTemplate');
|
||||
|
@ -42,13 +42,13 @@ export class TemplatesPage extends BasePage {
|
|||
cy.wait(['@createWorkflow', '@getWorkflow']);
|
||||
},
|
||||
|
||||
importTemplate: (id: number, name: string, workflow: object) => {
|
||||
importTemplate: (id: number, name: string, workflow: object, templatesHost: string) => {
|
||||
const apiResponse = {
|
||||
id,
|
||||
name,
|
||||
workflow,
|
||||
};
|
||||
cy.intercept('GET', `https://api.n8n.io/api/workflows/templates/${id}`, {
|
||||
cy.intercept('GET', `${templatesHost}/api/workflows/templates/${id}`, {
|
||||
statusCode: 200,
|
||||
body: apiResponse,
|
||||
}).as('getTemplate');
|
||||
|
|
|
@ -118,6 +118,7 @@ import { useUIStore } from '@/stores/ui.store';
|
|||
import { useUsersStore } from '@/stores/users.store';
|
||||
import { useVersionsStore } from '@/stores/versions.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { useTemplatesStore } from '@/stores/templates.store';
|
||||
import ExecutionsUsage from '@/components/ExecutionsUsage.vue';
|
||||
import BecomeTemplateCreatorCta from '@/components/BecomeTemplateCreatorCta/BecomeTemplateCreatorCta.vue';
|
||||
import MainSidebarSourceControl from '@/components/MainSidebarSourceControl.vue';
|
||||
|
@ -162,6 +163,7 @@ export default defineComponent({
|
|||
useCloudPlanStore,
|
||||
useSourceControlStore,
|
||||
useBecomeTemplateCreatorStore,
|
||||
useTemplatesStore,
|
||||
),
|
||||
logoPath(): string {
|
||||
if (this.isCollapsed) return this.basePath + 'n8n-logo-collapsed.svg';
|
||||
|
@ -225,13 +227,28 @@ export default defineComponent({
|
|||
const regularItems: IMenuItem[] = [
|
||||
workflows,
|
||||
{
|
||||
// Link to in-app templates, available if custom templates are enabled
|
||||
id: 'templates',
|
||||
icon: 'box-open',
|
||||
label: this.$locale.baseText('mainSidebar.templates'),
|
||||
position: 'top',
|
||||
available: this.settingsStore.isTemplatesEnabled,
|
||||
available:
|
||||
this.settingsStore.isTemplatesEnabled && this.templatesStore.hasCustomTemplatesHost,
|
||||
route: { to: { name: VIEWS.TEMPLATES } },
|
||||
},
|
||||
{
|
||||
// Link to website templates, available if custom templates are not enabled
|
||||
id: 'templates',
|
||||
icon: 'box-open',
|
||||
label: this.$locale.baseText('mainSidebar.templates'),
|
||||
position: 'top',
|
||||
available:
|
||||
this.settingsStore.isTemplatesEnabled && !this.templatesStore.hasCustomTemplatesHost,
|
||||
link: {
|
||||
href: this.templatesStore.getWebsiteTemplateRepositoryURL,
|
||||
target: '_blank',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'credentials',
|
||||
icon: 'key',
|
||||
|
|
|
@ -58,6 +58,7 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
|||
import type { SimplifiedNodeType } from '@/Interface';
|
||||
import type { INodeTypeDescription } from 'n8n-workflow';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import { useTemplatesStore } from '@/stores/templates.store';
|
||||
|
||||
export interface NodeViewItemSection {
|
||||
key: string;
|
||||
|
@ -116,6 +117,7 @@ function getAiNodesBySubcategory(nodes: INodeTypeDescription[], subcategory: str
|
|||
export function AIView(_nodes: SimplifiedNodeType[]): NodeView {
|
||||
const i18n = useI18n();
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const templatesStore = useTemplatesStore();
|
||||
|
||||
const chainNodes = getAiNodesBySubcategory(nodeTypesStore.allLatestNodeTypes, AI_CATEGORY_CHAINS);
|
||||
const agentNodes = getAiNodesBySubcategory(nodeTypesStore.allLatestNodeTypes, AI_CATEGORY_AGENTS);
|
||||
|
@ -124,7 +126,9 @@ export function AIView(_nodes: SimplifiedNodeType[]): NodeView {
|
|||
value: AI_NODE_CREATOR_VIEW,
|
||||
title: i18n.baseText('nodeCreator.aiPanel.aiNodes'),
|
||||
subtitle: i18n.baseText('nodeCreator.aiPanel.selectAiNode'),
|
||||
info: i18n.baseText('nodeCreator.aiPanel.infoBox'),
|
||||
info: i18n.baseText('nodeCreator.aiPanel.infoBox', {
|
||||
interpolate: { link: templatesStore.getWebsiteCategoryURL('ai') },
|
||||
}),
|
||||
items: [
|
||||
...chainNodes,
|
||||
...agentNodes,
|
||||
|
|
|
@ -737,3 +737,12 @@ export const MOUSE_EVENT_BUTTONS = {
|
|||
BROWSER_BACK: 8,
|
||||
BROWSER_FORWARD: 16,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Urls used to route users to the right template repository
|
||||
*/
|
||||
export const TEMPLATES_URLS = {
|
||||
DEFAULT_API_HOST: 'https://api.n8n.io/api/',
|
||||
BASE_WEBSITE_URL: 'https://n8n.io/workflows',
|
||||
UTM_QUERY: 'utm_source=n8n_app&utm_medium=template_library',
|
||||
};
|
||||
|
|
|
@ -946,7 +946,7 @@
|
|||
"nodeCreator.aiPanel.newTag": "New",
|
||||
"nodeCreator.aiPanel.langchainAiNodes": "Advanced AI",
|
||||
"nodeCreator.aiPanel.title": "When should this workflow run?",
|
||||
"nodeCreator.aiPanel.infoBox": "Check out our <a href=\"/collections/8\" target=\"_blank\">templates</a> for workflow examples and inspiration.",
|
||||
"nodeCreator.aiPanel.infoBox": "Check out our <a href=\"{link}\" target=\"_blank\">templates</a> for workflow examples and inspiration.",
|
||||
"nodeCreator.aiPanel.scheduleTriggerDisplayName": "On a schedule",
|
||||
"nodeCreator.aiPanel.scheduleTriggerDescription": "Runs the flow every day, hour, or custom interval",
|
||||
"nodeCreator.aiPanel.webhookTriggerDisplayName": "On webhook call",
|
||||
|
|
|
@ -78,7 +78,7 @@ export const routes = [
|
|||
{
|
||||
path: '/',
|
||||
name: VIEWS.HOMEPAGE,
|
||||
redirect: (to) => {
|
||||
redirect: () => {
|
||||
return { name: VIEWS.WORKFLOWS };
|
||||
},
|
||||
meta: {
|
||||
|
@ -130,6 +130,15 @@ export const routes = [
|
|||
},
|
||||
middleware: ['authenticated'],
|
||||
},
|
||||
beforeEnter: (to, _from, next) => {
|
||||
const templatesStore = useTemplatesStore();
|
||||
if (!templatesStore.hasCustomTemplatesHost) {
|
||||
const id = Array.isArray(to.params.id) ? to.params.id[0] : to.params.id;
|
||||
window.location.href = templatesStore.getWebsiteTemplatePageURL(id);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/templates/:id/setup',
|
||||
|
@ -180,6 +189,14 @@ export const routes = [
|
|||
},
|
||||
middleware: ['authenticated'],
|
||||
},
|
||||
beforeEnter: (_to, _from, next) => {
|
||||
const templatesStore = useTemplatesStore();
|
||||
if (!templatesStore.hasCustomTemplatesHost) {
|
||||
window.location.href = templatesStore.getWebsiteTemplateRepositoryURL;
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/credentials',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import { STORES } from '@/constants';
|
||||
import { STORES, TEMPLATES_URLS } from '@/constants';
|
||||
import type {
|
||||
INodeUi,
|
||||
ITemplatesCategory,
|
||||
|
@ -109,6 +109,38 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
|
|||
);
|
||||
};
|
||||
},
|
||||
hasCustomTemplatesHost(): boolean {
|
||||
const settingsStore = useSettingsStore();
|
||||
return settingsStore.templatesHost !== TEMPLATES_URLS.DEFAULT_API_HOST;
|
||||
},
|
||||
/**
|
||||
* Construct the URL for the template repository on the website
|
||||
* @returns {string}
|
||||
*/
|
||||
getWebsiteTemplateRepositoryURL(): string {
|
||||
return `${TEMPLATES_URLS.BASE_WEBSITE_URL}?${TEMPLATES_URLS.UTM_QUERY}&utm_instance=${this.getCurrentN8nPath}`;
|
||||
},
|
||||
/**
|
||||
* Construct the URL for the template page on the website for a given template id
|
||||
* @returns {function(string): string}
|
||||
*/
|
||||
getWebsiteTemplatePageURL() {
|
||||
return (id: string) => {
|
||||
return `${TEMPLATES_URLS.BASE_WEBSITE_URL}/${id}?${TEMPLATES_URLS.UTM_QUERY}&utm_instance=${this.getCurrentN8nPath}`;
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Construct the URL for the template category page on the website for a given category id
|
||||
* @returns {function(string): string}
|
||||
*/
|
||||
getWebsiteCategoryURL() {
|
||||
return (id: string) => {
|
||||
return `${TEMPLATES_URLS.BASE_WEBSITE_URL}/?categories=${id}&${TEMPLATES_URLS.UTM_QUERY}&utm_instance=${this.getCurrentN8nPath}`;
|
||||
};
|
||||
},
|
||||
getCurrentN8nPath(): string {
|
||||
return `${window.location.host}${window.BASE_PATH}`;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
addCategories(categories: ITemplatesCategory[]): void {
|
||||
|
|
Loading…
Reference in a new issue