Skip to content

Commit

Permalink
Use different query parameter managements between data-catalog page a…
Browse files Browse the repository at this point in the history
…nd new E&A page (#1021)

**Related Ticket:** #1016 

### Description of Changes

This PR makes Catalog View on the data-catalog page use QS-state for
query parameters, while Catalog View on the new E&A page uses Jotai. The
current Atoms related to URL management is not designed for cross-page
navigation. I drew a diagram to describe the navigation problem between
catalog page and overview page.

![Screenshot 2024-06-26 at 1 19
05 PM](https://github.com/NASA-IMPACT/veda-ui/assets/4583806/c11f216d-b3a9-4c01-b3b5-fa747d8999b1)

More context: 
We moved our query parameter management to use Jotai because of the
concerns about the mix-use of QS-State and Jotai for query parameters on
the same page. (Specifically - for the new E&A page, other parameters
are managed by Jotai). More details on this PR that initially introduced
data-catalog view to new E&A page:
#989 (comment)
In the end, we ditched the Query parameter manipulation thinking that we
don't need. (Details are in this comment:
#989 (comment))
But the bug reported #1016 made me realize that was a wrong call.


### Notes & Questions About Changes
- We need a better solution for long-term url management. 

### Validation / Testing
Navigate back and forth from data catalog to overview, from overview to
data catalog (especially through taxonomy pills on the overview page)
Navigate to the new E&A page through the 'Explore Data' button on the
overview page.


I opened a pr on ghg for testing:
US-GHG-Center/veda-config-ghg#421
  • Loading branch information
hanbyul-here authored Jun 28, 2024
2 parents 891d988 + 7a08578 commit 9b97013
Show file tree
Hide file tree
Showing 16 changed files with 263 additions and 345 deletions.
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ API_STAC_ENDPOINT='https://staging-stac.delta-backend.com'

# Google form for feedback
GOOGLE_FORM = 'https://docs.google.com/forms/d/e/1FAIpQLSfGcd3FDsM3kQIOVKjzdPn4f88hX8RZ4Qef7qBsTtDqxjTSkg/viewform?embedded=true'

FEATURE_NEW_EXPLORATION = 'TRUE'
6 changes: 6 additions & 0 deletions app/scripts/components/common/browse-controls/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const optionAll = {
id: 'all',
name: 'All'
};

export const minSearchLength = 3;
35 changes: 19 additions & 16 deletions app/scripts/components/common/browse-controls/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,21 @@ import {
import { glsp, truncated } from '@devseed-ui/theme-provider';
import { DropMenu, DropTitle } from '@devseed-ui/dropdown';

import {
Actions,
FilterOption,
optionAll,
useBrowserControls
} from './use-browse-controls';
import { useFiltersWithQS } from '../catalog/controls/hooks/use-filters-with-query';
import { optionAll } from './constants';
import { FilterActions } from '$components/common/catalog/utils';

import DropdownScrollable from '$components/common/dropdown-scrollable';
import DropMenuItemButton from '$styles/drop-menu-item-button';
import { variableGlsp } from '$styles/variable-utils';
import SearchField from '$components/common/search-field';
import { useMediaQuery } from '$utils/use-media-query';

export interface FilterOption {
id: string;
name: string;
}

const BrowseControlsWrapper = styled.div`
display: flex;
flex-flow: column;
Expand Down Expand Up @@ -67,7 +69,7 @@ const ButtonPrefix = styled(Overline).attrs({ as: 'small' })`
white-space: nowrap;
`;

interface BrowseControlsProps extends ReturnType<typeof useBrowserControls> {
interface BrowseControlsProps extends ReturnType<typeof useFiltersWithQS> {
taxonomiesOptions: Taxonomy[];
}

Expand All @@ -84,20 +86,21 @@ function BrowseControls(props: BrowseControlsProps) {
const filterWrapConstant = 4;
const wrapTaxonomies = taxonomiesOptions.length > filterWrapConstant; // wrap list of taxonomies when more then 4 filter options

const createFilterList = (filterList: Taxonomy[]) => (
filterList.map(({ name, values }) => (

const createFilterList = (filterList: Taxonomy[]) => {
return filterList.map(({ name, values }) => (
<DropdownOptions
key={name}
prefix={name}
items={[optionAll].concat(values)}
currentId={(taxonomies?.[name] as string) || 'all'}
currentId={(taxonomies[name]?.length ? taxonomies[name][0] as string : 'all')}
onChange={(v) => {
onAction(Actions.TAXONOMY, { key: name, value: v });
onAction(FilterActions.TAXONOMY, { key: name, value: v });
}}
size={isLargeUp ? 'large' : 'medium'}
/>
))
);
));
};

return (
<BrowseControlsWrapper {...rest}>
Expand All @@ -106,8 +109,8 @@ function BrowseControls(props: BrowseControlsProps) {
size={isLargeUp ? 'large' : 'medium'}
placeholder='Title, description...'
keepOpen={isLargeUp}
value={search ?? ''}
onChange={(v) => onAction(Actions.SEARCH, v)}
value={search}
onChange={(v) => onAction(FilterActions.SEARCH, v)}
/>
<FilterOptionsWrapper>
{createFilterList(taxonomiesOptions.slice(0, filterWrapConstant))}
Expand Down Expand Up @@ -140,7 +143,7 @@ function DropdownOptions(props: DropdownOptionsProps) {
const { size, items, currentId, onChange, prefix } = props;

const currentItem = items.find((d) => d.id === currentId);

return (
<DropdownScrollable
alignment='left'
Expand Down
134 changes: 2 additions & 132 deletions app/scripts/components/common/browse-controls/use-browse-controls.ts
Original file line number Diff line number Diff line change
@@ -1,137 +1,7 @@
import { useCallback } from 'react';
import { useNavigate } from 'react-router';
import useQsStateCreator from 'qs-state-hook';
import { set, omit } from 'lodash';

// @DEPRECATE it when ghg instance doesn't point this anymore
// https://github.com/US-GHG-Center/veda-config-ghg/blob/develop/overrides/home/keypoints.tsx#L11
export enum Actions {
CLEAR = 'clear',
SEARCH = 'search',
SORT_FIELD = 'sfield',
SORT_DIR = 'sdir',
TAXONOMY = 'taxonomy'
}

export type BrowserControlsAction = (action: Actions, value?: any) => void;

export interface FilterOption {
id: string;
name: string;
}

export interface TaxonomyFilterOption {
taxonomyType: string;
value: string;
exclusion?: string;
}

interface BrowseControlsHookParams {
sortOptions: FilterOption[];
}

export const sortDirOptions: FilterOption[] = [
{
id: 'asc',
name: 'Ascending'
},
{
id: 'desc',
name: 'Descending'
}
];

export const optionAll = {
id: 'all',
name: 'All'
};

export const minSearchLength = 3;

// This hook is only used for the Stories Hub to manage browsing controls
// such as search, sort, and taxonomy filters.
export function useBrowserControls({ sortOptions }: BrowseControlsHookParams) {
// Setup Qs State to store data in the url's query string
// react-router function to get the navigation.
const navigate = useNavigate();
const useQsState = useQsStateCreator({
commit: navigate
});

const [sortField, setSortField] = useQsState.memo(
{
key: Actions.SORT_FIELD,
// If pubDate exists, default sorting to this
default:
sortOptions.find((o) => o.id === 'pubDate')?.id || sortOptions[0]?.id,
validator: sortOptions.map((d) => d.id)
},
[sortOptions]
);

const [sortDir, setSortDir] = useQsState.memo(
{
key: Actions.SORT_DIR,
default: sortDirOptions[0].id,
validator: sortDirOptions.map((d) => d.id)
},
[]
);

const [search, setSearch] = useQsState.memo(
{
key: Actions.SEARCH,
default: ''
},
[]
);

const [taxonomies, setTaxonomies] = useQsState.memo<
Record<string, string | string[]>
>(
{
key: Actions.TAXONOMY,
default: {},
dehydrator: (v) => JSON.stringify(v), // dehydrator defines how a value is stored in the url
hydrator: (v) => (v ? JSON.parse(v) : {}) // hydrator defines how a value is read from the url
},
[]
);

const onAction = useCallback<BrowserControlsAction>(
(action, value) => {
switch (action) {
case Actions.CLEAR:
setSearch('');
setTaxonomies({});
break;
case Actions.SEARCH:
setSearch(value);
break;
case Actions.SORT_FIELD:
setSortField(value);
break;
case Actions.SORT_DIR:
setSortDir(value);
break;
case Actions.TAXONOMY:
{
const { key, value: val } = value;
if (val === optionAll.id) {
setTaxonomies(omit(taxonomies, key));
} else {
setTaxonomies(set({ ...taxonomies }, key, val));
}
}
break;
}
},
[setSortField, setSortDir, taxonomies, setTaxonomies, setSearch]
);

return {
search,
sortField,
sortDir,
taxonomies,
onAction
};
}
4 changes: 2 additions & 2 deletions app/scripts/components/common/card-sources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import styled from 'styled-components';
import { TaxonomyItem } from 'veda';
import { Link } from 'react-router-dom';
import { listReset } from '@devseed-ui/theme-provider';
import { Actions } from '$components/common/browse-controls/use-browse-controls';
import { FilterActions } from '$components/common//catalog/utils';

const SourcesUl = styled.ul`
${listReset()}
Expand Down Expand Up @@ -51,7 +51,7 @@ export function CardSourcesList(props: SourcesListProps) {
{sources.map((source) => (
<li key={source.id}>
<Link
to={`${rootPath}?${Actions.TAXONOMY}=${encodeURIComponent(
to={`${rootPath}?${FilterActions.TAXONOMY}=${encodeURIComponent(
JSON.stringify({
Source: source.id
})
Expand Down
24 changes: 9 additions & 15 deletions app/scripts/components/common/catalog/catalog-content.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import React, { useState, useMemo, useEffect, useCallback } from 'react';
import styled from 'styled-components';
import { DatasetData } from 'veda';
import { useNavigate } from 'react-router-dom';

import { glsp, themeVal } from '@devseed-ui/theme-provider';
import TextHighlight from '../text-highlight';
import { CollecticonDatasetLayers } from '../icons/dataset-layers';
import prepareDatasets from './prepare-datasets';
import { prepareDatasets } from './prepare-datasets';
import FiltersControl from './filters-control';
import { CatalogCard } from './catalog-card';
import CatalogTagsContainer from './catalog-tags';

import { CatalogActions } from './utils';
import { FilterActions } from './utils';
import { CardList } from '$components/common/card/styles';
import EmptyHub from '$components/common/empty-hub';
import { DATASETS_PATH } from '$utils/routes';

import {
getTaxonomyByIds,
generateTaxonomies,
Expand All @@ -35,7 +35,7 @@ export interface CatalogContentProps {
emptyStateContent?: React.ReactNode;
search: string;
taxonomies: Record<string, string[]>;
onAction: (action: CatalogActions, value?: any) => void;
onAction: (action: FilterActions, value?: any) => void;
}

const DEFAULT_SORT_OPTION = 'asc';
Expand All @@ -53,8 +53,6 @@ function CatalogContent({
const [exclusiveSourceSelected, setExclusiveSourceSelected] = useState<string | null>(null);
const isSelectable = selectedIds !== undefined;

const navigate = useNavigate();

const datasetTaxonomies = generateTaxonomies(datasets);
const urlTaxonomyItems = taxonomies ? Object.entries(taxonomies).map(([key, val]) => getTaxonomyByIds(key, val, datasetTaxonomies)).flat() : [];

Expand Down Expand Up @@ -84,7 +82,7 @@ function CatalogContent({
setSelectedFilters(selectedFilters.filter((selected) => selected.id !== item.id));
}

onAction(CatalogActions.TAXONOMY_MULTISELECT, { key: item.taxonomy, value: item.id });
onAction(FilterActions.TAXONOMY_MULTISELECT, { key: item.taxonomy, value: item.id });
}, [setSelectedFilters, selectedFilters, onAction]);

const handleClearTag = useCallback((item: OptionItem) => {
Expand All @@ -95,24 +93,20 @@ function CatalogContent({
const handleClearTags = useCallback(() => {
setSelectedFilters([]);
setExclusiveSourceSelected(null);
onAction(CatalogActions.CLEAR);
onAction(FilterActions.CLEAR_TAXONOMY);
}, [onAction]);

useEffect(() => {
if (clearedTagItem && (selectedFilters.length == prevSelectedFilters.length - 1)) {
onAction(CatalogActions.TAXONOMY_MULTISELECT, { key: clearedTagItem.taxonomy, value: clearedTagItem.id});
onAction(FilterActions.TAXONOMY_MULTISELECT, { key: clearedTagItem.taxonomy, value: clearedTagItem.id});
setClearedTagItem(undefined);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedFilters, clearedTagItem]);

useEffect(() => {
if (!selectedFilters.length) {
onAction(CatalogActions.CLEAR);

if (!isSelectable) {
navigate(DATASETS_PATH);
}
onAction(FilterActions.CLEAR_TAXONOMY);
}

setExclusiveSourceSelected(null);
Expand Down

This file was deleted.

Loading

0 comments on commit 9b97013

Please sign in to comment.