Skip to content

Commit

Permalink
Respect queryParameter updates in pickers
Browse files Browse the repository at this point in the history
Signed-off-by: Tim Hansen <timbonicus@gmail.com>
  • Loading branch information
timbonicus committed Feb 4, 2022
1 parent d5b6653 commit 680e7c7
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 37 deletions.
5 changes: 5 additions & 0 deletions .changeset/seven-apes-shave.md
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-react': patch
---

Updated `useEntityListProvider` and catalog pickers to respond to external changes to query parameters in the URL, such as two sidebar links that apply different catalog filters.
Expand Up @@ -51,15 +51,19 @@ export const EntityLifecyclePicker = () => {
const { updateFilters, backendEntities, filters, queryParameters } =
useEntityListProvider();

const queryParamLifecycles = [queryParameters.lifecycles]
.flat()
.filter(Boolean) as string[];
const [selectedLifecycles, setSelectedLifecycles] = useState(
queryParamLifecycles.length
? queryParamLifecycles
: filters.lifecycles?.values ?? [],
filters.lifecycles?.values ?? [],
);

// Set selected lifecycles on query parameter updates; this happens at initial page load and from
// external updates to the page location.
useEffect(() => {
const queryParamLifecycles = [queryParameters.lifecycles]
.flat()
.filter(Boolean) as string[];
setSelectedLifecycles(queryParamLifecycles);
}, [queryParameters]);

useEffect(() => {
updateFilters({
lifecycles: selectedLifecycles.length
Expand Down
Expand Up @@ -53,13 +53,19 @@ export const EntityOwnerPicker = () => {
const { updateFilters, backendEntities, filters, queryParameters } =
useEntityListProvider();

const queryParamOwners = [queryParameters.owners]
.flat()
.filter(Boolean) as string[];
const [selectedOwners, setSelectedOwners] = useState(
queryParamOwners.length ? queryParamOwners : filters.owners?.values ?? [],
filters.owners?.values ?? [],
);

// Set selected owners on query parameter updates; this happens at initial page load and from
// external updates to the page location.
useEffect(() => {
const queryParamOwners = [queryParameters.owners]
.flat()
.filter(Boolean) as string[];
setSelectedOwners(queryParamOwners);
}, [queryParameters]);

useEffect(() => {
updateFilters({
owners: selectedOwners.length
Expand Down
Expand Up @@ -51,12 +51,16 @@ export const EntityTagPicker = () => {
const { updateFilters, backendEntities, filters, queryParameters } =
useEntityListProvider();

const queryParamTags = [queryParameters.tags]
.flat()
.filter(Boolean) as string[];
const [selectedTags, setSelectedTags] = useState(
queryParamTags.length ? queryParamTags : filters.tags?.values ?? [],
);
const [selectedTags, setSelectedTags] = useState(filters.tags?.values ?? []);

// Set selected tags on query parameter updates; this happens at initial page load and from
// external updates to the page location.
useEffect(() => {
const queryParamTags = [queryParameters.tags]
.flat()
.filter(Boolean) as string[];
setSelectedTags(queryParamTags);
}, [queryParameters]);

useEffect(() => {
updateFilters({
Expand Down
Expand Up @@ -161,9 +161,7 @@ export const UserListPicker = ({
[isOwnedEntity, isStarredEntity],
);

const [selectedUserFilter, setSelectedUserFilter] = useState(
[queryParameters.user].flat()[0] ?? initialFilter,
);
const [selectedUserFilter, setSelectedUserFilter] = useState(initialFilter);

// To show proper counts for each section, apply all other frontend filters _except_ the user
// filter that's controlled by this picker.
Expand All @@ -190,6 +188,13 @@ export const UserListPicker = ({
[entitiesWithoutUserFilter, starredFilter, ownedFilter],
);

// Set selected user filter on query parameter updates; this happens at initial page load and from
// external updates to the page location.
useEffect(() => {
const queryParamUserFilter = [queryParameters.user].flat()[0];
setSelectedUserFilter(queryParamUserFilter as UserListFilterKind);
}, [queryParameters]);

useEffect(() => {
if (
!loading &&
Expand Down
33 changes: 19 additions & 14 deletions plugins/catalog-react/src/hooks/useEntityListProvider.tsx
Expand Up @@ -25,6 +25,7 @@ import React, {
useMemo,
useState,
} from 'react';
import { useLocation } from 'react-router';
import useAsyncFn from 'react-use/lib/useAsyncFn';
import useDebounce from 'react-use/lib/useDebounce';
import useMountedState from 'react-use/lib/useMountedState';
Expand Down Expand Up @@ -98,7 +99,6 @@ type OutputState<EntityFilters extends DefaultEntityFilters> = {
appliedFilters: EntityFilters;
entities: Entity[];
backendEntities: Entity[];
queryParameters: Record<string, string | string[]>;
};

export const EntityListProvider = <EntityFilters extends DefaultEntityFilters>({
Expand All @@ -109,19 +109,26 @@ export const EntityListProvider = <EntityFilters extends DefaultEntityFilters>({
const [requestedFilters, setRequestedFilters] = useState<EntityFilters>(
{} as EntityFilters,
);

// We use react-router's useLocation hook so updates from external sources trigger an update to
// the queryParameters in outputState. Updates from this hook use replaceState below and won't
// trigger a useLocation change; this would instead come from an external source, such as a manual
// update of the URL or two catalog sidebar links with different catalog filters.
const location = useLocation();
const queryParameters = useMemo(
() =>
(qs.parse(location.search, {
ignoreQueryPrefix: true,
}).filters ?? {}) as Record<string, string | string[]>,
[location],
);

const [outputState, setOutputState] = useState<OutputState<EntityFilters>>(
() => {
const query = qs.parse(window.location.search, {
ignoreQueryPrefix: true,
});
return {
appliedFilters: {} as EntityFilters,
entities: [],
backendEntities: [],
queryParameters: (query.filters ?? {}) as Record<
string,
string | string[]
>,
};
},
);
Expand Down Expand Up @@ -163,19 +170,17 @@ export const EntityListProvider = <EntityFilters extends DefaultEntityFilters>({
appliedFilters: requestedFilters,
backendEntities: response.items,
entities: response.items.filter(entityFilter),
queryParameters: queryParams,
});
} else {
setOutputState({
appliedFilters: requestedFilters,
backendEntities: outputState.backendEntities,
entities: outputState.backendEntities.filter(entityFilter),
queryParameters: queryParams,
});
}

if (isMounted()) {
const oldParams = qs.parse(window.location.search, {
const oldParams = qs.parse(location.search, {
ignoreQueryPrefix: true,
});
const newParams = qs.stringify(
Expand All @@ -191,7 +196,7 @@ export const EntityListProvider = <EntityFilters extends DefaultEntityFilters>({
window.history?.replaceState(null, document.title, newUrl);
}
},
[catalogApi, requestedFilters, outputState],
[catalogApi, queryParameters, requestedFilters, outputState],
{ loading: true },
);

Expand Down Expand Up @@ -220,11 +225,11 @@ export const EntityListProvider = <EntityFilters extends DefaultEntityFilters>({
entities: outputState.entities,
backendEntities: outputState.backendEntities,
updateFilters,
queryParameters: outputState.queryParameters,
queryParameters,
loading,
error,
}),
[outputState, updateFilters, loading, error],
[outputState, updateFilters, queryParameters, loading, error],
);

return (
Expand Down
14 changes: 10 additions & 4 deletions plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx
Expand Up @@ -42,13 +42,19 @@ export function useEntityTypeFilter(): EntityTypeReturn {
updateFilters,
} = useEntityListProvider();

const queryParamTypes = [queryParameters.type]
.flat()
.filter(Boolean) as string[];
const [selectedTypes, setSelectedTypes] = useState(
queryParamTypes.length ? queryParamTypes : typeFilter?.getTypes() ?? [],
typeFilter?.getTypes() ?? [],
);

// Set selected types on query parameter updates; this happens at initial page load and from
// external updates to the page location.
useEffect(() => {
const queryParamTypes = [queryParameters.type]
.flat()
.filter(Boolean) as string[];
setSelectedTypes(queryParamTypes);
}, [queryParameters]);

const [availableTypes, setAvailableTypes] = useState<string[]>([]);
const kind = useMemo(() => kindFilter?.value, [kindFilter]);

Expand Down

0 comments on commit 680e7c7

Please sign in to comment.