1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

feat: connect sort table to backend (#5338)

Now FE sorting is done in backend.
This commit is contained in:
Jaanus Sellin 2023-11-15 16:01:02 +02:00 committed by GitHub
parent db77962a72
commit 4e1040c849
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 84 additions and 17 deletions

View File

@ -63,11 +63,18 @@ import { ListItemType } from './ProjectFeatureToggles.types';
import { createFeatureToggleCell } from './FeatureToggleSwitch/createFeatureToggleCell'; import { createFeatureToggleCell } from './FeatureToggleSwitch/createFeatureToggleCell';
import { useFeatureToggleSwitch } from './FeatureToggleSwitch/useFeatureToggleSwitch'; import { useFeatureToggleSwitch } from './FeatureToggleSwitch/useFeatureToggleSwitch';
import useLoading from 'hooks/useLoading'; import useLoading from 'hooks/useLoading';
import { DEFAULT_PAGE_LIMIT } from '../ProjectOverview';
const StyledResponsiveButton = styled(ResponsiveButton)(() => ({ const StyledResponsiveButton = styled(ResponsiveButton)(() => ({
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
})); }));
export interface ISortingRules {
sortBy: string;
sortOrder: 'asc' | 'desc';
isFavoritesPinned: boolean;
}
interface IPaginatedProjectFeatureTogglesProps { interface IPaginatedProjectFeatureTogglesProps {
features: IProject['features']; features: IProject['features'];
environments: IProject['environments']; environments: IProject['environments'];
@ -78,6 +85,8 @@ interface IPaginatedProjectFeatureTogglesProps {
searchValue: string; searchValue: string;
setSearchValue: React.Dispatch<React.SetStateAction<string>>; setSearchValue: React.Dispatch<React.SetStateAction<string>>;
paginationBar: JSX.Element; paginationBar: JSX.Element;
sortingRules: ISortingRules;
setSortingRules: (sortingRules: ISortingRules) => void;
} }
const staticColumns = ['Select', 'Actions', 'name', 'favorite']; const staticColumns = ['Select', 'Actions', 'name', 'favorite'];
@ -96,6 +105,8 @@ export const PaginatedProjectFeatureToggles = ({
searchValue, searchValue,
setSearchValue, setSearchValue,
paginationBar, paginationBar,
sortingRules,
setSortingRules,
}: IPaginatedProjectFeatureTogglesProps) => { }: IPaginatedProjectFeatureTogglesProps) => {
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
const bodyLoadingRef = useLoading(loading); const bodyLoadingRef = useLoading(loading);
@ -223,7 +234,11 @@ export const PaginatedProjectFeatureToggles = ({
{ {
Header: 'Name', Header: 'Name',
accessor: 'name', accessor: 'name',
Cell: ({ value }: { value: string }) => ( Cell: ({
value,
}: {
value: string;
}) => (
<Tooltip title={value} arrow describeChild> <Tooltip title={value} arrow describeChild>
<span> <span>
<LinkCell <LinkCell
@ -285,7 +300,7 @@ export const PaginatedProjectFeatureToggles = ({
return { return {
Header: loading ? () => '' : name, Header: loading ? () => '' : name,
maxWidth: 90, maxWidth: 90,
id: `environments.${name}`, id: `environment:${name}`,
accessor: (row: ListItemType) => accessor: (row: ListItemType) =>
row.environments[name]?.enabled, row.environments[name]?.enabled,
align: 'center', align: 'center',
@ -301,7 +316,11 @@ export const PaginatedProjectFeatureToggles = ({
id: 'Actions', id: 'Actions',
maxWidth: 56, maxWidth: 56,
width: 56, width: 56,
Cell: (props: { row: { original: ListItemType } }) => ( Cell: (props: {
row: {
original: ListItemType;
};
}) => (
<ActionsCell <ActionsCell
projectId={projectId} projectId={projectId}
onOpenArchiveDialog={setFeatureArchiveState} onOpenArchiveDialog={setFeatureArchiveState}
@ -372,7 +391,10 @@ export const PaginatedProjectFeatureToggles = ({
name: `Feature name ${index}`, name: `Feature name ${index}`,
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
environments: { environments: {
production: { name: 'production', enabled: false }, production: {
name: 'production',
enabled: false,
},
}, },
})); }));
// Coerce loading data to FeatureSchema[] // Coerce loading data to FeatureSchema[]
@ -419,7 +441,7 @@ export const PaginatedProjectFeatureToggles = ({
id: id:
searchParams.get('sort') || searchParams.get('sort') ||
storedParams.id || storedParams.id ||
'createdAt', sortingRules.sortBy,
desc: searchParams.has('order') desc: searchParams.has('order')
? searchParams.get('order') === 'desc' ? searchParams.get('order') === 'desc'
: storedParams.desc, : storedParams.desc,
@ -462,10 +484,12 @@ export const PaginatedProjectFeatureToggles = ({
if (loading) { if (loading) {
return; return;
} }
const sortedByColumn = sortBy[0].id;
const sortOrder = sortBy[0].desc ? 'desc' : 'asc';
const tableState: Record<string, string> = {}; const tableState: Record<string, string> = {};
tableState.sort = sortBy[0].id; tableState.sort = sortedByColumn;
if (sortBy[0].desc) { if (sortBy[0].desc) {
tableState.order = 'desc'; tableState.order = sortOrder;
} }
if (searchValue) { if (searchValue) {
tableState.search = searchValue; tableState.search = searchValue;
@ -490,10 +514,17 @@ export const PaginatedProjectFeatureToggles = ({
desc: sortBy[0].desc || false, desc: sortBy[0].desc || false,
columns: tableState.columns.split(','), columns: tableState.columns.split(','),
})); }));
const favoritesPinned = Boolean(isFavoritesPinned);
setGlobalStore((params) => ({ setGlobalStore((params) => ({
...params, ...params,
favorites: Boolean(isFavoritesPinned), favorites: favoritesPinned,
})); }));
setSortingRules({
sortBy: sortedByColumn,
sortOrder,
isFavoritesPinned: favoritesPinned,
});
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
loading, loading,
@ -504,9 +535,12 @@ export const PaginatedProjectFeatureToggles = ({
isFavoritesPinned, isFavoritesPinned,
]); ]);
const showPaginationBar = Boolean(total && total > 25); const showPaginationBar = Boolean(total && total > DEFAULT_PAGE_LIMIT);
const style = showPaginationBar const style = showPaginationBar
? { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 } ? {
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
}
: {}; : {};
return ( return (

View File

@ -12,10 +12,14 @@ import { ProjectStats } from './ProjectStats/ProjectStats';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useUiFlag } from 'hooks/useUiFlag'; import { useUiFlag } from 'hooks/useUiFlag';
import { useFeatureSearch } from 'hooks/api/getters/useFeatureSearch/useFeatureSearch'; 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 { useSearchParams } from 'react-router-dom';
import { PaginationBar } from 'component/common/PaginationBar/PaginationBar'; import { PaginationBar } from 'component/common/PaginationBar/PaginationBar';
import { SortingRule } from 'react-table';
const refreshInterval = 15 * 1000; const refreshInterval = 15 * 1000;
@ -38,28 +42,43 @@ const StyledContentContainer = styled(Box)(() => ({
minWidth: 0, minWidth: 0,
})); }));
export const DEFAULT_PAGE_LIMIT = 25;
const PaginatedProjectOverview = () => { const PaginatedProjectOverview = () => {
const projectId = useRequiredPathParam('projectId'); const projectId = useRequiredPathParam('projectId');
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const { project, loading: projectLoading } = useProject(projectId, { const { project, loading: projectLoading } = useProject(projectId, {
refreshInterval, refreshInterval,
}); });
const [pageLimit, setPageLimit] = useState(25); const [pageLimit, setPageLimit] = useState(DEFAULT_PAGE_LIMIT);
const [currentOffset, setCurrentOffset] = useState(0); const [currentOffset, setCurrentOffset] = useState(0);
const [searchValue, setSearchValue] = useState( const [searchValue, setSearchValue] = useState(
searchParams.get('search') || '', searchParams.get('search') || '',
); );
const [sortingRules, setSortingRules] = useState<ISortingRules>({
sortBy: 'createdBy',
sortOrder: 'desc',
isFavoritesPinned: false,
});
const { const {
features: searchFeatures, features: searchFeatures,
total, total,
refetch, refetch,
loading, loading,
initialLoad, initialLoad,
} = useFeatureSearch(currentOffset, pageLimit, projectId, searchValue, { } = useFeatureSearch(
refreshInterval, currentOffset,
}); pageLimit,
sortingRules,
projectId,
searchValue,
{
refreshInterval,
},
);
const { members, features, health, description, environments, stats } = const { members, features, health, description, environments, stats } =
project; project;
@ -102,6 +121,8 @@ const PaginatedProjectOverview = () => {
total={total} total={total}
searchValue={searchValue} searchValue={searchValue}
setSearchValue={setSearchValue} setSearchValue={setSearchValue}
sortingRules={sortingRules}
setSortingRules={setSortingRules}
paginationBar={ paginationBar={
<StickyPaginationBar> <StickyPaginationBar>
<PaginationBar <PaginationBar

View File

@ -4,6 +4,7 @@ import { IFeatureToggleListItem } from 'interfaces/featureToggle';
import { formatApiPath } from 'utils/formatPath'; import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler'; import handleErrorResponses from '../httpErrorResponseHandler';
import { translateToQueryParams } from './searchToQueryParams'; import { translateToQueryParams } from './searchToQueryParams';
import { ISortingRules } from 'component/project/Project/ProjectFeatureToggles/PaginatedProjectFeatureToggles';
type IFeatureSearchResponse = { type IFeatureSearchResponse = {
features: IFeatureToggleListItem[]; features: IFeatureToggleListItem[];
@ -62,6 +63,7 @@ const createFeatureSearch = () => {
return ( return (
offset: number, offset: number,
limit: number, limit: number,
sortingRules: ISortingRules,
projectId = '', projectId = '',
searchValue = '', searchValue = '',
options: SWRConfiguration = {}, options: SWRConfiguration = {},
@ -71,6 +73,7 @@ const createFeatureSearch = () => {
offset, offset,
limit, limit,
searchValue, searchValue,
sortingRules,
); );
useEffect(() => { useEffect(() => {
@ -113,9 +116,11 @@ const getFeatureSearchFetcher = (
offset: number, offset: number,
limit: number, limit: number,
searchValue: string, searchValue: string,
sortingRules: ISortingRules,
) => { ) => {
const searchQueryParams = translateToQueryParams(searchValue); 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 fetcher = () => {
const path = formatApiPath(KEY); const path = formatApiPath(KEY);
return fetch(path, { return fetch(path, {
@ -130,3 +135,9 @@ const getFeatureSearchFetcher = (
KEY, KEY,
}; };
}; };
const translateToSortQueryParams = (sortingRules: ISortingRules) => {
const { sortBy, sortOrder, isFavoritesPinned } = sortingRules;
const sortQueryParams = `sortBy=${sortBy}&sortOrder=${sortOrder}&favoritesFirst=${isFavoritesPinned}`;
return sortQueryParams;
};

View File

@ -713,7 +713,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
const [, envName] = sortBy.split(':'); const [, envName] = sortBy.split(':');
query = query query = query
.orderByRaw( .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], [envName],
) )
.orderBy('created_at', 'asc'); .orderBy('created_at', 'asc');
@ -733,6 +733,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
.select(selectColumns) .select(selectColumns)
.limit(limit * environmentCount) .limit(limit * environmentCount)
.offset(offset * environmentCount); .offset(offset * environmentCount);
const rows = await query; const rows = await query;
if (rows.length > 0) { if (rows.length > 0) {