Skip to content

Commit

Permalink
Merge pull request #1129 from salvatore-fxpig/no-unexisting-datasets
Browse files Browse the repository at this point in the history
Handle non-valid dataset paths
  • Loading branch information
jameshadfield committed May 28, 2020
2 parents 00aaaae + 41f4847 commit ec8b86d
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 52 deletions.
3 changes: 2 additions & 1 deletion cli/server/getDataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const setUpGetDatasetHandler = ({datasetsPath}) => {
try {
const availableDatasets = await getAvailable.getAvailableDatasets(datasetsPath);
const info = helpers.interpretRequest(req, datasetsPath);
helpers.extendDataPathsToMatchAvailable(info, availableDatasets);
const redirected = helpers.redirectIfDatapathMatchFound(res, info, availableDatasets);
if (redirected) return;
helpers.makeFetchAddresses(info, datasetsPath, availableDatasets);
await helpers.sendJson(res, info);
} catch (err) {
Expand Down
34 changes: 22 additions & 12 deletions cli/server/getDatasetHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,29 @@ const interpretRequest = (req) => {
};

/**
* Given a request, does the dataset exist?
* In the future, if there is no exact match but a partial one we
* should extend this. E.g. `["flu"]` -> `["flu", "h3n2", "ha", "3y"]`
* In that case, we should utilise `res.redirect` as we do in the nextstrain.org server
* @throws
* Given a request, if the dataset does not exist then either
* (a) redirect to an appropriate dataset if possible & return `true`
* (b) throw.
* If the dataset existed then return `false` as no redirect necessary.
*/
const extendDataPathsToMatchAvailable = (info, availableDatasets) => {
const requestStrToMatch = info.parts.join("/"); // TO DO
/* TODO currently there must be an _exact_ match in the available datasets */
if (!availableDatasets.map((d) => d.request).includes(requestStrToMatch)) {
throw new Error(`${requestStrToMatch} not in available datasets`);
const redirectIfDatapathMatchFound = (res, info, availableDatasets) => {
let matchingDatasets = availableDatasets;
let i;
const matchDatasetRequest = (d) => d.request.split("/")[i] === info.parts[i];
// Filter gradually by path fragment, starting from the root
for (i = 0; i < info.parts.length; i++) {
const newMatchingDatasets = matchingDatasets.filter(matchDatasetRequest);
if (!newMatchingDatasets.length) break;
matchingDatasets = newMatchingDatasets;
}
// If root fragment does cannot be matched, throw
if (!i) throw new Error(`${info.parts.join("/")} not in available datasets`);
// If best match is not equal to path requested, redirect
if (matchingDatasets[0].request !== info.parts.join("/")) {
res.redirect(`./getDataset?prefix=/${matchingDatasets[0].request}`);
return true;
}
/* what we want to do is modify `info.parts` to match the available dataset, if possible */
return false;
};

/**
Expand Down Expand Up @@ -157,7 +167,7 @@ const findAvailableSecondTreeOptions = (currentDatasetUrl, availableDatasetUrls)

module.exports = {
interpretRequest,
extendDataPathsToMatchAvailable,
redirectIfDatapathMatchFound,
makeFetchAddresses,
handleError,
sendJson,
Expand Down
20 changes: 6 additions & 14 deletions src/components/controls/choose-dataset-select.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ import { controlsWidth } from "../../util/globals";


class ChooseDatasetSelect extends React.Component {
createDataPath(dataset) {
let p = (this.props.choice_tree.length > 0) ? "/" : "";
p += this.props.choice_tree.join("/") + "/" + dataset;
p = p.replace(/\/+/, "/");
return p;
}
changeDataset(newPath) {
// 0 analytics (optional)
analyticsControlsEvent(`change-virus-to-${newPath.replace(/\//g, "")}`);
Expand All @@ -24,22 +18,20 @@ class ChooseDatasetSelect extends React.Component {
}
this.props.dispatch(changePage({path: newPath}));
}
getDatasetOptions() {
return this.props.options ?
this.props.options.map((opt) => ({value: opt, label: opt})) :
{};
}
render() {
const datasetOptions = this.getDatasetOptions();
return (
<div style={{width: controlsWidth, fontSize: 14}}>
<Select
value={this.props.selected}
options={datasetOptions}
options={this.props.options || []}
clearable={false}
searchable={false}
multi={false}
onChange={(opt) => {this.changeDataset(this.createDataPath(opt.value));}}
onChange={(opt) => {
if (opt.value !== this.props.selected) {
this.changeDataset(`/${opt.value}`);
}
}}
/>
</div>
);
Expand Down
39 changes: 14 additions & 25 deletions src/components/controls/choose-dataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,39 +38,28 @@ class ChooseDataset extends React.Component {
.replace(/\/$/, '')
.split(":")[0];
const displayedDataset = displayedDatasetString.split("/");
const options = [[]];

this.props.available.datasets.forEach((d) => {
const firstField = d.request.split("/")[0];
if (!options[0].includes(firstField)) {
options[0].push(firstField);
}
});


for (let idx=1; idx<displayedDataset.length; idx++) {
/* going through the fields which comprise the current dataset
in order to create available alternatives for each field */
options[idx] = [];
this.props.available.datasets.forEach((singleAvailableOption) => {
/* if the parents (and their parents etc) of this choice match,
then we add that as a valid option */
const fields = singleAvailableOption.request.split("/");
if (checkEqualityOfArrays(fields, displayedDataset, idx) && options[idx].indexOf(fields[idx]) === -1) {
options[idx].push(fields[idx]);
}
});
}
const options = displayedDataset.map((_, i) =>
Array.from(
new Set(
this.props.available.datasets
.filter((ds) => checkEqualityOfArrays(ds.request.split("/"), displayedDataset, i))
.map((ds) => ds.request.split("/")[i])
)
).map((opt) => ({
value: displayedDataset.slice(0, i).concat(opt).join("/"),
label: opt
}))
);

return (
<>
<SidebarHeader>{t("sidebar:Dataset")}</SidebarHeader>
{options.map((option, optionIdx) => (
<ChooseDatasetSelect
key={option}
key={displayedDataset[optionIdx]}
dispatch={this.props.dispatch}
choice_tree={displayedDataset.slice(0, optionIdx)}
selected={displayedDataset[optionIdx]}
selected={displayedDataset.slice(0, optionIdx + 1).join("/")}
options={option}
/>
))}
Expand Down

0 comments on commit ec8b86d

Please sign in to comment.