Skip to content

Commit 0a0a921

Browse files
authored
Merge pull request #238 from bcgsc/release/v6.13.0
Release/v6.13.0
2 parents 7d9d57c + e1b3f0b commit 0a0a921

File tree

10 files changed

+183
-94
lines changed

10 files changed

+183
-94
lines changed

app/components/Sidebar/__tests__/Sidebar.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import ResourceContext from '../../../context/ResourceContext';
99
const context = {};
1010
const permissions = {
1111
germlineAccess: true,
12-
reportAccess: true,
12+
reportsAccess: true,
1313
adminAccess: true,
14+
reportSettingAccess: true,
1415
};
1516

1617
const theme = createTheme();

app/components/Sidebar/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import './index.scss';
3131
const Sidebar = (): JSX.Element => {
3232
const { pathname } = useLocation();
3333
const { sidebarMaximized, setSidebarMaximized } = useContext(SidebarContext);
34-
const { germlineAccess, reportAccess, adminAccess } = useResource();
34+
const { germlineAccess, reportsAccess, adminAccess } = useResource();
3535

3636
const handleSidebarClose = useCallback(() => {
3737
setSidebarMaximized(false);
@@ -48,7 +48,7 @@ const Sidebar = (): JSX.Element => {
4848
</div>
4949
<Divider />
5050
<List disablePadding>
51-
{reportAccess && (
51+
{reportsAccess && (
5252
<ListItem
5353
className={`
5454
sidebar__list-item

app/components/SvgImage/index.tsx

Lines changed: 91 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, {
2-
useEffect, useState, useRef,
2+
useEffect, useState, useRef, useMemo,
33
} from 'react';
44
import { UncontrolledReactSVGPanZoom } from 'react-svg-pan-zoom';
55
import InlineSVG from 'svg-inline-react';
@@ -10,14 +10,23 @@ import './index.scss';
1010
type SvgImageProps = {
1111
image: string;
1212
isPrint?: boolean;
13+
printOrientation: 'portrait' | 'landscape';
1314
};
1415

16+
// These should be same as index.css under @page
17+
// Accounts for padding via magical numbers 32 and 16
18+
const INCH_TO_PX = 96;
19+
const MAX_PRINT_WIDTH = Math.floor((9 - 0.4 * 2) * INCH_TO_PX) - 32;
20+
const MAX_PRINT_HEIGHT = Math.floor((11.5 - 0.4 * 2) * INCH_TO_PX) - 16;
21+
1522
const PRINT_WIDTH = 816;
1623
const ICON_WIDTH = 48;
1724

1825
const SvgImage = ({
1926
image,
2027
isPrint = false,
28+
// Only applicable when in print mode
29+
printOrientation = 'portrait',
2130
}: SvgImageProps): JSX.Element => {
2231
const Viewer = useRef();
2332
const [svgHeight, setSvgHeight] = useState<number>();
@@ -56,33 +65,89 @@ const SvgImage = ({
5665
Viewer?.current?.fitToViewer();
5766
};
5867

59-
return (
60-
<div className="svg-image">
61-
{processedImage && svgHeight && svgWidth && (
68+
const svgComponent = useMemo(() => {
69+
if (processedImage && svgHeight && svgWidth) {
70+
return (
6271
<AutoSizer disableHeight defaultWidth={PRINT_WIDTH} onResize={handleFit}>
63-
{({ width = PRINT_WIDTH }) => (
64-
<UncontrolledReactSVGPanZoom
65-
ref={Viewer}
66-
/*
67-
48px is removed since with a float icon to the right
68-
then the SVG is moved down too far.
69-
*/
70-
width={width - ICON_WIDTH}
71-
height={svgHeight}
72-
background="#FFFFFF"
73-
detectAutoPan={false}
74-
defaultTool="auto"
75-
customMiniature={() => null}
76-
customToolbar={isPrint ? () => null : undefined}
77-
toolbarProps={{ position: 'left' }}
78-
>
79-
<svg width={svgWidth} height={svgHeight}>
80-
<InlineSVG src={processedImage} raw />
81-
</svg>
82-
</UncontrolledReactSVGPanZoom>
83-
)}
72+
{({ width = PRINT_WIDTH }) => {
73+
if (isPrint) {
74+
let overHeightRatio = svgHeight / MAX_PRINT_HEIGHT;
75+
let overWidthRatio = svgWidth / MAX_PRINT_WIDTH;
76+
if (printOrientation === 'landscape') {
77+
overHeightRatio = svgHeight / MAX_PRINT_WIDTH;
78+
overWidthRatio = svgHeight / MAX_PRINT_WIDTH;
79+
}
80+
let nextRatio = 1;
81+
82+
if (overHeightRatio > 1 && overWidthRatio > 1) {
83+
// Both over, find higher ratio
84+
nextRatio = Math.max(overHeightRatio, overWidthRatio);
85+
} else if (overHeightRatio > 1) {
86+
nextRatio = overHeightRatio;
87+
} else if (overWidthRatio > 1) {
88+
nextRatio = overWidthRatio;
89+
}
90+
91+
const nextHeight = svgHeight / nextRatio;
92+
const nextWidth = svgWidth / nextRatio;
93+
94+
const svgStyle = {
95+
transformOrigin: 'top left',
96+
transform: printOrientation === 'landscape' ? `rotate(90deg) translate(0, -${nextHeight}px)` : '',
97+
};
98+
99+
return (
100+
<div style={svgStyle}>
101+
<UncontrolledReactSVGPanZoom
102+
ref={Viewer}
103+
width={svgWidth > nextWidth ? nextWidth : svgWidth}
104+
height={svgHeight > nextHeight ? nextHeight : svgHeight}
105+
background="#FFFFFF"
106+
detectAutoPan={false}
107+
defaultTool="auto"
108+
customMiniature={() => null}
109+
customToolbar={() => null}
110+
toolbarProps={{ position: 'left' }}
111+
>
112+
<svg width={svgWidth} height={svgHeight}>
113+
<InlineSVG src={processedImage} raw />
114+
</svg>
115+
</UncontrolledReactSVGPanZoom>
116+
</div>
117+
);
118+
}
119+
120+
return (
121+
<UncontrolledReactSVGPanZoom
122+
ref={Viewer}
123+
/*
124+
48px is removed since with a float icon to the right
125+
then the SVG is moved down too far.
126+
*/
127+
width={width - ICON_WIDTH}
128+
height={svgHeight}
129+
background="#FFFFFF"
130+
detectAutoPan={false}
131+
defaultTool="auto"
132+
customMiniature={() => null}
133+
customToolbar={isPrint ? () => null : undefined}
134+
toolbarProps={{ position: 'left' }}
135+
>
136+
<svg width={svgWidth} height={svgHeight}>
137+
<InlineSVG src={processedImage} raw />
138+
</svg>
139+
</UncontrolledReactSVGPanZoom>
140+
);
141+
}}
84142
</AutoSizer>
85-
)}
143+
);
144+
}
145+
return null;
146+
}, [isPrint, printOrientation, processedImage, svgHeight, svgWidth]);
147+
148+
return (
149+
<div className="svg-image">
150+
{svgComponent}
86151
</div>
87152
);
88153
};

app/context/ResourceContext/index.tsx

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,87 @@
1-
import React, { createContext, ReactChild } from 'react';
1+
import React, {
2+
createContext, ReactChild, useContext, useState, useEffect, useMemo,
3+
} from 'react';
4+
import SecurityContext from '@/context/SecurityContext';
25

3-
import { useResources } from '@/hooks/useResource';
6+
import checkAccess from '@/utils/checkAccess';
47
import ResourceContextType from './types';
58

9+
const GERMLINE_ACCESS = ['admin', 'analyst', 'bioinformatician', 'projects', 'manager'];
10+
const GERMLINE_BLOCK = ['clinician', 'collaborator'];
11+
const REPORTS_ACCESS = ['*'];
12+
const REPORTS_BLOCK = [];
13+
const ADMIN_ACCESS = ['admin'];
14+
const ADMIN_BLOCK = [];
15+
16+
type UseResourcesReturnType = {
17+
germlineAccess: boolean;
18+
reportsAccess: boolean;
19+
adminAccess: boolean;
20+
reportSettingAccess: boolean;
21+
};
22+
23+
const useResources = (): UseResourcesReturnType => {
24+
const { userDetails } = useContext(SecurityContext);
25+
26+
const [germlineAccess, setGermlineAccess] = useState(false);
27+
const [reportsAccess, setReportsAccess] = useState(false);
28+
const [adminAccess, setAdminAccess] = useState(false);
29+
const [reportSettingAccess, setReportSettingAccess] = useState(false);
30+
31+
useEffect(() => {
32+
if (userDetails?.groups) {
33+
if (checkAccess(userDetails.groups, GERMLINE_ACCESS, GERMLINE_BLOCK)) {
34+
setGermlineAccess(true);
35+
}
36+
37+
if (checkAccess(userDetails.groups, REPORTS_ACCESS, REPORTS_BLOCK)) {
38+
setReportsAccess(true);
39+
}
40+
41+
if (checkAccess(userDetails.groups, ADMIN_ACCESS, ADMIN_BLOCK)) {
42+
setAdminAccess(true);
43+
}
44+
if (checkAccess(userDetails.groups, [...ADMIN_ACCESS, 'manager'], ADMIN_BLOCK)) {
45+
setReportSettingAccess(true);
46+
}
47+
}
48+
}, [userDetails?.groups]);
49+
50+
return {
51+
germlineAccess, reportsAccess, adminAccess, reportSettingAccess,
52+
};
53+
};
54+
655
const ResourceContext = createContext<ResourceContextType>({
756
germlineAccess: false,
8-
reportAccess: false,
57+
reportsAccess: false,
958
adminAccess: false,
59+
reportSettingAccess: false,
1060
});
1161

1262
type ResourceContextProviderProps = {
1363
children: ReactChild,
1464
};
1565

1666
const ResourceContextProvider = ({ children }: ResourceContextProviderProps): JSX.Element => {
17-
const { germlineAccess, reportAccess, adminAccess } = useResources();
67+
const {
68+
germlineAccess, reportsAccess, adminAccess, reportSettingAccess,
69+
} = useResources();
70+
71+
const providerValue = useMemo(() => ({
72+
germlineAccess,
73+
reportsAccess,
74+
adminAccess,
75+
reportSettingAccess,
76+
}), [
77+
germlineAccess,
78+
reportsAccess,
79+
adminAccess,
80+
reportSettingAccess,
81+
]);
1882

1983
return (
20-
<ResourceContext.Provider value={{ germlineAccess, reportAccess, adminAccess }}>
84+
<ResourceContext.Provider value={providerValue}>
2185
{children}
2286
</ResourceContext.Provider>
2387
);
@@ -29,6 +93,6 @@ export type {
2993

3094
export {
3195
ResourceContextProvider,
32-
}
96+
};
3397

3498
export default ResourceContext;
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
type ResourceContextType = {
22
germlineAccess: boolean;
3-
reportAccess: boolean;
3+
reportsAccess: boolean;
44
adminAccess: boolean;
5+
reportSettingAccess: boolean;
56
};
67

78
export default ResourceContextType;

app/hooks/useResource.ts

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,7 @@
1-
import { useState, useEffect, useContext } from 'react';
1+
import { useContext } from 'react';
22

3-
import SecurityContext from '@/context/SecurityContext';
43
import ResourceContext, { ResourceContextType } from '@/context/ResourceContext';
5-
import checkAccess from '@/utils/checkAccess';
6-
7-
const GERMLINE_ACCESS = ['admin', 'analyst', 'bioinformatician', 'projects', 'manager'];
8-
const GERMLINE_BLOCK = ['clinician', 'collaborator'];
9-
const REPORT_ACCESS = ['*'];
10-
const REPORT_BLOCK = [];
11-
const ADMIN_ACCESS = ['admin'];
12-
const ADMIN_BLOCK = [];
13-
14-
type UseResourcesReturnType = {
15-
germlineAccess: boolean;
16-
reportAccess: boolean;
17-
adminAccess: boolean;
18-
};
19-
20-
const useResources = (): UseResourcesReturnType => {
21-
const { userDetails } = useContext(SecurityContext);
22-
23-
const [germlineAccess, setGermlineAccess] = useState(false);
24-
const [reportAccess, setReportAccess] = useState(false);
25-
const [adminAccess, setAdminAccess] = useState(false);
26-
27-
useEffect(() => {
28-
if (userDetails) {
29-
if (checkAccess(userDetails.groups, GERMLINE_ACCESS, GERMLINE_BLOCK)) {
30-
setGermlineAccess(true);
31-
}
32-
33-
if (checkAccess(userDetails.groups, REPORT_ACCESS, REPORT_BLOCK)) {
34-
setReportAccess(true);
35-
}
36-
37-
if (checkAccess(userDetails.groups, ADMIN_ACCESS, ADMIN_BLOCK)) {
38-
setAdminAccess(true);
39-
}
40-
}
41-
}, [userDetails]);
42-
43-
return { germlineAccess, reportAccess, adminAccess };
44-
};
454

465
const useResource = (): ResourceContextType => useContext(ResourceContext);
476

48-
export { useResources };
49-
507
export default useResource;

app/views/ReportView/components/KbMatches/index.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import {
99
FilterList,
1010
} from '@mui/icons-material';
1111

12-
import partition from 'lodash.partition';
13-
1412
import api, { ApiCallSet } from '@/services/api';
1513
import snackbar from '@/services/SnackbarUtils';
1614
import DemoDescription from '@/components/DemoDescription';
@@ -75,6 +73,8 @@ const KbMatches = ({
7573
api.get(`${baseUri}?approvedTherapy=true&category=therapeutic&matchedCancer=true`, {}),
7674
api.get(`${baseUri}?approvedTherapy=true&category=therapeutic&matchedCancer=false`, {}),
7775
api.get(`/reports/${report.ident}/probe-results`, {}),
76+
api.get(`${baseUri}?category=pharmacogenomic`, {}),
77+
api.get(`${baseUri}?category=cancer predisposition`, {}),
7878
]);
7979

8080
const [
@@ -85,11 +85,11 @@ const KbMatches = ({
8585
unknownResp,
8686
thisCancerResp,
8787
otherCancerResp,
88-
targetedGenesResp,
88+
targetedSomaticGenesResp,
89+
pharmacogenomicResp,
90+
cancerPredisResp,
8991
] = await apiCalls.request();
9092

91-
const [targetedGermlineGenes, targetedSomaticGenes] = partition(targetedGenesResp, (tg) => /germline/.test(tg?.sample));
92-
9393
setGroupedMatches({
9494
thisCancer: coalesceEntries(thisCancerResp),
9595
otherCancer: coalesceEntries(otherCancerResp),
@@ -98,8 +98,11 @@ const KbMatches = ({
9898
diagnostic: coalesceEntries(diagnosticResp),
9999
prognostic: coalesceEntries(prognosticResp),
100100
unknown: coalesceEntries(unknownResp),
101-
targetedGermlineGenes,
102-
targetedSomaticGenes,
101+
targetedGermlineGenes: coalesceEntries([
102+
...pharmacogenomicResp,
103+
...cancerPredisResp.filter(({ variant }) => variant?.germline),
104+
]),
105+
targetedSomaticGenes: targetedSomaticGenesResp.filter((tg) => !/germline/.test(tg?.sample)),
103106
});
104107
} catch (err) {
105108
snackbar.error(`Network error: ${err}`);
@@ -160,9 +163,7 @@ const KbMatches = ({
160163
<DataTable
161164
canDelete={canEdit}
162165
canToggleColumns
163-
columnDefs={(key === 'targetedSomaticGenes' || key === 'targetedGermlineGenes')
164-
? targetedColumnDefs
165-
: columnDefs}
166+
columnDefs={(key === 'targetedSomaticGenes') ? targetedColumnDefs : columnDefs}
166167
filterText={filterText}
167168
isPrint={isPrint}
168169
onDelete={handleDelete}

0 commit comments

Comments
 (0)