Skip to content

Commit f389539

Browse files
rszwajkosjd78
andauthored
🐛 Support custom serializers in table hooks (konveyor#2079)
Changes: 1. add persistence provider to feature level persistence (IFeaturePersistenceArgs) but not to table layer persistence (ITablePersistenceArgs). This simplifies the provider code (feature-specific providers). 2. remove meta-persistence target "default" - no target has the same effect. 3. when in persistence provider mode use default values when the deserialized value is nulish. This solves the problem of initialFilterValues being overwritten in target cards scenario (the hook is called more then once and useState() based logic fails to detect initial load). Using the newly added feature, store the filters selected in the Target step (Analysis wizard) inside react-hook form in the same way as other values. Resolves: https://issues.redhat.com/browse/MTA-3438 Signed-off-by: Radoslaw Szwajkowski <[email protected]> Co-authored-by: Scott Dickerson <[email protected]>
1 parent 0d87a57 commit f389539

File tree

11 files changed

+150
-33
lines changed

11 files changed

+150
-33
lines changed

client/src/app/hooks/table-controls/active-item/useActiveItemState.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { parseMaybeNumericString } from "@app/utils/utils";
2-
import { IFeaturePersistenceArgs } from "../types";
2+
import { IFeaturePersistenceArgs, isPersistenceProvider } from "../types";
33
import { usePersistentState } from "@app/hooks/usePersistentState";
44

55
/**
@@ -76,7 +76,13 @@ export const useActiveItemState = <
7676
persistTo,
7777
key: "activeItem",
7878
}
79-
: { persistTo }),
79+
: isPersistenceProvider(persistTo)
80+
? {
81+
persistTo: "provider",
82+
serialize: persistTo.write,
83+
deserialize: () => persistTo.read() as string | number | null,
84+
}
85+
: { persistTo: "state" }),
8086
});
8187
return { activeItemId, setActiveItemId };
8288
};

client/src/app/hooks/table-controls/expansion/useExpansionState.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { usePersistentState } from "@app/hooks/usePersistentState";
22
import { objectKeys } from "@app/utils/utils";
3-
import { IFeaturePersistenceArgs } from "../types";
3+
import { IFeaturePersistenceArgs, isPersistenceProvider } from "../types";
44
import { DiscriminatedArgs } from "@app/utils/type-utils";
55

66
/**
@@ -93,7 +93,9 @@ export const useExpansionState = <
9393
? {
9494
persistTo,
9595
keys: ["expandedCells"],
96-
serialize: (expandedCellsObj) => {
96+
serialize: (
97+
expandedCellsObj: Partial<TExpandedCells<TColumnKey>>
98+
) => {
9799
if (!expandedCellsObj || objectKeys(expandedCellsObj).length === 0)
98100
return { expandedCells: null };
99101
return { expandedCells: JSON.stringify(expandedCellsObj) };
@@ -111,7 +113,13 @@ export const useExpansionState = <
111113
persistTo,
112114
key: "expandedCells",
113115
}
114-
: { persistTo }),
116+
: isPersistenceProvider(persistTo)
117+
? {
118+
persistTo: "provider",
119+
serialize: persistTo.write,
120+
deserialize: () => persistTo.read() as TExpandedCells<TColumnKey>,
121+
}
122+
: { persistTo: "state" }),
115123
});
116124
return { expandedCells, setExpandedCells };
117125
};

client/src/app/hooks/table-controls/filtering/useFilterState.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { FilterCategory, IFilterValues } from "@app/components/FilterToolbar";
2-
import { IFeaturePersistenceArgs } from "../types";
2+
import { IFeaturePersistenceArgs, isPersistenceProvider } from "../types";
33
import { usePersistentState } from "@app/hooks/usePersistentState";
44
import { serializeFilterUrlParams } from "./helpers";
55
import { deserializeFilterUrlParams } from "./helpers";
@@ -90,7 +90,6 @@ export const useFilterState = <
9090
"filters"
9191
>({
9292
isEnabled: !!isFilterEnabled,
93-
defaultValue: initialFilterValues,
9493
persistenceKeyPrefix,
9594
// Note: For the discriminated union here to work without TypeScript getting confused
9695
// (e.g. require the urlParams-specific options when persistTo === "urlParams"),
@@ -99,12 +98,21 @@ export const useFilterState = <
9998
? {
10099
persistTo,
101100
keys: ["filters"],
101+
defaultValue: initialFilterValues,
102102
serialize: serializeFilterUrlParams,
103103
deserialize: deserializeFilterUrlParams,
104104
}
105105
: persistTo === "localStorage" || persistTo === "sessionStorage"
106-
? { persistTo, key: "filters" }
107-
: { persistTo }),
106+
? { persistTo, key: "filters", defaultValue: initialFilterValues }
107+
: isPersistenceProvider(persistTo)
108+
? {
109+
persistTo: "provider",
110+
serialize: persistTo.write,
111+
deserialize: () =>
112+
persistTo.read() as IFilterValues<TFilterCategoryKey>,
113+
defaultValue: isFilterEnabled ? args?.initialFilterValues ?? {} : {},
114+
}
115+
: { persistTo: "state", defaultValue: initialFilterValues }),
108116
});
109117
return { filterValues, setFilterValues };
110118
};

client/src/app/hooks/table-controls/pagination/usePaginationState.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { usePersistentState } from "@app/hooks/usePersistentState";
2-
import { IFeaturePersistenceArgs } from "../types";
2+
import { IFeaturePersistenceArgs, isPersistenceProvider } from "../types";
33
import { DiscriminatedArgs } from "@app/utils/type-utils";
44

55
/**
@@ -94,7 +94,7 @@ export const usePaginationState = <
9494
? {
9595
persistTo,
9696
keys: ["pageNumber", "itemsPerPage"],
97-
serialize: (state) => {
97+
serialize: (state: Partial<IActivePagination>) => {
9898
const { pageNumber, itemsPerPage } = state || {};
9999
return {
100100
pageNumber: pageNumber ? String(pageNumber) : undefined,
@@ -116,7 +116,13 @@ export const usePaginationState = <
116116
persistTo,
117117
key: "pagination",
118118
}
119-
: { persistTo }),
119+
: isPersistenceProvider(persistTo)
120+
? {
121+
persistTo: "provider",
122+
serialize: persistTo.write,
123+
deserialize: () => persistTo.read() as IActivePagination,
124+
}
125+
: { persistTo: "state" }),
120126
});
121127
const { pageNumber, itemsPerPage } = paginationState || defaultValue;
122128
const setPageNumber = (num: number) =>

client/src/app/hooks/table-controls/sorting/useSortState.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DiscriminatedArgs } from "@app/utils/type-utils";
2-
import { IFeaturePersistenceArgs } from "..";
2+
import { IFeaturePersistenceArgs, isPersistenceProvider } from "..";
33
import { usePersistentState } from "@app/hooks/usePersistentState";
44

55
/**
@@ -96,7 +96,9 @@ export const useSortState = <
9696
? {
9797
persistTo,
9898
keys: ["sortColumn", "sortDirection"],
99-
serialize: (activeSort) => ({
99+
serialize: (
100+
activeSort: Partial<IActiveSort<TSortableColumnKey> | null>
101+
) => ({
100102
sortColumn: activeSort?.columnKey || null,
101103
sortDirection: activeSort?.direction || null,
102104
}),
@@ -113,7 +115,14 @@ export const useSortState = <
113115
persistTo,
114116
key: "sort",
115117
}
116-
: { persistTo }),
118+
: isPersistenceProvider(persistTo)
119+
? {
120+
persistTo: "provider",
121+
serialize: persistTo.write,
122+
deserialize: () =>
123+
persistTo.read() as IActiveSort<TSortableColumnKey> | null,
124+
}
125+
: { persistTo: "state" }),
117126
});
118127
return { activeSort, setActiveSort };
119128
};

client/src/app/hooks/table-controls/types.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@ export type TableFeature =
6464
| "activeItem"
6565
| "columns";
6666

67+
export interface PersistenceProvider<T> {
68+
write: (value: T) => void;
69+
read: () => T;
70+
}
71+
72+
export const isPersistenceProvider = (
73+
persistTo?: PersistTarget | PersistenceProvider<unknown>
74+
): persistTo is PersistenceProvider<unknown> =>
75+
!!(persistTo as PersistenceProvider<unknown>)?.write &&
76+
!!(persistTo as PersistenceProvider<unknown>)?.read;
77+
6778
/**
6879
* Identifier for where to persist state for a single table feature or for all table features.
6980
* - "state" (default) - Plain React state. Resets on component unmount or page reload.
@@ -106,7 +117,7 @@ export type IFeaturePersistenceArgs<
106117
/**
107118
* Where to persist state for this feature.
108119
*/
109-
persistTo?: PersistTarget;
120+
persistTo?: PersistTarget | PersistenceProvider<unknown>;
110121
};
111122

