Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 37 additions & 7 deletions frontend/src/libs/filters.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,54 @@
import type { PropertyFilterProps } from 'components';

export const tokensToRequestParams = <RequestParamsKeys extends string>(
export const tokensToSearchParams = <RequestParamsKeys extends string>(
tokens: PropertyFilterProps.Query['tokens'],
onlyActive?: boolean,
) => {
const params: Record<RequestParamsKeys | 'only_active', string> = tokens.reduce((acc, token) => {
const params = new URLSearchParams();

tokens.forEach((token) => {
if (token.propertyKey) {
acc[token.propertyKey as RequestParamsKeys] = token.value;
params.append(token.propertyKey as RequestParamsKeys, token.value);
}

return acc;
}, {} as Record<RequestParamsKeys | 'only_active', string>);
});

if (onlyActive) {
params['only_active'] = 'true';
params.append('only_active', 'true');
}

return params;
};

export const tokensToRequestParams = <RequestParamsKeys extends string>({
tokens,
arrayFieldKeys,
}: {
tokens: PropertyFilterProps.Query['tokens'];
arrayFieldKeys?: RequestParamsKeys[];
}) => {
return tokens.reduce<Record<RequestParamsKeys, string | string[]>>((acc, token) => {
const propertyKey = token.propertyKey as RequestParamsKeys;

if (!propertyKey) {
return acc;
}

if (arrayFieldKeys?.includes(propertyKey)) {
if (Array.isArray(acc[propertyKey])) {
acc[propertyKey].push(token.value);
} else {
acc[propertyKey] = [token.value];
}

return acc;
}

acc[propertyKey] = token.value;

return acc;
}, {} as Record<RequestParamsKeys, string>);
};

export const EMPTY_QUERY: PropertyFilterProps.Query = {
tokens: [],
operation: 'and',
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/Fleets/Details/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export const FleetDetails: React.FC = () => {
<Box variant="awsui-key-label">{t('fleets.instances.title')}</Box>

<div>
<NavigateLink href={ROUTES.INSTANCES.LIST + `?fleetId=${data.id}`}>
<NavigateLink href={ROUTES.INSTANCES.LIST + `?fleet_ids=${data.id}`}>
{getFleetInstancesLinkText(data)}
</NavigateLink>
</div>
Expand Down
14 changes: 8 additions & 6 deletions frontend/src/pages/Fleets/List/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Button, ListEmptyMessage, NavigateLink, StatusIndicator, TableProps } f

import { DATE_TIME_FORMAT } from 'consts';
import { useProjectFilter } from 'hooks/useProjectFilter';
import { EMPTY_QUERY, requestParamsToTokens, tokensToRequestParams } from 'libs/filters';
import { EMPTY_QUERY, requestParamsToTokens, tokensToRequestParams, tokensToSearchParams } from 'libs/filters';
import { getFleetInstancesLinkText, getFleetPrice, getFleetStatusIconType } from 'libs/fleet';
import { ROUTES } from 'routes';

Expand Down Expand Up @@ -76,7 +76,7 @@ export const useColumnsDefinitions = () => {
id: 'instances',
header: t('fleets.instances.title'),
cell: (item) => (
<NavigateLink href={ROUTES.INSTANCES.LIST + `?fleetId=${item.id}`}>
<NavigateLink href={ROUTES.INSTANCES.LIST + `?fleet_ids=${item.id}`}>
{getFleetInstancesLinkText(item)}
</NavigateLink>
),
Expand Down Expand Up @@ -153,7 +153,7 @@ export const useFilters = (localStorePrefix = 'fleet-list-page') => {
return !tokens.some((item, index) => token.propertyKey === item.propertyKey && index > tokenIndex);
});

setSearchParams(tokensToRequestParams<RequestParamsKeys>(filteredTokens, onlyActive));
setSearchParams(tokensToSearchParams<RequestParamsKeys>(filteredTokens, onlyActive));

setPropertyFilterQuery({
operation,
Expand All @@ -164,16 +164,18 @@ export const useFilters = (localStorePrefix = 'fleet-list-page') => {
const onChangeOnlyActive: ToggleProps['onChange'] = ({ detail }) => {
setOnlyActive(detail.checked);

setSearchParams(tokensToRequestParams<RequestParamsKeys>(propertyFilterQuery.tokens, detail.checked));
setSearchParams(tokensToSearchParams<RequestParamsKeys>(propertyFilterQuery.tokens, detail.checked));
};

const filteringRequestParams = useMemo(() => {
const params = tokensToRequestParams<RequestParamsKeys>(propertyFilterQuery.tokens);
const params = tokensToRequestParams<RequestParamsKeys>({
tokens: propertyFilterQuery.tokens,
});

return {
...params,
only_active: onlyActive,
};
} as Partial<TFleetListRequestParams>;
}, [propertyFilterQuery, onlyActive]);

const isDisabledClearFilter = !propertyFilterQuery.tokens.length && !onlyActive;
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/pages/Instances/List/hooks/useEmptyMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { useTranslation } from 'react-i18next';
import { Button, ListEmptyMessage } from 'components';

export const useEmptyMessages = ({
clearFilters,
clearFilter,
isDisabledClearFilter,
}: {
clearFilters?: () => void;
clearFilter?: () => void;
isDisabledClearFilter?: boolean;
}) => {
const { t } = useTranslation();
Expand All @@ -18,25 +18,25 @@ export const useEmptyMessages = ({
title={t('fleets.instances.empty_message_title')}
message={t('fleets.instances.empty_message_text')}
>
<Button disabled={isDisabledClearFilter} onClick={clearFilters}>
<Button disabled={isDisabledClearFilter} onClick={clearFilter}>
{t('common.clearFilter')}
</Button>
</ListEmptyMessage>
);
}, [clearFilters, isDisabledClearFilter]);
}, [clearFilter, isDisabledClearFilter]);

const renderNoMatchMessage = useCallback<() => React.ReactNode>(() => {
return (
<ListEmptyMessage
title={t('fleets.instances.nomatch_message_title')}
message={t('fleets.instances.nomatch_message_text')}
>
<Button disabled={isDisabledClearFilter} onClick={clearFilters}>
<Button disabled={isDisabledClearFilter} onClick={clearFilter}>
{t('common.clearFilter')}
</Button>
</ListEmptyMessage>
);
}, [clearFilters, isDisabledClearFilter]);
}, [clearFilter, isDisabledClearFilter]);

