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:
parent
db77962a72
commit
4e1040c849
@ -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 (
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
};
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user