From 85361607bcb4cffa0c1c9b65de315f64c855fc92 Mon Sep 17 00:00:00 2001 From: Georgy Karataev Date: Tue, 29 Mar 2022 14:29:03 +0200 Subject: [PATCH] feat(Clusters list table): filters edition modifies search params (#196) --- src/AppConstants.js | 9 ++ .../ClustersListTable/ClustersListTable.js | 54 ++++++++++-- .../ClustersListTable.spec.ct.js | 87 +++++++++++++++---- src/Components/Common/Tables.js | 2 + 4 files changed, 130 insertions(+), 22 deletions(-) diff --git a/src/AppConstants.js b/src/AppConstants.js index d47a9163..2dc690a5 100644 --- a/src/AppConstants.js +++ b/src/AppConstants.js @@ -310,3 +310,12 @@ export const LIKELIHOOD_LABEL_LOWER = { intlSettings ), }; +export const CLUSTERS_LIST_COLUMNS_KEYS = [ + 'name', + 'recommendations', + 'critical', + 'important', + 'moderate', + 'low', + 'last_seen', +]; diff --git a/src/Components/ClustersListTable/ClustersListTable.js b/src/Components/ClustersListTable/ClustersListTable.js index 12e2bc65..d80cbd01 100644 --- a/src/Components/ClustersListTable/ClustersListTable.js +++ b/src/Components/ClustersListTable/ClustersListTable.js @@ -2,6 +2,8 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; +import isEqual from 'lodash/isEqual'; +import { useLocation } from 'react-router-dom'; import { SortByDirection, @@ -25,6 +27,7 @@ import { } from '../../Services/Filters'; import { CLUSTERS_LIST_COLUMNS, + CLUSTERS_LIST_COLUMNS_KEYS, CLUSTER_FILTER_CATEGORIES, CLUSTER_LAST_CHECKED_CELL, CLUSTER_NAME_CELL, @@ -32,7 +35,10 @@ import { import { buildFilterChips, mapClustersToRows, + paramParser, passFiltersCluster, + translateSortParams, + updateSearchParams, } from '../Common/Tables'; import Loading from '../Loading/Loading'; import messages from '../../Messages'; @@ -41,7 +47,6 @@ import { NoMatchingClusters, NoRecsForClusters, } from '../MessageState/EmptyStates'; -import isEqual from 'lodash/isEqual'; const ClustersListTable = ({ query: { isError, isUninitialized, isFetching, isSuccess, data, refetch }, @@ -57,6 +62,13 @@ const ClustersListTable = ({ const [filteredRows, setFilteredRows] = useState([]); const [displayedRows, setDisplayedRows] = useState([]); + // helps to distinguish the state when the API data received but not yet filtered + const [rowsFiltered, setRowsFiltered] = useState(false); + const [filterBuilding, setFilterBuilding] = useState(true); + const { search } = useLocation(); + const loadingState = isUninitialized || isFetching || !rowsFiltered; + const errorState = isError; + const successState = isSuccess; useEffect(() => { setDisplayedRows( @@ -72,8 +84,40 @@ const ClustersListTable = ({ useEffect(() => { setFilteredRows(buildFilteredRows(clusters, filters)); + if (isSuccess && !rowsFiltered) { + setRowsFiltered(true); + } }, [data, filters.hits, filters.text]); + useEffect(() => { + if (search && filterBuilding) { + const paramsObject = paramParser(search); + + if (paramsObject.sort) { + const sortObj = translateSortParams(paramsObject.sort); + paramsObject.sortIndex = CLUSTERS_LIST_COLUMNS_KEYS.indexOf( + sortObj.name + ); + paramsObject.sortDirection = sortObj.direction; + } + paramsObject.offset && + (paramsObject.offset = Number(paramsObject.offset[0])); + paramsObject.limit && + (paramsObject.limit = Number(paramsObject.limit[0])); + paramsObject.impacting && + !Array.isArray(paramsObject.impacting) && + (paramsObject.impacting = [`${paramsObject.impacting}`]); + updateFilters({ ...filters, ...paramsObject }); + } + setFilterBuilding(false); + }, []); + + useEffect(() => { + if (!filterBuilding) { + updateSearchParams(filters, CLUSTERS_LIST_COLUMNS_KEYS); + } + }, [filters, filterBuilding]); + const buildFilteredRows = (allRows, filters) => mapClustersToRows( allRows.filter((cluster) => passFiltersCluster(cluster, filters)) @@ -184,7 +228,7 @@ const ClustersListTable = ({ return ( <> - {isUninitialized || isFetching ? ( + {loadingState ? ( @@ -210,15 +254,15 @@ const ClustersListTable = ({ filterConfig={{ items: filterConfigItems }} activeFiltersConfig={activeFiltersConfig} /> - {(isUninitialized || isFetching) && } - {isError && ( + {loadingState && } + {errorState && ( )} - {!(isUninitialized || isFetching) && isSuccess && ( + {!loadingState && successState && ( { it('has values', () => { cy.wrap(data).its('length').should('be.gte', 1); @@ -185,6 +196,7 @@ describe('clusters list table', () => { .find('b') .eq(0) .should('have.text', '1 - 20'); + expect(window.location.search).to.contain('limit=20'); }); it('can change page limit', () => { @@ -196,7 +208,8 @@ describe('clusters list table', () => { .find('ul[class=pf-c-options-menu__menu]') .find(DROPDOWN_ITEM) .contains(`${el}`) - .click(); + .click() + .then(() => expect(window.location.search).to.contain(`limit=${el}`)); checkRowCounts(Math.min(el, data.length)); }); }); @@ -217,7 +230,11 @@ describe('clusters list table', () => { it('can iterate over pages', () => { cy.wrap(itemsPerPage()).each((el, index, list) => { - checkRowCounts(el); + checkRowCounts(el).then(() => { + expect(window.location.search).to.contain( + `offset=${DEFAULT_ROW_COUNT * index}` + ); + }); cy.get(TOOLBAR) .find(PAGINATION) .find('button[data-action="next"]') @@ -239,6 +256,7 @@ describe('clusters list table', () => { .find('.pf-c-chip__text') .should('have.length', 1) .should('have.text', 'All clusters'); + expect(window.location.search).to.contain(`hits=all`); }); it('reset filters button is displayed', () => { @@ -265,7 +283,8 @@ describe('clusters list table', () => { .find('.pf-c-select__menu') .find('input') .eq(0) - .click(); + .click() + .then(() => expect(window.location.search).to.not.contain(`hits=`)); // open pagination cy.get(PAGINATION).eq(0).find('.pf-c-options-menu__toggle-button').click(); // set to 50 clusters per page @@ -275,7 +294,8 @@ describe('clusters list table', () => { .find('li') .eq(2) .find('button') - .click(); + .click() + .then(() => expect(window.location.search).to.contain(`limit=50`)); cy.getTotalClusters().should('have.text', 26); // check all shown clusters have recommendations > 0 cy.get('TBODY') @@ -293,16 +313,20 @@ describe('clusters list table', () => { .eq(1) .click(); cy.get(TOOLBAR_FILTER).find('.pf-c-select__toggle').click(); + // unflag "All clusters" cy.get(TOOLBAR_FILTER) .find('.pf-c-select__menu') .find('input') .eq(0) - .click(); + .click() + .then(() => expect(window.location.search).to.not.contain(`hits=`)); + // flag "Critical" cy.get(TOOLBAR_FILTER) .find('.pf-c-select__menu') .find('input') .eq(1) - .click(); + .click() + .then(() => expect(window.location.search).to.contain(`hits=4`)); cy.get('.pf-c-table__sort').eq(2).click(); cy.getFirstRow().find('td[data-label=Critical]').should('have.text', 1); cy.get('.pf-c-table__sort').eq(2).click(); @@ -321,12 +345,14 @@ describe('clusters list table', () => { .find('.pf-c-select__menu') .find('input') .eq(0) - .click(); + .click() + .then(() => expect(window.location.search).to.not.contain(`hits=`)); cy.get(TOOLBAR_FILTER) .find('.pf-c-select__menu') .find('input') .eq(2) - .click(); + .click() + .then(() => expect(window.location.search).to.contain(`hits=3`)); cy.get('.pf-c-table__sort').eq(3).click(); cy.getFirstRow().find('td[data-label=Important]').should('have.text', 1); cy.get('.pf-c-table__sort').eq(3).click(); @@ -345,12 +371,14 @@ describe('clusters list table', () => { .find('.pf-c-select__menu') .find('input') .eq(0) - .click(); + .click() + .then(() => expect(window.location.search).to.not.contain(`hits=`)); cy.get(TOOLBAR_FILTER) .find('.pf-c-select__menu') .find('input') .eq(3) - .click(); + .click() + .then(() => expect(window.location.search).to.contain(`hits=2`)); cy.get('.pf-c-table__sort').eq(4).click(); cy.getFirstRow().find('td[data-label=Moderate]').should('have.text', 3); cy.get('.pf-c-table__sort').eq(4).click(); @@ -369,12 +397,14 @@ describe('clusters list table', () => { .find('.pf-c-select__menu') .find('input') .eq(0) - .click(); + .click() + .then(() => expect(window.location.search).to.not.contain(`hits=`)); cy.get(TOOLBAR_FILTER) .find('.pf-c-select__menu') .find('input') .eq(4) - .click(); + .click() + .then(() => expect(window.location.search).to.contain(`hits=1`)); cy.get('.pf-c-table__sort').eq(5).click(); cy.getFirstRow().find('td[data-label=Low]').should('have.text', 1); cy.get('.pf-c-table__sort').eq(5).click(); @@ -383,7 +413,10 @@ describe('clusters list table', () => { it('can filter by name', () => { // search by "cc" search input - cy.get(TOOLBAR_FILTER).find('.pf-c-form-control').type('cc'); + cy.get(TOOLBAR_FILTER) + .find('.pf-c-form-control') + .type('cc') + .then(() => expect(window.location.search).to.contain(`text=cc`)); // should be 4 clusters left cy.get(TBODY) .children() @@ -399,12 +432,20 @@ describe('clusters list table', () => { .find('td[data-label=Name]') .should('have.text', '947b8f15-cc44-47ca-9265-945085d4f3b8'); // click on the Name sorting button - cy.get('.pf-c-table__sort').eq(0).click(); + cy.get('.pf-c-table__sort') + .eq(0) + .click() + .then(() => expect(window.location.search).to.contain(`sort=name`)); cy.getFirstRow() .find('td[data-label=Name]') .should('have.text', '1ghhxwjfoi 5b5hbyv07'); // click on the Recommendations sorting button - cy.get('.pf-c-table__sort').eq(1).click(); + cy.get('.pf-c-table__sort') + .eq(1) + .click() + .then(() => + expect(window.location.search).to.contain(`sort=recommendations`) + ); // the first cluster has 0 recommendations cy.getFirstRow() .find('td[data-label=Recommendations]') @@ -433,7 +474,10 @@ describe('clusters list table', () => { }); it('sorts N/A in last seen correctly', () => { - cy.get('.pf-c-table__sort').eq(6).click(); + cy.get('.pf-c-table__sort') + .eq(6) + .click() + .then(() => expect(window.location.search).to.contain(`sort=last_seen`)); cy.getFirstRow().find('span').should('have.text', 'N/A'); cy.get('.pf-c-table__sort').eq(6).click(); cy.get(PAGINATION).eq(0).find('.pf-c-options-menu__toggle-button').click(); @@ -476,7 +520,16 @@ describe('clusters list table', () => { Math.min(DEFAULT_ROW_COUNT, namedClusters.length) ); if (order === 'ascending') { - cy.get(header).find('button').click(); + cy.get(header) + .find('button') + .click() + .then(() => + expect(window.location.search).to.contain( + `sort=${order === 'descending' ? '-' : ''}${ + TABLE_HEADERS_SORT_KEYS[label] + }` + ) + ); } else { cy.get(header).find('button').dblclick(); } diff --git a/src/Components/Common/Tables.js b/src/Components/Common/Tables.js index 054d201c..79d6b33e 100644 --- a/src/Components/Common/Tables.js +++ b/src/Components/Common/Tables.js @@ -168,6 +168,7 @@ export const buildFilterChips = (filters, categories) => { const localFilters = cloneDeep(filters); delete localFilters.sortIndex; delete localFilters.sortDirection; + delete localFilters.sort; delete localFilters.offset; delete localFilters.limit; localFilters?.hits && @@ -233,6 +234,7 @@ export const updateSearchParams = (filters = {}, columnMapping) => { key !== 'sortDirection' && key !== 'sort' && value !== '' && + !(Array.isArray(value) && value.length === 0) && url.searchParams.set(key, value) ); });