Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only support downloading Auspice JSONs if dataset name can be parsed #1804

Merged
merged 4 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

* Fix bug where app crashed if measurements JSON did not define thresholds ([#1802](https://github.com/nextstrain/auspice/pull/1802))
* Fix bug where measurements display did not honor the default `measurements_display` ([#1802](https://github.com/nextstrain/auspice/pull/1802))
* Only display download-JSON button if the dataset name can be parsed from pathname ([#1804](https://github.com/nextstrain/auspice/pull/1804))

## version 2.56.0 - 2024/07/01

Expand Down
9 changes: 7 additions & 2 deletions src/components/download/downloadButtons.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { materialButton } from "../../globalStyles";
import * as helpers from "./helperFunctions";
import { getFullAuthorInfoFromNode } from "../../util/treeMiscHelpers";
import { getNumSelectedTips } from "../../util/treeVisibilityHelpers";
import { getDatasetNamesFromUrl } from "../../actions/loadData";

const RectangularTreeIcon = withTheme(icons.RectangularTree);
const PanelsGridIcon = withTheme(icons.PanelsGrid);
Expand Down Expand Up @@ -35,6 +36,10 @@ export const DownloadButtons = ({dispatch, t, tree, entropy, metadata, colorBy,
}
}
}
/* Verify that we can parse dataset name from URL to support Auspice JSON download */
const datasetNames = getDatasetNamesFromUrl(window.location.pathname);
const supportAuspiceJsonDownload = !gisaidProvenance && datasetNames.some(Boolean);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a bug in auspice.us (?) where dragging on two trees results in them being added to the URL. As such, datasetNames contains 2 truthy values, and we allow the download, which then fails.

Using dev_multi_h3n2.json and ebola.json (any two JSONs will do) we get a URL of https://auspice-us-nextstrain-b-q2kkxc.herokuapp.com/dev/multi/h3n2:ebola, and the following error when trying to download:

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I did not realize that the URL gets updated in auspice.us when there's a two trees.
Looks like it's coming from the changeURLMiddleware in auspice:

/* we also double check that if there are 2 trees both are represented
in the URL */
if (action.tree.name && action.treeToo && action.treeToo.name && !action.narrative) {
const treeUrlShouldBe = `${action.tree.name}:${action.treeToo.name}`;
if (!window.location.pathname.includes(treeUrlShouldBe)) {
pathname = treeUrlShouldBe;
}
}

I assume the URL should never be updated in auspice.us if you are calling this a bug? How is that configured?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, maybe we just shouldn't rely on the URL parsing here to determine if download is supported. We can add a disableAuspiceJsonDownload config param to the extensions and have auspice.us explicitly disable the downloads.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume the URL should never be updated in auspice.us if you are calling this a bug? How is that configured?

auspice.us, once it's parsed the dropped files, initialises the Auspice viz via:

  /* load (into redux state) the main dataset(s) */
  try {
    dispatch({
      type: "CLEAN_START",
      pathnameShouldBe: "",
      ...createStateFromQueryOrJSONs({

and the intention is that Auspice updates the pathname (URL) to the empty string via:

case types.CLEAN_START:
if (action.pathnameShouldBe && !action.narrative) {
pathname = action.pathnameShouldBe;
break;

and then the switch statement ends. But "" is falsey, so we keep going in the switch statement and then, when there are two trees, we hit the case you linked above. I think checking if pathnameShouldBe is a property on the action rather than just the truthyness of its value is the correct fix.

Hmm, maybe we just shouldn't rely on the URL parsing here to determine if download is supported. We can add a disableAuspiceJsonDownload config param to the extensions and have auspice.us explicitly disable the downloads.

I think once the bugs identified here are fixed the URL parsing will still be appropriate. (The particular bug in this thread is, strictly speaking, separate to this PR, but it'd be good to just fix it here.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in a9c4610.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed in auspice.us test app that the URL does not update with multiple trees.


const entropyBar = entropy?.selectedCds===nucleotide_gene ? "nucleotide" : "codon";

return (
Expand All @@ -48,12 +53,12 @@ export const DownloadButtons = ({dispatch, t, tree, entropy, metadata, colorBy,
<p/>
{partialData ? `Currently ${selectedTipsCount}/${totalTipCount} tips are displayed and will be downloaded.` : `Currently the entire dataset (${totalTipCount} tips) will be downloaded.`}
</div>
{!gisaidProvenance && (
{supportAuspiceJsonDownload && (
<Button
name="Auspice (Nextstrain) JSON"
description={`The main Auspice dataset JSON(s) for the current view`}
icon={<DatasetIcon width={iconWidth} selected />}
onClick={() => helpers.auspiceJSON(dispatch)}
onClick={() => helpers.auspiceJSON(dispatch, datasetNames)}
/>
)}
<Button
Expand Down
16 changes: 10 additions & 6 deletions src/components/download/helperFunctions.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { NODE_VISIBLE, nucleotide_gene } from "../../util/globals";
import { datasetSummary } from "../info/datasetSummary";
import { isColorByGenotype } from "../../util/getGenotype";
import { EmptyNewickTreeCreated } from "../../util/exceptions";
import { getDatasetNamesFromUrl, Dataset } from "../../actions/loadData";
import { Dataset } from "../../actions/loadData";

export const isPaperURLValid = (d) => {
return (
Expand Down Expand Up @@ -623,15 +623,19 @@ export const entropyTSV = (dispatch, filePrefix, entropy) => {
/**
* Write out Auspice JSON(s) for the current view. We do this by re-fetching the original
* JSON because we don't keep a copy of the unprocessed data around.
*
*
* Sidecar files are not fetched, but we can also download them if desired.
*
*
* Note that we are not viewing a narrative, as the download button functionality is disabled
* for narratives.
*/
export const auspiceJSON = (dispatch) => {
export const auspiceJSON = (dispatch, datasetNames) => {
const filenames = [];
for (const datasetName of getDatasetNamesFromUrl(window.location.pathname)) {
if (!datasetNames.some(Boolean)) {
console.error(`Unable to fetch empty dataset names: ${JSON.stringify(datasetNames)}`);
return dispatch(errorNotification({message: "Unable to download Auspice JSON (see console for more info)"}))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

datasetNames is always an array of length 2. So for /zika it's [ "zika", undefined ], and thus we end up showing a error notification and not downloading the JSON.

Perhaps the intention here was .all(Boolean)? But that's unnecessary because of the conditions under which auspiceJSON() gets called.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks for catching that. I meant .some(Boolean) to verify at least one of them is truthy.

But that's unnecessary because of the conditions under which auspiceJSON() gets called.

Yeah, I added this guard to get error notifications on unexpected use of auspiceJSON() instead of just letting it hang on the "Preparing Auspice JSON..." notification.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed up in force-push

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks for catching that. I meant .some(Boolean) to verify at least one of them is truthy.

Nice one. I don't know what I meant by suggesting .all, which isn't even an array prototype method!

}
for (const datasetName of datasetNames) {
if (!datasetName) continue; // e.g. no 2nd tree
const filename = datasetName.replace('/', '_') + '.json';
filenames.push(filename);
Expand All @@ -647,4 +651,4 @@ export const auspiceJSON = (dispatch) => {
});
}
dispatch(infoNotification({message: `Preparing Auspice JSON(s) for download: ${filenames.join(', ')}`}));
};
};
2 changes: 1 addition & 1 deletion src/middleware/changeURL.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ export const changeURLMiddleware = (store) => (next) => (action) => {
/* second switch: path change */
switch (action.type) {
case types.CLEAN_START:
if (action.pathnameShouldBe && !action.narrative) {
if (typeof action.pathnameShouldBe === "string" && !action.narrative) {
pathname = action.pathnameShouldBe;
break;
}
Expand Down