fix(editor): Prevent pagination setting from being overwritten in URL (#13266)

This commit is contained in:
Milorad FIlipović 2025-02-17 11:38:35 +01:00 committed by GitHub
parent 8e15ebf833
commit d1e65a1cd5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 92 additions and 17 deletions

View file

@ -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');
});
}); });

View file

@ -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'),

View file

@ -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>

View file

@ -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 });