Skip to content

Commit 5e4484a

Browse files
committed
OCLOMRS-1044:Bug Fix: Pick Concepts from Source and Dictionaries
1 parent 2840ebc commit 5e4484a

File tree

9 files changed

+943
-25836
lines changed

9 files changed

+943
-25836
lines changed

Diff for: package-lock.json

+735-25,773
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"react-csv": "^2.0.3",
4242
"react-dom": "^17.0.2",
4343
"react-ga": "^3.3.0",
44+
"react-infinite-scroll-component": "^6.1.0",
4445
"react-redux": "^7.2.2",
4546
"react-router-dom": "^5.1.2",
4647
"react-scripts": "^4.0.3",
@@ -97,7 +98,7 @@
9798
"@types/jest": "^26.0.23",
9899
"@types/node": "^12.7.12",
99100
"cypress": "^7.5.0",
100-
"cypress-cucumber-preprocessor": "^4.1.2",
101+
"cypress-cucumber-preprocessor": "^4.1.0",
101102
"cypress-wait-until": "^1.7.1",
102103
"eslint-plugin-cypress": "^2.11.2",
103104
"husky": "^6.0.0",

Diff for: src/apps/concepts/components/ViewConceptsHeader.tsx

+154-55
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,27 @@ import { getContainerIdFromUrl } from "../utils";
1010
import {
1111
Button,
1212
createStyles,
13+
FormControlLabel,
14+
Grid,
15+
IconButton,
16+
Input,
17+
InputAdornment,
1318
makeStyles,
1419
Menu,
1520
MenuItem,
16-
TextField,
21+
Switch,
1722
Theme
1823
} from "@material-ui/core";
19-
import {
20-
PREFERRED_SOURCES_VIEW_ONLY,
21-
useAnchor
22-
} from "../../../utils";
24+
import { PREFERRED_SOURCES_VIEW_ONLY, useAnchor } from "../../../utils";
2325
import { APISource } from "../../sources";
24-
import { AccountTreeOutlined, FolderOpen } from "@material-ui/icons";
25-
import { APIDictionary } from '../../dictionaries/types';
26-
26+
import {
27+
AccountTreeOutlined,
28+
FolderOpen,
29+
Search as SearchIcon
30+
} from "@material-ui/icons";
31+
import { APIDictionary, Dictionary } from '../../dictionaries/types';
32+
import { VerifiedSource } from "../../../components/VerifiedSource";
33+
import InfiniteScroll from "react-infinite-scroll-component";
2734
interface Props {
2835
containerType: string;
2936
containerUrl?: string;
@@ -32,8 +39,14 @@ interface Props {
3239
children?: React.ReactNode[];
3340
sources: APISource[];
3441
dictionaries: APIDictionary[];
42+
showOnlyVerified: boolean;
43+
toggleShowVerified: React.ChangeEventHandler<HTMLInputElement>;
44+
goTo: Function;
45+
initialSearch: string;
46+
pathUrl: Function;
47+
dictionaryMeta?: { num_found?: number };
48+
sourcesMeta?: { num_found?: number };
3549
}
36-
3750
const useStyles = makeStyles((theme: Theme) =>
3851
createStyles({
3952
lightColour: {
@@ -43,6 +56,9 @@ const useStyles = makeStyles((theme: Theme) =>
4356
padding: "0.2rem 1rem",
4457
cursor: "none"
4558
},
59+
sourcesDropdownHeader: {
60+
padding: "0.5rem 1rem"
61+
},
4662
input: {
4763
cursor: "pointer",
4864
borderBottom: "1px dotted black",
@@ -60,30 +76,56 @@ const useStyles = makeStyles((theme: Theme) =>
6076
marginRight: "0.2rem",
6177
fill: "#8080809c"
6278
},
79+
searchInput: {
80+
textAlign: "center",
81+
fontSize: "larger"
82+
}
6383
})
6484
);
65-
6685
const ViewConceptsHeader: React.FC<Props> = ({
6786
containerType,
6887
containerUrl,
6988
gimmeAUrl,
7089
addConceptToDictionary,
7190
children,
7291
sources,
73-
dictionaries
92+
dictionaries,
93+
goTo,
94+
initialSearch,
95+
pathUrl,
96+
dictionaryMeta,
97+
sourcesMeta
7498
}) => {
75-
const [showSources, setShowSources] = useState(false);
76-
const [preferredSources, setPreferredSources] = useState< { name: string; url: string }[] >();
99+
const [showAllSources, setShowAllSources] = useState(false);
100+
const [queryString, setQueryString] = useState(initialSearch);
101+
const [currentSources, setCurrentSources] = useState<
102+
{ name: string; url: string }[]
103+
>();
77104
useEffect(() => {
78-
const defaultSources = Object.entries( PREFERRED_SOURCES_VIEW_ONLY).map(([key, value]) => ({ name: key, url: value }));
79-
if (showSources) {
80-
const allSources = defaultSources
105+
const defaultSources = Object.entries(
106+
PREFERRED_SOURCES_VIEW_ONLY
107+
).map(([key, value]) => ({ name: key, url: value }));
108+
if (showAllSources) {
109+
const allSources = defaultSources
81110
.concat(sources.map(s => ({ name: s.name, url: s.url })))
82111
.concat(dictionaries.map(d => ({ name: d.name, url: d.url })));
83-
setPreferredSources(allSources);
84-
} else setPreferredSources(defaultSources);
85-
}, [showSources, sources, dictionaries]);
86-
112+
setCurrentSources(allSources);
113+
console.log(allSources);
114+
} else setCurrentSources(defaultSources);
115+
}, [showAllSources, sources, dictionaries, initialSearch]);
116+
// TODO - Check if this is correct
117+
const fetchMoreData = () => {
118+
setTimeout(() => {
119+
const previousSources = currentSources
120+
?.filter(Boolean)
121+
.concat(currentSources.slice(11));
122+
setCurrentSources(previousSources);
123+
}, 1000);
124+
};
125+
const handleSearch = (q: string) => goTo(pathUrl({ q }));
126+
const handleShowSources = (event: React.ChangeEvent<HTMLInputElement>) => {
127+
setShowAllSources(event.target.checked);
128+
};
87129
const classes = useStyles();
88130
const isSourceContainer = containerType === SOURCE_CONTAINER;
89131
const isAddToDictionary = isSourceContainer && !!addConceptToDictionary;
@@ -92,14 +134,19 @@ const ViewConceptsHeader: React.FC<Props> = ({
92134
handleSwitchSourceClick,
93135
handleSwitchSourceClose
94136
] = useAnchor();
95-
96137
const getTitleBasedOnContainerType = () => {
97138
return isAddToDictionary
98139
? `Import existing concept from ${getContainerIdFromUrl(containerUrl)}`
99140
: `Concepts in ${
100141
containerType === DICTIONARY_VERSION_CONTAINER ? "v" : ""
101142
}${getContainerIdFromUrl(containerUrl)}`;
102143
};
144+
const dictionaryObj = dictionaryMeta ? dictionaryMeta:{};
145+
const dictionaryCount = dictionaryObj.num_found ? dictionaryObj.num_found:0;
146+
147+
const sourceObj = sourcesMeta ? sourcesMeta:{};
148+
const sourcesCount = sourceObj.num_found ? sourceObj.num_found:0;
149+
const totalCount = sourcesCount + dictionaryCount
103150

104151
const showSwitchSourceBasedOnContainerType = () => {
105152
return !isAddToDictionary ? null : (
@@ -123,46 +170,99 @@ const ViewConceptsHeader: React.FC<Props> = ({
123170
}}
124171
anchorEl={switchSourceAnchor}
125172
keepMounted
126-
open={Boolean(switchSourceAnchor)}
173+
open={!!switchSourceAnchor}
127174
onClose={handleSwitchSourceClose}
128175
>
129-
<TextField
130-
multiline
131-
className={classes.textField}
132-
InputProps={{
133-
className: classes.underline
134-
}}
135-
inputProps={{
136-
className: classes.input
137-
}}
138-
value={
139-
showSources ? "Choose a source/dictionary" : "Select a different source/dictionary"
140-
}
141-
onClick={() => setShowSources(!showSources)}
142-
/>
143-
{preferredSources?.map(({ name, url }) => (
144-
<MenuItem
145-
// replace because we want to keep the back button useful
146-
replace
147-
to={gimmeAUrl({}, `${url}concepts/`)}
148-
key={name}
149-
component={Link}
150-
onClick={handleSwitchSourceClose}
151-
data-testid={name}
176+
<Grid container direction="column" className={classes.sourcesDropdownHeader}>
177+
<FormControlLabel
178+
control={
179+
<Switch
180+
checked={showAllSources}
181+
onChange={handleShowSources}
182+
color="primary"
183+
name="displayVerified"
184+
/>
185+
}
186+
label={
187+
showAllSources
188+
? `Showing all Sources`
189+
: `Show all Sources`
190+
}
191+
/>
192+
{showAllSources && <form
193+
onSubmit={(e: React.SyntheticEvent) => {
194+
e.preventDefault();
195+
handleSearch(queryString);
196+
}}
152197
>
153-
{url?.includes("/collection") ? (
154-
<FolderOpen className={classes.sourceIcon} />
155-
) : (
156-
<AccountTreeOutlined className={classes.sourceIcon} />
157-
)}
158-
{name}
159-
</MenuItem>
160-
))}
198+
<Input
199+
color="primary"
200+
type="search"
201+
fullWidth
202+
placeholder={"Select an alternative source"}
203+
value={queryString}
204+
onChange={e => setQueryString(e.target.value)}
205+
endAdornment={
206+
<InputAdornment position="end">
207+
<IconButton onClick={() => handleSearch(queryString)}>
208+
<SearchIcon/>
209+
</IconButton>
210+
</InputAdornment>
211+
}
212+
/>
213+
</form>}
214+
</Grid>
215+
{showAllSources ? (
216+
<InfiniteScroll
217+
dataLength={totalCount}
218+
next={fetchMoreData}
219+
hasMore={true}
220+
loader={<h4>Loading...</h4>}
221+
endMessage={<h4>end</h4>}
222+
scrollableTarget="scrollableDiv"
223+
>
224+
{currentSources?.map(({ name, url }) => (
225+
<MenuItem
226+
// replace because we want to keep the back button useful
227+
replace
228+
to={gimmeAUrl({}, `${url}concepts/`)}
229+
key={name}
230+
component={Link}
231+
onClick={handleSwitchSourceClose}
232+
>
233+
{url?.includes("/collection") ? (
234+
<FolderOpen className={classes.sourceIcon} />
235+
) : (
236+
<AccountTreeOutlined className={classes.sourceIcon} />
237+
)}
238+
{name}
239+
</MenuItem>
240+
))}
241+
</InfiniteScroll>
242+
) : (
243+
currentSources?.map(({ name, url }) => (
244+
<MenuItem
245+
// replace because we want to keep the back button useful
246+
replace
247+
to={gimmeAUrl({}, `${url}concepts/`)}
248+
key={name}
249+
component={Link}
250+
onClick={handleSwitchSourceClose}
251+
data-testid={name}
252+
>
253+
{url?.includes("/collection") ? (
254+
<FolderOpen className={classes.sourceIcon} />
255+
) : (
256+
<AccountTreeOutlined className={classes.sourceIcon} />
257+
)}
258+
{name}
259+
</MenuItem>
260+
))
261+
)}
161262
</Menu>
162263
</>
163264
);
164265
};
165-
166266
return (
167267
<Header
168268
title={getTitleBasedOnContainerType()}
@@ -180,5 +280,4 @@ const ViewConceptsHeader: React.FC<Props> = ({
180280
</Header>
181281
);
182282
};
183-
184283
export default ViewConceptsHeader;

Diff for: src/apps/concepts/pages/ViewConceptsPage.tsx

+31-2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ export interface StateProps {
6060
loading: boolean;
6161
errors?: {};
6262
meta?: { num_found?: number };
63+
dictionaryMeta?: { num_found?: number };
64+
sourcesMeta?: { num_found?: number };
6365
profile?: APIProfile;
6466
usersOrgs?: APIOrg[];
6567
}
@@ -128,6 +130,8 @@ const ViewConceptsPage: React.FC<Props> = ({
128130
retrievePublicDictionaries,
129131
retrieveSource,
130132
meta = {},
133+
dictionaryMeta = {},
134+
sourcesMeta = {},
131135
profile,
132136
usersOrgs,
133137
containerType,
@@ -173,6 +177,9 @@ const ViewConceptsPage: React.FC<Props> = ({
173177
addToDictionary: dictionaryToAddTo
174178
} = queryParams;
175179

180+
const headerQueryParams: { q?: string } = useQueryParams();
181+
const { q: initialSearch = "" } = queryParams;
182+
176183
const sourceUrl = "/sources/";
177184
const collectionsUrl = "/collections/";
178185
const sourcesLimit = 0;
@@ -221,6 +228,7 @@ const ViewConceptsPage: React.FC<Props> = ({
221228
const isImporting = dictionaryToAddTo !== undefined;
222229

223230
const [q, setQ] = useState(initialQ);
231+
const [showOnlyVerified, setShowOnlyVerified] = useState(false);
224232

225233
const gimmeAUrl = (params: QueryParams = {}, conceptsUrl: string = url) => {
226234
const newParams: QueryParams = {
@@ -231,13 +239,21 @@ const ViewConceptsPage: React.FC<Props> = ({
231239
generalFilters: generalFilters,
232240
sourceFilters: sourceFilters,
233241
page: 1,
234-
q
242+
q,
235243
},
236244
...params
237245
};
238246
return `${conceptsUrl}?${qs.stringify(newParams)}`;
239247
};
240248

249+
const pathUrl = (params: { q?: string }) => {
250+
const newParams: { q?: string } = {
251+
...headerQueryParams,
252+
...params
253+
};
254+
return `${url}?${qs.stringify(newParams)}`;
255+
};
256+
241257
useEffect(() => {
242258
if (containerUrl) {
243259
// we don't make this reactive(only depend on the initial values), because the requirement
@@ -305,7 +321,14 @@ const ViewConceptsPage: React.FC<Props> = ({
305321
gimmeAUrl={gimmeAUrl}
306322
addConceptToDictionary={dictionaryToAddTo}
307323
sources={sources}
308-
dictionaries={dictionaries}
324+
dictionaries={dictionaries}
325+
showOnlyVerified={showOnlyVerified}
326+
toggleShowVerified={(e)=>setShowOnlyVerified(e.target.checked)}
327+
goTo={goTo}
328+
initialSearch={initialSearch}
329+
pathUrl={pathUrl}
330+
dictionaryMeta={dictionaryMeta}
331+
sourcesMeta={sourcesMeta}
309332
>
310333
<Grid
311334
container
@@ -430,6 +453,12 @@ const mapStateToProps = (state: AppState) => {
430453
meta: state.concepts.concepts
431454
? state.concepts.concepts.responseMeta
432455
: undefined,
456+
dictionaryMeta: state.dictionaries.dictionaries[PUBLIC_DICTIONARIES_ACTION_INDEX]
457+
? state.dictionaries.dictionaries[PUBLIC_DICTIONARIES_ACTION_INDEX].responseMeta
458+
: undefined,
459+
sourcesMeta: state.sources.sources[PUBLIC_SOURCES_ACTION_INDEX]
460+
? state.sources.sources[PUBLIC_SOURCES_ACTION_INDEX].responseMeta
461+
: undefined,
433462
loading:
434463
viewConceptsLoadingSelector(state) ||
435464
retrieveDictionaryLoadingSelector(state) ||

0 commit comments

Comments
 (0)