Skip to content

Commit

Permalink
feat(editor): Add pagination to the workflows list (#13100)
Browse files Browse the repository at this point in the history
  • Loading branch information
MiloradFilipovic authored Feb 14, 2025
1 parent 67a4ed1 commit 8e37088
Show file tree
Hide file tree
Showing 17 changed files with 745 additions and 288 deletions.
4 changes: 4 additions & 0 deletions cypress/e2e/23-variables.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,22 @@ describe('Variables', () => {
variablesPage.getters.searchBar().type('NEW');
variablesPage.getters.variablesRows().should('have.length', 1);
variablesPage.getters.variableRow('NEW').should('contain.text', 'ENV_VAR_NEW');
cy.url().should('include', 'search=NEW');

// Multiple Results
variablesPage.getters.searchBar().clear().type('ENV_VAR');
variablesPage.getters.variablesRows().should('have.length', 2);
cy.url().should('include', 'search=ENV_VAR');

// All Results
variablesPage.getters.searchBar().clear().type('ENV');
variablesPage.getters.variablesRows().should('have.length', 3);
cy.url().should('include', 'search=ENV');

// No Results
variablesPage.getters.searchBar().clear().type('Some non-existent variable');
variablesPage.getters.variablesRows().should('not.exist');
cy.url().should('include', 'search=Some+non-existent+variable');

cy.contains('No variables found').should('be.visible');
});
Expand Down
7 changes: 4 additions & 3 deletions packages/editor-ui/src/api/workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
ExecutionSummary,
IDataObject,
} from 'n8n-workflow';
import { makeRestApiRequest } from '@/utils/apiUtils';
import { getFullApiResponse, makeRestApiRequest } from '@/utils/apiUtils';

export async function getNewWorkflow(context: IRestApiContext, data?: IDataObject) {
const response = await makeRestApiRequest<NewWorkflowResponse>(
Expand All @@ -32,10 +32,11 @@ export async function getWorkflow(context: IRestApiContext, id: string, filter?:
return await makeRestApiRequest<IWorkflowDb>(context, 'GET', `/workflows/${id}`, sendData);
}

export async function getWorkflows(context: IRestApiContext, filter?: object) {
return await makeRestApiRequest<IWorkflowDb[]>(context, 'GET', '/workflows', {
export async function getWorkflows(context: IRestApiContext, filter?: object, options?: object) {
return await getFullApiResponse<IWorkflowDb[]>(context, 'GET', '/workflows', {
includeScopes: true,
...(filter ? { filter } : {}),
...(options ? options : {}),
});
}

Expand Down
5 changes: 3 additions & 2 deletions packages/editor-ui/src/components/DuplicateWorkflowDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Modal from '@/components/Modal.vue';
import { useSettingsStore } from '@/stores/settings.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import type { IWorkflowDataUpdate } from '@/Interface';
import { createEventBus } from 'n8n-design-system/utils';
import { createEventBus, type EventBus } from 'n8n-design-system/utils';
import { useCredentialsStore } from '@/stores/credentials.store';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
import { useRouter } from 'vue-router';
Expand All @@ -17,7 +17,7 @@ import { useTelemetry } from '@/composables/useTelemetry';
const props = defineProps<{
modalName: string;
isActive: boolean;
data: { tags: string[]; id: string; name: string };
data: { tags: string[]; id: string; name: string; externalEventBus?: EventBus };
}>();
const router = useRouter();
Expand Down Expand Up @@ -111,6 +111,7 @@ const save = async (): Promise<void> => {
workflow_id: props.data.id,
sharing_role: workflowHelpers.getWorkflowProjectRole(props.data.id),
});
props.data.externalEventBus?.emit('workflow-duplicated', { id: props.data.id });
}
} catch (error) {
if (error.httpStatusCode === 403) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import ProjectMoveSuccessToastMessage from '@/components/Projects/ProjectMoveSuc
import { useToast } from '@/composables/useToast';
import { getResourcePermissions } from '@/permissions';
import { sortByProperty } from '@/utils/sortUtils';
import type { EventBus } from 'n8n-design-system/utils';
const props = defineProps<{
modalName: string;
data: {
resource: IWorkflowDb | ICredentialsResponse;
resourceType: ResourceType;
resourceTypeLabel: string;
eventBus?: EventBus;
};
}>();
Expand Down Expand Up @@ -104,6 +106,13 @@ const moveResource = async () => {
type: 'success',
duration: 8000,
});
if (props.data.eventBus) {
props.data.eventBus.emit('resource-moved', {
resourceId: props.data.resource.id,
resourceType: props.data.resourceType,
targetProjectId: selectedProject.value.id,
});
}
} catch (error) {
toast.showError(
error.message,
Expand Down
11 changes: 10 additions & 1 deletion packages/editor-ui/src/components/WorkflowActivator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ const props = defineProps<{
workflowId: string;
workflowPermissions: PermissionsRecord['workflow'];
}>();
const emit = defineEmits<{
'update:workflowActive': [value: { id: string; active: boolean }];
}>();
const { showMessage } = useToast();
const workflowActivate = useWorkflowActivate();
Expand Down Expand Up @@ -109,7 +114,11 @@ const shouldShowFreeAiCreditsWarning = computed((): boolean => {
});
async function activeChanged(newActiveState: boolean) {
return await workflowActivate.updateWorkflowActivation(props.workflowId, newActiveState);
const newState = await workflowActivate.updateWorkflowActivation(
props.workflowId,
newActiveState,
);
emit('update:workflowActive', { id: props.workflowId, active: newState });
}
async function displayActivationError() {
Expand Down
13 changes: 13 additions & 0 deletions packages/editor-ui/src/components/WorkflowCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { useI18n } from '@/composables/useI18n';
import { useRouter } from 'vue-router';
import { useTelemetry } from '@/composables/useTelemetry';
import { ResourceType } from '@/utils/projects.utils';
import type { EventBus } from 'n8n-design-system/utils';
const WORKFLOW_LIST_ITEM_ACTIONS = {
OPEN: 'open',
Expand All @@ -38,6 +39,7 @@ const props = withDefaults(
defineProps<{
data: IWorkflowDb;
readOnly?: boolean;
workflowListEventBus?: EventBus;
}>(),
{
data: () => ({
Expand All @@ -53,12 +55,15 @@ const props = withDefaults(
versionId: '',
}),
readOnly: false,
workflowListEventBus: undefined,
},
);
const emit = defineEmits<{
'expand:tags': [];
'click:tag': [tagId: string, e: PointerEvent];
'workflow:deleted': [];
'workflow:active-toggle': [value: { id: string; active: boolean }];
}>();
const toast = useToast();
Expand Down Expand Up @@ -160,6 +165,7 @@ async function onAction(action: string) {
tags: (props.data.tags ?? []).map((tag) =>
typeof tag !== 'string' && 'id' in tag ? tag.id : tag,
),
externalEventBus: props.workflowListEventBus,
},
});
break;
Expand Down Expand Up @@ -217,6 +223,7 @@ async function deleteWorkflow() {
title: locale.baseText('mainSidebar.showMessage.handleSelect1.title'),
type: 'success',
});
emit('workflow:deleted');
}
function moveResource() {
Expand All @@ -226,9 +233,14 @@ function moveResource() {
resource: props.data,
resourceType: ResourceType.Workflow,
resourceTypeLabel: resourceTypeLabel.value,
eventBus: props.workflowListEventBus,
},
});
}
const emitWorkflowActiveToggle = (value: { id: string; active: boolean }) => {
emit('workflow:active-toggle', value);
};
</script>

