auspice
Version:
Web app for visualizing pathogen evolution
143 lines (129 loc) • 5.34 kB
JavaScript
import queryString from "query-string";
import * as types from "./types";
import { charonAPIAddress } from "../util/globals";
import { goTo404 } from "./navigation";
import { createStateFromQueryOrJSONs, createTreeTooState } from "./recomputeReduxState";
import { loadFrequencies } from "./frequencies";
import { fetchJSON } from "../util/serverInteraction";
import { warningNotification } from "./notifications";
import { hasExtension, getExtension } from "../util/extensions";
/* TODO: make a default auspice server (not charon) and make charon the nextstrain server. Or vice versa. */
const serverAddress = hasExtension("serverAddress") ? getExtension("serverAddress") : charonAPIAddress;
const getDatasetFromCharon = (prefix, {type, narrative=false}={}) => {
let path = `${serverAddress}/${narrative?"getNarrative":"getDataset"}`;
path += `?prefix=${prefix}`;
if (type) path += `&type=${type}`;
const p = fetch(path)
.then((res) => {
if (res.status !== 200) {
throw new Error(res.statusText);
}
return res;
})
.then((res) => res.json());
return p;
};
const getHardcodedData = (prefix, {type="mainJSON", narrative=false}={}) => {
/* we currently expect a single dataset to be present in "hardcodedDataPaths".
This may be extended to multiple in the future... */
const datapaths = getExtension("hardcodedDataPaths");
console.log("FETCHING", datapaths[type]);
const p = fetch(datapaths[type])
.then((res) => {
if (res.status !== 200) {
throw new Error(res.statusText);
}
return res;
})
.then((res) => res.json());
return p;
};
// const fetchData = hasExtension("hardcodedDataPaths") ? getHardcodedData : getDatasetFromCharon;
const getDataset = hasExtension("hardcodedDataPaths") ? getHardcodedData : getDatasetFromCharon;
const fetchDataAndDispatch = (dispatch, url, query, narrativeBlocks) => {
let warning = false;
let fetchExtras = "";
/* currently we support backwards compatability with the old (deprecated) tt=... URL query
syntax for defining the second tree. This is not guaranteed to stay around */
if (query.tt) { /* deprecated form of adding a second tree */
warning = {
message: `Specifing a second tree via "tt=${query.tt}" is deprecated.`,
details: "The URL has been updated to reflect the new syntax 🙂"
};
fetchExtras += `&deprecatedSecondTree=${query.tt}`;
}
// fetchJSON(`${charonAPIAddress}request=mainJSON&url=${url}${fetchExtras}`)
getDataset(`${url}${fetchExtras}`)
.then((json) => {
dispatch({
type: types.CLEAN_START,
...createStateFromQueryOrJSONs({json, query, narrativeBlocks})
});
return {
frequencies: (json.meta.panels && json.meta.panels.indexOf("frequencies") !== -1)
};
})
.then((result) => {
if (result.frequencies === true) {
getDataset(url, {type: "tip-frequencies"})
.then((res) => dispatch(loadFrequencies(res)))
.catch((err) => console.error("Frequencies failed to fetch", err.message));
}
return false;
})
.then(() => {
/* Get available datasets -- this is needed for the sidebar dataset-change dropdowns etc */
fetchJSON(`${charonAPIAddress}/getAvailable?prefix=${window.location.pathname}`)
.then((res) => dispatch({type: types.SET_AVAILABLE, data: res}));
})
.catch((err) => {
if (err.message === "No Content") { // status code 204
/* TODO: add more helper functions for moving between pages in auspice */
return dispatch({
type: types.PAGE_CHANGE,
displayComponent: "splash",
pushState: true
});
}
console.warn(err, err.message);
dispatch(goTo404(`Couldn't load JSONs for ${url}`));
});
if (warning) {
dispatch(warningNotification(warning));
}
};
export const loadJSONs = ({url = window.location.pathname, search = window.location.search} = {}) => {
return (dispatch, getState) => {
const { tree } = getState();
if (tree.loaded) {
dispatch({type: types.DATA_INVALID});
}
const query = queryString.parse(search);
if (url.indexOf("narratives") !== -1) {
/* we want to have an additional fetch to get the narrative JSON, which in turn
tells us which data JSON to fetch... */
getDatasetFromCharon(url, {narrative: true})
.then((blocks) => {
const firstURL = blocks[0].dataset;
const firstQuery = queryString.parse(blocks[0].query);
if (query.n) firstQuery.n = query.n;
fetchDataAndDispatch(dispatch, firstURL, firstQuery, blocks);
})
.catch((err) => {
console.error("Error obtaining narratives", err.message);
dispatch(goTo404(`Couldn't load narrative for ${url}`));
});
} else {
fetchDataAndDispatch(dispatch, url, query);
}
};
};
export const loadTreeToo = (name, fields) => (dispatch, getState) => {
const oldState = getState();
getDataset(fields.join("/"), {type: "tree"})
.then((json) => {
const newState = createTreeTooState({treeTooJSON: json.tree, oldState, segment: name});
dispatch({type: types.TREE_TOO_DATA, segment: name, ...newState});
})
.catch((err) => console.error("Failed to fetch additional tree", err.message));
};