Skip to content

Commit

Permalink
PXP-2464 fix/homepage chart REST API (#476)
Browse files Browse the repository at this point in the history
* fix(rest): add rest api for homepage chart

* fix(access): add option for public index

* fix(404): if get 404 from datasets endpoint, hit graphql

* fix(login): check login status for public pages

* fix(login): providers return after rendering login will cause err

* fix(prop): default prop already set

* fix(undefined): checking undefined

* fix(public): remove public option from config, redirect to login if 401

* fix(constructor): move to componentDidMount
  • Loading branch information
qingyashu authored Mar 1, 2019
1 parent 9860eab commit 4c5e421
Show file tree
Hide file tree
Showing 16 changed files with 261 additions and 56 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ module.exports = {
"data/getTexts.js",
"data/gqlSetup.js",
"src/SessionMonitor/index.js",
"src/Index/utils.js",
],
"rules": {
"no-console": "off" // for logging errors
Expand Down
14 changes: 14 additions & 0 deletions data/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@
"link": "/submission",
"label": "Submit data"
}
],
"homepageChartNodes": [
{
"node": "case",
"name": "Cases"
},
{
"node": "experiment",
"name": "Experiments"
},
{
"node": "aliquot",
"name": "Aliquots"
}
]
},
"navigation": {
Expand Down
19 changes: 18 additions & 1 deletion data/config/ndh.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,24 @@
"text": "The website combines government datasets from 3 divisions of NIAID to create clean, easy to navigate visualizations for data-driven discovery within Allergy and Infectious Diseases.",
"contact": "If you have any questions about access or the registration process, please contact ",
"email": "[email protected]"
}
},
"footerLogos": [
{
"src": "/src/img/gen3.png",
"href": "https://ctds.uchicago.edu/gen3",
"alt": "Gen3 Data Commons"
},
{
"src": "/src/img/createdby.png",
"href": "https://ctds.uchicago.edu/",
"alt": "Center for Translational Data Science at the University of Chicago"
},
{
"src": "/src/img/sponsors/niaid.png",
"href": "https://niaid.bionimbus.org",
"alt": "NIAID Data Hub"
}
]
},
"featureFlags": {
"explorer": true
Expand Down
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/Homepage/page.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React from 'react';
import { ReduxProjectDashboard, ReduxTransaction } from './reduxer';
import getProjectsList from '../Index/relayer';
import getProjectNodeCounts from '../Index/utils';
import getTransactionList from './relayer';

class HomePage extends React.Component {
constructor(props) {
super(props);
getProjectsList();
getProjectNodeCounts();
getTransactionList();
}

Expand Down
76 changes: 75 additions & 1 deletion src/Homepage/reducers.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { components } from '../params';

const homepage = (state = {}, action) => {
switch (action.type) {
case 'RECEIVE_PROJECT_LIST': {
Expand All @@ -15,7 +17,13 @@ const homepage = (state = {}, action) => {
);
const lastestListUpdating = Date.now();
// const { error, ...state } = state;
return { ...state, projectsByName, summaryCounts, lastestListUpdating };
return {
...state,
projectsByName,
summaryCounts,
lastestListUpdating,
countNames: components.charts.indexChartNames,
};
}
case 'RECEIVE_PROJECT_DETAIL': {
const projectsByName = Object.assign({}, state.projectsByName || {});
Expand All @@ -29,6 +37,72 @@ const homepage = (state = {}, action) => {
case 'RECEIVE_RELAY_FAIL': {
return { ...state, error: action.data };
}
case 'RECEIVE_PROJECT_NODE_DATASETS': {
const { projectNodeCounts, homepageChartNodes, fileNodes } = action;
const nodesForIndexChart = homepageChartNodes.map(item => item.node);

// adding counts by node
const summaryCounts = nodesForIndexChart.reduce((acc, curNode, index) => {
Object.keys(projectNodeCounts).forEach((proj) => {
if (projectNodeCounts[proj][curNode]) {
acc[index] += projectNodeCounts[proj][curNode];
}
});
return acc;
}, nodesForIndexChart.map(() => 0));

// keep previous design: if less than 4 nodes, calculate all files number
if (nodesForIndexChart.length < 4) {
// add counts for all file type nodes, as the last count
const fileCount = fileNodes.reduce((acc, fileNode) => {
let newAcc = acc;
Object.keys(projectNodeCounts).forEach((proj) => {
if (projectNodeCounts[proj][fileNode]) {
newAcc += projectNodeCounts[proj][fileNode];
}
});
return newAcc;
}, 0);
summaryCounts.push(fileCount);
}

// constructing projct counts for index bar chart
const projectsByName = {};
Object.keys(projectNodeCounts).forEach((proj) => {
let code = proj;
const projCodeIndex = proj.indexOf('-');
if (projCodeIndex !== -1) {
code = proj.substring(projCodeIndex + 1);
}
let counts = 0;
if (projectNodeCounts[proj]) {
counts = nodesForIndexChart.map(node => projectNodeCounts[proj][node]);
}

if (nodesForIndexChart.length < 4) {
const fileCountsForProj = fileNodes.reduce((acc, fileNode) => {
let newAcc = acc;
if (projectNodeCounts[proj][fileNode]) {
newAcc += projectNodeCounts[proj][fileNode];
}
return newAcc;
}, 0);
counts.push(fileCountsForProj);
}

projectsByName[proj] = {
code,
counts,
name: proj,
};
});

const countNames = homepageChartNodes.map(item => item.name);
if (countNames.length < 4) {
countNames.push('Files');
}
return { ...state, projectsByName, summaryCounts, countNames };
}
default:
return state;
}
Expand Down
17 changes: 13 additions & 4 deletions src/Index/page.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import Introduction from '../components/Introduction';
import { ReduxIndexButtonBar, ReduxIndexBarChart } from './reduxer';
import dictIcons from '../img/icons';
import { components } from '../params';
import getProjectsList from './relayer';
import getProjectNodeCounts from './utils';
import './page.less';

class IndexPageComponent extends React.Component {
constructor(props) {
super(props);
getProjectsList();
componentDidMount() {
getProjectNodeCounts((res) => {
// If Peregrine returns unauthorized, need to redirect to `/login` page
if (res.needLogin) {
this.props.history.push('/login');
}
});
}

render() {
Expand All @@ -25,6 +30,10 @@ class IndexPageComponent extends React.Component {
}
}

IndexPageComponent.propTypes = {
history: PropTypes.object.isRequired,
};

const IndexPage = IndexPageComponent;

export default IndexPage;
5 changes: 4 additions & 1 deletion src/Index/reduxer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ export const ReduxIndexBarChart = (() => {
const projectList = Object.values(
state.homepage.projectsByName,
).sort(sortCompare);
return { projectList, countNames: components.charts.indexChartNames };
return {
projectList,
countNames: state.homepage.countNames,
};
}
return {};
};
Expand Down
63 changes: 63 additions & 0 deletions src/Index/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import _ from 'underscore';
import { fetchWithCreds } from '../actions';
import { homepageChartNodes, datasetUrl } from '../localconf';
import getReduxStore from '../reduxStore';
import getProjectsList from './relayer';

const updateRedux = async projectNodeCounts => getReduxStore().then(
(store) => {
store.dispatch({
type: 'RECEIVE_PROJECT_NODE_DATASETS',
projectNodeCounts,
homepageChartNodes,
fileNodes: store.getState().submission.file_nodes,
});
},
(err) => {
console.error('WARNING: failed to load redux store', err);
return 'ERR';
},
);

const getProjectNodeCounts = async (callback) => {
const resultStatus = { needLogin: false };
if (typeof homepageChartNodes === 'undefined') {
getProjectsList();
callback(resultStatus);
return;
}

const store = await getReduxStore();
const fileNodes = store.getState().submission.file_nodes;
const nodesForIndexChart = homepageChartNodes.map(item => item.node);
const nodesToRequest = _.union(fileNodes, nodesForIndexChart);
const url = `${datasetUrl}?nodes=${nodesToRequest.join(',')}`;

fetchWithCreds({
path: url,
}).then((res) => {
switch (res.status) {
case 200:
updateRedux(res.data);
callback(resultStatus);
break;
case 404:
// Shouldn't happen, this means peregrine datasets endpoint not enabled
console.error(`REST endpoint ${datasetUrl} not enabled in Peregrine yet.`);
callback(resultStatus);
break;
case 401:
case 403:
resultStatus.needLogin = true;
callback(resultStatus);
break;
default:
break;
}
})
.catch((err) => {
console.log(err);
});
};

export default getProjectNodeCounts;
14 changes: 12 additions & 2 deletions src/Login/Login.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import querystring from 'querystring';
import PropTypes from 'prop-types'; // see https://github.com/facebook/prop-types#prop-types

import { basename } from '../localconf';
import { basename, loginPath } from '../localconf';
import SlidingWindow from '../components/SlidingWindow';
import './Login.less';

Expand All @@ -12,12 +12,22 @@ class Login extends React.Component {
static propTypes = {
providers: PropTypes.arrayOf(
PropTypes.objectOf(PropTypes.any),
).isRequired,
),
location: PropTypes.object.isRequired,
dictIcons: PropTypes.object.isRequired,
data: PropTypes.object.isRequired,
};

static defaultProps = {
providers: [
{
id: 'google',
name: 'Google OAuth',
url: `${loginPath}google/`,
},
],
};

constructor(props) {
super(props);
this.state = getInitialState(window.innerHeight - 221);
Expand Down
Loading

0 comments on commit 4c5e421

Please sign in to comment.