diff --git a/cypress/e2e/8-http-request-node.cy.ts b/cypress/e2e/8-http-request-node.cy.ts
index c8a4e1c9ec..eb4d93c171 100644
--- a/cypress/e2e/8-http-request-node.cy.ts
+++ b/cypress/e2e/8-http-request-node.cy.ts
@@ -2,15 +2,17 @@ import { WorkflowPage, WorkflowsPage, NDV } from '../pages';
const workflowsPage = new WorkflowsPage();
const workflowPage = new WorkflowPage();
-const ndv = new NDV()
+const ndv = new NDV();
describe('HTTP Request node', () => {
- before(() => {
+ beforeEach(() => {
cy.resetAll();
cy.skipSetup();
});
it('should make a request with a URL and receive a response', () => {
+ cy.visit(workflowsPage.url);
+
workflowsPage.actions.createWorkflowFromCard();
workflowPage.actions.addInitialNodeToCanvas('Manual Trigger');
workflowPage.actions.addNodeToCanvas('HTTP Request');
diff --git a/packages/design-system/src/components/N8nRecycleScroller/RecycleScroller.stories.ts b/packages/design-system/src/components/N8nRecycleScroller/RecycleScroller.stories.ts
new file mode 100644
index 0000000000..d1b1ed0e0b
--- /dev/null
+++ b/packages/design-system/src/components/N8nRecycleScroller/RecycleScroller.stories.ts
@@ -0,0 +1,59 @@
+/* eslint-disable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */
+import type { StoryFn } from '@storybook/vue';
+import N8nRecycleScroller from './RecycleScroller.vue';
+import { ComponentInstance } from 'vue';
+
+export default {
+ title: 'Atoms/RecycleScroller',
+ component: N8nRecycleScroller,
+ argTypes: {},
+};
+
+const Template: StoryFn = () => ({
+ components: {
+ N8nRecycleScroller,
+ },
+ data() {
+ return {
+ items: Array.from(Array(256).keys()).map((i) => ({ id: i })) as Array<{
+ id: number;
+ height: number;
+ }>,
+ };
+ },
+ methods: {
+ resizeItem(item: { id: string; height: string }, fn: (item: { id: string }) => void) {
+ const itemRef = (this as ComponentInstance).$refs[`item-${item.id}`] as HTMLElement;
+
+ item.height = '200px';
+ itemRef.style.height = '200px';
+ fn(item);
+ },
+ getItemStyle(item: { id: string; height?: string }) {
+ return {
+ height: item.height || '100px',
+ width: '100%',
+ backgroundColor: `hsl(${parseInt(item.id, 10) * 1.4}, 100%, 50%)`,
+ cursor: 'pointer',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ };
+ },
+ },
+ template: `
+
+
+
+ {{item.id}}
+
+
+
+
`,
+});
+
+export const RecycleScroller = Template.bind({});
diff --git a/packages/design-system/src/components/N8nRecycleScroller/RecycleScroller.vue b/packages/design-system/src/components/N8nRecycleScroller/RecycleScroller.vue
new file mode 100644
index 0000000000..a9943b1201
--- /dev/null
+++ b/packages/design-system/src/components/N8nRecycleScroller/RecycleScroller.vue
@@ -0,0 +1,274 @@
+
+
+
+
+
+
+
diff --git a/packages/design-system/src/components/N8nRecycleScroller/index.ts b/packages/design-system/src/components/N8nRecycleScroller/index.ts
new file mode 100644
index 0000000000..4ada18c3b2
--- /dev/null
+++ b/packages/design-system/src/components/N8nRecycleScroller/index.ts
@@ -0,0 +1,3 @@
+import N8nRecycleScroller from './RecycleScroller.vue';
+
+export default N8nRecycleScroller;
diff --git a/packages/design-system/src/components/N8nTags/Tags.vue b/packages/design-system/src/components/N8nTags/Tags.vue
index 8b483d8dcb..4a104f284a 100644
--- a/packages/design-system/src/components/N8nTags/Tags.vue
+++ b/packages/design-system/src/components/N8nTags/Tags.vue
@@ -11,7 +11,7 @@
theme="text"
underline
size="small"
- @click.stop.prevent="showAll = true"
+ @click.stop.prevent="onExpand"
>
{{ t('tags.showMore', hiddenTagsLength) }}
@@ -67,6 +67,12 @@ export default mixins(Locale).extend({
return this.tags.length - this.truncateAt;
},
},
+ methods: {
+ onExpand() {
+ this.showAll = true;
+ this.$emit('expand', true);
+ },
+ },
});
diff --git a/packages/design-system/src/plugins/n8nComponents.ts b/packages/design-system/src/plugins/n8nComponents.ts
index dc5a03285f..c3bc377d7e 100644
--- a/packages/design-system/src/plugins/n8nComponents.ts
+++ b/packages/design-system/src/plugins/n8nComponents.ts
@@ -45,6 +45,7 @@ import N8nUserInfo from '../components/N8nUserInfo';
import N8nUserSelect from '../components/N8nUserSelect';
import N8nUsersList from '../components/N8nUsersList';
import N8nResizeWrapper from '../components/N8nResizeWrapper';
+import N8nRecycleScroller from '../components/N8nRecycleScroller';
export default {
install: (app: typeof Vue) => {
@@ -94,5 +95,6 @@ export default {
app.component('n8n-users-list', N8nUsersList);
app.component('n8n-user-select', N8nUserSelect);
app.component('n8n-resize-wrapper', N8nResizeWrapper);
+ app.component('n8n-recycle-scroller', N8nRecycleScroller);
},
};
diff --git a/packages/editor-ui/src/api/workflows.ts b/packages/editor-ui/src/api/workflows.ts
index f34eb77fdf..7e109b631a 100644
--- a/packages/editor-ui/src/api/workflows.ts
+++ b/packages/editor-ui/src/api/workflows.ts
@@ -10,6 +10,12 @@ export async function getNewWorkflow(context: IRestApiContext, name?: string) {
};
}
+export async function getWorkflow(context: IRestApiContext, id: string, filter?: object) {
+ const sendData = filter ? { filter } : undefined;
+
+ return await makeRestApiRequest(context, 'GET', `/workflows/${id}`, sendData);
+}
+
export async function getWorkflows(context: IRestApiContext, filter?: object) {
const sendData = filter ? { filter } : undefined;
diff --git a/packages/editor-ui/src/components/WorkflowCard.vue b/packages/editor-ui/src/components/WorkflowCard.vue
index 983cbbc279..c4d76d7e06 100644
--- a/packages/editor-ui/src/components/WorkflowCard.vue
+++ b/packages/editor-ui/src/components/WorkflowCard.vue
@@ -28,6 +28,7 @@
:truncateAt="3"
truncate
@click="onClickTag"
+ @expand="onExpandTags"
data-test-id="workflow-card-tags"
/>
@@ -189,6 +190,9 @@ export default mixins(showMessage, restApi).extend({
this.$emit('click:tag', tagId, event);
},
+ onExpandTags() {
+ this.$emit('expand:tags');
+ },
async onAction(action: string) {
if (action === WORKFLOW_LIST_ITEM_ACTIONS.OPEN) {
await this.onClick();
diff --git a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
index b5ecf5b8ce..3f5961b066 100644
--- a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
+++ b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
@@ -154,6 +154,7 @@ import { useWorkflowsStore } from '@/stores/workflows';
import { useWorkflowsEEStore } from '@/stores/workflows.ee';
import { ITelemetryTrackProperties } from 'n8n-workflow';
import { useUsageStore } from '@/stores/usage';
+import { BaseTextKey } from '@/plugins/i18n';
export default mixins(showMessage).extend({
name: 'workflow-share-modal',
@@ -175,7 +176,7 @@ export default mixins(showMessage).extend({
return {
WORKFLOW_SHARE_MODAL_KEY,
- loading: false,
+ loading: true,
modalBus: new Vue(),
sharedWith: [...(workflow.sharedWith || [])] as Array>,
EnterpriseEditionFeature,
@@ -199,8 +200,9 @@ export default mixins(showMessage).extend({
modalTitle(): string {
return this.$locale.baseText(
this.isSharingEnabled
- ? this.uiStore.contextBasedTranslationKeys.workflows.sharing.title
- : this.uiStore.contextBasedTranslationKeys.workflows.sharing.unavailable.title,
+ ? (this.uiStore.contextBasedTranslationKeys.workflows.sharing.title as BaseTextKey)
+ : (this.uiStore.contextBasedTranslationKeys.workflows.sharing.unavailable
+ .title as BaseTextKey),
{
interpolate: { name: this.workflow.name },
},
@@ -380,7 +382,7 @@ export default mixins(showMessage).extend({
},
),
this.$locale.baseText('workflows.shareModal.list.delete.confirm.title', {
- interpolate: { name: user.fullName },
+ interpolate: { name: user.fullName as string },
}),
null,
this.$locale.baseText('workflows.shareModal.list.delete.confirm.confirmButtonText'),
@@ -437,18 +439,37 @@ export default mixins(showMessage).extend({
});
},
goToUpgrade() {
- let linkUrl = this.$locale.baseText(this.uiStore.contextBasedTranslationKeys.upgradeLinkUrl);
+ let linkUrl = this.$locale.baseText(
+ this.uiStore.contextBasedTranslationKeys.upgradeLinkUrl as BaseTextKey,
+ );
if (linkUrl.includes('subscription')) {
linkUrl = `${this.usageStore.viewPlansUrl}&source=workflow_sharing`;
}
window.open(linkUrl, '_blank');
},
+ async initialize() {
+ if (this.isSharingEnabled) {
+ await this.loadUsers();
+
+ if (
+ this.workflow.id !== PLACEHOLDER_EMPTY_WORKFLOW_ID &&
+ !this.workflow.sharedWith?.length // Sharing info already loaded
+ ) {
+ await this.workflowsStore.fetchWorkflow(this.workflow.id);
+ }
+ }
+
+ this.loading = false;
+ },
},
mounted() {
- if (this.isSharingEnabled) {
- this.loadUsers();
- }
+ this.initialize();
+ },
+ watch: {
+ workflow(workflow) {
+ this.sharedWith = workflow.sharedWith;
+ },
},
});
diff --git a/packages/editor-ui/src/components/layouts/PageViewLayoutList.vue b/packages/editor-ui/src/components/layouts/PageViewLayoutList.vue
index 345b291cb1..f66ee0a2ad 100644
--- a/packages/editor-ui/src/components/layouts/PageViewLayoutList.vue
+++ b/packages/editor-ui/src/components/layouts/PageViewLayoutList.vue
@@ -1,7 +1,7 @@
-
+
@@ -21,12 +21,16 @@
.list {
display: flex;
flex-direction: column;
- align-items: stretch;
width: 100%;
height: 100%;
-}
-.body {
- overflow: auto;
+ .header {
+ flex: 0 0 auto;
+ }
+
+ .body {
+ overflow: hidden;
+ flex: 1 1;
+ }
}
diff --git a/packages/editor-ui/src/components/layouts/ResourcesListLayout.vue b/packages/editor-ui/src/components/layouts/ResourcesListLayout.vue
index 08aee20b34..3c5a8e7601 100644
--- a/packages/editor-ui/src/components/layouts/ResourcesListLayout.vue
+++ b/packages/editor-ui/src/components/layouts/ResourcesListLayout.vue
@@ -106,56 +106,56 @@
+
+
+
+
+
+ {{ $locale.baseText(`${resourceKey}.filters.active`) }}
+
+ {{ $locale.baseText(`${resourceKey}.filters.active.reset`) }}
+
+
+
+
+
-
+
+
+
+
+
-
-
- {{ $locale.baseText(`${resourceKey}.filters.active`) }}
-
- {{ $locale.baseText(`${resourceKey}.filters.active.reset`) }}
-
-
-
+
+ {{ $locale.baseText(`${resourceKey}.noResults`) }}
+
+
+ ({{ $locale.baseText(`${resourceKey}.noResults.switchToShared.preamble`) }}
+
+ {{ $locale.baseText(`${resourceKey}.noResults.switchToShared.link`) }} )
+
-
-
-
- {{ $locale.baseText(`${resourceKey}.noResults`) }}
-
-
- ({{ $locale.baseText(`${resourceKey}.noResults.switchToShared.preamble`) }}
- {{
- $locale.baseText(`${resourceKey}.noResults.switchToShared.link`)
- }})
-
-
- ({{
- $locale.baseText(`${resourceKey}.noResults.withSearch.switchToShared.preamble`)
- }}
- {{
+
+ ({{
+ $locale.baseText(`${resourceKey}.noResults.withSearch.switchToShared.preamble`)
+ }}
+
+ {{
$locale.baseText(`${resourceKey}.noResults.withSearch.switchToShared.link`)
- }})
-
-
-
-
+ }} )
+
+
+
@@ -217,6 +217,10 @@ export default mixins(showMessage, debounceHelper).extend({
type: Array,
default: (): IResource[] => [],
},
+ itemSize: {
+ type: Number,
+ default: 80,
+ },
initialize: {
type: Function as PropType<() => Promise
>,
default: () => () => Promise.resolve(),
@@ -438,8 +442,8 @@ export default mixins(showMessage, debounceHelper).extend({
}
.list {
- display: flex;
- flex-direction: column;
+ //display: flex;
+ //flex-direction: column;
}
.sort-and-filter {
diff --git a/packages/editor-ui/src/plugins/components.ts b/packages/editor-ui/src/plugins/components.ts
index 5c4d549135..567a26d1cc 100644
--- a/packages/editor-ui/src/plugins/components.ts
+++ b/packages/editor-ui/src/plugins/components.ts
@@ -2,11 +2,10 @@
import Vue from 'vue';
import Fragment from 'vue-fragment';
+import VueAgile from 'vue-agile';
import 'regenerator-runtime/runtime';
-import VueAgile from 'vue-agile';
-
import ElementUI from 'element-ui';
import { Loading, MessageBox, Message, Notification } from 'element-ui';
import { designSystemComponents } from 'n8n-design-system';
@@ -14,13 +13,13 @@ import { ElMessageBoxOptions } from 'element-ui/types/message-box';
import EnterpriseEdition from '@/components/EnterpriseEdition.ee.vue';
Vue.use(Fragment.Plugin);
+Vue.use(VueAgile);
Vue.use(ElementUI);
Vue.use(designSystemComponents);
Vue.component('enterprise-edition', EnterpriseEdition);
-Vue.use(VueAgile);
Vue.use(Loading.directive);
Vue.prototype.$loading = Loading.service;
diff --git a/packages/editor-ui/src/stores/workflows.ts b/packages/editor-ui/src/stores/workflows.ts
index b156c70ab3..e32f1d6183 100644
--- a/packages/editor-ui/src/stores/workflows.ts
+++ b/packages/editor-ui/src/stores/workflows.ts
@@ -50,6 +50,7 @@ import {
getExecutionData,
getFinishedExecutions,
getNewWorkflow,
+ getWorkflow,
getWorkflows,
} from '@/api/workflows';
import { useUIStore } from './ui';
@@ -258,6 +259,13 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
return workflows;
},
+ async fetchWorkflow(id: string): Promise {
+ const rootStore = useRootStore();
+ const workflow = await getWorkflow(rootStore.getRestApiContext, id);
+ this.addWorkflow(workflow);
+ return workflow;
+ },
+
async getNewWorkflowData(name?: string): Promise {
const workflowsEEStore = useWorkflowsEEStore();
diff --git a/packages/editor-ui/src/views/CredentialsView.vue b/packages/editor-ui/src/views/CredentialsView.vue
index 3ff980da74..901ca18ebe 100644
--- a/packages/editor-ui/src/views/CredentialsView.vue
+++ b/packages/editor-ui/src/views/CredentialsView.vue
@@ -6,11 +6,12 @@
:initialize="initialize"
:filters="filters"
:additional-filters-handler="onFilter"
+ :item-size="77"
@click:add="addCredential"
@update:filters="filters = $event"
>
-
+
diff --git a/packages/editor-ui/src/views/WorkflowsView.vue b/packages/editor-ui/src/views/WorkflowsView.vue
index 277629b62b..abe2a082ad 100644
--- a/packages/editor-ui/src/views/WorkflowsView.vue
+++ b/packages/editor-ui/src/views/WorkflowsView.vue
@@ -22,8 +22,14 @@
-
-
+
+