<template>
Expand Down Expand Up @@ -280,6 +292,7 @@ function moveResource() {
:workflow-id="data.id"
:workflow-permissions="workflowPermissions"
data-test-id="workflow-card-activator"
@update:workflow-active="emitWorkflowActiveToggle"
/>

<n8n-action-toggle
Expand Down
28 changes: 17 additions & 11 deletions packages/editor-ui/src/components/forms/ResourceFiltersDropdown.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script setup lang="ts">
import { ref, computed, watch, onBeforeMount } from 'vue';
import { computed, watch, onBeforeMount } from 'vue';
import { EnterpriseEditionFeature } from '@/constants';
import { useProjectsStore } from '@/stores/projects.store';
import type { ProjectSharingData } from '@/types/projects.types';
import ProjectSharing from '@/components/Projects/ProjectSharing.vue';
import type { IFilters } from '../layouts/ResourcesListLayout.vue';
import type { BaseFilters } from '../layouts/ResourcesListLayout.vue';
import { useI18n } from '@/composables/useI18n';
type IResourceFiltersType = Record<string, boolean | string | string[]>;
Expand All @@ -25,16 +25,25 @@ const props = withDefaults(
);
const emit = defineEmits<{
'update:modelValue': [value: IFilters];
'update:modelValue': [value: BaseFilters];
'update:filtersLength': [value: number];
}>();
const selectedProject = ref<ProjectSharingData | null>(null);
const projectsStore = useProjectsStore();
const i18n = useI18n();
const selectedProject = computed<ProjectSharingData | null>({
get: () => {
return (
projectsStore.availableProjects.find(
(project) => project.id === props.modelValue.homeProject,
) ?? null
);
},
set: (value) => setKeyValue('homeProject', value?.id ?? ''),
});
const filtersLength = computed(() => {
let length = 0;
Expand Down Expand Up @@ -67,7 +76,7 @@ const setKeyValue = (key: string, value: unknown) => {
const filters = {
...props.modelValue,
[key]: value,
} as IFilters;
} as BaseFilters;
emit('update:modelValue', filters);
};
Expand All @@ -76,7 +85,7 @@ const resetFilters = () => {
if (props.reset) {
props.reset();
} else {
const filters = { ...props.modelValue } as IFilters;
const filters = { ...props.modelValue } as BaseFilters;
props.keys.forEach((key) => {
filters[key] = Array.isArray(props.modelValue[key]) ? [] : '';
Expand All @@ -93,10 +102,6 @@ watch(filtersLength, (value) => {
onBeforeMount(async () => {
await projectsStore.getAvailableProjects();
selectedProject.value =
projectsStore.availableProjects.find(
(project) => project.id === props.modelValue.homeProject,
) ?? null;
});
</script>

Expand All @@ -113,6 +118,7 @@ onBeforeMount(async () => {
<n8n-badge
v-show="filtersLength > 0"
:class="$style['filter-button-count']"
data-test-id="resources-list-filters-count"
theme="primary"
>
{{ filtersLength }}
Expand Down
Loading

0 comments on commit 8e37088

Please # to comment.