return { renderEmptyMessage, renderNoMatchMessage } as const;
};
106 changes: 83 additions & 23 deletions frontend/src/pages/Instances/List/hooks/useFilters.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,104 @@
import { useMemo } from 'react';
import { useMemo, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { ToggleProps } from '@cloudscape-design/components';

import type { PropertyFilterProps } from 'components';

import { useLocalStorageState } from 'hooks/useLocalStorageState';
import { useProjectFilter } from 'hooks/useProjectFilter';
import { EMPTY_QUERY, requestParamsToTokens, tokensToRequestParams, tokensToSearchParams } from 'libs/filters';

type RequestParamsKeys = keyof Pick<TInstanceListRequestParams, 'only_active' | 'project_names' | 'fleet_ids'>;

const filterKeys: Record<string, RequestParamsKeys> = {
PROJECT_NAMES: 'project_names',
FLEET_IDS: 'fleet_ids',
};

export const useFilters = (localStorePrefix = 'instances-list-page') => {
const [searchParams, setSearchParams] = useSearchParams();
const [onlyActive, setOnlyActive] = useLocalStorageState<boolean>(`${localStorePrefix}-is-active`, false);
const { selectedProject, setSelectedProject, projectOptions } = useProjectFilter({ localStorePrefix });
const [onlyActive, setOnlyActive] = useState(() => searchParams.get('only_active') === 'true');
const { projectOptions } = useProjectFilter({ localStorePrefix });

const [propertyFilterQuery, setPropertyFilterQuery] = useState<PropertyFilterProps.Query>(() => {
console.log(requestParamsToTokens<RequestParamsKeys>({ searchParams, filterKeys }));

const clearFilters = () => {
return requestParamsToTokens<RequestParamsKeys>({ searchParams, filterKeys });
});

const clearFilter = () => {
setSearchParams({});
setOnlyActive(false);
setSelectedProject(null);
setPropertyFilterQuery(EMPTY_QUERY);
};

setSearchParams((prev) => {
prev.delete('fleetId');
return prev;
const filteringOptions = useMemo(() => {
const options: PropertyFilterProps.FilteringOption[] = [];

projectOptions.forEach(({ value }) => {
if (value)
options.push({
propertyKey: filterKeys.PROJECT_NAMES,
value,
});
});

return options;
}, [projectOptions]);

const filteringProperties = [
{
key: filterKeys.PROJECT_NAMES,
operators: ['='],
propertyLabel: 'Project',
groupValuesLabel: 'Project values',
},
{
key: filterKeys.FLEET_IDS,
operators: ['='],
propertyLabel: 'Fleet ID',
},
];

const onChangePropertyFilter: PropertyFilterProps['onChange'] = ({ detail }) => {
const { tokens, operation } = detail;

setSearchParams(tokensToSearchParams<RequestParamsKeys>(tokens, onlyActive));

setPropertyFilterQuery({
operation,
tokens,
});
};

const selectedFleet = useMemo(() => {
const fleetName = searchParams.get('fleetId');
const onChangeOnlyActive: ToggleProps['onChange'] = ({ detail }) => {
setOnlyActive(detail.checked);

setSearchParams(tokensToSearchParams<RequestParamsKeys>(propertyFilterQuery.tokens, detail.checked));
};

if (fleetName) {
return { label: fleetName, value: fleetName };
}
const filteringRequestParams = useMemo(() => {
const params = tokensToRequestParams<RequestParamsKeys>({
tokens: propertyFilterQuery.tokens,
arrayFieldKeys: [filterKeys.PROJECT_NAMES, filterKeys.FLEET_IDS],
});

return null;
}, [searchParams]);
return {
...params,
only_active: onlyActive,
} as Partial<TInstanceListRequestParams>;
}, [propertyFilterQuery, onlyActive]);

const isDisabledClearFilter = !selectedProject && !onlyActive && !selectedFleet;
const isDisabledClearFilter = !propertyFilterQuery.tokens.length && !onlyActive;

return {
projectOptions,
selectedProject,
setSelectedProject,
selectedFleet,
filteringRequestParams,
clearFilter,
propertyFilterQuery,
onChangePropertyFilter,
filteringOptions,
filteringProperties,
onlyActive,
setOnlyActive,
clearFilters,
onChangeOnlyActive,
isDisabledClearFilter,
} as const;
};
Loading
Loading