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

PXP-2464 fix/homepage chart REST API #476

Merged
merged 12 commits into from
Mar 1, 2019
Merged
Show file tree
Hide file tree
Changes from 8 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 .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
17 changes: 16 additions & 1 deletion data/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,22 @@
"link": "/submission",
"label": "Submit data"
}
]
],
"homepageChartNodes": [
{
"node": "case",
"name": "Cases"
},
{
"node": "experiment",
"name": "Experiments"
},
{
"node": "aliquot",
"name": "Aliquots"
}
],
"public": true
},
"navigation": {
"title": "Generic Data Commons",
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 (typeof projectNodeCounts[proj][curNode] !== 'undefined') {
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 (typeof projectNodeCounts[proj][fileNode] !== 'undefined') {
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 (typeof projectNodeCounts[proj] !== 'undefined') {
counts = nodesForIndexChart.map(node => projectNodeCounts[proj][node]);
}

if (nodesForIndexChart.length < 4) {
const fileCountsForProj = fileNodes.reduce((acc, fileNode) => {
let newAcc = acc;
if (typeof projectNodeCounts[proj][fileNode] !== 'undefined') {
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
4 changes: 2 additions & 2 deletions src/Index/page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ 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();
getProjectNodeCounts();
}

render() {
Expand Down
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
49 changes: 49 additions & 0 deletions src/Index/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
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 () => {
if (typeof homepageChartNodes === 'undefined') {
getProjectsList();
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) => {
if (res.status === 200) {
updateRedux(res.data);
} else if (res.status === 404) {
console.error(`REST endpoint ${datasetUrl} not enabled in Peregrine yet.`);
getProjectsList();
}
})
.catch((err) => {
console.log(err);
});
};

export default getProjectNodeCounts;
16 changes: 13 additions & 3 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 Expand Up @@ -68,7 +78,7 @@ class Login extends React.Component {
<hr className='login-page__separator' />
<div className='body-typo'>{this.props.data.text}</div>
{
this.props.providers.map(
this.props.providers && this.props.providers.map(
Copy link
Contributor

Choose a reason for hiding this comment

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

is this necessary to add if you added default props?

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 changed the code yesterday and forgot to commit... let me update that! thanks

p => (
<div key={p.id} className='login-page__entries'>
<a href={`${p.url}?redirect=${window.location.origin}${next}`}>
Expand Down
61 changes: 30 additions & 31 deletions src/Login/ProtectedContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,37 +71,36 @@ class ProtectedContent extends React.Component {
* in the various ways we want it to be.
*/
componentDidMount() {
if (!this.props.public) {
getReduxStore().then(
store =>
Promise.all(
[
store.dispatch({ type: 'CLEAR_COUNTS' }), // clear some counters
store.dispatch({ type: 'CLEAR_QUERY_NODES' }),
],
).then(
() => this.checkLoginStatus(store, this.state)
.then(newState => this.checkQuizStatus(newState))
.then(newState => this.checkApiToken(store, newState)),
).then(
(newState) => {
const filterPromise = (newState.authenticated
&& typeof this.props.filter === 'function')
? this.props.filter()
: Promise.resolve('ok');
// finally update the component state
const finish = () => {
const latestState = Object.assign({}, newState);
latestState.dataLoaded = true;
this.setState(latestState);
};
return filterPromise.then(
finish, finish,
);
},
),
);
} else {
getReduxStore().then(
store =>
Promise.all(
[
store.dispatch({ type: 'CLEAR_COUNTS' }), // clear some counters
store.dispatch({ type: 'CLEAR_QUERY_NODES' }),
],
).then(
() => this.checkLoginStatus(store, this.state)
.then(newState => this.props.public || this.checkQuizStatus(newState))
.then(newState => this.props.public || this.checkApiToken(store, newState)),
).then(
(newState) => {
const filterPromise = (!this.props.public && newState.authenticated
&& typeof this.props.filter === 'function')
? this.props.filter()
: Promise.resolve('ok');
// finally update the component state
const finish = () => {
const latestState = Object.assign({}, newState);
latestState.dataLoaded = true;
this.setState(latestState);
};
return filterPromise.then(
finish, finish,
);
},
),
);
if (this.props.public) {
getReduxStore().then(
(store) => {
const filterPromise = (
Expand Down
Loading