Skip to content

Commit

Permalink
remove manifest, s3bucket etc from client
Browse files Browse the repository at this point in the history
remove entire datasets reducer
  • Loading branch information
jameshadfield committed Jul 24, 2018
1 parent 7329458 commit a7bfd62
Show file tree
Hide file tree
Showing 25 changed files with 122 additions and 533 deletions.
30 changes: 0 additions & 30 deletions src/actions/getAvailableDatasets.js

This file was deleted.

174 changes: 49 additions & 125 deletions src/actions/loadData.js
Original file line number Diff line number Diff line change
@@ -1,92 +1,25 @@
import queryString from "query-string";
import * as types from "./types";
import { charonAPIAddress } from "../util/globals";
import { getDatapath, goTo404, chooseDisplayComponentFromPathname, makeDataPathFromPathname } from "./navigation";
import { goTo404 } from "./navigation";
import { createStateFromQueryOrJSONs, createTreeTooState } from "./recomputeReduxState";
import parseParams, { createDatapathForSecondSegment } from "../util/parseParams";
import { loadFrequencies } from "./frequencies";
import { fetchJSON } from "../util/serverInteraction";

export const getManifest = (dispatch, s3bucket = "live") => {
const charonErrorHandler = () => {
console.warn("Failed to get manifest JSON from server");

const datapath = makeDataPathFromPathname(window.location.pathname);

dispatch({type: types.PROCEED_SANS_MANIFEST, datapath});
};
const processData = (data) => {
const datasets = JSON.parse(data);
// console.log("SERVER API REQUEST RETURNED:", datasets);
const availableDatasets = {pathogen: datasets.pathogen};
const datapath = chooseDisplayComponentFromPathname(window.location.pathname) === "app" ?
getDatapath(window.location.pathname, availableDatasets) :
undefined;
dispatch({
type: types.MANIFEST_RECEIVED,
s3bucket,
splash: datasets.splash,
availableDatasets,
user: "guest",
datapath
});
};

/* who am i? */
const query = queryString.parse(window.location.search);
const user = Object.keys(query).indexOf("user") === -1 ? "guest" : query.user;

const xmlHttp = new XMLHttpRequest();
xmlHttp.onload = () => {
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
processData(xmlHttp.responseText);
} else {
charonErrorHandler();
}
};
xmlHttp.onerror = charonErrorHandler;
xmlHttp.open("get", `${charonAPIAddress}request=manifest&user=${user}&s3=${s3bucket}`, true); // true for asynchronous
xmlHttp.send(null);
};

const getSegmentName = (datapath, availableDatasets) => {
/* this code is duplicated too many times. TODO */
if (!availableDatasets || !datapath) {
return undefined;
}

const paramFields = parseParams(datapath, availableDatasets).dataset;
const fields = Object.keys(paramFields).sort((a, b) => paramFields[a][0] > paramFields[b][0]);
const choices = fields.map((d) => paramFields[d][1]);
let level = availableDatasets;
for (let vi = 0; vi < fields.length; vi++) {
if (choices[vi]) {
const options = Object.keys(level[fields[vi]]).filter((d) => d !== "default");
if (Object.keys(level).indexOf("segment") !== -1 && options.length > 1) {
return choices[vi];
}
// move to the next level in the data set hierarchy
level = level[fields[vi]][choices[vi]];
}
}
return undefined;
};