112123
export interface ColumnSetting {
@@ -131,7 +142,9 @@ export type ITablePersistenceArgs<
131142
*/
132143
persistTo?:
133144
| PersistTarget
134-
| Partial<Record<TableFeature | "default", PersistTarget>>;
145+
| Partial<
146+
Record<TableFeature, PersistTarget | PersistenceProvider<unknown>>
147+
>;
135148
};
136149

137150
/**

client/src/app/hooks/table-controls/useTableControlState.ts

+25-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {
2+
IFeaturePersistenceArgs,
23
ITableControlState,
4+
ITablePersistenceArgs,
35
IUseTableControlStateArgs,
4-
PersistTarget,
56
TableFeature,
67
} from "./types";
78
import { useFilterState } from "./filtering";
@@ -11,6 +12,21 @@ import { useActiveItemState } from "./active-item";
1112
import { useExpansionState } from "./expansion";
1213
import { useColumnState } from "./column/useColumnState";
1314

15+
const getPersistTo = ({
16+
feature,
17+
persistTo,
18+
}: {
19+
feature: TableFeature;
20+
persistTo: ITablePersistenceArgs["persistTo"];
21+
}): {
22+
persistTo: IFeaturePersistenceArgs["persistTo"];
23+
} => ({
24+
persistTo:
25+
!persistTo || typeof persistTo === "string"
26+
? persistTo
27+
: persistTo[feature],
28+
});
29+
1430
/**
1531
* Provides the "source of truth" state for all table features.
1632
* - State can be persisted in one or more configurable storage targets, either the same for the entire table or different targets per feature.
@@ -41,31 +57,29 @@ export const useTableControlState = <
4157
TFilterCategoryKey,
4258
TPersistenceKeyPrefix
4359
> => {
44-
const getPersistTo = (feature: TableFeature): PersistTarget | undefined =>
45-
!args.persistTo || typeof args.persistTo === "string"
46-
? args.persistTo
47-
: args.persistTo[feature] || args.persistTo.default;
48-
4960
const filterState = useFilterState<
5061
TItem,
5162
TFilterCategoryKey,
5263
TPersistenceKeyPrefix
53-
>({ ...args, persistTo: getPersistTo("filter") });
64+
>({
65+
...args,
66+
...getPersistTo({ feature: "filter", persistTo: args.persistTo }),
67+
});
5468
const sortState = useSortState<TSortableColumnKey, TPersistenceKeyPrefix>({
5569
...args,
56-
persistTo: getPersistTo("sort"),
70+
...getPersistTo({ feature: "sort", persistTo: args.persistTo }),
5771
});
5872
const paginationState = usePaginationState<TPersistenceKeyPrefix>({
5973
...args,
60-
persistTo: getPersistTo("pagination"),
74+
...getPersistTo({ persistTo: args.persistTo, feature: "pagination" }),
6175
});
6276
const expansionState = useExpansionState<TColumnKey, TPersistenceKeyPrefix>({
6377
...args,
64-
persistTo: getPersistTo("expansion"),
78+
...getPersistTo({ persistTo: args.persistTo, feature: "expansion" }),
6579
});
6680
const activeItemState = useActiveItemState<TPersistenceKeyPrefix>({
6781
...args,
68-
persistTo: getPersistTo("activeItem"),
82+
...getPersistTo({ persistTo: args.persistTo, feature: "activeItem" }),
6983
});
7084

7185
const { columnNames, tableName, initialColumns } = args;

client/src/app/hooks/usePersistentState.ts

+40-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@ import { DisallowCharacters } from "@app/utils/type-utils";
99

1010
type PersistToStateOptions = { persistTo?: "state" };
1111

12-
type PersistToUrlParamsOptions<
12+
type PersistToProvider<TValue> = {
13+
persistTo: "provider";
14+
defaultValue: TValue;
15+
isEnabled?: boolean;
16+
serialize: (params: TValue) => void;
17+
deserialize: () => TValue;
18+
};
19+
20+
export type PersistToUrlParamsOptions<
1321
TValue,
1422
TPersistenceKeyPrefix extends string,
1523
TURLParamKey extends string,
@@ -33,6 +41,7 @@ export type UsePersistentStateOptions<
3341
| PersistToStateOptions
3442
| PersistToUrlParamsOptions<TValue, TPersistenceKeyPrefix, TURLParamKey>
3543
| PersistToStorageOptions<TValue>
44+
| PersistToProvider<TValue>
3645
);
3746

3847
export const usePersistentState = <
@@ -92,7 +101,37 @@ export const usePersistentState = <
92101
? { ...options, key: prefixKey(options.key) }
93102
: { ...options, isEnabled: false, key: "" }
94103
),
104+
provider: usePersistenceProvider<TValue>(
105+
isPersistenceProviderOptions(options)
106+
? options
107+
: {
108+
serialize: () => {},
109+
deserialize: () => defaultValue,
110+
defaultValue,
111+
isEnabled: false,
112+
persistTo: "provider",
113+
}
114+
),
95115
};
96116
const [value, setValue] = persistence[persistTo || "state"];
97117
return isEnabled ? [value, setValue] : [defaultValue, () => {}];
98118
};
119+
120+
const usePersistenceProvider = <TValue>({
121+
serialize,
122+
deserialize,
123+
defaultValue,
124+
}: PersistToProvider<TValue>): [TValue, (val: TValue) => void] => {
125+
// use default value if nulish value was deserialized
126+
return [deserialize() ?? defaultValue, serialize];
127+
};
128+
129+
export const isPersistenceProviderOptions = <
130+
TValue,
131+
TPersistenceKeyPrefix extends string,
132+
TURLParamKey extends string,
133+
>(
134+
o: Partial<
135+
UsePersistentStateOptions<TValue, TPersistenceKeyPrefix, TURLParamKey>
136+
>
137+
): o is PersistToProvider<TValue> => o.persistTo === "provider";

client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ export const AnalysisWizard: React.FC<IAnalysisWizard> = ({
166166
mode: "source-code-deps",
167167
formLabels: [],
168168
selectedTargets: [],
169+
// defaults will be passed as initialFilterValues to the table hook
170+
targetFilters: undefined,
169171
selectedSourceLabels: [],
170172
withKnownLibs: "app",
171173
includedPackages: [],

client/src/app/pages/applications/analysis-wizard/schema.ts

+2
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,14 @@ const useModeStepSchema = ({
5757
export interface TargetsStepValues {
5858
formLabels: TargetLabel[];
5959
selectedTargets: Target[];
60+
targetFilters?: Record<string, string[]>;
6061
}
6162

6263
const useTargetsStepSchema = (): yup.SchemaOf<TargetsStepValues> => {
6364
return yup.object({
6465
formLabels: yup.array(),
6566
selectedTargets: yup.array(),
67+
targetFilters: yup.object(),
6668
});
6769
};
6870

0 commit comments

Comments
 (0)