Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tweak(tupaiaWeb): RN-1420: Use dashboard codes in URLs #5884

Open
wants to merge 10 commits into
base: dev
Choose a base branch
from
5 changes: 4 additions & 1 deletion packages/tupaia-web-server/src/routes/EmailDashboardRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,15 @@ export class EmailDashboardRoute extends Route<EmailDashboardRequest> {
},
columns: ['id'],
})) as Pick<Project, 'id'>[];

const projectEntity = (await this.req.ctx.services.entity.getEntity(projectCode, projectCode, {
fields: ['name'],
})) as Pick<Entity, 'name'>;

const entity = (await this.req.ctx.services.entity.getEntity(projectCode, entityCode, {
fields: ['id', 'name', 'country_code'],
})) as Pick<Entity, 'id' | 'name' | 'country_code'>;

const [dashboard] = (await this.req.ctx.services.central.fetchResources('dashboards', {
filter: { code: dashboardCode },
columns: ['id', 'name'],
Expand Down Expand Up @@ -102,7 +105,7 @@ export class EmailDashboardRoute extends Route<EmailDashboardRequest> {
const buffer = await downloadDashboardAsPdf(
projectCode,
entityCode,
dashboard.name,
dashboardCode,
baseUrl,
cookie,
cookieDomain,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export class ExportDashboardRoute extends Route<ExportDashboardRequest> {

const [dashboard] = (await this.req.ctx.services.central.fetchResources('dashboards', {
filter: { code: dashboardCode },
columns: ['name'],
})) as Pick<Dashboard, 'name'>[];
columns: ['code'],
})) as Pick<Dashboard, 'code'>[];

if (!dashboard) {
throw new Error(`Cannot find dashboard with code: ${dashboardCode}`);
Expand All @@ -40,7 +40,7 @@ export class ExportDashboardRoute extends Route<ExportDashboardRequest> {
const buffer = await downloadDashboardAsPdf(
projectCode,
entityCode,
dashboard.name,
dashboardCode,
baseUrl,
cookie,
cookieDomain,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { stringifyQuery } from '@tupaia/utils';
export const downloadDashboardAsPdf = (
projectCode: string,
entityCode: string,
dashboardName: string,
dashboardCode: string,
baseUrl: TupaiaWebExportDashboardRequest.ReqBody['baseUrl'],
cookie: string,
cookieDomain: TupaiaWebExportDashboardRequest.ReqBody['cookieDomain'],
Expand All @@ -20,7 +20,7 @@ export const downloadDashboardAsPdf = (
exportWithTable: false,
},
) => {
const endpoint = `${projectCode}/${entityCode}/${dashboardName}/dashboard-pdf-export`;
const endpoint = `${projectCode}/${entityCode}/${dashboardCode}/dashboard-pdf-export`;
const pdfPageUrl = stringifyQuery(baseUrl, endpoint, {
selectedDashboardItems: selectedDashboardItems?.join(','),
settings: JSON.stringify(settings),
Expand Down
52 changes: 47 additions & 5 deletions packages/tupaia-web/src/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd
*/
import React from 'react';
import { Navigate, Route, Routes as RouterRoutes, useLocation } from 'react-router-dom';
import { Navigate, Route, Routes as RouterRoutes, useLocation, useParams } from 'react-router-dom';
import {
DashboardPDFExport,
LandingPage,
Expand All @@ -13,10 +13,10 @@ import {
} from './views';
import { Dashboard } from './features';
import { MODAL_ROUTES, DEFAULT_URL, ROUTE_STRUCTURE, MAP_OVERLAY_EXPORT_ROUTE } from './constants';
import { useUser } from './api/queries';
import { useDashboards, useProject, useUser } from './api/queries';
import { MainLayout } from './layout';
import { LoadingScreen } from './components';
import { gaEvent, useEntityLink } from './utils';
import { gaEvent, getDefaultDashboard, useEntityLink } from './utils';

const HomeRedirect = () => {
const { isLoggedIn } = useUser();
Expand Down Expand Up @@ -52,6 +52,48 @@ const UserPageRedirect = ({ modal }: { modal: MODAL_ROUTES }) => {
);
};

const DashboardNameToCodeRedirect = () => {
const { dashboardCode, projectCode, entityCode } = useParams();
const location = useLocation();

// if the code is not a valid code but is a valid name, redirect to the correct code
const {
data: dashboards = [],
isLoading: isLoadingDashboards,
isError,
} = useDashboards(projectCode, entityCode);

const { data: project, isLoading: isLoadingProject } = useProject(projectCode);

// if the project or dashboards are still loading, show the dashboard as this will handle loading state
if (isLoadingDashboards || isLoadingProject) {
return <Dashboard />;
}

const uriDecodedDashboardCode = decodeURIComponent(dashboardCode!);

// if the dashboard code is valid, render the dashboard with the code
if (dashboards.find(d => d.code === uriDecodedDashboardCode)) {
return <Dashboard />;
}

const dashboardByName = dashboards.find(d => d.name === uriDecodedDashboardCode);

// if the dashboard name is valid, redirect to the correct code
if (dashboardByName) {
const to = {
...location,
pathname: `/${projectCode}/${entityCode}/${dashboardByName.code}`,
};
return <Navigate to={to} replace />;
}

const defaultDashboard = getDefaultDashboard(project, dashboards, false, isError);

// if the dashboard name is not valid, redirect to the default dashboard
return <Navigate to={`/${projectCode}/${entityCode}/${defaultDashboard}`} replace />;
};

/**
* This Router is using [version 6.3]{@link https://reactrouter.com/en/v6.3.0}, as later versions are not supported by our TS setup. See [this issue here]{@link https://github.com/remix-run/react-router/discussions/8364}
* This means the newer 'createBrowserRouter' and 'RouterProvider' can't be used here.
Expand All @@ -70,7 +112,7 @@ export const Routes = () => {
return (
<RouterRoutes>
<Route
path="/:projectCode/:entityCode/:dashboardName/dashboard-pdf-export"
path="/:projectCode/:entityCode/:dashboardCode/dashboard-pdf-export"
element={<DashboardPDFExport />}
/>
<Route path={MAP_OVERLAY_EXPORT_ROUTE} element={<MapOverlayPDFExport />} />
Expand All @@ -92,7 +134,7 @@ export const Routes = () => {
<Route path="/:projectCode/:entityCode" element={<ProjectPageDashboardRedirect />} />

{/* The Dashboard has to be rendered below the Map, otherwise the map will re-mount on route changes */}
<Route path={ROUTE_STRUCTURE} element={<Dashboard />} />
<Route path={ROUTE_STRUCTURE} element={<DashboardNameToCodeRedirect />} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty minor but I was initially confused by the naming of DashboardNameToCodeRedirect as I didn't realise that it included the Dashboard component. Maybe it should be just called DashboardRoute or DashboardCodeRoute or something?

</Route>
</Route>
</RouterRoutes>
Expand Down
4 changes: 2 additions & 2 deletions packages/tupaia-web/src/api/mutations/useExportDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
import { useMutation } from 'react-query';
import { TupaiaWebExportDashboardRequest } from '@tupaia/types';
import { API_URL, post } from '../api';
import { DashboardName, EntityCode, ProjectCode } from '../../types';
import { Dashboard, EntityCode, ProjectCode } from '../../types';
import { downloadPDF } from '../../utils';

type ExportDashboardBody = {
projectCode?: ProjectCode;
entityCode?: EntityCode;
dashboardCode?: DashboardName;
dashboardCode?: Dashboard['code'];
selectedDashboardItems?: TupaiaWebExportDashboardRequest.ReqBody['selectedDashboardItems'];
settings?: TupaiaWebExportDashboardRequest.ReqBody['settings'];
};
Expand Down
4 changes: 2 additions & 2 deletions packages/tupaia-web/src/constants/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export enum MODAL_ROUTES {
}

export const DEFAULT_PROJECT_ENTITY = '/explore/explore';
export const DEFAULT_URL = `${DEFAULT_PROJECT_ENTITY}/General`;
export const DEFAULT_URL = `${DEFAULT_PROJECT_ENTITY}/explore_General`;

export enum TABS {
MAP = 'map',
Expand All @@ -35,6 +35,6 @@ export const DEFAULT_PERIOD_PARAM_STRING = 'DEFAULT_PERIOD';

export const DEFAULT_MAP_OVERLAY_ID = '126'; // 'Operational Facilities'

export const ROUTE_STRUCTURE = '/:projectCode/:entityCode/:dashboardName';
export const ROUTE_STRUCTURE = '/:projectCode/:entityCode/:dashboardCode';

export const MAP_OVERLAY_EXPORT_ROUTE = '/:projectCode/:entityCode/map-overlay-pdf-export';
47 changes: 8 additions & 39 deletions packages/tupaia-web/src/features/Dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
* Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd
*/

import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import styled from 'styled-components';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import { Typography } from '@material-ui/core';
import { DEFAULT_BOUNDS } from '@tupaia/ui-map-components';
import { ErrorBoundary, SpinningLoader } from '@tupaia/ui-components';
import { MatrixConfig } from '@tupaia/types';
import { MOBILE_BREAKPOINT } from '../../constants';
import { useDashboards, useEntity, useProject } from '../../api/queries';
import { DashboardItem as DashboardItemType } from '../../types';
import { gaEvent, getDefaultDashboard } from '../../utils';
import { gaEvent } from '../../utils';
import { DashboardItem } from '../DashboardItem';
import { EnlargedDashboardItem } from '../EnlargedDashboardItem';
import { ExpandButton } from './ExpandButton';
Expand All @@ -34,7 +34,9 @@ const Panel = styled.div<{
}>`
position: relative;
background-color: ${({ theme }) => theme.palette.background.paper};
transition: width 0.3s ease, max-width 0.3s ease;
transition:
width 0.3s ease,
max-width 0.3s ease;
width: 100%;
overflow: visible;
min-height: 100%;
Expand Down Expand Up @@ -106,54 +108,21 @@ const DashboardItemsWrapper = styled.div<{
`;

export const Dashboard = () => {
const navigate = useNavigate();
const location = useLocation();
const { projectCode, entityCode } = useParams();
const { data: project, isLoading: isLoadingProject } = useProject(projectCode);
const { data: project } = useProject(projectCode);

const { activeDashboard } = useDashboard();
const {
data: dashboards,
isLoading: isLoadingDashboards,
isError,
isFetched,
} = useDashboards(projectCode, entityCode);
const { isLoading: isLoadingDashboards } = useDashboards(projectCode, entityCode);
const [isExpanded, setIsExpanded] = useState(false);

const { data: entity } = useEntity(projectCode, entityCode);
const bounds = entity?.bounds || DEFAULT_BOUNDS;

// we don't want useEntityLink to take care of this because useEntityLink gets called for all child entities on the map, meaning lots of extra queries when we don't need them. Instead the redirect will be taken care of in the useEffect below, as needed
const defaultDashboardName = getDefaultDashboard(
project,
dashboards,
isLoadingDashboards,
isError,
);

const toggleExpanded = () => {
setIsExpanded(!isExpanded);
gaEvent('Pages', 'Toggle Info Panel');
};

// check for valid dashboard name, and if not valid and not still loading, redirect to default dashboard
const dashboardNotFound =
isFetched &&
!isError &&
!isLoadingDashboards &&
!isLoadingProject &&
project?.code === projectCode &&
!activeDashboard;

useEffect(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bonus to replace this but of code I reckon

if (dashboardNotFound) {
navigate({
...location,
pathname: `/${projectCode}/${entityCode}/${defaultDashboardName}`,
});
}
}, [dashboardNotFound, defaultDashboardName]);

// Filter out drill down items from the dashboard items
const visibleDashboards =
(activeDashboard?.items as DashboardItemType[])?.reduce(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,25 @@ const StyledPaper = styled(Paper)`
interface DashboardMenuItemProps {
dashboardName: Dashboard['name'];
onClose: () => void;
code: Dashboard['code'];
}

const DashboardMenuItem = ({ dashboardName, onClose }: DashboardMenuItemProps) => {
const DashboardMenuItem = ({ dashboardName, code, onClose }: DashboardMenuItemProps) => {
const location = useLocation();
const { projectCode, entityCode, dashboardName: selectedDashboardName } = useParams();
const { projectCode, entityCode, dashboardCode: selectedDashboardCode } = useParams();

const encodedDashboardName = encodeURIComponent(dashboardName);
const encodedDashboardCode = encodeURIComponent(code);
const link = {
...location,
pathname: `/${projectCode}/${entityCode}/${encodedDashboardName}`,
pathname: `/${projectCode}/${entityCode}/${encodedDashboardCode}`,
};

return (
<MenuItem
to={link}
onClick={onClose}
component={Link}
selected={dashboardName === selectedDashboardName}
selected={code === selectedDashboardCode}
>
{dashboardName}
</MenuItem>
Expand Down Expand Up @@ -116,7 +117,7 @@ export const DashboardMenu = () => {
PaperProps={{ component: StyledPaper }}
>
{dashboards.map(({ name, code }) => (
<DashboardMenuItem key={code} dashboardName={name} onClose={handleClose} />
<DashboardMenuItem key={code} dashboardName={name} onClose={handleClose} code={code} />
))}
</StyledMenu>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ interface ExportDashboardProps {
}

export const ExportConfig = ({ onClose, selectedDashboardItems }: ExportDashboardProps) => {
const { projectCode, entityCode, dashboardName } = useParams();
const { projectCode, entityCode, dashboardCode } = useParams();
const { data: project } = useProject(projectCode);
const { data: entity } = useEntity(projectCode, entityCode);
const { activeDashboard } = useDashboard();
const { exportWithLabels, exportWithTable } = useExportSettings();

const exportFileName = `${project?.name}-${entity?.name}-${dashboardName}-dashboard-export`;
const exportFileName = `${project?.name}-${entity?.name}-${dashboardCode}-dashboard-export`;

const { mutate: requestPdfExport, error, isLoading, reset } = useExportDashboard(exportFileName);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { DashboardContext } from './DashboardContext';

// Contains the dashboards, active dashboard, and export and subscribe state
export const useDashboard = () => {
const { dashboardName, entityCode, projectCode } = useParams();
const { dashboardCode, entityCode, projectCode } = useParams();
const { data: dashboards = [] } = useDashboards(projectCode, entityCode);
const { exportModalOpen, subscribeModalOpen, setExportModalOpen, setSubscribeModalOpen } =
useContext(DashboardContext);
Expand All @@ -22,7 +22,7 @@ export const useDashboard = () => {
};
// trim dashboard name to avoid issues with trailing or leading spaces
const activeDashboard =
dashboards?.find(dashboard => dashboard.name.trim() === dashboardName?.trim()) ?? undefined;
dashboards?.find(dashboard => dashboard.code.trim() === dashboardCode?.trim()) ?? undefined;

useGAEffect('Dashboard', 'Change Tab', activeDashboard?.name);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const useNavigateToEntity = () => {
const navigate = useNavigate();
const { data: project } = useProject(projectCode);

const dashboardNameParam = project?.dashboardGroupName
const projectDashboardName = project?.dashboardGroupName
? encodeURIComponent(project.dashboardGroupName)
: '';

Expand All @@ -29,7 +29,7 @@ export const useNavigateToEntity = () => {
const link = {
...location,
search: urlSearchParams.toString(),
pathname: `/${projectCode}/${entityCode}/${dashboardNameParam}`,
pathname: `/${projectCode}/${entityCode}/${projectDashboardName}`,
};
navigate(link);
};
Expand Down
21 changes: 8 additions & 13 deletions packages/tupaia-web/src/utils/getDefaultDashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,14 @@ export const getDefaultDashboard = (
) => {
// when loading, return '' so that the user doesn't get redirected more than once, e.g. if there is a dashboardGroupName but it ends up not being valid after loading
if (isLoadingDashboards || (!dashboards && !hasDashboardError)) return '';
let defaultDashboardName = project?.dashboardGroupName || '';
const defaultDashboardName = project?.dashboardGroupName || '';

if (
!defaultDashboardName ||
(dashboards &&
dashboards?.length > 0 &&
!dashboards?.find(
(dashboard: Dashboard) =>
dashboard.name.trim() === decodeURIComponent(defaultDashboardName),
))
) {
defaultDashboardName = dashboards?.[0]?.name || '';
}
const dashboard =
dashboards?.find(
(dashboard: Dashboard) => dashboard.name.trim() === decodeURIComponent(defaultDashboardName),
) ||
dashboards?.[0] ||
null;

return encodeURIComponent(defaultDashboardName);
return encodeURIComponent(dashboard?.code || '');
};
Loading