const fetchDataAndDispatch = (dispatch, datasets, query, s3bucket, narrativeJSON) => {
const requestJSONPath = window.location.pathname; // .slice(1).replace(/_/g, "/");
const apiPath = (jsonType) => `${charonAPIAddress}request=json&want=${requestJSONPath}&type=${jsonType}`;
const fetchDataAndDispatch = (dispatch, url, query) => {
const apiPath = (jsonType) => `${charonAPIAddress}request=json&url=${url}&type=${jsonType}`;


// const treeName = getSegmentName(datasets.datapath, datasets.availableDatasets);
if (query.tt) { /* SECOND TREE */
console.warn("SECOND TREE TODO -- SERVER SHOULD ADD IT TO THE TREE/UNIFIED JSON");
}
Promise.all([fetch(apiPath("meta")).then((res) => res.json()), fetch(apiPath("tree")).then((res) => res.json())])
Promise.all([fetchJSON(apiPath("meta")), fetchJSON(apiPath("tree"))])
.then((values) => {
const data = {JSONs: {meta: values[0], tree: values[1]}, query};
if (narrativeJSON) {
data.JSONs.narrative = narrativeJSON;
}
// if (narrativeJSON) {
// data.JSONs.narrative = narrativeJSON;
// }
dispatch({
type: types.CLEAN_START,
...createStateFromQueryOrJSONs(data)
Expand All @@ -104,65 +37,56 @@ const fetchDataAndDispatch = (dispatch, datasets, query, s3bucket, narrativeJSON
})
.catch((err) => {
console.error(err.message);
dispatch(goTo404(`Couldn't load JSONs for ${requestJSONPath}`));
dispatch(goTo404(`Couldn't load JSONs for ${url}`));
});
};

const fetchNarrativesAndDispatch = (dispatch, datasets, query, s3bucket) => {
fetch(`${charonAPIAddress}request=narrative&name=${datasets.datapath.replace(/^\//, '').replace(/\//, '_').replace(/narratives_/, '')}`)
.then((res) => res.json())
.then((blocks) => {
const newDatasets = {...datasets};
newDatasets.datapath = getDatapath(blocks[0].dataset, datasets.availableDatasets);
fetchDataAndDispatch(dispatch, newDatasets, query, s3bucket, blocks);
})
.catch((err) => {
// some coding error in handling happened. This is not the rejection of the promise you think it is!
// syntax error is akin to a 404
console.error("Error in fetchNarrativesAndDispatch", err);
});

};

export const loadJSONs = (s3override = undefined) => {
// const fetchNarrativesAndDispatch = (dispatch, datasets, query) => {
// fetch(`${charonAPIAddress}request=narrative&name=${datasets.datapath.replace(/^\//, '').replace(/\//, '_').replace(/narratives_/, '')}`)
// .then((res) => res.json())
// .then((blocks) => {
// const newDatasets = {...datasets};
// newDatasets.datapath = getDatapath(blocks[0].dataset, datasets.availableDatasets);
// fetchDataAndDispatch(dispatch, newDatasets, query, blocks);
// })
// .catch((err) => {
// // some coding error in handling happened. This is not the rejection of the promise you think it is!
// // syntax error is akin to a 404
// console.error("Error in fetchNarrativesAndDispatch", err);
// });
//
// };

export const loadJSONs = ({url = window.location.pathname, search = window.location.search} = {}) => {
return (dispatch, getState) => {
const { datasets, tree } = getState();
const { tree } = getState();
if (tree.loaded) {
dispatch({type: types.DATA_INVALID});
}
const query = queryString.parse(window.location.search);
const s3bucket = s3override ? s3override : datasets.s3bucket;
if (datasets.datapath.startsWith("narrative")) {
fetchNarrativesAndDispatch(dispatch, datasets, query, s3bucket);
} else {
fetchDataAndDispatch(dispatch, datasets, query, s3bucket, false);
}
};
};

export const changeS3Bucket = () => {
return (dispatch, getState) => {
const {datasets} = getState();
const newBucket = datasets.s3bucket === "live" ? "staging" : "live";
// 1. re-fetch the manifest
getManifest(dispatch, newBucket);
// 2. this can *only* be toggled through the app, so we must reload data
dispatch(loadJSONs(newBucket));
const query = queryString.parse(search);
fetchDataAndDispatch(dispatch, url, query);

// if (datasets.datapath.startsWith("narrative")) {
// fetchNarrativesAndDispatch(dispatch, datasets, query);
// } else {
// fetchDataAndDispatch(dispatch, datasets, query, false);
// }
};
};

export const loadTreeToo = (name, path) => (dispatch, getState) => {
const { datasets } = getState();
const apiCall = `${charonAPIAddress}request=json&path=${path}_tree.json&s3=${datasets.s3bucket}`;
fetch(apiCall)
.then((res) => res.json())
.then((res) => {
const newState = createTreeTooState(
{treeTooJSON: res, oldState: getState(), segment: name}
);
dispatch({ type: types.TREE_TOO_DATA, treeToo: newState.treeToo, controls: newState.controls, segment: name});
})
.catch((err) => {
console.error("Error while loading second tree", err);
});
console.log("loadTreeToo not yet implemented");
// const { datasets } = getState();
// const apiCall = `${charonAPIAddress}request=json&path=${path}_tree.json`;
// fetch(apiCall)
// .then((res) => res.json())
// .then((res) => {
// const newState = createTreeTooState(
// {treeTooJSON: res, oldState: getState(), segment: name}
// );
// dispatch({ type: types.TREE_TOO_DATA, treeToo: newState.treeToo, controls: newState.controls, segment: name});
// })
// .catch((err) => {
// console.error("Error while loading second tree", err);
// });
};
63 changes: 21 additions & 42 deletions src/actions/navigation.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,10 @@
import queryString from "query-string";
import parseParams from "../util/parseParams";
import { createStateFromQueryOrJSONs } from "./recomputeReduxState";
import { PAGE_CHANGE, URL_QUERY_CHANGE_WITH_COMPUTED_STATE } from "./types";
import { loadJSONs } from "./loadData";

// make prefix for data files with fields joined by _ instead of / as in URL
const makeDataPathFromParsedParams = (parsedParams) => {
const tmp_levels = Object.keys(parsedParams.dataset).map((d) => parsedParams.dataset[d]);
tmp_levels.sort((x, y) => x[0] > y[0]);
return tmp_levels.map((d) => d[1]).join("_");
};

export const makeDataPathFromPathname = (pathname) => {
return pathname
.replace(/^\/+/, '') // strip leading
.replace(/\/+$/, '') // and trailing slashes
.replace(/\/+/g, '_'); // replacing all internal ones with underscores
};

/* match URL pathname to datasets (from manifest) */
export const getDatapath = (pathname, availableDatasets) => {
if (!availableDatasets) {return undefined;}
const parsedParams = parseParams(pathname, availableDatasets);
return parsedParams.valid
? makeDataPathFromParsedParams(parsedParams)
: makeDataPathFromPathname(pathname);
};

export const chooseDisplayComponentFromPathname = (pathname) => {
const parts = pathname.toLowerCase().replace(/^\/+/, "").replace(/\/+$/, "").split("/");
export const chooseDisplayComponentFromURL = (url) => {
const parts = url.toLowerCase().replace(/^\/+/, "").replace(/\/+$/, "").split("/");
if (
!parts.length || (parts.length === 1 && parts[0] === "") ||
(parts.length === 1 && parts[0] === "local") ||
Expand Down Expand Up @@ -59,18 +36,19 @@ In <App>, this causes a call to loadJSONs, which will, as part of it's dispatch,
In this way, the URL query is "used".
*/
export const changePage = ({path, query = undefined, push = true}) => (dispatch, getState) => {
if (!path) {console.error("changePage called without a path"); return;}
const { datasets } = getState();
const d = {
type: PAGE_CHANGE,
displayComponent: chooseDisplayComponentFromPathname(path),
errorMessage: undefined
};
d.datapath = d.displayComponent === "app" ? getDatapath(path, datasets.availableDatasets) : undefined;
if (query !== undefined) { d.query = query; }
if (push) { d.pushState = true; }
/* check if this is "valid" - we can change it here before it is dispatched */
dispatch(d);
if (!path) {
console.error("changePage called without a path");
return;
}
const displayComponent = chooseDisplayComponentFromURL(path);
const { general } = getState();
if (general.displayComponent === displayComponent && displayComponent === "app") {
dispatch(loadJSONs({url: path}));
return;
}
const action = {type: PAGE_CHANGE, displayComponent, pushState: push};
if (query !== undefined) { action.query = query; }
dispatch(action);
};

/* a 404 uses the same machinery as changePage, but it's not a thunk */
Expand Down Expand Up @@ -98,10 +76,11 @@ export const changePageQuery = ({queryToUse, queryToDisplay = false, push = true
};

export const browserBackForward = () => (dispatch, getState) => {
const { datasets } = getState();
/* if the pathname has changed, trigger the changePage action (will trigger new post to load, new dataset to load, etc) */
// console.log("broswer back/forward detected. From: ", datasets.urlPath, datasets.urlSearch, "to:", window.location.pathname, window.location.search)
if (datasets.urlPath !== window.location.pathname) {
const { general } = getState();
const potentiallyOutOfDatePathname = general.pathname;
/* differentiate between ∆pathname and ∆query (only) */
console.log("broswer back/forward detected. From: ", potentiallyOutOfDatePathname, "to:", window.location.pathname, window.location.search)
if (potentiallyOutOfDatePathname !== window.location.pathname) {
dispatch(changePage({path: window.location.pathname}));
} else {
dispatch(changePageQuery({queryToUse: queryString.parse(window.location.search)}));
Expand Down
4 changes: 1 addition & 3 deletions src/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ export const TOGGLE_PANEL_DISPLAY = "TOGGLE_PANEL_DISPLAY";
export const TRIGGER_DOWNLOAD_MODAL = "TRIGGER_DOWNLOAD_MODAL";
export const DISMISS_DOWNLOAD_MODAL = "DISMISS_DOWNLOAD_MODAL";
export const ADD_COLOR_BYS = "ADD_COLOR_BYS";
export const MANIFEST_RECEIVED = "MANIFEST_RECEIVED";
export const PROCEED_SANS_MANIFEST = "PROCEED_SANS_MANIFEST";
export const CHANGE_TREE_ROOT_IDX = "CHANGE_TREE_ROOT_IDX";
export const TOGGLE_NARRATIVE = "TOGGLE_NARRATIVE";
export const ENTROPY_DATA = "ENTROPY_DATA";
Expand All @@ -48,4 +46,4 @@ export const URL_QUERY_CHANGE_WITH_COMPUTED_STATE = "URL_QUERY_CHANGE_WITH_COMPU
export const TREE_TOO_DATA = "TREE_TOO_DATA";
export const REMOVE_TREE_TOO = "REMOVE_TREE_TOO";
export const TOGGLE_TANGLE = "TOGGLE_TANGLE";
export const AVAILABLE_DATASETS = "AVAILABLE_DATASETS";
export const UPDATE_PATHNAME = "UPDATE_PATHNAME";
11 changes: 1 addition & 10 deletions src/components/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,6 @@ const Overlay = ({styles, mobileDisplay, handler}) => {
};

@connect((state) => ({
readyToLoad: state.datasets.ready,
datapath: state.datasets.datapath,
metadataLoaded: state.metadata.loaded,
treeLoaded: state.tree.loaded,
panelsToDisplay: state.controls.panelsToDisplay,
Expand Down Expand Up @@ -135,9 +133,7 @@ class App extends React.Component {
}
}
componentWillMount() {
if (this.props.datapath) { /* datapath (pathname) only appears after manifest JSON has arrived */
this.props.dispatch(loadJSONs());
}
this.props.dispatch(loadJSONs()); // choose via URL
}
componentDidMount() {
document.addEventListener("dragover", (e) => {e.preventDefault();}, false);
Expand All @@ -146,11 +142,6 @@ class App extends React.Component {
return this.props.dispatch(filesDropped(e.dataTransfer.files));
}, false);
}
componentDidUpdate(prevProps) {
if (prevProps.datapath !== this.props.datapath) {
this.props.dispatch(loadJSONs());
}
}
render() {
/* D I M E N S I O N S */
let availableWidth = this.props.browserDimensions.width;
Expand Down
Loading

0 comments on commit a7bfd62

Please sign in to comment.