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 5 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;
4 changes: 2 additions & 2 deletions src/Submission/MapDataModel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { fetchQuery } from 'relay-runtime';
import Button from '@gen3/ui-component/dist/components/Button';
import BackLink from '../components/BackLink';
import getProjectsList from '../Index/relayer';
import getProjectNodeCounts from '../Index/utils';
import CheckmarkIcon from '../img/icons/status_confirm.svg';
import InputWithIcon from '../components/InputWithIcon';
import { GQLHelper } from '../gqlHelper';
Expand Down Expand Up @@ -34,7 +34,7 @@ class MapDataModel extends React.Component {
if (this.props.filesToMap.length === 0) { // redirect if no files
this.props.history.push('/submission/files');
}
getProjectsList();
getProjectNodeCounts();
}

setRequiredProperties = () => {
Expand Down
6 changes: 3 additions & 3 deletions src/Submission/MapDataModel.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { createMemoryHistory } from 'history';
import { StaticRouter } from 'react-router-dom';
import MapDataModel from './MapDataModel';
import * as testData from './__test__/data.json';
import getProjectsList from '../Index/relayer';
import getProjectNodeCounts from '../Index/utils';

jest.mock('../Index/relayer');
getProjectsList.mockImplementation(() => jest.fn());
jest.mock('../Index/utils');
getProjectNodeCounts.mockImplementation(() => jest.fn());

describe('MapDataModel', () => {
const history = createMemoryHistory('/submission/map');
Expand Down
2 changes: 0 additions & 2 deletions src/components/charts/IndexBarChart/index.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ResponsiveContainer, Legend, Tooltip, BarChart, Bar, XAxis, YAxis } from 'recharts';
import PropTypes from 'prop-types'; // see https://github.com/facebook/prop-types#prop-types
import React from 'react';
import { browserHistory } from 'react-router-dom';
import Spinner from '../../Spinner';
import TooltipCDIS from '../TooltipCDIS/.';
import Tick from '../Tick';
Expand Down Expand Up @@ -116,7 +115,6 @@ class IndexBarChart extends React.Component {
<div className='index-bar-chart'>
<ResponsiveContainer width='100%' height='100%'>
<BarChart
onClick={(e) => { browserHistory.push(`/${e.activeLabel}`); window.location.reload(false); }}
data={indexChart}
margin={barChartStyle.margins}
layout={barChartStyle.layout}
Expand Down
10 changes: 8 additions & 2 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import theme from './theme';
import getReduxStore from './reduxStore';
import { ReduxNavBar, ReduxTopBar, ReduxFooter } from './Layout/reduxer';
import ReduxQueryNode, { submitSearchForm } from './QueryNode/ReduxQueryNode';
import { basename, dev, gaDebug, workspaceUrl, workspaceErrorUrl } from './localconf';
import { basename, dev, gaDebug, workspaceUrl, workspaceErrorUrl, indexPublic } from './localconf';
import ReduxAnalysis from './Analysis/ReduxAnalysis.js';
import { gaTracking, components } from './params';
import GA, { RouteTracker } from './components/GoogleAnalytics';
Expand Down Expand Up @@ -99,7 +99,13 @@ async function init() {
exact
path='/'
component={
props => <ProtectedContent component={IndexPage} {...props} />
props => (
<ProtectedContent
public={indexPublic}
component={IndexPage}
{...props}
/>
)
}
/>
<Route
Expand Down
13 changes: 13 additions & 0 deletions src/localconf.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ function buildConfig(opts) {
const graphqlSchemaUrl = `${hostname}data/schema.json`;
const workspaceUrl = '/lw-workspace/';
const workspaceErrorUrl = '/no-workspace-access/';
const datasetUrl = `${hostname}api/search/datasets`;

// see index page without login
let indexPublic = typeof components.index.public === 'undefined'
? false : components.index.public;
// backward compatible: homepageChartNodes not set means using graphql query,
// which will return 401 UNAUTHORIZED, thus not making public
if (typeof components.index.homepageChartNodes === 'undefined') {
indexPublic = false;
}

const colorsForCharts = {
categorical9Colors: [
Expand Down Expand Up @@ -111,6 +121,9 @@ function buildConfig(opts) {
certs: components.certs,
workspaceUrl,
workspaceErrorUrl,
homepageChartNodes: components.index.homepageChartNodes,
datasetUrl,
indexPublic,
};
}

Expand Down