From 4e1040c849e7e1e8e8a0a0576bb842739bf6e792 Mon Sep 17 00:00:00 2001 From: Jaanus Sellin Date: Wed, 15 Nov 2023 16:01:02 +0200 Subject: [PATCH] feat: connect sort table to backend (#5338) Now FE sorting is done in backend. --- .../PaginatedProjectFeatureToggles.tsx | 54 +++++++++++++++---- .../project/Project/ProjectOverview.tsx | 31 +++++++++-- .../useFeatureSearch/useFeatureSearch.ts | 13 ++++- .../feature-toggle-strategies-store.ts | 3 +- 4 files changed, 84 insertions(+), 17 deletions(-) diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/PaginatedProjectFeatureToggles.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/PaginatedProjectFeatureToggles.tsx index 30ddac286a..a0c28ca99f 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/PaginatedProjectFeatureToggles.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/PaginatedProjectFeatureToggles.tsx @@ -63,11 +63,18 @@ import { ListItemType } from './ProjectFeatureToggles.types'; import { createFeatureToggleCell } from './FeatureToggleSwitch/createFeatureToggleCell'; import { useFeatureToggleSwitch } from './FeatureToggleSwitch/useFeatureToggleSwitch'; import useLoading from 'hooks/useLoading'; +import { DEFAULT_PAGE_LIMIT } from '../ProjectOverview'; const StyledResponsiveButton = styled(ResponsiveButton)(() => ({ whiteSpace: 'nowrap', })); +export interface ISortingRules { + sortBy: string; + sortOrder: 'asc' | 'desc'; + isFavoritesPinned: boolean; +} + interface IPaginatedProjectFeatureTogglesProps { features: IProject['features']; environments: IProject['environments']; @@ -78,6 +85,8 @@ interface IPaginatedProjectFeatureTogglesProps { searchValue: string; setSearchValue: React.Dispatch>; paginationBar: JSX.Element; + sortingRules: ISortingRules; + setSortingRules: (sortingRules: ISortingRules) => void; } const staticColumns = ['Select', 'Actions', 'name', 'favorite']; @@ -96,6 +105,8 @@ export const PaginatedProjectFeatureToggles = ({ searchValue, setSearchValue, paginationBar, + sortingRules, + setSortingRules, }: IPaginatedProjectFeatureTogglesProps) => { const { classes: styles } = useStyles(); const bodyLoadingRef = useLoading(loading); @@ -223,7 +234,11 @@ export const PaginatedProjectFeatureToggles = ({ { Header: 'Name', accessor: 'name', - Cell: ({ value }: { value: string }) => ( + Cell: ({ + value, + }: { + value: string; + }) => ( '' : name, maxWidth: 90, - id: `environments.${name}`, + id: `environment:${name}`, accessor: (row: ListItemType) => row.environments[name]?.enabled, align: 'center', @@ -301,7 +316,11 @@ export const PaginatedProjectFeatureToggles = ({ id: 'Actions', maxWidth: 56, width: 56, - Cell: (props: { row: { original: ListItemType } }) => ( + Cell: (props: { + row: { + original: ListItemType; + }; + }) => ( = {}; - tableState.sort = sortBy[0].id; + tableState.sort = sortedByColumn; if (sortBy[0].desc) { - tableState.order = 'desc'; + tableState.order = sortOrder; } if (searchValue) { tableState.search = searchValue; @@ -490,10 +514,17 @@ export const PaginatedProjectFeatureToggles = ({ desc: sortBy[0].desc || false, columns: tableState.columns.split(','), })); + const favoritesPinned = Boolean(isFavoritesPinned); setGlobalStore((params) => ({ ...params, - favorites: Boolean(isFavoritesPinned), + favorites: favoritesPinned, })); + setSortingRules({ + sortBy: sortedByColumn, + sortOrder, + isFavoritesPinned: favoritesPinned, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ loading, @@ -504,9 +535,12 @@ export const PaginatedProjectFeatureToggles = ({ isFavoritesPinned, ]); - const showPaginationBar = Boolean(total && total > 25); + const showPaginationBar = Boolean(total && total > DEFAULT_PAGE_LIMIT); const style = showPaginationBar - ? { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 } + ? { + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, + } : {}; return ( diff --git a/frontend/src/component/project/Project/ProjectOverview.tsx b/frontend/src/component/project/Project/ProjectOverview.tsx index 3b54ba0cc3..113e42c910 100644 --- a/frontend/src/component/project/Project/ProjectOverview.tsx +++ b/frontend/src/component/project/Project/ProjectOverview.tsx @@ -12,10 +12,14 @@ import { ProjectStats } from './ProjectStats/ProjectStats'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useUiFlag } from 'hooks/useUiFlag'; import { useFeatureSearch } from 'hooks/api/getters/useFeatureSearch/useFeatureSearch'; -import { PaginatedProjectFeatureToggles } from './ProjectFeatureToggles/PaginatedProjectFeatureToggles'; +import { + ISortingRules, + PaginatedProjectFeatureToggles, +} from './ProjectFeatureToggles/PaginatedProjectFeatureToggles'; import { useSearchParams } from 'react-router-dom'; import { PaginationBar } from 'component/common/PaginationBar/PaginationBar'; +import { SortingRule } from 'react-table'; const refreshInterval = 15 * 1000; @@ -38,28 +42,43 @@ const StyledContentContainer = styled(Box)(() => ({ minWidth: 0, })); +export const DEFAULT_PAGE_LIMIT = 25; + const PaginatedProjectOverview = () => { const projectId = useRequiredPathParam('projectId'); const [searchParams, setSearchParams] = useSearchParams(); const { project, loading: projectLoading } = useProject(projectId, { refreshInterval, }); - const [pageLimit, setPageLimit] = useState(25); + const [pageLimit, setPageLimit] = useState(DEFAULT_PAGE_LIMIT); const [currentOffset, setCurrentOffset] = useState(0); const [searchValue, setSearchValue] = useState( searchParams.get('search') || '', ); + const [sortingRules, setSortingRules] = useState({ + sortBy: 'createdBy', + sortOrder: 'desc', + isFavoritesPinned: false, + }); + const { features: searchFeatures, total, refetch, loading, initialLoad, - } = useFeatureSearch(currentOffset, pageLimit, projectId, searchValue, { - refreshInterval, - }); + } = useFeatureSearch( + currentOffset, + pageLimit, + sortingRules, + projectId, + searchValue, + { + refreshInterval, + }, + ); const { members, features, health, description, environments, stats } = project; @@ -102,6 +121,8 @@ const PaginatedProjectOverview = () => { total={total} searchValue={searchValue} setSearchValue={setSearchValue} + sortingRules={sortingRules} + setSortingRules={setSortingRules} paginationBar={ { return ( offset: number, limit: number, + sortingRules: ISortingRules, projectId = '', searchValue = '', options: SWRConfiguration = {}, @@ -71,6 +73,7 @@ const createFeatureSearch = () => { offset, limit, searchValue, + sortingRules, ); useEffect(() => { @@ -113,9 +116,11 @@ const getFeatureSearchFetcher = ( offset: number, limit: number, searchValue: string, + sortingRules: ISortingRules, ) => { const searchQueryParams = translateToQueryParams(searchValue); - const KEY = `api/admin/search/features?projectId=${projectId}&offset=${offset}&limit=${limit}&${searchQueryParams}`; + const sortQueryParams = translateToSortQueryParams(sortingRules); + const KEY = `api/admin/search/features?projectId=${projectId}&offset=${offset}&limit=${limit}&${searchQueryParams}&${sortQueryParams}`; const fetcher = () => { const path = formatApiPath(KEY); return fetch(path, { @@ -130,3 +135,9 @@ const getFeatureSearchFetcher = ( KEY, }; }; + +const translateToSortQueryParams = (sortingRules: ISortingRules) => { + const { sortBy, sortOrder, isFavoritesPinned } = sortingRules; + const sortQueryParams = `sortBy=${sortBy}&sortOrder=${sortOrder}&favoritesFirst=${isFavoritesPinned}`; + return sortQueryParams; +}; diff --git a/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts b/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts index bcf0587560..50f67cc984 100644 --- a/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts +++ b/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts @@ -713,7 +713,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { const [, envName] = sortBy.split(':'); query = query .orderByRaw( - `CASE WHEN feature_environments.environment = ? THEN feature_environments.enabled ELSE NULL END ${validatedSortOrder}`, + `CASE WHEN feature_environments.environment = ? THEN feature_environments.enabled ELSE NULL END ${validatedSortOrder} NULLS LAST`, [envName], ) .orderBy('created_at', 'asc'); @@ -733,6 +733,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { .select(selectColumns) .limit(limit * environmentCount) .offset(offset * environmentCount); + const rows = await query; if (rows.length > 0) {