Commit 3a9cffb8 authored by Alejandro De Maria Antolinos's avatar Alejandro De Maria Antolinos
Browse files

Merge branch 'milestone-logbook-search-settings' into 'master'

Milestone logbook search settings

Closes #130, #132, #137, #138, #139, #140, #147, #155, #177, #179, #186, #192, #197, #198, #210, #215, #218, #221, #222, #225, #229, #232, #236, #242, #243, #244, #246, #254, #256, #265, #266, #268, #272, #274, #275, #276, #279, #339, #347, #350, #351, #377, #387, #390, #400, #401, #413, #414, #415, #417, #418, #421, #428, #430, #463, #469, #481, #493, #492, and #489

See merge request !524
parents 22f8ec62 5bf42452
Pipeline #48308 passed with stages
in 13 minutes and 16 seconds
This diff is collapsed.
...@@ -37,6 +37,7 @@ import { ...@@ -37,6 +37,7 @@ import {
CLOSED_DATA_PATH, CLOSED_DATA_PATH,
MY_DATA_PATH, MY_DATA_PATH,
OPEN_DATA_PATH, OPEN_DATA_PATH,
BEAMLINE_PATH,
} from './constants/routePaths'; } from './constants/routePaths';
function App() { function App() {
...@@ -133,7 +134,11 @@ function App() { ...@@ -133,7 +134,11 @@ function App() {
<Route exact path="/public/:prefix/:suffix" component={DOIPage} /> <Route exact path="/public/:prefix/:suffix" component={DOIPage} />
<Route exact path={CLOSED_DATA_PATH} component={ClosedDataPage} /> <Route exact path={CLOSED_DATA_PATH} component={ClosedDataPage} />
<Route exact path="/beamline/:name" component={BeamlineDataPage} /> <Route
exact
path={`${BEAMLINE_PATH}:name`}
component={BeamlineDataPage}
/>
{isSampleTrackingEnabled && ( {isSampleTrackingEnabled && (
<Route exact path="/parcels" component={MyParcelsPage} /> <Route exact path="/parcels" component={MyParcelsPage} />
......
import { SET_LOGBOOK_CONTEXT } from '../constants/actionTypes'; import {
SET_CATEGORY_TYPE,
UNSET_CATEGORY_TYPE,
SET_LOGBOOK_CONTEXT,
SET_AUTOMATIC_COLLAPSING,
SET_AUTOMATIC_REFRESH,
SET_IS_SORTING_LATESTS_EVENTS_FIRST,
} from '../constants/actionTypes';
/** /**
* Set the logbook context ie the context in which the logbook is used. Here the context corresponds to : * Set the logbook context ie the context in which the logbook is used. Here the context corresponds to :
...@@ -13,3 +20,58 @@ export function setLogbookContextAction(context) { ...@@ -13,3 +20,58 @@ export function setLogbookContextAction(context) {
context, context,
}; };
} }
/**
* Set a type, category to be shown in the logbook page
* @param {*} type object with the different filters status
*/
export function setCategoryTypes(categoryType) {
return {
type: SET_CATEGORY_TYPE,
categoryType,
};
}
/**
* Unset a type, category to be shown in the logbook page
* @param {*} type object with the different filters status
*/
export function unsetCategoryTypes(categoryType) {
return {
type: UNSET_CATEGORY_TYPE,
categoryType,
};
}
/**
* Set the automatic collapsing events
* @param {*} automaticCollapsing automaticCollapsing value
*/
export function setAutomaticCollapsing(automaticCollapsing) {
return {
type: SET_AUTOMATIC_COLLAPSING,
automaticCollapsing,
};
}
/**
* Set the automatic refresh of events
* @param {*} automaticRefresh automaticRefresh value
*/
export function setAutomaticRefresh(automaticRefresh) {
return {
type: SET_AUTOMATIC_REFRESH,
automaticRefresh,
};
}
/**
* Set the sorting order
* @param {*} isSortingLatestEventsFirst isSortingLatestEventsFirst value
*/
export function setIsSortingLatestEventsFirst(isSortingLatestEventsFirst) {
return {
type: SET_IS_SORTING_LATESTS_EVENTS_FIRST,
isSortingLatestEventsFirst,
};
}
...@@ -29,6 +29,7 @@ export function doSignIn(plugin, username, password) { ...@@ -29,6 +29,7 @@ export function doSignIn(plugin, username, password) {
name, name,
fullName, fullName,
isAdministrator, isAdministrator,
isInstrumentScientist,
lifeTimeMinutes, lifeTimeMinutes,
} = data; } = data;
dispatch({ dispatch({
...@@ -37,6 +38,7 @@ export function doSignIn(plugin, username, password) { ...@@ -37,6 +38,7 @@ export function doSignIn(plugin, username, password) {
name, name,
fullName, fullName,
isAdministrator, isAdministrator,
isInstrumentScientist,
lifeTimeMinutes, lifeTimeMinutes,
}); });
}) })
...@@ -65,6 +67,7 @@ export function doSilentRefreshFromSSO() { ...@@ -65,6 +67,7 @@ export function doSilentRefreshFromSSO() {
name, name,
fullName, fullName,
isAdministrator, isAdministrator,
isInstrumentScientist,
lifeTimeMinutes, lifeTimeMinutes,
} = data; } = data;
dispatch({ dispatch({
...@@ -73,6 +76,7 @@ export function doSilentRefreshFromSSO() { ...@@ -73,6 +76,7 @@ export function doSilentRefreshFromSSO() {
name, name,
fullName, fullName,
isAdministrator, isAdministrator,
isInstrumentScientist,
lifeTimeMinutes, lifeTimeMinutes,
}); });
}) })
......
import ICATPLUS from '../../config/icatPlus'; import ICATPLUS from '../../config/icatPlus';
/**
* Get URL needed to retrieve events for a given investigation
* @param {String} sessionId the session identifier
* @param {String} investigationId the session identifier
* @return {String} the URL to get the requested events. Null if investigationId or sessionId is missing.
*/
export function getEventsByInvestigationId(sessionId, investigationId) {
return `${ICATPLUS.server}/logbook/${sessionId}/investigation/id/${investigationId}/event/query`;
}
/**
* Get URL needed to retrieve the event count for a given investigation
* @param {string} sessionId the session identifier
* @param {String} investigationId the investigation identifier
* @return {String} the URL to get the requested event count
*/
export function getEventCountByInvestigationId(sessionId, investigationId) {
return `${ICATPLUS.server}/logbook/${sessionId}/investigation/id/${investigationId}/event/count`;
}
/**
* Get URL used to create a new event for a given investigation on ICAT+
* @param {*} investigationId investigation indentifier
* @param {String} sessionId session identifier
* @return {String} URL to get the requested events
*/
export function createEvent(sessionId, investigationId) {
return `${ICATPLUS.server}/logbook/${sessionId}/investigation/id/${investigationId}/event/create`;
}
/** /**
* Get URL used to update an event on a given investigation on ICAT+ * Get URL used to update an event on a given investigation on ICAT+
* @param {String} sessionId the session identifier * @param {String} sessionId the session identifier
* @param {String} investigationId the investigation indentifier * @param {String} investigationId the investigation indentifier
* @return {String} the URL to get the requested events * @return {String} the URL to get the requested events
*/ */
export function updateEvent(sessionId, investigationId) { export function getEventURL(
return `${ICATPLUS.server}/logbook/${sessionId}/investigation/id/${investigationId}/event/update`; sessionId,
} investigationId,
skip,
/** limit,
* Get URL used to download a PDF file for a given investigation from the logbook sortOrder,
* @param {string} sessionId session identifier sortBy,
* @param {*} investigationId investigation identifier types,
* @param {object} selectionFilter selection filter used to retrieve part of the logbook. This is URI encoded and passed as query string format,
*/ search
export function getPDF(sessionId, investigationId, selectionFilter) { ) {
return `${ICATPLUS.server}/logbook/${sessionId}/investigation/id/${investigationId}/event/pdf?find=&sort=&skip=&limit=` const params = new URLSearchParams();
.replace('find=', () => { params.set('investigationId', investigationId);
return `find=${ if (limit) params.set('limit', limit);
selectionFilter && selectionFilter.find if (sortBy) params.set('sortBy', sortBy);
? JSON.stringify(selectionFilter.find) if (sortOrder) params.set('sortOrder', sortOrder);
: '' if (types) params.set('types', types);
}`; if (skip) params.set('skip', skip);
}) if (format) params.set('format', format);
.replace('&sort=', () => { if (search) params.set('search', search);
return `&sort=${ return `${ICATPLUS.server}/logbook/${sessionId}/event?${params.toString()}`;
selectionFilter && selectionFilter.sort
? JSON.stringify(selectionFilter.sort)
: ''
}`;
})
.replace('&skip=', () => {
return `&skip=${
selectionFilter && selectionFilter.skip !== undefined
? JSON.stringify(selectionFilter.skip)
: ''
}`;
})
.replace('&limit=', () => {
return `&limit=${
selectionFilter && selectionFilter.limit !== undefined
? JSON.stringify(selectionFilter.limit)
: ''
}`;
});
} }
/** Get the tags associated to a given investigation /** Get the tags associated to a given investigation
......
...@@ -27,7 +27,9 @@ function BreadCrumbs(props) { ...@@ -27,7 +27,9 @@ function BreadCrumbs(props) {
let badges = ''; let badges = '';
if (item.badges) { if (item.badges) {
badges = item.badges.map((badge) => ( badges = item.badges.map((badge) => (
<>{badge && <Label className={styles.badge}>{badge}</Label>}</> <span key={badge}>
{badge && <Label className={styles.badge}>{badge}</Label>}
</span>
)); ));
} }
return ( return (
......
...@@ -107,7 +107,7 @@ class EventVersionItem extends React.Component { ...@@ -107,7 +107,7 @@ class EventVersionItem extends React.Component {
function buildTheDisplay(event, type, mostRecent) { function buildTheDisplay(event, type, mostRecent) {
return ( return (
<Well bsSize="small" style={{ marginBottom: 5, cursor: 'pointer' }}> <Well bsSize="small" style={{ marginBottom: 5, cursor: 'pointer' }}>
<b> {event.username} </b> <b> {event.fullName} </b>
{type === 'creation' ? 'created on ' : ''} {type === 'creation' ? 'created on ' : ''}
{type === 'edition' ? 'edited on ' : ''} {type === 'edition' ? 'edited on ' : ''}
{moment(event.creationDate).format('MMMM DD HH:mm')} {moment(event.creationDate).format('MMMM DD HH:mm')}
......
import React from 'react'; import React, { useState } from 'react';
import moment from 'moment'; import moment from 'moment';
import PropTypes from 'prop-types'; import { useHistory } from 'react-router';
import { useQuery } from '../../../helpers/hooks';
import { Button, Glyphicon, Label } from 'react-bootstrap'; import { Button, Glyphicon, Label } from 'react-bootstrap';
import { import { getOriginalEvent } from '../../../helpers/eventHelpers';
getOriginalEvent,
getPreviousVersionNumber,
} from '../../../helpers/eventHelpers';
import TagListInLine from '../Tag/TagListInLine'; import TagListInLine from '../Tag/TagListInLine';
import styles from './EventList.module.css'; import styles from './EventList.module.css';
import EventTextBox from './EventTextBox'; import EventTextBox from './EventTextBox';
...@@ -13,124 +11,107 @@ import EventTextBox from './EventTextBox'; ...@@ -13,124 +11,107 @@ import EventTextBox from './EventTextBox';
/** React component which renders an event. Here 'event can be the classical event as found in the logbook but /** React component which renders an event. Here 'event can be the classical event as found in the logbook but
* could also be a list of event corresponding to a collapsed line containing several events * could also be a list of event corresponding to a collapsed line containing several events
*/ */
class Event extends React.Component { function Event(props) {
constructor(props) { let events = [props.event];
super(props); const { isReleased, search } = props;
this.state = {
collapsed: true,
};
this.handleClick = this.handleClick.bind(this);
}
render() { const history = useHistory();
let events = [this.props.event]; const query = useQuery();
if (this.props.event.events && !this.state.collapsed) { const [collapsed, setCollapsed] = useState(true);
events = events.concat(this.props.event.events);
}
const getButtonIcon = (event) => { if (props.event.events && !collapsed) {
if (this.props.logbookContext.isReleased) { events = events.concat(props.event.events);
return <Glyphicon glyph="eye-open" />; }
}
if (getPreviousVersionNumber(event) === 0) {
return <Glyphicon glyph="pencil" style={{ width: 10 }} />;
}
return <Glyphicon glyph="pencil" style={{ width: 10 }} />;
};
const getTimeComponent = (event) => { const getButtonIcon = () => {
return ( if (isReleased) {
<a return <Glyphicon glyph="eye-open" />;
id={event._id} }
href={`events?page=${event.meta.page.currentPage}#${event._id}`} return <Glyphicon glyph="pencil" style={{ width: 10 }} />;
style={{ fontWeight: 'bold' }} };
>
{moment(getOriginalEvent(event).creationDate).format(
moment.HTML5_FMT.TIME_SECONDS
)}
</a>
);
};
return events.map((event, index) => (
<tr key={index} style={{ backgroundColor: '#f0f0f6' }}>
<td
style={{ width: 16 }}
className={styles.borderTopSeparatorBetweenEvents}
>
<Button
bsStyle="default"
bsSize="small"
style={{ width: 25, position: 'static', padding: 0 }}
onClick={() => this.props.onEventClicked(event)}
>
{getButtonIcon(event)}
</Button>
</td>
<td const getTimeComponent = (event) => {
className={styles.borderTopSeparatorBetweenEvents} const page = event.meta.search
style={{ width: 16, backgroundColor: 'white' }} ? event.meta.search.page
> : event.meta.page.currentPage;
{getTimeComponent(event)} const eventCreationDate = moment(getOriginalEvent(event).creationDate);
</td> return (
<a
href={`events?page=${page}#${event._id}`}
style={{ fontWeight: 'bold' }}
className="text-muted"
title={eventCreationDate.format(
moment.HTML5_FMT.DATETIME_LOCAL_SECONDS
)}
>
{eventCreationDate.format(moment.HTML5_FMT.TIME_SECONDS)}
</a>
);
};
<td const onHandleClick = () => {
className={styles.borderTopSeparatorBetweenEvents} setCollapsed(!collapsed);
style={{ };
paddingBottom: 0,
backgroundColor: 'white', return events.map((event, index) => (
<tr id={event._id} key={index} style={{ backgroundColor: '#f0f0f6' }}>
<td
style={{ width: 16 }}
className={styles.borderTopSeparatorBetweenEvents}
>
<Button
bsStyle="default"
bsSize="small"
style={{ width: 25, position: 'static', padding: 0 }}
onClick={() => {
query.set('edit', event._id);
history.push({ search: query.toString() });
}} }}
> >
<div style={{ marginLeft: 5, backgroundColor: 'white' }}> {getButtonIcon(event)}
<EventTextBox event={event} /> </Button>
</td>
{event.events && this.state.collapsed && ( <td
<Label className={styles.borderTopSeparatorBetweenEvents}
style={{ style={{ width: 16, backgroundColor: 'white' }}
color: 'blue', >
backgroundColor: '#f8f8f8', {getTimeComponent(event)}
cursor: 'pointer', </td>
}}
onClick={this.handleClick}
>
.... {event.events.length} command lines more
</Label>
)}
</div>
</td>
<td
className={styles.borderTopSeparatorBetweenEvents}
style={{ width: 50, backgroundColor: 'white' }}
>
<TagListInLine tags={event.tag} />
</td>
</tr>
));
}
handleClick() { <td
this.setState({ collapsed: !this.state.collapsed }); className={styles.borderTopSeparatorBetweenEvents}
} style={{
paddingBottom: 0,
backgroundColor: 'white',
}}
>
<div style={{ marginLeft: 5, backgroundColor: 'white' }}>
<EventTextBox event={event} search={search} />
getUncollapsedEvents() { {event.events && collapsed && (
return ( <Label
<tbody> style={{
{this.props.event.events.map((event) => color: 'blue',
this.getEventContentBody(event) backgroundColor: '#f8f8f8',
)} cursor: 'pointer',
</tbody> }}
); onClick={onHandleClick}
} >
.... {event.events.length} command lines more
</Label>
)}
</div>
</td>
<td
className={styles.borderTopSeparatorBetweenEvents}
style={{ width: 50, backgroundColor: 'white' }}
>
<TagListInLine tags={event.tag} />
</td>
</tr>
));
} }
Event.protypes = {
/** A classical event or a structure representing a collapsed line containing several similar events */
event: PropTypes.object,
/** Context in which the logbook is run */
logbookContext: PropTypes.object,
/** Callback function triggered which the user clicks a link to edit/consult the detailed event */
onEventClicked: PropTypes.func,
};
export default Event; export default Event;
...@@ -16,14 +16,18 @@ function collapse(items) { ...@@ -16,14 +16,18 @@ function collapse(items) {
const collapsed = []; const collapsed = [];
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
const event = items[i]; const event = items[i];
if (event.category) {
if (event.category && !isEventSearchedResult(event)) {
if ( if (
event.category.toLowerCase() === EVENT_CATEGORY_COMMANDLINE && event.category.toLowerCase() === EVENT_CATEGORY_COMMANDLINE &&
!event.previousVersionEvent !event.previousVersionEvent
) { ) {
const lastEvent = collapsed[collapsed.length - 1]; const lastEvent = collapsed[collapsed.length - 1];
if (lastEvent && lastEvent.category) { if (lastEvent && lastEvent.category) {
if (lastEvent.category.toLowerCase() === EVENT_CATEGORY_COMMANDLINE) {