Skip to content

Commit

Permalink
Merge pull request #936 from VEuPathDB/SAM-map-redirects
Browse files Browse the repository at this point in the history
Wire up redirect from legacy map to new map
  • Loading branch information
jernestmyers authored Mar 18, 2024
2 parents e989633 + 520f88c commit 4fb2be7
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 7 deletions.
1 change: 1 addition & 0 deletions packages/libs/eda/src/lib/core/api/AnalysisClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export class AnalysisClient extends FetchClientWithCredentials {
const body: NewAnalysis = pick(analysis, [
'displayName',
'description',
'notes',
'descriptor',
'isPublic',
'studyId',
Expand Down
24 changes: 22 additions & 2 deletions packages/libs/eda/src/lib/core/types/analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as t from 'io-ts';
import { Filter } from './filter';
import { VariableDescriptor } from './variable';
import { Computation } from './visualization';
import { merge } from 'lodash';

export type AnalysisPreferences = t.TypeOf<typeof AnalysisPreferences>;
export const AnalysisPreferences = t.intersection([
Expand Down Expand Up @@ -131,13 +132,31 @@ export const Analysis = t.intersection([
}),
]);

export type AdditionalAnalysisConfig = t.TypeOf<
typeof AdditionalAnalysisConfig
>;
export const AdditionalAnalysisConfig = t.intersection([
t.type({
description: t.string,
}),
t.partial({
notes: t.string,
descriptor: t.partial({
subset: t.type({
descriptor: t.array(Filter),
}),
}),
}),
]);

export const DEFAULT_ANALYSIS_NAME = 'Unnamed Analysis';

export function makeNewAnalysis(
studyId: string,
computation?: Computation
computation?: Computation,
additionalAnalysisConfig?: AdditionalAnalysisConfig
): NewAnalysis {
return {
const defaultAnalysis = {
displayName: DEFAULT_ANALYSIS_NAME,
studyId,
isPublic: false,
Expand All @@ -154,4 +173,5 @@ export function makeNewAnalysis(
computations: computation ? [computation] : [],
},
};
return merge(defaultAnalysis, additionalAnalysisConfig);
}
58 changes: 53 additions & 5 deletions packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
FloatingButton,
H5,
Table,
Modal,
} from '@veupathdb/coreui';
import { useEntityCounts } from '../../core/hooks/entityCounts';
import ShowHideVariableContextProvider from '../../core/utils/show-hide-variable-context';
Expand Down Expand Up @@ -98,6 +99,13 @@ const mapStyle: React.CSSProperties = {
pointerEvents: 'auto',
};

export type LegacyRedirectState =
| undefined
| {
projectId?: string;
showLegacyMapRedirectModal: boolean;
};

interface Props {
analysisId?: string;
sharingUrl: string;
Expand All @@ -115,6 +123,8 @@ export function MapAnalysis(props: Props) {
);
const geoConfigs = useGeoConfig(useStudyEntities());
const location = useLocation();
const locationState = location.state as LegacyRedirectState;
const [showRedirectModal, setShowRedirectModal] = useState(!!locationState);

if (geoConfigs == null || geoConfigs.length === 0)
return (
Expand All @@ -139,11 +149,49 @@ export function MapAnalysis(props: Props) {
if (appStateAndSetters.appState == null) return null;

return (
<MapAnalysisImpl
{...props}
{...(appStateAndSetters as CompleteAppState)}
geoConfigs={geoConfigs}
/>
<>
<Modal
visible={Boolean(locationState && showRedirectModal)}
toggleVisible={() => setShowRedirectModal(false)}
styleOverrides={{
content: {
size: {
height: '100%',
width: '100%',
},
padding: {
top: 25,
right: 25,
bottom: 25,
left: 25,
},
},
size: {
width: 400,
height: 200,
},
}}
>
<div className="LegacyMapRedirectModalContainer">
<p>
You have been redirected from the legacy PopBio map
{locationState?.projectId &&
` to the same study (${locationState.projectId}) in our new map`}
. Settings encoded in your URL are not applied but are kept in the{' '}
<strong>Notes</strong> section (see left side panel).
</p>
<FilledButton
text="Dismiss"
onPress={() => setShowRedirectModal(false)}
/>
</div>
</Modal>
<MapAnalysisImpl
{...props}
{...(appStateAndSetters as CompleteAppState)}
geoConfigs={geoConfigs}
/>
</>
);
}

Expand Down
149 changes: 149 additions & 0 deletions packages/libs/web-common/src/controllers/LegacyMapRedirectHandler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import React, { useCallback, useMemo, useState } from 'react';
import QueryString from 'querystring';
import { edaServiceUrl } from '../config';
import { useConfiguredAnalysisClient } from '@veupathdb/eda/lib/core/hooks/client';
import { createComputation } from '@veupathdb/eda/lib/core/components/computations/Utils';
import { makeNewAnalysis } from '@veupathdb/eda/lib/core';
import { RouteComponentProps } from 'react-router';
import { LegacyRedirectState } from '@veupathdb/eda/lib/map/analysis/MapAnalysis';

// Define constants to create new computations and analyses
const MEGA_STUDY_ID = 'DS_480c976ef9';
const MEGA_STUDIES_ENTITY_ID = 'EUPATH_0000605';
const POPBIO_ID_VARIABLE_ID = 'POPBIO_8000215';
const DESCRIPTOR_TYPE = 'stringSet';
const REDIRECT_ANALYSIS_DESCRIPTION =
'This map was created from a legacy PopBio map link.';
const DEFAULT_COMPUTATION = createComputation(
'pass',
undefined,
[],
[],
undefined,
'Unnamed computation'
);

export function LegacyMapRedirectHandler({
history,
match,
location,
}: RouteComponentProps) {
const analysisClient = useConfiguredAnalysisClient(edaServiceUrl);
const queryParams = useMemo(
() => QueryString.parse(location.search.slice(1)),
[location.search]
);
const [hasCreatedAnalysis, setHasCreatedAnalysis] = useState(false);

const handleLegacyMapRedirect = useCallback(
async (computation, additionalConfig = {}, legacyMapRedirectState) => {
if (hasCreatedAnalysis) return;
setHasCreatedAnalysis(true);
const { analysisId } = await analysisClient.createAnalysis(
makeNewAnalysis(MEGA_STUDY_ID, computation, additionalConfig)
);
history.push({
pathname: `${match.url.replace(
'/legacy-redirect-handler',
''
)}/${MEGA_STUDY_ID}/${analysisId}`,
state: legacyMapRedirectState as LegacyRedirectState,
});
},
[analysisClient, history, match, hasCreatedAnalysis, setHasCreatedAnalysis]
);

const baseAdditionalAnalysisConfig = {
description: REDIRECT_ANALYSIS_DESCRIPTION,
};

const paramKeys = Object.keys(queryParams);

if (paramKeys.length) {
if ('projectID' in queryParams) {
const descriptorConfig = {
descriptor: {
subset: {
descriptor: [
{
entityId: MEGA_STUDIES_ENTITY_ID,
variableId: POPBIO_ID_VARIABLE_ID,
type: DESCRIPTOR_TYPE,
[DESCRIPTOR_TYPE]: [queryParams['projectID']],
},
],
},
},
};

if (paramKeys.length === 1) {
// We know we have only the projectID param, so make new analysis and redirect
const additionalAnalysisConfig = {
...baseAdditionalAnalysisConfig,
...descriptorConfig,
};
handleLegacyMapRedirect(
DEFAULT_COMPUTATION,
additionalAnalysisConfig,
undefined
);
} else {
// Here we have a projectID and other param(s), so populate the Notes -> Analysis Details info
// with the additional param(s) and pass along the legacyMapRedirectState object
const notes = composeParamListForNotesString(queryParams);
const additionalAnalysisConfig = {
...descriptorConfig,
...baseAdditionalAnalysisConfig,
notes,
};
const legacyMapRedirectState = {
showLegacyMapRedirectModal: true,
projectId: queryParams['projectID'],
};
handleLegacyMapRedirect(
DEFAULT_COMPUTATION,
additionalAnalysisConfig,
legacyMapRedirectState
);
}
} else {
// Here we have query params but no projectID, so populate the Notes -> Analysis Description info
// with the additional param(s) and pass along the legacyMapRedirectState object
const notes = composeParamListForNotesString(queryParams);
const additionalAnalysisConfig = {
...baseAdditionalAnalysisConfig,
notes,
};
const legacyMapRedirectState = {
showLegacyMapRedirectModal: true,
projectId: undefined,
};
handleLegacyMapRedirect(
DEFAULT_COMPUTATION,
additionalAnalysisConfig,
legacyMapRedirectState
);
}
} else {
// no query params, so create a default map analysis with only the baseAdditionalAnalysisConfig and redirect
handleLegacyMapRedirect(
DEFAULT_COMPUTATION,
baseAdditionalAnalysisConfig,
undefined
);
}
return null;
}

function composeParamListForNotesString(
queryParams: QueryString.ParsedUrlQuery
) {
let description =
'The following parameters from the old link were present but ignored:\n';
for (const [key, value] of Object.entries(queryParams)) {
if (key !== 'projectID') {
description += `${key}: ${JSON.stringify(value)}\n`;
}
}
return description;
}
9 changes: 9 additions & 0 deletions packages/libs/web-common/src/routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
showUnreleasedData,
} from './config';
import { EdaMapController } from './controllers/EdaMapController';
import { LegacyMapRedirectHandler } from './controllers/LegacyMapRedirectHandler';

export const STATIC_ROUTE_PATH = '/static-content';

Expand Down Expand Up @@ -79,6 +80,14 @@ export const wrapRoutes = (wdkRoutes) => [
component: EdaMapController,
},

{
path: makeMapRoute() + '/legacy-redirect-handler',
exact: false,
isFullscreen: true,
rootClassNameModifier: 'MapVEu',
component: LegacyMapRedirectHandler,
},

{
path: makeMapRoute(),
exact: false,
Expand Down
13 changes: 13 additions & 0 deletions packages/libs/web-common/src/styles/client.scss
Original file line number Diff line number Diff line change
Expand Up @@ -463,3 +463,16 @@ div.eupathdb-content {
.smaller-font {
font-size: 90%;
}

.LegacyMapRedirectModalContainer {
font-size: 1.2em;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
text-align: justify;
p {
margin-top: 0;
margin-bottom: 2em;
}
}

0 comments on commit 4fb2be7

Please sign in to comment.