Skip to content

Commit be5c55c

Browse files
committed
Merge branch 'main' into issue-676
2 parents e3f21e3 + 708f71b commit be5c55c

File tree

13 files changed

+438
-205
lines changed

13 files changed

+438
-205
lines changed

.github/workflows/pr-check-ts.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ jobs:
1919
steps:
2020
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
2121
- uses: actions/checkout@v2
22+
- uses: actions/setup-node@v2
23+
with:
24+
node-version: '14'
2225

2326
# Runs a set of commands using the runners shell
2427
- name: Check TypeScript compilation

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ The following environment variables are used by the `yarn start` script:
6363
| `EDA_DATA_SERVICE_URL` | Full url to a running EDA Data Service |
6464
| `EDA_USER_SERVICE_URL` | Full url to a running EDA User Service |
6565
| `DATASET_ACCESS_SERVICE_URL` | Full url to a running Dataset Access Service |
66-
| `WDK_CHECK_AUTH` | An optional auth key for a registered WDK user |
6766
| `REACT_APP_DISABLE_DATA_RESTRICTIONS` | If present and `true`, disables data restrictions |
6867
| `REACT_APP_EXAMPLE_ANALYSES_AUTHOR` | The ID of the WDK user who maintains "example strategies" (optional) |
6968

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
{
22
"name": "@veupathdb/eda",
3-
"version": "1.1.33",
3+
"version": "1.1.37",
44
"dependencies": {
55
"@emotion/react": "^11.4.1",
66
"@emotion/styled": "^11.3.0",
77
"@material-ui/core": "^4.11.3",
88
"@material-ui/icons": "^4.11.2",
99
"@material-ui/lab": "^4.0.0-alpha.58",
1010
"@types/debounce-promise": "^3.1.3",
11-
"@veupathdb/components": "^0.11.1",
11+
"@veupathdb/components": "^0.11.2",
1212
"@veupathdb/core-components": "^0.2.45",
1313
"@veupathdb/http-utils": "^1.0.1",
14-
"@veupathdb/study-data-access": "^0.0.3",
15-
"@veupathdb/wdk-client": "^0.4.9",
14+
"@veupathdb/study-data-access": "^0.1.2",
15+
"@veupathdb/wdk-client": "^0.4.12",
1616
"debounce-promise": "^3.1.2",
1717
"file-saver": "^2.0.5",
1818
"fp-ts": "^2.9.3",
@@ -74,7 +74,7 @@
7474
"@veupathdb/prettier-config": "^1.0.0",
7575
"@veupathdb/react-scripts": "^1.0.1",
7676
"@veupathdb/tsconfig": "^1.0.1",
77-
"@veupathdb/web-common": "^0.1.10",
77+
"@veupathdb/web-common": "^0.1.12",
7878
"bubleify": "^2.0.0",
7979
"http-proxy-middleware": "^1.0.6",
8080
"husky": "^4.3.8",

src/Header.tsx

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,39 @@
11
import * as React from 'react';
2+
import { Link } from 'react-router-dom';
3+
import { useNonNullableContext } from '@veupathdb/wdk-client/lib/Hooks/NonNullableContext';
4+
import { WdkDependenciesContext } from '@veupathdb/wdk-client/lib/Hooks/WdkDependenciesEffect';
5+
import { User } from '@veupathdb/wdk-client/lib/Utils/WdkUser';
6+
import { endpoint } from './constants';
27

38
export default function Header() {
9+
const [showLoginForm, setShowLoginForm] = React.useState(false);
10+
const [email, setEmail] = React.useState('');
11+
const [pwd, setPwd] = React.useState('');
12+
const [user, setUser] = React.useState<User>();
13+
const [errorMsg, setErrorMsg] = React.useState<string>('');
14+
const { wdkService } = useNonNullableContext(WdkDependenciesContext);
15+
16+
React.useEffect(() => {
17+
wdkService.getCurrentUser().then(setUser);
18+
}, [wdkService]);
19+
20+
async function login() {
21+
setErrorMsg('');
22+
const response = await wdkService.tryLogin(email, pwd, '');
23+
if (response.success) {
24+
window.location.reload();
25+
} else {
26+
setErrorMsg(response.message);
27+
}
28+
}
29+
30+
async function logout() {
31+
await fetch(`${endpoint}/logout`, {
32+
credentials: 'include',
33+
});
34+
window.location.assign('/');
35+
}
36+
437
return (
538
<h1 style={{ background: 'black', color: 'whitesmoke' }}>
639
{/* eslint-disable-next-line react/jsx-no-comment-textnodes */}
@@ -9,6 +42,110 @@ export default function Header() {
942
<code>||| VEUPATHDB DEVELOPMENT SITE |||</code>
1043
<br />
1144
<code>\\\ ========================== ///</code>
45+
<div
46+
style={{ fontSize: '1rem', padding: '1em 1em 0', position: 'relative' }}
47+
>
48+
{user == null ? (
49+
<>Loading user...</>
50+
) : user.isGuest ? (
51+
<>
52+
<button
53+
type="button"
54+
className="link"
55+
style={{ color: 'whitesmoke' }}
56+
onClick={() => setShowLoginForm(true)}
57+
>
58+
Login
59+
</button>
60+
</>
61+
) : (
62+
<button
63+
type="button"
64+
className="link"
65+
style={{ color: 'whitesmoke' }}
66+
onClick={() => logout()}
67+
>
68+
Logout ({user.email})
69+
</button>
70+
)}
71+
{showLoginForm && (
72+
<div
73+
style={{
74+
position: 'absolute',
75+
background: 'black',
76+
zIndex: 100,
77+
padding: '1em',
78+
left: 0,
79+
}}
80+
>
81+
<form
82+
onSubmit={(e) => {
83+
e.preventDefault();
84+
login();
85+
}}
86+
>
87+
<div>
88+
<label>
89+
Username:{' '}
90+
<input
91+
autoFocus
92+
value={email}
93+
onChange={(e) => setEmail(e.target.value)}
94+
style={{ color: 'black', width: '100%' }}
95+
type="text"
96+
/>
97+
</label>
98+
</div>
99+
<div>
100+
<label>
101+
Password:{' '}
102+
<input
103+
value={pwd}
104+
onChange={(e) => setPwd(e.target.value)}
105+
style={{ color: 'black', width: '100%' }}
106+
type="password"
107+
/>
108+
</label>
109+
</div>
110+
<div
111+
style={{
112+
paddingTop: '1em',
113+
fontSize: '.85em',
114+
display: 'flex',
115+
justifyContent: 'space-between',
116+
alignItems: 'end',
117+
}}
118+
>
119+
<div>
120+
<button style={{ fontSize: '1em' }} type="submit">
121+
Submit
122+
</button>{' '}
123+
&nbsp;
124+
<button
125+
style={{ fontSize: '1em' }}
126+
type="button"
127+
onClick={() => setShowLoginForm(false)}
128+
>
129+
Cancel
130+
</button>
131+
</div>
132+
<Link
133+
to="/user/registration"
134+
style={{ color: 'whitesmoke' }}
135+
onClick={() => setShowLoginForm(false)}
136+
>
137+
Register
138+
</Link>
139+
</div>
140+
<div
141+
style={{ color: 'red', fontSize: '.8em', paddingTop: '.5em' }}
142+
>
143+
{errorMsg}
144+
</div>
145+
</form>
146+
</div>
147+
)}
148+
</div>
12149
</h1>
13150
);
14151
}

src/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import './globals'; // Don't move this. There is a brittle dependency that relies on this being first.
22
import React, { useEffect } from 'react';
33

4+
import { partial } from 'lodash';
5+
46
import { initialize } from '@veupathdb/web-common/lib/bootstrap';
57
import { Link } from '@veupathdb/wdk-client/lib/Components';
68
import { RouteEntry } from '@veupathdb/wdk-client/lib/Core/RouteEntry';
@@ -9,6 +11,7 @@ import '@veupathdb/web-common/lib/styles/client.scss';
911
import { Props } from '@veupathdb/wdk-client/lib/Components/Layout/Page';
1012

1113
import { DataRestrictionDaemon } from '@veupathdb/study-data-access/lib/data-restriction';
14+
import { wrapWdkDependencies } from '@veupathdb/study-data-access/lib/shared/wrapWdkDependencies';
1215
import {
1316
disableRestriction,
1417
enableRestriction,
@@ -104,6 +107,7 @@ initialize({
104107
};
105108
},
106109
},
110+
wrapWdkDependencies: partial(wrapWdkDependencies, '/eda-dataset-access'),
107111
endpoint,
108112
additionalMiddleware: [reduxMiddleware],
109113
} as any);
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import React from 'react';
2+
import {
3+
XYPlotDataSeries,
4+
XYPlotData,
5+
FacetedData,
6+
} from '@veupathdb/components/lib/types/plots';
7+
import { VariableTreeNode } from '../types/study';
8+
9+
type FacetDataProps = FacetedData<XYPlotData>['facets'];
10+
11+
// introduce discriminated union
12+
type TypedScatterplotRsquareData =
13+
| {
14+
isFaceted: false;
15+
data?: XYPlotDataSeries[];
16+
}
17+
| {
18+
isFaceted: true;
19+
// FacetDataProps is already array, so not FacetDataProps[]
20+
data?: FacetDataProps;
21+
};
22+
23+
export interface Props {
24+
typedData: TypedScatterplotRsquareData;
25+
overlayVariable?: VariableTreeNode;
26+
facetVariable?: VariableTreeNode;
27+
}
28+
29+
export function ScatterplotRsquareTable({
30+
typedData,
31+
overlayVariable,
32+
facetVariable,
33+
}: Props) {
34+
// non-facet or facet data
35+
const typedFilteredData: TypedScatterplotRsquareData = !typedData.isFaceted
36+
? {
37+
isFaceted: false,
38+
data:
39+
overlayVariable != null
40+
? typedData.data?.filter((data) =>
41+
data.name?.includes(', Best fit')
42+
)
43+
: typedData.data?.filter((data) => data.name?.includes('Best fit')),
44+
}
45+
: {
46+
isFaceted: true,
47+
data: typedData.data?.filter((element) => element.data != null),
48+
};
49+
50+
if (!typedFilteredData.isFaceted) {
51+
return (
52+
<div className={'ScatterRsquareTable'}>
53+
<table>
54+
<tbody>
55+
<tr>
56+
<th>
57+
{overlayVariable != null ? overlayVariable.displayName : 'Name'}
58+
</th>
59+
<th className="numeric">
60+
R<sup>2</sup> (Best fit)
61+
</th>
62+
</tr>
63+
{typedFilteredData.data?.map((data) => (
64+
<tr key={data.name}>
65+
<td>{data?.name?.split(', Best fit')[0]}</td>
66+
<td>{data.r2 ?? 'N/A'}</td>
67+
</tr>
68+
))}
69+
</tbody>
70+
</table>
71+
</div>
72+
);
73+
} else {
74+
return (
75+
<div
76+
className={'ScatterRsquareTable'}
77+
style={{ maxHeight: 250, overflowX: 'hidden', overflowY: 'auto' }}
78+
>
79+
<table>
80+
<tbody>
81+
<tr>
82+
<th>{facetVariable?.displayName}</th>
83+
{overlayVariable != null && (
84+
<th>{overlayVariable.displayName}</th>
85+
)}
86+
<th className="numeric">
87+
R<sup>2</sup> (Best fit)
88+
</th>
89+
</tr>
90+
{typedFilteredData.data?.map((data) => (
91+
<React.Fragment key={data.label}>
92+
{data?.data?.series != null
93+
? data.data.series
94+
.filter((series) =>
95+
overlayVariable != null
96+
? series?.name?.includes(', Best fit')
97+
: series?.name?.includes('Best fit')
98+
)
99+
.map((series, index, array) => {
100+
if (index === 0) {
101+
return (
102+
<tr key={data.label + series.name}>
103+
{/* each vocabulary/name have different number of available data ,so need to check rowSpan per data */}
104+
<td
105+
rowSpan={
106+
overlayVariable != null
107+
? array.filter((arr) =>
108+
arr?.name?.includes(', Best fit')
109+
).length
110+
: 1
111+
}
112+
>
113+
{data.label}
114+
</td>
115+
{overlayVariable != null && (
116+
<td>{series?.name?.split(', Best fit')[0]}</td>
117+
)}
118+
<td>{series.r2 ?? 'N/A'}</td>
119+
</tr>
120+
);
121+
} else {
122+
return (
123+
<tr key={data.label + series.name}>
124+
{overlayVariable != null && (
125+
<td>{series?.name?.split(', Best fit')[0]}</td>
126+
)}
127+
<td>{series.r2 ?? 'N/A'}</td>
128+
</tr>
129+
);
130+
}
131+
})
132+
: ''}
133+
</React.Fragment>
134+
))}
135+
</tbody>
136+
</table>
137+
</div>
138+
);
139+
}
140+
}

src/lib/core/components/visualizations/Visualizations.scss

100644100755
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,20 @@ $data-table-inner-border: 1px solid #888;
274274
visibility: hidden;
275275
}
276276
}
277+
278+
// R-square table for Scatter plot's Best fit option
279+
.ScatterRsquareTable {
280+
@include data-table;
281+
width: 400px;
282+
283+
.percentage {
284+
color: #333;
285+
font-size: 0.85em;
286+
}
287+
th {
288+
vertical-align: baseline;
289+
}
290+
th:first-of-type {
291+
border-bottom: $data-table-inner-border;
292+
}
293+
}

0 commit comments

Comments
 (0)