Skip to content

Commit

Permalink
Merge pull request #179 from bento-platform/features/disable-scope-se…
Browse files Browse the repository at this point in the history
…lector

feat(discovery): disable scope selection when possible
  • Loading branch information
v-rocheleau authored Sep 16, 2024
2 parents eee8676 + c44f934 commit 3d942ab
Show file tree
Hide file tree
Showing 12 changed files with 258 additions and 134 deletions.
22 changes: 11 additions & 11 deletions src/js/components/BentoAppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,21 @@ const ScopedRoute = () => {
useEffect(() => {
// Update selectedScope based on URL parameters
const valid = validProjectDataset(projects, projectId, datasetId);
if (datasetId === valid.dataset && projectId === valid.project) {
dispatch(selectScope(valid));
if (datasetId === valid.scope.dataset && projectId === valid.scope.project) {
dispatch(selectScope(valid.scope));
} else {
const oldpath = location.pathname.split('/').filter(Boolean);
const newPath = [oldpath[0]];
const oldPath = location.pathname.split('/').filter(Boolean);
const newPath = [oldPath[0]];

if (valid.dataset) {
newPath.push('p', valid.project as string, 'd', valid.dataset);
} else if (valid.project) {
newPath.push('p', valid.project);
if (valid.scope.dataset) {
newPath.push('p', valid.scope.project as string, 'd', valid.scope.dataset);
} else if (valid.scope.project) {
newPath.push('p', valid.scope.project);
}

const oldPathLength = oldpath.length;
if (oldpath[oldPathLength - 3] === 'p' || oldpath[oldPathLength - 3] === 'd') {
newPath.push(oldpath[oldPathLength - 1]);
const oldPathLength = oldPath.length;
if (oldPath[oldPathLength - 3] === 'p' || oldPath[oldPathLength - 3] === 'd') {
newPath.push(oldPath[oldPathLength - 1]);
}
const newPathString = '/' + newPath.join('/');
navigate(newPathString, { replace: true });
Expand Down
82 changes: 0 additions & 82 deletions src/js/components/ChooseProjectModal.tsx

This file was deleted.

6 changes: 3 additions & 3 deletions src/js/components/Overview/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ interface ChartEvent {
const Chart = memo(({ chartConfig, data, units, id, isClickable }: ChartProps) => {
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const { selectedScope } = useAppSelector((state) => state.metadata);
const { scope } = useAppSelector((state) => state.metadata.selectedScope);
const translateMap = ({ x, y }: { x: string; y: number }) => ({ x: t(x), y });
const removeMissing = ({ x }: { x: string }) => x !== 'missing';

const barChartOnChartClickHandler: BarChartProps['onChartClick'] = (e: ChartEvent) => {
if (e.activePayload.length === 0) return;
const d = e.activePayload[0];
navigate(`/${i18n.language}${scopeToUrl(selectedScope)}/search?${id}=${d.payload.x}`);
navigate(`/${i18n.language}${scopeToUrl(scope)}/search?${id}=${d.payload.x}`);
};
const pieChartOnClickHandler = (d: { name: string }) => {
navigate(`/${i18n.language}${scopeToUrl(selectedScope)}/search?${id}=${d.name}`);
navigate(`/${i18n.language}${scopeToUrl(scope)}/search?${id}=${d.name}`);
};

const { chart_type: type } = chartConfig;
Expand Down
75 changes: 75 additions & 0 deletions src/js/components/Scope/DatasetScopePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { useMemo } from 'react';
import { List, Avatar, Space, Typography } from 'antd';
import { useAppSelector, useTranslationCustom, useTranslationDefault } from '@/hooks';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import { FaDatabase } from 'react-icons/fa';
import { getCurrentPage } from '@/utils/router';
import type { Project } from '@/types/metadata';

type DatasetScopePickerProps = {
parentProject: Project;
};

const DatasetScopePicker = ({ parentProject }: DatasetScopePickerProps) => {
const td = useTranslationDefault();
const t = useTranslationCustom();
const location = useLocation();
const baseURL = '/' + location.pathname.split('/')[1];
const navigate = useNavigate();
const page = getCurrentPage();

const { selectedScope } = useAppSelector((state) => state.metadata);
const scopeObj = selectedScope.scope;

const showClearDataset = useMemo(
() =>
// only show the clear dataset option if the selected dataset belongs to the parentProject
scopeObj.dataset && parentProject.datasets.some((d) => d.identifier == scopeObj.dataset),
[scopeObj, parentProject]
);
const showSelectProject = !selectedScope.fixedProject && parentProject.identifier != scopeObj.project;

const projectURL = `${baseURL}/p/${parentProject.identifier}/${page}`;

return (
<Space direction="vertical" style={{ display: 'flex' }}>
<Space align="baseline" size="large">
<Typography.Title level={4} className="no-margin-top">
{td('Project')}: {t(parentProject.title)}
</Typography.Title>
{showSelectProject && <Link to={projectURL}>{td('Select')}</Link>}
</Space>
<Typography.Text>{t(parentProject.description)}</Typography.Text>
<Space align="baseline" size="large">
<Typography.Title level={5} className="no-margin-top">
{td('Datasets')}
</Typography.Title>
{showClearDataset && <Link to={projectURL}>{td('Clear dataset selection')}</Link>}
</Space>
<List
dataSource={parentProject.datasets}
bordered
renderItem={(dataset) => {
const datasetURL = `${baseURL}/p/${parentProject.identifier}/d/${dataset.identifier}/${page}`;
const selected = scopeObj.dataset && dataset.identifier === scopeObj.dataset;
return (
<List.Item
className={`select-dataset-item${selected ? ' selected' : ''}`}
key={dataset.identifier}
onClick={() => navigate(datasetURL)}
style={{ cursor: 'pointer' }}
>
<List.Item.Meta
avatar={<Avatar icon={<FaDatabase />} />}
title={t(dataset.title)}
description={t(dataset.description)}
/>
</List.Item>
);
}}
/>
</Space>
);
};

export default DatasetScopePicker;
61 changes: 61 additions & 0 deletions src/js/components/Scope/ProjectScopePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { type CSSProperties, useCallback, useMemo, useState } from 'react';
import { Tabs, Button } from 'antd';
import { useAppSelector, useTranslationCustom, useTranslationDefault } from '@/hooks';
import { useLocation, useNavigate } from 'react-router-dom';
import DatasetScopePicker from './DatasetScopePicker';

const styles: Record<string, CSSProperties> = {
tabs: {
// Cancel out padding from modal on left side for button and item alignment
marginLeft: -24,
},
};

const ProjectScopePicker = () => {
const td = useTranslationDefault();
const t = useTranslationCustom();

const location = useLocation();
const baseURL = '/' + location.pathname.split('/')[1];

const navigate = useNavigate();

const { projects, selectedScope } = useAppSelector((state) => state.metadata);
const { scope: scopeObj, fixedProject } = selectedScope;

const [selectedProject, setSelectedProject] = useState<string | undefined>(
scopeObj.project ?? projects[0]?.identifier ?? undefined
);

const onProjectClear = useCallback(() => navigate(baseURL), [baseURL, navigate]);
const onTabChange = useCallback((key: string) => setSelectedProject(key), []);
const tabItems = useMemo(
() =>
projects.map((p) => ({
key: p.identifier,
label: t(p.title),
children: <DatasetScopePicker parentProject={p} />,
})),
[projects, t]
);

return (
<>
{fixedProject ? (
<DatasetScopePicker parentProject={projects[0]} />
) : (
// Project tabs if multiple projects
<Tabs
tabPosition="left"
activeKey={selectedProject}
onChange={onTabChange}
tabBarExtraContent={<Button onClick={onProjectClear}>{td('Clear')}</Button>}
items={tabItems}
style={styles.tabs}
/>
)}
</>
);
};

export default ProjectScopePicker;
20 changes: 20 additions & 0 deletions src/js/components/Scope/ScopePickerModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import { Modal } from 'antd';
import { useTranslationDefault } from '@/hooks';
import ProjectScopePicker from './ProjectScopePicker';

interface ScopePickerModalProps {
isModalOpen: boolean;
setIsModalOpen: (isOpen: boolean) => void;
}
const ScopePickerModal = ({ isModalOpen, setIsModalOpen }: ScopePickerModalProps) => {
const td = useTranslationDefault();
const closeModal = () => setIsModalOpen(false);
return (
<Modal title={td('Select Scope')} open={isModalOpen} onCancel={closeModal} footer={null} width={700}>
<ProjectScopePicker />
</Modal>
);
};

export default ScopePickerModal;
54 changes: 31 additions & 23 deletions src/js/components/SiteHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';

import { Button, Flex, Layout, Typography, Space } from 'antd';
Expand All @@ -14,7 +14,7 @@ import { scopeToUrl } from '@/utils/router';
import { DEFAULT_TRANSLATION, LNG_CHANGE, LNGS_FULL_NAMES } from '@/constants/configConstants';
import { CLIENT_NAME, PORTAL_URL, TRANSLATED } from '@/config';

import ChooseProjectModal from '@/components/ChooseProjectModal';
import ScopePickerModal from './Scope/ScopePickerModal';

const { Header } = Layout;

Expand All @@ -28,15 +28,21 @@ const SiteHeader = () => {
const { isFetching: openIdConfigFetching } = useOpenIdConfig();
const { isHandingOffCodeForToken } = useAuthState();
const { projects, selectedScope } = useAppSelector((state) => state.metadata);

const scopeProps = {
projectTitle: projects.find((project) => project.identifier === selectedScope.project)?.title,
datasetTitle: selectedScope.dataset
? projects
.find((project) => project.identifier === selectedScope.project)
?.datasets.find((dataset) => dataset.identifier === selectedScope.dataset)?.title
: null,
};
const { scope: scopeObj } = selectedScope;

const scopeSelectionEnabled = !(selectedScope.fixedProject && selectedScope.fixedDataset);

const scopeProps = useMemo(
() => ({
projectTitle: projects.find((project) => project.identifier === scopeObj.project)?.title,
datasetTitle: scopeObj.dataset
? projects
.find((project) => project.identifier === scopeObj.project)
?.datasets.find((dataset) => dataset.identifier === scopeObj.dataset)?.title
: null,
}),
[projects, scopeObj]
);

const [isModalOpen, setIsModalOpen] = useState(false);

Expand Down Expand Up @@ -64,7 +70,7 @@ const SiteHeader = () => {
src="/public/assets/branding.png"
alt="logo"
style={{ height: '32px', verticalAlign: 'middle', transform: 'translateY(-3px)' }}
onClick={() => navigate(`/${i18n.language}${scopeToUrl(selectedScope)}`)}
onClick={() => navigate(`/${i18n.language}${scopeToUrl(scopeObj)}`)}
/>
<Typography.Title
level={1}
Expand All @@ -73,18 +79,20 @@ const SiteHeader = () => {
>
{CLIENT_NAME}
</Typography.Title>
<Typography.Title
className="select-project-title"
level={5}
style={{ fontSize: '16px', margin: 0, lineHeight: '64px', color: 'lightgray' }}
onClick={() => setIsModalOpen(true)}
>
<ProfileOutlined style={{ marginRight: '5px', fontSize: '16px' }} />
{scopeSelectionEnabled && (
<Typography.Title
className="select-project-title"
level={5}
style={{ fontSize: '16px', margin: 0, lineHeight: '64px', color: 'lightgray' }}
onClick={() => setIsModalOpen(true)}
>
<ProfileOutlined style={{ marginRight: '5px', fontSize: '16px' }} />

{selectedScope.project && scopeProps.projectTitle}
{scopeProps.datasetTitle ? ` / ${scopeProps.datasetTitle}` : ''}
</Typography.Title>
<ChooseProjectModal isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen} />
{scopeObj.project && scopeProps.projectTitle}
{scopeProps.datasetTitle ? ` / ${scopeProps.datasetTitle}` : ''}
</Typography.Title>
)}
<ScopePickerModal isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen} />
</Space>

<Space size="small">
Expand Down
Loading

0 comments on commit 3d942ab

Please sign in to comment.