mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(editor): Prevent pagination setting from being overwritten in URL (#13266)
This commit is contained in:
parent
8e15ebf833
commit
d1e65a1cd5
|
@ -97,4 +97,45 @@ describe('Workflows', () => {
|
||||||
|
|
||||||
WorkflowsPage.getters.workflowCards().should('have.length', 1);
|
WorkflowsPage.getters.workflowCards().should('have.length', 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should preserve filters and pagination in URL', () => {
|
||||||
|
// Add a search query
|
||||||
|
WorkflowsPage.getters.searchBar().type('My');
|
||||||
|
// Add a tag filter
|
||||||
|
WorkflowsPage.getters.workflowFilterButton().click();
|
||||||
|
WorkflowsPage.getters.workflowTagsDropdown().click();
|
||||||
|
WorkflowsPage.getters.workflowTagItem('other-tag-1').click();
|
||||||
|
WorkflowsPage.getters.workflowsListContainer().click();
|
||||||
|
// Update sort order
|
||||||
|
WorkflowsPage.getters.workflowSortDropdown().click();
|
||||||
|
WorkflowsPage.getters.workflowSortItem('Sort by last created').click({ force: true });
|
||||||
|
// Update page size
|
||||||
|
WorkflowsPage.getters.workflowListPageSizeDropdown().click();
|
||||||
|
WorkflowsPage.getters.workflowListPageSizeItem('25').click();
|
||||||
|
|
||||||
|
// URL should contain all applied filters and pagination
|
||||||
|
cy.url().should('include', 'search=My');
|
||||||
|
// Cannot really know tag id, so just check if it contains 'tags='
|
||||||
|
cy.url().should('include', 'tags=');
|
||||||
|
cy.url().should('include', 'sort=lastCreated');
|
||||||
|
cy.url().should('include', 'pageSize=25');
|
||||||
|
|
||||||
|
// Reload the page
|
||||||
|
cy.reload();
|
||||||
|
// Check if filters and pagination are preserved
|
||||||
|
WorkflowsPage.getters.searchBar().should('have.value', 'My');
|
||||||
|
WorkflowsPage.getters.workflowFilterButton().click();
|
||||||
|
WorkflowsPage.getters.workflowTagsDropdown().should('contain.text', 'other-tag-1');
|
||||||
|
WorkflowsPage.getters
|
||||||
|
.workflowSortItem('Sort by last created')
|
||||||
|
.should('have.attr', 'aria-selected', 'true');
|
||||||
|
WorkflowsPage.getters
|
||||||
|
.workflowListPageSizeItem('25', false)
|
||||||
|
.should('have.attr', 'aria-selected', 'true');
|
||||||
|
// Aso, check if the URL is preserved
|
||||||
|
cy.url().should('include', 'search=My');
|
||||||
|
cy.url().should('include', 'tags=');
|
||||||
|
cy.url().should('include', 'sort=lastCreated');
|
||||||
|
cy.url().should('include', 'pageSize=25');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -47,6 +47,18 @@ export class WorkflowsPage extends BasePage {
|
||||||
workflowOwnershipDropdown: () => cy.getByTestId('user-select-trigger'),
|
workflowOwnershipDropdown: () => cy.getByTestId('user-select-trigger'),
|
||||||
workflowOwner: (email: string) => cy.getByTestId('user-email').contains(email),
|
workflowOwner: (email: string) => cy.getByTestId('user-email').contains(email),
|
||||||
workflowResetFilters: () => cy.getByTestId('workflows-filter-reset'),
|
workflowResetFilters: () => cy.getByTestId('workflows-filter-reset'),
|
||||||
|
workflowSortDropdown: () => cy.getByTestId('resources-list-sort'),
|
||||||
|
workflowSortItem: (sort: string) =>
|
||||||
|
cy.getByTestId('resources-list-sort-item').contains(sort).parent(),
|
||||||
|
workflowPagination: () => cy.getByTestId('resources-list-pagination'),
|
||||||
|
workflowListPageSizeDropdown: () => this.getters.workflowPagination().find('.select-trigger'),
|
||||||
|
workflowListPageSizeItem: (pageSize: string, visible: boolean = true) => {
|
||||||
|
if (visible) {
|
||||||
|
return cy.get('[role=option]').filter(':visible').contains(`${pageSize}/page`);
|
||||||
|
}
|
||||||
|
return cy.get('[role=option]').contains(`${pageSize}/page`).parent();
|
||||||
|
},
|
||||||
|
workflowsListContainer: () => cy.getByTestId('resources-list-wrapper'),
|
||||||
// Not yet implemented
|
// Not yet implemented
|
||||||
// myWorkflows: () => cy.getByTestId('my-workflows'),
|
// myWorkflows: () => cy.getByTestId('my-workflows'),
|
||||||
// allWorkflows: () => cy.getByTestId('all-workflows'),
|
// allWorkflows: () => cy.getByTestId('all-workflows'),
|
||||||
|
|
|
@ -557,6 +557,7 @@ const loadPaginationFromQueryString = async () => {
|
||||||
<div
|
<div
|
||||||
v-else-if="filteredAndSortedResources.length > 0"
|
v-else-if="filteredAndSortedResources.length > 0"
|
||||||
ref="listWrapperRef"
|
ref="listWrapperRef"
|
||||||
|
data-test-id="resources-list-wrapper"
|
||||||
:class="$style.listWrapper"
|
:class="$style.listWrapper"
|
||||||
>
|
>
|
||||||
<!-- FULL SCROLLING LIST (Shows all resources, filtering and sorting is done in this component) -->
|
<!-- FULL SCROLLING LIST (Shows all resources, filtering and sorting is done in this component) -->
|
||||||
|
@ -588,6 +589,7 @@ const loadPaginationFromQueryString = async () => {
|
||||||
:total="totalItems"
|
:total="totalItems"
|
||||||
:page-sizes="availablePageSizeOptions"
|
:page-sizes="availablePageSizeOptions"
|
||||||
layout="total, prev, pager, next, sizes"
|
layout="total, prev, pager, next, sizes"
|
||||||
|
data-test-id="resources-list-pagination"
|
||||||
@update:current-page="setCurrentPage"
|
@update:current-page="setCurrentPage"
|
||||||
@size-change="setRowsPerPage"
|
@size-change="setRowsPerPage"
|
||||||
></el-pagination>
|
></el-pagination>
|
||||||
|
|
|
@ -185,12 +185,11 @@ const onFiltersUpdated = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSearchUpdated = async (search: string) => {
|
const onSearchUpdated = async (search: string) => {
|
||||||
|
currentPage.value = 1;
|
||||||
|
saveFiltersOnQueryString();
|
||||||
if (search) {
|
if (search) {
|
||||||
currentPage.value = 1;
|
|
||||||
saveFiltersOnQueryString();
|
|
||||||
await callDebounced(fetchWorkflows, { debounceTime: 500, trailing: true });
|
await callDebounced(fetchWorkflows, { debounceTime: 500, trailing: true });
|
||||||
} else {
|
} else {
|
||||||
currentPage.value = 1;
|
|
||||||
// No need to debounce when clearing search
|
// No need to debounce when clearing search
|
||||||
await fetchWorkflows();
|
await fetchWorkflows();
|
||||||
}
|
}
|
||||||
|
@ -306,46 +305,67 @@ function isValidProjectId(projectId: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const setFiltersFromQueryString = async () => {
|
const setFiltersFromQueryString = async () => {
|
||||||
|
const newQuery: LocationQueryRaw = { ...route.query };
|
||||||
const { tags, status, search, homeProject, sort } = route.query ?? {};
|
const { tags, status, search, homeProject, sort } = route.query ?? {};
|
||||||
const newQuery: LocationQueryRaw = {};
|
|
||||||
|
|
||||||
if (homeProject && typeof homeProject === 'string') {
|
// Helper to check if string value is not empty
|
||||||
|
const isValidString = (value: unknown): value is string =>
|
||||||
|
typeof value === 'string' && value.trim().length > 0;
|
||||||
|
|
||||||
|
// Handle home project
|
||||||
|
if (isValidString(homeProject)) {
|
||||||
await projectsStore.getAvailableProjects();
|
await projectsStore.getAvailableProjects();
|
||||||
if (isValidProjectId(homeProject)) {
|
if (isValidProjectId(homeProject)) {
|
||||||
newQuery.homeProject = homeProject;
|
newQuery.homeProject = homeProject;
|
||||||
filters.value.homeProject = homeProject;
|
filters.value.homeProject = homeProject;
|
||||||
|
} else {
|
||||||
|
delete newQuery.homeProject;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
delete newQuery.homeProject;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search && typeof search === 'string') {
|
// Handle search
|
||||||
|
if (isValidString(search)) {
|
||||||
newQuery.search = search;
|
newQuery.search = search;
|
||||||
filters.value.search = search;
|
filters.value.search = search;
|
||||||
|
} else {
|
||||||
|
delete newQuery.search;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tags && typeof tags === 'string') {
|
// Handle tags
|
||||||
|
if (isValidString(tags)) {
|
||||||
await tagsStore.fetchAll();
|
await tagsStore.fetchAll();
|
||||||
const currentTags = tagsStore.allTags.map((tag) => tag.id);
|
const validTags = tags
|
||||||
const validTags = tags.split(',').filter((tag) => currentTags.includes(tag));
|
.split(',')
|
||||||
|
.filter((tag) => tagsStore.allTags.map((t) => t.id).includes(tag));
|
||||||
|
|
||||||
if (validTags.length) {
|
if (validTags.length) {
|
||||||
newQuery.tags = validTags.join(',');
|
newQuery.tags = validTags.join(',');
|
||||||
filters.value.tags = validTags;
|
filters.value.tags = validTags;
|
||||||
|
} else {
|
||||||
|
delete newQuery.tags;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
delete newQuery.tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
// Handle status
|
||||||
status &&
|
const validStatusValues = [StatusFilter.ACTIVE.toString(), StatusFilter.DEACTIVATED.toString()];
|
||||||
typeof status === 'string' &&
|
if (isValidString(status) && validStatusValues.includes(status)) {
|
||||||
[StatusFilter.ACTIVE.toString(), StatusFilter.DEACTIVATED.toString()].includes(status)
|
newQuery.status = status;
|
||||||
) {
|
filters.value.status = status === 'true';
|
||||||
newQuery.status = status; // Keep as string in URL
|
} else {
|
||||||
filters.value.status = status === 'true'; // Convert to boolean for filters
|
delete newQuery.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sort && typeof sort === 'string') {
|
// Handle sort
|
||||||
|
if (isValidString(sort)) {
|
||||||
const newSort = WORKFLOWS_SORT_MAP[sort as keyof typeof WORKFLOWS_SORT_MAP] ?? 'updatedAt:desc';
|
const newSort = WORKFLOWS_SORT_MAP[sort as keyof typeof WORKFLOWS_SORT_MAP] ?? 'updatedAt:desc';
|
||||||
newQuery.sort = sort;
|
newQuery.sort = sort;
|
||||||
currentSort.value = newSort;
|
currentSort.value = newSort;
|
||||||
|
} else {
|
||||||
|
delete newQuery.sort;
|
||||||
}
|
}
|
||||||
|
|
||||||
void router.replace({ query: newQuery });
|
void router.replace({ query: newQuery });
|
||||||
|
|
Loading…
Reference in a new issue