Commit 934cc8d9 authored by Alejandro De Maria Antolinos's avatar Alejandro De Maria Antolinos

Merge branch 'master' of https://gitlab.esrf.fr/icat/E-DataPortal

parents 8573bfd2 dcdce4c4
Pipeline #33917 passed with stages
in 3 minutes and 17 seconds
......@@ -157,8 +157,13 @@ function App() {
</LoadingBoundary>
</Route>
<Route exact path="/selection" component={SelectionPage} />
<Route exact path="/selection/mint" component={MintSelectionPage} />
<Route exact path="/selection">
<SelectionPage />
</Route>
<Route exact path="/selection/mint">
<MintSelectionPage />
</Route>
{user.isAdministrator && (
<Route exact path="/manager/stats">
......
import {
ADD_DATASET_BY_ID,
ADD_INVESTIGATION_BY_ID,
REMOVE_DATASET_BY_ID,
REMOVE_INVESTIGATION_BY_ID,
} from '../constants/actionTypes';
import { SELECT_DATASETS, DESELECT_DATASETS } from '../constants/actionTypes';
export function addInvestigationById(investigationId) {
return function (dispatch) {
dispatch({
type: ADD_INVESTIGATION_BY_ID,
payload: investigationId,
});
export function selectDatasets(ids) {
return {
type: SELECT_DATASETS,
payload: ids,
};
}
export function removeInvestigationById(investigationId) {
return function (dispatch) {
dispatch({
type: REMOVE_INVESTIGATION_BY_ID,
payload: investigationId,
});
};
}
export function removeDatasetById(datasetId) {
return function (dispatch) {
dispatch({
type: REMOVE_DATASET_BY_ID,
payload: datasetId,
});
};
}
export function addDatasetById(datasetId) {
return function (dispatch) {
dispatch({
type: ADD_DATASET_BY_ID,
payload: datasetId,
});
export function deselectDatasets(ids) {
return {
type: DESELECT_DATASETS,
payload: ids,
};
}
import React from 'react';
import styles from './MyAddressesSummary.module.css';
import { LinkContainer } from 'react-router-bootstrap';
import { Button, Glyphicon } from 'react-bootstrap';
function AddressHeaderWithActions(props) {
const { address, onDelete } = props;
return (
<div className={styles.header}>
<span className={styles.investigation}>{address.investigationName}</span>
<div className={styles.actions}>
<LinkContainer to={`/addresses?edit=${address._id}`}>
<Button bsSize="xsmall" bsStyle="link" aria-label="Edit">
<Glyphicon glyph="pencil" />
<span className={styles.actionLabel}> Edit</span>
</Button>
</LinkContainer>
<Button
bsSize="xsmall"
bsStyle="link"
aria-label="Remove"
onClick={() => onDelete(address)}
>
<Glyphicon glyph="trash" />
<span className={styles.actionLabel}> Remove</span>
</Button>
</div>
</div>
);
}
export default AddressHeaderWithActions;
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
.investigation {
font-weight: 600;
}
.actions {
display: flex;
justify-content: flex-end;
}
@media (max-width: 50em) {
.actionLabel {
display: none;
}
}
......@@ -17,6 +17,7 @@ import { useHistory } from 'react-router';
import AddressPanel from './AddressPanel';
import AddressFormModal from './AddressFormModal';
import ModalLoadingBoundary from '../ModalLoadingBoundary';
import AddressHeaderWithActions from './AddressHeaderWithActions';
function MyAddressesSummary() {
const [alert, setAlert] = useState();
......@@ -60,42 +61,17 @@ function MyAddressesSummary() {
handleCloseModal();
}
function renderPanelHeader(address) {
return (
<div className={styles.addressHeading}>
<span className={styles.investigation}>
{address.investigationName}
</span>
<div className={styles.actions}>
<LinkContainer to={`/addresses?edit=${address._id}`}>
<Button bsSize="xsmall" bsStyle="link" aria-label="Edit">
<Glyphicon glyph="pencil" />
<span className={styles.actionLabel}> Edit</span>
</Button>
</LinkContainer>
<Button
bsSize="xsmall"
bsStyle="link"
aria-label="Remove"
onClick={async () => {
try {
await deleteAddress(address);
setAlert({
style: 'success',
message: 'Address was removed !',
});
} catch (error) {
setAlert({ type: 'danger', message: `An error occurred.` });
console.log(error);
}
}}
>
<Glyphicon glyph="trash" />
<span className={styles.actionLabel}> Remove</span>
</Button>
</div>
</div>
);
async function handleDelete(address) {
try {
await deleteAddress(address);
setAlert({
style: 'success',
message: 'Address was removed !',
});
} catch (error) {
setAlert({ type: 'danger', message: `An error occurred.` });
console.log(error);
}
}
return (
......@@ -131,7 +107,12 @@ function MyAddressesSummary() {
addresses.map((address) => (
<Col key={address._id} sm={6} md={3}>
<AddressPanel
header={renderPanelHeader(address)}
header={
<AddressHeaderWithActions
address={address}
onDelete={handleDelete}
/>
}
address={address}
/>
</Col>
......
.actions {
display: flex;
justify-content: flex-end;
}
.investigation {
font-weight: 600;
}
.addressHeading {
display: flex;
justify-content: space-between;
align-items: center;
}
.heading {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
@media (max-width: 50em) {
.actionLabel {
display: none;
}
}
This diff is collapsed.
import React from 'react';
import moment from 'moment';
import { Glyphicon } from 'react-bootstrap';
import { stringifyBytesSize, getDatasetParameterByName } from '../../helpers';
import { VOLUME, FILE_COUNT } from '../../constants/parameterTypes';
import DatasetDownloadButton from './DatasetDownloadButton';
export function dateFormatter(date) {
if (!date) {
return '';
}
const parsedDate = moment(date);
return (
<>
<Glyphicon glyph="time" style={{ marginRight: 5, top: 2 }} />
{parsedDate.format(moment.HTML5_FMT.TIME)}
<span style={{ marginLeft: 5, color: '#777', fontSize: 11 }}>
{parsedDate.format('D MMM YYYY')}
</span>
</>
);
}
export function volumeFormatter(dataset) {
return stringifyBytesSize(getDatasetParameterByName(dataset, VOLUME));
}
export function fileCountFormatter(dataset) {
return getDatasetParameterByName(dataset, FILE_COUNT);
}
export function techniqueFormatter(dataset) {
const definition = getDatasetParameterByName(dataset, 'definition');
return definition || '';
}
export function downloadFormatter(dataset, sessionId) {
const dataArchived = getDatasetParameterByName(dataset, '__dataArchived');
if (dataArchived === 'False') {
return 'N/A';
}
return <DatasetDownloadButton sessionId={sessionId} id={dataset.id} />;
}
......@@ -15,7 +15,7 @@ function Menu() {
const user = useSelector((state) => state.user);
const { sessionId, fullName, isSSO, isAnonymous } = user;
const selection = useSelector((state) => state.selection);
const selectedDatasets = useSelector((state) => state.selectedDatasets);
const breadcrumbsList = useSelector((state) => state.breadcrumbsList);
const dispatch = useDispatch();
......@@ -72,6 +72,20 @@ function Menu() {
<NavItem eventKey="my">My Data</NavItem>
</LinkContainer>
{UI.menu.isMySelectionVisible && selectedDatasets.length > 0 && (
<LinkContainer to="/selection">
<NavItem eventKey="selection">
My Selection
<span
className="badge"
style={{ marginLeft: 10, padding: '2px 7px' }}
>
{selectedDatasets.length}
</span>
</NavItem>
</LinkContainer>
)}
{UI.menu.isOpenDataVisible && (
<LinkContainer to="/public">
<NavItem eventKey="open" href="/public">
......@@ -88,17 +102,6 @@ function Menu() {
</LinkContainer>
)}
{UI.menu.isMySelectionVisible && selection.datasetIds.length > 0 && (
<LinkContainer to="/selection">
<NavItem eventKey="selection">
My Selection
<span className="badge" style={{ marginLeft: 10 }}>
{selection.datasetIds.length}
</span>
</NavItem>
</LinkContainer>
)}
{UI.sampleTracking.enabled && !isAnonymous && (
<NavDropdown
eventKey={3}
......
......@@ -27,6 +27,13 @@ function DangerousGoodsModal(props) {
<Modal.Body>
{submitError && <Alert bsStyle="danger">{submitError}</Alert>}
<p style={{ fontWeight: 600 }}>
Please ensure you reply correctly to this question after consultation
with your lab safety referent and/or the transport company.
</p>
<p style={{ color: '#d43f3a', fontWeight: 600 }}>
You will not be able to edit your parcel afterwards !
</p>
<HelpBlock>{UI.dangerousGoodsHelp}</HelpBlock>
</Modal.Body>
......
......@@ -35,11 +35,8 @@ class DOIForm extends React.Component {
this.authorCount = 0;
}
/**
* Add an author to the table
*/
addAuthor(investigationUser) {
const authors = this.state.authors;
const authors = [...this.state.authors];
if (!investigationUser.fullName) {
authors.push({
name: this.name.value,
......@@ -135,20 +132,22 @@ class DOIForm extends React.Component {
renderButton() {
return (
<SplitButton
id="add-author"
bsStyle="default"
title="Add author"
style={{ width: 100 }}
onClick={this.addAuthor}
>
{this.props.investigationUsers.map(function (user) {
{this.props.investigationUsers.map((user) => {
const bsStyleRole =
user.role === USER_ROLES.PrincipalInvestigator
? 'primary'
: 'default';
return (
<MenuItem
style={{ width: 350 }}
key={`${user.fullName} ${user.investigationId} ${user.role}`}
eventKey={user}
style={{ width: 350 }}
onSelect={this.addAuthor}
>
<span style={{ marginLeft: 10 }}>{user.fullName} </span>
......@@ -161,7 +160,6 @@ class DOIForm extends React.Component {
</MenuItem>
);
}, this)}
<MenuItem divider />
</SplitButton>
);
}
......@@ -201,7 +199,7 @@ class DOIForm extends React.Component {
</Panel.Heading>
<Panel.Body>
<Form inline>
<Form inline componentClass="div">
<FormGroup controlId="formInlineName">
<ControlLabel>Name</ControlLabel>{' '}
<FormControl
......
......@@ -52,7 +52,7 @@ const UI = {
AUTOREFRESH_DELAY: 60000,
},
dangerousGoodsHelp:
'Please ensure you reply correctly to this question after consultation with your lab safety referent and/or the transport company. This question is for ESRF internal use only - you must correctly declare your parcel in the official transport documents',
'This question is for ESRF internal use only - you must correctly declare your parcel in the official transport documents',
footer: {
text: 'European Synchrotron Radiation Facility',
},
......
......@@ -31,10 +31,8 @@ export const FETCH_DATASETS_BY_DOI_PENDING = 'FETCH_DATASETS_BY_DOI_PENDING';
export const SET_LOGBOOK_CONTEXT = 'SET_LOGBOOK_CONTEXT';
/** selection */
export const ADD_DATASET_BY_ID = 'ADD_DATASET_BY_ID';
export const REMOVE_DATASET_BY_ID = 'REMOVE_DATASET_BY_ID';
export const ADD_INVESTIGATION_BY_ID = 'ADD_INVESTIGATION_BY_ID';
export const REMOVE_INVESTIGATION_BY_ID = 'REMOVE_INVESTIGATION_BY_ID';
export const SELECT_DATASETS = 'SELECT_DATASETS';
export const DESELECT_DATASETS = 'DESELECT_DATASETS';
/** breadcrumbs */
export const SET_BREADCRUMBS = 'SET_BREADCRUMBS';
......
......@@ -7,7 +7,6 @@ import TabContainerMenu from '../components/TabContainerMenu/TabContainerMenu';
import DatasetTable from '../components/Dataset/DatasetTable';
import Loader from '../components/Loader';
import { fetchDatasetsByDOI } from '../actions/datasets';
import { removeDatasetById, addDatasetById } from '../actions/selection';
function DOIPage() {
const { prefix, suffix } = useParams();
......@@ -15,7 +14,6 @@ function DOIPage() {
const sessionId = useSelector((state) => state.user.sessionId);
const datasets = useSelector((state) => state.datasets);
const selection = useSelector((state) => state.selection);
const dispatch = useDispatch();
useEffect(() => {
......@@ -36,18 +34,7 @@ function DOIPage() {
{datasets.fetching ? (
<Loader message="Loading datasets..." spacedOut />
) : (
<DatasetTable
selection={selection}
removeDatasetById={(...params) =>
dispatch(removeDatasetById(...params))
}
addDatasetById={(...params) =>
dispatch(addDatasetById(...params))
}
sessionId={sessionId}
datasets={datasets.data}
expanded={[]}
/>
<DatasetTable datasets={datasets.data} />
)}
</Col>
</Row>
......
......@@ -6,7 +6,6 @@ import { setBreadCrumbs } from '../actions/breadcrumbs';
import TabContainerMenu from '../components/TabContainerMenu/TabContainerMenu';
import Loader from '../components/Loader';
import DatasetTable from '../components/Dataset/DatasetTable';
import { removeDatasetById, addDatasetById } from '../actions/selection';
import PageNotFound from './PageNotFound';
import { fetchDatasetsByInvestigationId } from '../actions/datasets';
import { useResource } from 'rest-hooks';
......@@ -17,9 +16,9 @@ function DatasetsPage() {
const investigation = useResource(InvestigationResource.detailShape(), {
id: investigationId,
});
const sessionId = useSelector((state) => state.user.sessionId);
const datasets = useSelector((state) => state.datasets);
const selection = useSelector((state) => state.selection);
const dispatch = useDispatch();
useEffect(() => {
......@@ -52,18 +51,7 @@ function DatasetsPage() {
{datasets.fetching ? (
<Loader message="Loading datasets..." spacedOut />
) : (
<DatasetTable
selection={selection}
removeDatasetById={(...params) =>
dispatch(removeDatasetById(...params))
}
addDatasetById={(...params) =>
dispatch(addDatasetById(...params))
}
sessionId={sessionId}
expanded={expanded}
datasets={datasets.data}
/>
<DatasetTable datasets={datasets.data} expanded={expanded} />
)}
</Col>
</Row>
......
import axios from 'axios';
import { uniq } from 'lodash-es';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import React, { useEffect, useState } from 'react';
import { Col, Grid, Panel, Row } from 'react-bootstrap';
import BootstrapTable2 from 'react-bootstrap-table-next';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { doSignIn } from '../../actions/login';
import { addDatasetById, removeDatasetById } from '../../actions/selection';
import { useDispatch, useSelector } from 'react-redux';
import {
getDatasetsById,
getUsersByInvestigationIds,
} from '../../api/icat-plus/catalogue';
import DOIForm from '../../components/doi/DOIForm';
import Loader from '../../components/Loader';
import { PERSPECTIVE } from '../../constants';
class MintSelectionPage extends Component {
constructor(props) {
super(props);
this.state = {
investigationId: this.props.match.params.id,
username: this.props.user.username,
sessionId: this.props.user.sessionId,
datasets: [],
filtered: [],
fetching: false,
fetched: false,
datasetIds: this.props.selection.datasetIds,
investigationUsers: [],
};
}
/*
groupBy = function (xs, key) {
return xs.reduce(function (rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
parametersToDatasetObject = function (parametersGroupedByInvestigationId) {
let datasets = {};
for (var datasetId in parametersGroupedByInvestigationId) {
for (var index in parametersGroupedByInvestigationId[datasetId]) {
var param = parametersGroupedByInvestigationId[datasetId][index];
var key = param[5];
var value = param[6];
if (datasets[datasetId] == null) {
datasets[datasetId] = {};
}
datasets[datasetId][key] = value;
datasets[datasetId]["id"] = param[0];
datasets[datasetId]["name"] = param[1];
datasets[datasetId]["startDate"] = param[2];
datasets[datasetId]["endDate"] = param[3];
datasets[datasetId]["investigationName"] = param[4];
datasets[datasetId]["location"] = param[8];
datasets[datasetId]["investigationId"] = param[9];
}
}
var array = [];
for (var ds in datasets) {
array.push(datasets[ds]);
}
return array;
};
*/
onLogbookButtonClicked() {
this.setState({
perspective: PERSPECTIVE.Events,
});
}
import { setBreadCrumbs } from '../../actions/breadcrumbs';
function MintSelectionPage() {
const sessionId = useSelector((state) => state.user.sessionId);
const selectedDatasets = useSelector((state) => state.selectedDatasets);
const dispatch = useDispatch();
const [state, setState] = useState({
datasets: [],
users: [],
isFetching: false,
});
const { datasets, users, isFetching: fetching } = state;
useEffect(() => {
dispatch(
setBreadCrumbs([
{ name: 'My Selection', link: '/selection' },
{ name: 'Mint DOI' },
])
);
componentDidMount() {
this.retrieveSelectedDatasetsFromDatabase();
}
async function fetchDatasetsAndUsers() {
const datasets = await axios.get(
getDatasetsById(sessionId, selectedDatasets)
);
getUsers(investigationIds) {
this.setState({
fetching: true,
});
const investigationIds = datasets.data.map((d) => d.investigation.id);
const users = await axios.get(
getUsersByInvestigationIds(sessionId, [...new Set(investigationIds)])
);
axios
.get(getUsersByInvestigationIds(this.state.sessionId, investigationIds))
.then((res) => {
const investigationUsers = res.data;
this.setState({
investigationUsers,
fetching: false,
fetched: true,
});
})
.catch((error) => {
console.log(error);
});