Commit 4b7b7357 authored by Alejandro De Maria Antolinos's avatar Alejandro De Maria Antolinos

Merge branch 'issue_469' into 'master'

Refactorized the code and added parcel status plot

See merge request !480
parents d62e1c8e fb4ba599
Pipeline #37628 passed with stages
in 12 minutes and 12 seconds
......@@ -26,7 +26,7 @@ function ParameterTableWidget(props) {
<tbody>
{allParams.map(({ name, value }) => (
<tr
key={name}
key={Math.random()}
className={styles.row}
data-empty={!value || undefined}
>
......
import axios from 'axios';
import React, { Suspense, lazy } from 'react';
import React, { Suspense } from 'react';
import { Col, Grid, Row } from 'react-bootstrap';
import createPlotlyComponent from 'react-plotly.js/factory';
import { getInstrumentStatistics } from '../../../api/icat-plus/elasticsearch';
import ParameterTableWidget from '../../Instrument/ParameterTableWidget';
import Loader from '../../Loader';
import ErrorUserMessage from '../../UserMessages/ErrorUserMessage';
import { stringifyBytesSize } from '../../../helpers';
// https://reactjs.org/docs/code-splitting.html
// https://github.com/plotly/react-plotly.js#customizing-the-plotlyjs-bundle
const Plot = lazy(() =>
import('plotly.js-basic-dist').then((Plotly) => ({
default: createPlotlyComponent(Plotly),
}))
);
import PlotWidget from '../../../containers/Stats/PlotWidget';
class GeneralStatsPanel extends React.Component {
constructor(props) {
......@@ -145,7 +137,7 @@ class GeneralStatsPanel extends React.Component {
</Row>
<Row>
<Col xs={12}>
<Plot
<PlotWidget
data={[
{
type: 'pie',
......@@ -162,7 +154,7 @@ class GeneralStatsPanel extends React.Component {
/>
</Col>
<Col xs={12}>
<Plot
<PlotWidget
data={[
{
name: 'Datasets',
......
import React, { lazy } from 'react';
import createPlotlyComponent from 'react-plotly.js/factory';
// https://reactjs.org/docs/code-splitting.html
// https://github.com/plotly/react-plotly.js#customizing-the-plotlyjs-bundle
const Plot = lazy(() =>
import('plotly.js-basic-dist').then((Plotly) => ({
default: createPlotlyComponent(Plotly),
}))
);
function PlotWidget(props) {
return <Plot {...props} />;
}
export default PlotWidget;
......@@ -6,7 +6,7 @@ import {
const ParcelStatsColumns = [
{
text: 'id',
dataField: 'id',
dataField: 'investigationId',
hidden: true,
},
{
......
import _ from 'lodash-es';
import moment from 'moment';
/**
* Given a parcel and a date it returns the status of the parcel at that date
* @param {*} parcel
* @param {*} date
*/
export function getStatusByDate(parcel, date) {
let parcelStatusAtDate;
parcel.statuses.forEach(({ updatedAt, status }) => {
if (moment(new Date(updatedAt)).isBefore(moment(new Date(date)))) {
parcelStatusAtDate = status;
}
});
return parcelStatusAtDate;
}
/**
* Returns an object componsed by dates and statuses. Dates is an array with dates used as input and the statuses is an array of a dictionary that is has as key the name of the status and the value if the number of occurences
* @param {*} parcels
* @param {*} dates
*/
export function getStatuses(parcels, dates) {
const statuses = [];
dates.map((date) => {
statuses.push({});
return parcels.forEach((parcel) => {
const status = getStatusByDate(parcel, date);
statuses[statuses.length - 1][status]
? (statuses[statuses.length - 1][status] =
statuses[statuses.length - 1][status] + 1)
: (statuses[statuses.length - 1][status] = 1);
});
});
return {
dates,
statuses,
};
}
export function getItemsCount(parcels) {
const count = parcels.map((p) => {
return p.items.length;
......@@ -7,16 +44,42 @@ export function getItemsCount(parcels) {
return count.length === 0 ? 0 : count.reduce((total, num) => total + num);
}
/**
* Get the count of parcel by status
* @param {*} parcels
* @param {*} status
*/
export function getParcelCountByStatus(parcels, status) {
return parcels.filter((p) => p.status === status).length;
}
export function getCount(parcels, type) {
/**
* Get the count of parcel by status
* @param {*} parcels
* @param {*} status
*/
export function getParcelInstrumentByStatus(parcels, status) {
return parcels
.filter((p) => p.status === status)
.map((p) => (p.investigation ? p.investigation.instrument.name : null));
}
/**
* Given an array of parcels it will return the count by type
* @param {*} parcels
* @param {*} type SAMPLESHEET | TOOL | OTHER
*/
function getItemCountByType(parcels, type) {
const count = parcels.map((p) => {
return p.items.filter((item) => item.type === type).length;
});
return count.length === 0 ? 0 : count.reduce((total, num) => total + num);
}
/**
* Given a list of parcels it return some statistics
* @param {*} parcels Array with the object parcel
*/
export function getStatisticsByParcels(parcels) {
const groupedByInvestigationId = _.groupBy(parcels, 'investigationId');
......@@ -61,9 +124,9 @@ export function getStatisticsByParcels(parcels) {
sessions,
parcelsCount: parcels.length,
itemsCount: getItemsCount(parcels),
samplesCount: getCount(parcels, 'SAMPLESHEET'),
toolsCount: getCount(parcels, 'TOOL'),
othersCount: getCount(parcels, 'OTHER'),
samplesCount: getItemCountByType(parcels, 'SAMPLESHEET'),
toolsCount: getItemCountByType(parcels, 'TOOL'),
othersCount: getItemCountByType(parcels, 'OTHER'),
beamlinesCount: beamlines.length,
beamlines,
parcelsPerBeamline,
......
import React from 'react';
import { getParcelInstrumentByStatus } from './ParcelStatsUtils';
import PlotWidget from '../PlotWidget';
function PiePlotBeamlineByStatus(props) {
const { status, parcels } = props;
const statusPerInstrument = getParcelInstrumentByStatus(parcels, status);
const labels = [...new Set(statusPerInstrument)];
const values = labels
.map((instrumentName) =>
statusPerInstrument.filter((s) => s === instrumentName)
)
.map((s) => s.length);
return (
<PlotWidget
data={[
{
type: 'pie',
hole: 0.4,
textposition: 'inside',
labels,
values,
texttemplate: '%{label}: %{value}',
},
]}
layout={{
showLegend: true,
title: 'Parcels currently on beamlines',
}}
/>
);
}
export default PiePlotBeamlineByStatus;
import React from 'react';
import DayPickerInput from 'react-day-picker/DayPickerInput';
import { INVESTIGATION_DATE_FORMAT } from '../../../constants';
import 'react-day-picker/lib/style.css';
import { ControlLabel, Form, FormGroup } from 'react-bootstrap';
import Moment from 'moment';
function DayPicker(props) {
const { day, set, onDayChange, placeHolder } = props;
return (
<DayPickerInput
format={INVESTIGATION_DATE_FORMAT}
placeholder={placeHolder}
value={Moment(day).format(INVESTIGATION_DATE_FORMAT)}
onDayChange={(selectedDay, modifiers, dayPickerInput) => {
set(dayPickerInput.getInput().value);
onDayChange(dayPickerInput.getInput().value);
}}
dayPickerProps={{
todayButton: 'Today',
}}
/>
);
}
function SampleTrackingStatsDateFilter(props) {
const {
start,
setStart,
end,
setEnd,
onStartDayChange,
onEndDayChange,
} = props;
return (
<Form inline>
<FormGroup controlId="formInlineName">
<ControlLabel>
Search parcels on sessions starting between:{' '}
</ControlLabel>{' '}
<DayPicker
day={start}
set={setStart}
onDayChange={onStartDayChange}
placeHolder="Start Date"
></DayPicker>
</FormGroup>{' '}
<FormGroup controlId="formInlineEmail">
<ControlLabel>and</ControlLabel>{' '}
<DayPicker
day={end}
set={setEnd}
onDayChange={onEndDayChange}
placeHolder="End Date"
></DayPicker>
</FormGroup>{' '}
</Form>
);
}
export default SampleTrackingStatsDateFilter;
import React from 'react';
import { STATUS, STATUS_DEFS } from '../../../constants/parcelStatuses';
import { Glyphicon, Grid, Row, Col, Alert } from 'react-bootstrap';
import { getParcelCountByStatus } from './ParcelStatsUtils';
import ParameterTableWidget from '../../../components/Instrument/ParameterTableWidget';
import PlotWidget from '../PlotWidget';
import PiePlotBeamlineByStatus from './PiePlotBeamlineByStatus';
function SummaryParcelStats(props) {
const { beamlineTest, stats, parcels } = props;
const {
parcelsCount,
beamlinesCount,
samplesCount,
toolsCount,
othersCount,
itemsCount,
beamlines,
parcelsPerBeamline,
itemsPerBeamline,
} = stats;
return (
<>
<Alert bsStyle="warning">
<strong>Test parcels are filtered!</strong> A parcel is considered as
test if the beamline associated is <strong>{beamlineTest}</strong>
</Alert>
<Grid fluid style={{ margin: 20 }}>
<Row>
<Col xs={12} md={2}>
<ParameterTableWidget
striped
parameters={[
{ name: 'Parcels', value: parcelsCount },
{ name: 'Beamlines', value: beamlinesCount },
]}
></ParameterTableWidget>
<h4>Items</h4>
<ParameterTableWidget
striped
parameters={[
{ name: 'Samples', value: samplesCount },
{ name: 'Tools', value: toolsCount },
{ name: 'Others', value: othersCount },
{ name: 'Total items', value: itemsCount },
]}
></ParameterTableWidget>
</Col>
<Col xs={12} md={2}>
<ParameterTableWidget
striped
parameters={Object.keys(STATUS).map((status) => {
return {
name: (
<span>
<Glyphicon
style={{ marginRight: '10px' }}
glyph={STATUS_DEFS[status].icon}
></Glyphicon>
{status}
</span>
),
value: getParcelCountByStatus(parcels, status),
};
})}
></ParameterTableWidget>
</Col>
<Col xs={12} md={8}>
<PiePlotBeamlineByStatus parcels={parcels} status="BEAMLINE" />
</Col>
</Row>
<Row>
<PlotWidget
data={[
{
name: 'Parcels',
type: 'bar',
x: beamlines,
y: parcelsPerBeamline,
},
{
x: beamlines,
y: itemsPerBeamline,
type: 'scatter',
mode: 'lines+markers',
marker: { color: 'red' },
yaxis: 'y2',
name: 'Items',
},
]}
layout={{
width: window.innerWidth - window.innerWidth * 0.2,
title: 'Parcel and items/Beamlines',
xaxis: { title: 'Beamlines' },
yaxis: { title: 'Parcels' },
showlegend: true,
legend: {
y: 80,
orientation: 'h',
},
yaxis2: {
title: 'Items',
overlaying: 'y',
side: 'right',
showgrid: false,
},
}}
/>
</Row>
</Grid>
</>
);
}
export default SummaryParcelStats;
import moment from 'moment';
/**
* Given two dates it will return an array with every day between the two dates
* @param {*} startDate Date object
* @param {*} endDate Date object
*/
export function enumerateDaysBetweenDates(startDate, endDate) {
const dates = [];
const currDate = moment(startDate).startOf('day');
while (currDate.add(1, 'days').diff(moment(endDate).startOf('day')) < 0) {
dates.push(currDate.clone().toDate());
}
return dates;
}
/**
* Given two dates it returns true if both are the same day
* @param {*} date1
* @param {*} date2
*/
export function isSameDate(date1, date2) {
return (
moment(date1).isSame(moment(date2), 'day') &&
moment(date1).isSame(moment(date2), 'month') &&
moment(date1).isSame(moment(date2), 'year')
);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment