From 9e272587d3a580d3def8d11639a52d972a9c6ae3 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 23 Oct 2024 17:51:13 -0400 Subject: [PATCH] feat: add dataset card + show datasets in project overview --- src/js/components/BentoAppRouter.tsx | 7 +- src/js/components/Overview/Chart.tsx | 4 +- src/js/components/Overview/ChartCard.tsx | 21 ++---- src/js/components/Overview/PublicOverview.tsx | 22 +++++- src/js/components/Provenance/Dataset.tsx | 67 +++++++++++++++++++ .../components/Provenance/ProvenanceTab.tsx | 4 +- .../components/Scope/DatasetScopePicker.tsx | 30 +++------ .../components/Scope/ProjectScopePicker.tsx | 7 +- src/js/components/SiteHeader.tsx | 16 ++--- .../components/Util/SmallChartCardTitle.tsx | 32 +++++++++ src/js/features/metadata/hooks.ts | 19 ++++++ src/js/features/metadata/utils.ts | 0 12 files changed, 172 insertions(+), 57 deletions(-) create mode 100644 src/js/components/Provenance/Dataset.tsx create mode 100644 src/js/components/Util/SmallChartCardTitle.tsx create mode 100644 src/js/features/metadata/hooks.ts create mode 100644 src/js/features/metadata/utils.ts diff --git a/src/js/components/BentoAppRouter.tsx b/src/js/components/BentoAppRouter.tsx index b59c6191..e99b2512 100644 --- a/src/js/components/BentoAppRouter.tsx +++ b/src/js/components/BentoAppRouter.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { Routes, Route, useNavigate, useParams, Outlet } from 'react-router-dom'; import { useAutoAuthenticate, useIsAuthenticated } from 'bento-auth-js'; -import { useAppDispatch, useAppSelector } from '@/hooks'; +import { useAppDispatch } from '@/hooks'; import { makeGetConfigRequest, makeGetServiceInfoRequest } from '@/features/config/config.store'; import { makeGetAboutRequest } from '@/features/content/content.store'; @@ -11,6 +11,7 @@ import { getBeaconConfig } from '@/features/beacon/beacon.store'; import { getBeaconNetworkConfig } from '@/features/beacon/network.store'; import { fetchGohanData, fetchKatsuData } from '@/features/ingestion/lastIngestion.store'; import { makeGetDataTypes } from '@/features/dataTypes/dataTypes.store'; +import { useMetadata } from '@/features/metadata/hooks'; import { getProjects, markScopeSet, selectScope } from '@/features/metadata/metadata.store'; import Loader from '@/components/Loader'; @@ -29,7 +30,7 @@ const ScopedRoute = () => { const { projectId, datasetId } = useParams(); const dispatch = useAppDispatch(); const navigate = useNavigate(); - const { selectedScope, projects } = useAppSelector((state) => state.metadata); + const { selectedScope, projects } = useMetadata(); useEffect(() => { // Update selectedScope based on URL parameters @@ -76,7 +77,7 @@ const BentoAppRouter = () => { const { isAutoAuthenticating } = useAutoAuthenticate(); const isAuthenticated = useIsAuthenticated(); - const { selectedScope, isFetching: isFetchingProjects } = useAppSelector((state) => state.metadata); + const { selectedScope, isFetching: isFetchingProjects } = useMetadata(); useEffect(() => { if (!selectedScope.scopeSet) return; diff --git a/src/js/components/Overview/Chart.tsx b/src/js/components/Overview/Chart.tsx index 3d8a4853..a918b038 100644 --- a/src/js/components/Overview/Chart.tsx +++ b/src/js/components/Overview/Chart.tsx @@ -6,10 +6,10 @@ import { BarChart, Histogram, PieChart } from 'bento-charts'; import { ChoroplethMap } from 'bento-charts/dist/maps'; import { CHART_HEIGHT, PIE_CHART_HEIGHT } from '@/constants/overviewConstants'; +import { useSelectedScope } from '@/features/metadata/hooks'; import type { ChartData } from '@/types/data'; import type { ChartConfig } from '@/types/chartConfig'; import { CHART_TYPE_BAR, CHART_TYPE_HISTOGRAM, CHART_TYPE_CHOROPLETH, CHART_TYPE_PIE } from '@/types/chartConfig'; -import { useAppSelector } from '@/hooks'; import { scopeToUrl } from '@/utils/router'; interface ChartEvent { @@ -19,7 +19,7 @@ interface ChartEvent { const Chart = memo(({ chartConfig, data, units, id, isClickable }: ChartProps) => { const { t, i18n } = useTranslation(); const navigate = useNavigate(); - const { scope } = useAppSelector((state) => state.metadata.selectedScope); + const { scope } = useSelectedScope(); const translateMap = ({ x, y }: { x: string; y: number }) => ({ x: t(x), y }); const removeMissing = ({ x }: { x: string }) => x !== 'missing'; diff --git a/src/js/components/Overview/ChartCard.tsx b/src/js/components/Overview/ChartCard.tsx index d586e2a9..995968c8 100644 --- a/src/js/components/Overview/ChartCard.tsx +++ b/src/js/components/Overview/ChartCard.tsx @@ -1,30 +1,17 @@ import type React from 'react'; import { memo, useRef } from 'react'; -import { Button, Card, Row, Space, Tooltip, Typography } from 'antd'; +import { Button, Card, Row, Space, Tooltip } from 'antd'; import { CloseOutlined } from '@ant-design/icons'; import Chart from './Chart'; import CustomEmpty from '../Util/CustomEmpty'; import { BOX_SHADOW, CHART_HEIGHT } from '@/constants/overviewConstants'; import { useElementWidth, useTranslationCustom, useTranslationDefault } from '@/hooks'; import type { ChartDataField } from '@/types/data'; +import SmallChartCardTitle from '@/components/Util/SmallChartCardTitle'; const CARD_STYLE: React.CSSProperties = { height: '415px', borderRadius: '11px', ...BOX_SHADOW }; const ROW_EMPTY_STYLE: React.CSSProperties = { height: `${CHART_HEIGHT}px` }; -interface TitleComponentProps { - title: string; - description: string; -} - -const TitleComponent: React.FC = ({ title, description }) => ( - - {title} - - {description} - - -); - const ChartCard: React.FC = memo(({ section, chart, onRemoveChart }) => { const t = useTranslationCustom(); const td = useTranslationDefault(); @@ -51,7 +38,9 @@ const ChartCard: React.FC = memo(({ section, chart, onRemoveChar return (
} + title={ + + } style={CARD_STYLE} size="small" extra={ diff --git a/src/js/components/Overview/PublicOverview.tsx b/src/js/components/Overview/PublicOverview.tsx index c2778dbd..fad81974 100644 --- a/src/js/components/Overview/PublicOverview.tsx +++ b/src/js/components/Overview/PublicOverview.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback } from 'react'; -import { Row, Col, FloatButton, Card, Skeleton } from 'antd'; +import { Row, Col, FloatButton, Card, Skeleton, Typography } from 'antd'; import { PlusOutlined } from '@ant-design/icons'; import { convertSequenceAndDisplayData, saveValue } from '@/utils/localStorage'; @@ -14,7 +14,9 @@ import LastIngestionInfo from './LastIngestion'; import Loader from '@/components/Loader'; import { useAppSelector } from '@/hooks'; +import { useSelectedProject, useSelectedScope } from '@/features/metadata/hooks'; import { useTranslation } from 'react-i18next'; +import Dataset from '@/components/Provenance/Dataset'; const ABOUT_CARD_STYLE = { width: '100%', maxWidth: '1390px', borderRadius: '11pX', ...BOX_SHADOW }; const MANAGE_CHARTS_BUTTON_STYLE = { right: '5em', bottom: '1.5em', transform: 'scale(125%)' }; @@ -32,6 +34,9 @@ const PublicOverview = () => { } = useAppSelector((state) => state.data); const { isFetchingAbout, about } = useAppSelector((state) => state.content); + const selectedProject = useSelectedProject(); + const { scope } = useSelectedScope(); + useEffect(() => { // Save sections to localStorage when they change if (isFetchingOverviewData) return; @@ -69,6 +74,21 @@ const PublicOverview = () => { + {selectedProject && !scope.dataset && ( + // If we have a project with more than one dataset, show a dataset selector in the project overview + + + Datasets + + {selectedProject.datasets.map((d) => ( + + + + ))} + + + + )} {displayedSections.map(({ sectionTitle, charts }, i) => ( diff --git a/src/js/components/Provenance/Dataset.tsx b/src/js/components/Provenance/Dataset.tsx new file mode 100644 index 00000000..1b6322f0 --- /dev/null +++ b/src/js/components/Provenance/Dataset.tsx @@ -0,0 +1,67 @@ +import { useLocation, useNavigate } from 'react-router-dom'; +import { Avatar, Button, Card, List } from 'antd'; +import { FaDatabase } from 'react-icons/fa'; + +import type { DiscoveryScope } from '@/features/metadata/metadata.store'; +import type { Dataset } from '@/types/metadata'; +import { getCurrentPage, scopeToUrl } from '@/utils/router'; +import { useTranslationCustom } from '@/hooks'; +import { BOX_SHADOW } from '@/constants/overviewConstants'; +import { RightOutlined } from '@ant-design/icons'; +import { useCallback } from 'react'; +import SmallChartCardTitle from '@/components/Util/SmallChartCardTitle'; + +const Dataset = ({ + parentProjectID, + dataset, + format, + selected, +}: { + parentProjectID: string; + dataset: Dataset; + format: 'list-item' | 'small-card'; + selected?: boolean; +}) => { + const location = useLocation(); + const navigate = useNavigate(); + const page = getCurrentPage(); + + const t = useTranslationCustom(); + + const baseURL = '/' + location.pathname.split('/')[1]; + + const { identifier, title, description } = dataset; + + const scope: DiscoveryScope = { project: parentProjectID, dataset: identifier }; + const datasetURL = scopeToUrl(scope, baseURL, `/${page}`); + + const onNavigate = useCallback(() => navigate(datasetURL), [navigate, datasetURL]); + + if (format === 'list-item') { + return ( + + } />} title={t(title)} description={t(description)} /> + + ); + } else if (format === 'small-card') { + return ( + } + size="small" + style={{ ...BOX_SHADOW, height: 200 }} + extra={