Skip to content

Commit

Permalink
✨ Add useLocalTableControlsWithUrlParams hook and update archetypes t…
Browse files Browse the repository at this point in the history
…able to use it (#1392)

When using the table-control hooks for a server-paginated table, you can
use either `useTableControlState` or `useTableControlUrlParams` to
provide state, then you take the returned object from that and use its
properties to fetch API data, then pass the object with additional
API-dependent properties to `useTableControlProps`.

For a client-paginated table, there is no need to split this into two
steps because the API request is not dependent on the table state
(there's no need to take pagination/sort/filter state and include it in
the API request), so the shorthand `useLocalTableControls` hook exists
which is simply `useTableControlProps(useLocalTableControlState(args))`
(`useLocalTableControlState` is similar to `useTableControlState` but
performs client-side logic with the `getLocal[Feature]DerivedState`
helpers in between calling each feature hook). However,
`useLocalTableControls` only allows the use of React state as the source
of truth rather than URL params. This PR adds an equivalent
`useLocalTableControlsWithUrlParams` shorthand hook that is a drop-in
replacement for `useLocalTableControls` which uses URL params instead of
React state.

The only additional argument available when switching to this new hook
is `urlParamKeyPrefix`, which is optional and is used to distinguish the
params for multiple tables on the same page. Even though the archetypes
table is the only table on its page, I used it to be consistent in case
we do add additional tables in modals or drawers, etc. like we did for
Issues / Affected apps.

With the archetypes table now using URL params for its filter state, we
should now easily be able to navigate to the archetypes table filtered
by tags (as discussed with @ibolton336) by using an approach similar to
`getAffectedAppsUrl` on the issues page ([usage
here](https://github.com/konveyor/tackle2-ui/blob/main/client/src/app/pages/issues/affected-apps-link.tsx#L22C1-L28),
[implementation
here](https://github.com/konveyor/tackle2-ui/blob/main/client/src/app/pages/issues/helpers.ts#L100)),
which uses the `trimAndStringifyUrlParams` and
`serializeFilterUrlParams` helpers. This implementation might look
something like:

```ts
export const getArchetypesUrlFilteredByTags = (tags: string[]) => {
  const baseUrl = Paths.archetypes;
  return `${baseUrl}?${trimAndStringifyUrlParams({
    newPrefixedSerializedParams: {
      [`${TableURLParamKeyPrefix.archetypes}:filters`]: serializeFilterUrlParams({ tags }).filters,
    },
  })}`;
};
```

Note (see the code comment in this diff): Because of the way we
implemented `useUrlParams`, there is no way to conditionally use URL
params or React state within the same hook based on an argument. This is
why all the separate `use[Feature]State` and `use[Feature]UrlParams`
hooks exist, and it also results in a moderate amount of duplication of
code in this PR because of the logic required to feed the values
returned from these feature hooks into each other. In the future we
should refactor `useUrlParams` into something like `useStateOrUrlParams`
to eliminate this issue and the duplication it causes (I also mentioned
this in the initial docs in #1355). That will come in a future PR.

Signed-off-by: Mike Turley <mike.turley@alum.cs.umass.edu>
Co-authored-by: Ian Bolton <ibolton@redhat.com>
  • Loading branch information
mturley and ibolton336 committed Sep 26, 2023
1 parent 15ec166 commit f628007
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 11 deletions.
1 change: 1 addition & 0 deletions client/src/app/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,4 +242,5 @@ export enum TableURLParamKeyPrefix {
issuesAffectedFiles = "if",
issuesRemainingIncidents = "ii",
dependencyApplications = "da",
archetypes = "ar",
}
92 changes: 87 additions & 5 deletions client/src/app/hooks/table-controls/useLocalTableControlState.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import { useSelectionState } from "@migtools/lib-ui";
import { getLocalFilterDerivedState, useFilterState } from "./filtering";
import { useSortState, getLocalSortDerivedState } from "./sorting";
import {
getLocalFilterDerivedState,
useFilterState,
useFilterUrlParams,
} from "./filtering";
import {
useSortState,
getLocalSortDerivedState,
useSortUrlParams,
} from "./sorting";
import {
getLocalPaginationDerivedState,
usePaginationState,
usePaginationUrlParams,
} from "./pagination";
import { useExpansionState } from "./expansion";
import { useActiveRowState } from "./active-row";
import { useExpansionState, useExpansionUrlParams } from "./expansion";
import { useActiveRowState, useActiveRowUrlParams } from "./active-row";
import {
IExtraArgsForURLParamHooks,
IUseLocalTableControlStateArgs,
IUseTableControlPropsArgs,
} from "./types";

export const useLocalTableControlState = <
TItem,
TColumnKey extends string,
TSortableColumnKey extends TColumnKey
TSortableColumnKey extends TColumnKey,
>(
args: IUseLocalTableControlStateArgs<TItem, TColumnKey, TSortableColumnKey>
): IUseTableControlPropsArgs<TItem, TColumnKey, TSortableColumnKey> => {
Expand Down Expand Up @@ -77,3 +87,75 @@ export const useLocalTableControlState = <
currentPageItems: hasPagination ? currentPageItems : sortedItems,
};
};

// TODO refactor useUrlParams so it can be used conditionally (e.g. useStateOrUrlParams) so we don't have to duplicate all this.
// this would mean all use[Feature]UrlParams hooks could be consolidated into use[Feature]State with a boolean option for whether to use URL params.

export const useLocalTableControlUrlParams = <
TItem,
TColumnKey extends string,
TSortableColumnKey extends TColumnKey,
TURLParamKeyPrefix extends string = string,
>(
args: IUseLocalTableControlStateArgs<TItem, TColumnKey, TSortableColumnKey> &
IExtraArgsForURLParamHooks<TURLParamKeyPrefix>
): IUseTableControlPropsArgs<TItem, TColumnKey, TSortableColumnKey> => {
const {
items,
filterCategories = [],
sortableColumns = [],
getSortValues,
initialSort = null,
hasPagination = true,
initialItemsPerPage = 10,
idProperty,
initialSelected,
isItemSelectable,
} = args;

const filterState = useFilterUrlParams(args);
const { filteredItems } = getLocalFilterDerivedState({
items,
filterCategories,
filterState,
});

const selectionState = useSelectionState({
items: filteredItems,
isEqual: (a, b) => a[idProperty] === b[idProperty],
initialSelected,
isItemSelectable,
});

const sortState = useSortUrlParams({ ...args, sortableColumns, initialSort });
const { sortedItems } = getLocalSortDerivedState({
sortState,
items: filteredItems,
getSortValues,
});

const paginationState = usePaginationUrlParams({
...args,
initialItemsPerPage,
});
const { currentPageItems } = getLocalPaginationDerivedState({
paginationState,
items: sortedItems,
});

const expansionState = useExpansionUrlParams<TColumnKey>(args);

const activeRowState = useActiveRowUrlParams(args);

return {
...args,
filterState,
expansionState,
selectionState,
sortState,
paginationState,
activeRowState,
totalItemCount: items.length,
currentPageItems: hasPagination ? currentPageItems : sortedItems,
};
};
22 changes: 19 additions & 3 deletions client/src/app/hooks/table-controls/useLocalTableControls.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import { useLocalTableControlState } from "./useLocalTableControlState";
import {
useLocalTableControlState,
useLocalTableControlUrlParams,
} from "./useLocalTableControlState";
import { useTableControlProps } from "./useTableControlProps";
import { IUseLocalTableControlStateArgs } from "./types";
import {
IExtraArgsForURLParamHooks,
IUseLocalTableControlStateArgs,
} from "./types";

export const useLocalTableControls = <
TItem,
TColumnKey extends string,
TSortableColumnKey extends TColumnKey
TSortableColumnKey extends TColumnKey,
>(
args: IUseLocalTableControlStateArgs<TItem, TColumnKey, TSortableColumnKey>
) => useTableControlProps(useLocalTableControlState(args));

export const useLocalTableControlsWithUrlParams = <
TItem,
TColumnKey extends string,
TSortableColumnKey extends TColumnKey,
TURLParamKeyPrefix extends string = string,
>(
args: IUseLocalTableControlStateArgs<TItem, TColumnKey, TSortableColumnKey> &
IExtraArgsForURLParamHooks<TURLParamKeyPrefix>
) => useTableControlProps(useLocalTableControlUrlParams(args));
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const useTableControlUrlParams = <
TColumnKey extends string,
TSortableColumnKey extends TColumnKey,
TFilterCategoryKey extends string = string,
TURLParamKeyPrefix extends string = string
TURLParamKeyPrefix extends string = string,
>(
args: ITableControlCommonArgs<
TItem,
Expand Down
6 changes: 4 additions & 2 deletions client/src/app/pages/archetypes/archetypes-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import {
TableHeaderContentWithControls,
TableRowContentWithControls,
} from "@app/components/TableControls";
import { useLocalTableControls } from "@app/hooks/table-controls";
import { useLocalTableControlsWithUrlParams } from "@app/hooks/table-controls";
import {
useDeleteArchetypeMutation,
useFetchArchetypes,
Expand All @@ -57,6 +57,7 @@ import { formatPath, getAxiosErrorMessage } from "@app/utils/utils";
import { AxiosError } from "axios";
import { Paths } from "@app/Paths";
import { SimplePagination } from "@app/components/SimplePagination";
import { TableURLParamKeyPrefix } from "@app/Constants";

const Archetypes: React.FC = () => {
const { t } = useTranslation();
Expand Down Expand Up @@ -96,7 +97,8 @@ const Archetypes: React.FC = () => {
onError
);

const tableControls = useLocalTableControls({
const tableControls = useLocalTableControlsWithUrlParams({
urlParamKeyPrefix: TableURLParamKeyPrefix.archetypes,
idProperty: "id",
items: archetypes,
isLoading: isFetching,
Expand Down

0 comments on commit f628007

Please sign in to comment.