Commit 4969698e authored by Alejandro De Maria Antolinos's avatar Alejandro De Maria Antolinos
Browse files

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

Issue 491

See merge request !515
parents 06d5f2f6 934059ab
Pipeline #47534 passed with stage
in 3 minutes and 1 second
This diff is collapsed.
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+
* @param {String} sessionId the session identifier
* @param {String} investigationId the investigation indentifier
* @return {String} the URL to get the requested events
*/
export function updateEvent(sessionId, investigationId) {
return `${ICATPLUS.server}/logbook/${sessionId}/investigation/id/${investigationId}/event/update`;
}
/**
* Get URL used to download a PDF file for a given investigation from the logbook
* @param {string} sessionId session identifier
* @param {*} investigationId investigation identifier
* @param {object} selectionFilter selection filter used to retrieve part of the logbook. This is URI encoded and passed as query string
*/
export function getPDF(sessionId, investigationId, selectionFilter) {
return `${ICATPLUS.server}/logbook/${sessionId}/investigation/id/${investigationId}/event/pdf?find=&sort=&skip=&limit=`
.replace('find=', () => {
return `find=${
selectionFilter && selectionFilter.find
? JSON.stringify(selectionFilter.find)
: ''
}`;
})
.replace('&sort=', () => {
return `&sort=${
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)
: ''
}`;
});
export function getEventURL(
sessionId,
investigationId,
skip,
limit,
sortOrder,
sortBy,
types,
format
) {
const params = new URLSearchParams();
params.set('investigationId', investigationId);
if (limit) params.set('limit', limit);
if (sortBy) params.set('sortBy', sortBy);
if (sortOrder) params.set('sortOrder', sortOrder);
if (types) params.set('types', types);
if (skip) params.set('skip', skip);
if (format) params.set('format', format);
return `${ICATPLUS.server}/logbook/${sessionId}/event?${params.toString()}`;
}
/** Get the tags associated to a given investigation
......
import React from 'react';
import React, { useState } from 'react';
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 {
getOriginalEvent,
getPreviousVersionNumber,
} from '../../../helpers/eventHelpers';
import { getOriginalEvent } from '../../../helpers/eventHelpers';
import TagListInLine from '../Tag/TagListInLine';
import styles from './EventList.module.css';
import EventTextBox from './EventTextBox';
......@@ -13,124 +11,97 @@ import EventTextBox from './EventTextBox';
/** 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
*/
class Event extends React.Component {
constructor(props) {
super(props);
this.state = {
collapsed: true,
};
this.handleClick = this.handleClick.bind(this);
}
render() {
let events = [this.props.event];
function Event(props) {
let events = [props.event];
const { isReleased } = props;
if (this.props.event.events && !this.state.collapsed) {
events = events.concat(this.props.event.events);
}
const history = useHistory();
const query = useQuery();
const getButtonIcon = (event) => {
if (this.props.logbookContext.isReleased) {
return <Glyphicon glyph="eye-open" />;
}
if (getPreviousVersionNumber(event) === 0) {
return <Glyphicon glyph="pencil" style={{ width: 10 }} />;
}
return <Glyphicon glyph="pencil" style={{ width: 10 }} />;
};
const [collapsed, setCollapsed] = useState(true);
const getTimeComponent = (event) => {
return (
<a
id={event._id}
href={`events?page=${event.meta.page.currentPage}#${event._id}`}
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>
if (props.event.events && !collapsed) {
events = events.concat(props.event.events);
}
<td
className={styles.borderTopSeparatorBetweenEvents}
style={{ width: 16, backgroundColor: 'white' }}
>
{getTimeComponent(event)}
</td>
const getButtonIcon = () => {
if (isReleased) {
return <Glyphicon glyph="eye-open" />;
}
return <Glyphicon glyph="pencil" style={{ width: 10 }} />;
};
<td
className={styles.borderTopSeparatorBetweenEvents}
style={{
paddingBottom: 0,
backgroundColor: 'white',
const getTimeComponent = (event) => {
return (
<a
id={event._id}
href={`events?page=${event.meta.page.currentPage}#${event._id}`}
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={() => {
query.set('edit', event._id);
history.push({ search: query.toString() });
}}
>
<div style={{ marginLeft: 5, backgroundColor: 'white' }}>
<EventTextBox event={event} />
{getButtonIcon(event)}
</Button>
</td>
{event.events && this.state.collapsed && (
<Label
style={{
color: 'blue',
backgroundColor: '#f8f8f8',
cursor: 'pointer',
}}
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>
));
}
<td
className={styles.borderTopSeparatorBetweenEvents}
style={{ width: 16, backgroundColor: 'white' }}
>
{getTimeComponent(event)}
</td>
handleClick() {
this.setState({ collapsed: !this.state.collapsed });
}
<td
className={styles.borderTopSeparatorBetweenEvents}
style={{
paddingBottom: 0,
backgroundColor: 'white',
}}
>
<div style={{ marginLeft: 5, backgroundColor: 'white' }}>
<EventTextBox event={event} />
getUncollapsedEvents() {
return (
<tbody>
{this.props.event.events.map((event) =>
this.getEventContentBody(event)
)}
</tbody>
);
}
{event.events && collapsed && (
<Label
style={{
color: 'blue',
backgroundColor: '#f8f8f8',
cursor: 'pointer',
}}
onClick={setCollapsed(!collapsed)}
>
.... {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;
......@@ -76,7 +76,7 @@ function getItems(events, automaticCollapsing) {
* The list of the all events
*/
function EventList(props) {
const { events } = props;
const { events, isReleased } = props;
if (!events) {
return null;
......@@ -121,7 +121,7 @@ function EventList(props) {
<Event
key={index}
event={event}
logbookContext={props.logbookContext}
isReleased={isReleased}
onEventClicked={props.onEventClicked}
/>
);
......
import React from 'react';
import { useHistory } from 'react-router';
import { useQuery } from '../../helpers/hooks';
import PropTypes from 'prop-types';
import ReactPaginate from 'react-paginate';
import UI from '../../config/ui';
......@@ -24,6 +25,7 @@ const getTotalPageNumber = (eventCount) => {
/* This class handles the display of pages for the logbook */
function LogbookPager(props) {
const history = useHistory();
const query = useQuery();
const { eventCount = 0, isCentered, activePage } = props;
if (getTotalPageNumber(eventCount) <= 1 || eventCount === 0) {
......@@ -50,7 +52,8 @@ function LogbookPager(props) {
subContainerClassName={'pages pagination pagination-sm'}
activeClassName={'active'}
onPageChange={(data) => {
history.push(`${window.location.pathname}?page=${data.selected + 1}`);
query.set('page', data.selected + 1);
history.push({ search: query.toString() });
}}
/>
</div>
......
import PropTypes from 'prop-types';
import { useHistory } from 'react-router';
import { useQuery } from '../../../helpers/hooks';
import React, { useState } from 'react';
import { Nav, Navbar, NavItem, Well } from 'react-bootstrap';
import { ComboSearch } from 'react-combo-search';
import { getPDF } from '../../../api/icat-plus/logbook';
import { getEventURL } from '../../../api/icat-plus/logbook';
import { NEW_EVENT_VISIBLE } from '../../../constants/eventTypes';
import LogbookPager from '../LogbookPager';
import EventListMenuButton from './EventListMenuButton';
......@@ -13,6 +15,9 @@ import SettingLogbookMenuPanel from './SettingLogbookMenuPanel';
* The menu displayed above the event list
*/
function EventListMenu(props) {
const history = useHistory();
const query = useQuery();
const [isNavbarExpanded, setIsNavBarExpanded] = useState(false);
const [isSettingsDisplayed, setIsSettingsDisplayed] = useState(false);
......@@ -20,16 +25,18 @@ function EventListMenu(props) {
eventCountBySelectionFilter,
investigationId,
isNewButtonEnabled,
selectionFilter,
sessionId,
getEvents,
periodicdata,
setNewEventVisibility,
logbookContext,
isReleased,
eventCountSinceLastRefresh,
activePage,
searchEvents,
categoryTypes,
skip,
limit,
sortOrder,
sortBy,
types,
automaticCollapsing,
automaticRefresh,
isSortingLatestEventsFirst,
......@@ -42,10 +49,6 @@ function EventListMenu(props) {
setIsNavBarExpanded(!isNavbarExpanded);
};
const setViewSettings = () => {
setIsSettingsDisplayed(!isSettingsDisplayed);
};
const getSettingsTooltip = () => {
return isSettingsDisplayed ? 'Hide settings' : 'Show settings';
};
......@@ -57,16 +60,20 @@ function EventListMenu(props) {
const onSelectNavbar = (eventKey) => {
if (eventKey === 1) {
if (isNewButtonEnabled) {
setNewEventVisibility(NEW_EVENT_VISIBLE);
query.set('edit', NEW_EVENT_VISIBLE);
history.push({ search: query.toString() });
}
}
if (eventKey === 3) {
setViewSettings();
setIsSettingsDisplayed(!isSettingsDisplayed);
}
};
const onSearch = (data) => {
return searchEvents(data);
if (data?.length > 0) {
query.set('search', data[0].search);
history.push({ search: query.toString() });
}
};
return (
......@@ -96,7 +103,7 @@ function EventListMenu(props) {
glyph="plus"
tooltipText="Create a new event"
isEnabled={isNewButtonEnabled}
isVisible={!logbookContext.isReleased}
isVisible={!isReleased}
/>
</NavItem>
......@@ -111,7 +118,7 @@ function EventListMenu(props) {
glyph="camera"
tooltipText="Take a photo"
isEnabled
isVisible={!logbookContext.isReleased}
isVisible={!isReleased}
/>
</NavItem>
</Nav>
......@@ -133,7 +140,16 @@ function EventListMenu(props) {
href={
!isNewButtonEnabled || eventCountBySelectionFilter === 0
? null
: getPDF(sessionId, investigationId, selectionFilter)
: getEventURL(
sessionId,
investigationId,
skip,
limit,
sortOrder,
sortBy,
types,
'pdf'
)
}
target="_blank"
className="logbookNavItem"
......@@ -160,16 +176,18 @@ function EventListMenu(props) {
isEnabled
/>
</NavItem>
<NavItem eventKey={5} className="logbookNavItem">
<div className="hidden-xs hidden-sm">
<NewlyAvailableEventsDialogue
autorefreshEventList={automaticRefresh}
eventCountByPeriodicRefresher={periodicdata}
eventCountSinceLastRefresh={eventCountSinceLastRefresh}
onIconClicked={() => getEvents(periodicdata)}
/>
</div>
</NavItem>
{false && (
<NavItem eventKey={5} className="logbookNavItem">
<div className="hidden-xs hidden-sm">
<NewlyAvailableEventsDialogue
autorefreshEventList={false} //{isAutorefreshEnabled}
eventCountByPeriodicRefresher={periodicdata}
eventCountSinceLastRefresh={eventCountSinceLastRefresh}
onIconClicked={() => getEvents(periodicdata)}
/>
</div>
</NavItem>
)}
<NavItem className="logbookNavItem">
<div className="hidden-xs hidden-sm hidden-md">
......@@ -215,7 +233,7 @@ function EventListMenu(props) {
{isSettingsDisplayed && (
<SettingLogbookMenuPanel
categoryTypes={categoryTypes}
logbookContext={logbookContext}
isReleased={isReleased}
automaticCollapsing={automaticCollapsing}
automaticRefresh={automaticRefresh}
isSortingLatestEventsFirst={isSortingLatestEventsFirst}
......@@ -238,10 +256,8 @@ EventListMenu.propTypes = {
investigationId: PropTypes.number,
/** Whether the New button is enabled or not */
isNewButtonEnabled: PropTypes.bool,
/** Context in which the logbook is run */
logbookContext: PropTypes.object,
/** Callback function which reloads the events based on search criteria*/
searchEvents: PropTypes.func,
/** If investigation is relased */
isReleased: PropTypes.bool,
/** Selection filter for mongo request (used for PDF request) */
selectionFilter: PropTypes.object,
/** Session identifier */
......
......@@ -58,14 +58,17 @@ const checkBoxes = [
export default function SettingLogbookMenuPanel(props) {
const dispatch = useDispatch();
const {
isReleased,
automaticCollapsing,
automaticRefresh,
isSortingLatestEventsFirst,
} = props;
const getValueByName = (name) =>
checkBoxes.find((cb) => cb.name === name).value;
const logbookContext = props.logbookContext;
const toggleColor = '#428bca';
const automaticCollapsing = props.automaticCollapsing;
const automaticRefresh = props.automaticRefresh;
const isSortingLatestEventsFirst = props.isSortingLatestEventsFirst;
/** Checkbox will be checked if and only if its values are in the categoryTypes */
const isChecked = (values) => {
......@@ -129,7 +132,7 @@ export default function SettingLogbookMenuPanel(props) {
</Col>
<Col md={2} sm={6} xs={12}>
<Grid>
{!logbookContext.isReleased && (
{!isReleased && (
<Row>
<Col md={12} sm={12} xs={12}>
<Switch
......
import React from 'react';
import PropTypes from 'prop-types';
import React, { useState } from 'react';
import { useFetcher } from 'rest-hooks';
import { useHistory } from 'react-router';
import { useQuery } from '../../helpers/hooks';
import Loader from '../Loader';
import {
Button,
Glyphicon,
......@@ -7,6 +10,7 @@ import {
OverlayTrigger,
Panel,
Tooltip,
Alert,
} from 'react-bootstrap';
import TimeAgo from 'react-timeago';
import {
......@@ -17,57 +21,202 @@ import {
LOCALSTORAGE_KEY_NEW_EVENT_CONTENT_IN_HTML_FORMAT,
LOCALSTORAGE_KEY_NEW_EVENT_CONTENT_IN_PLAINTEXT_FORMAT,
} from '../../constants/eventTypes';
import { isEqual } from 'lodash-es';
import TagsSelectorContainer from '../../containers/Logbook/Tags/TagsSelectorContainer';
import { getText } from '../../helpers/eventHelpers';
import EditorWrapper from './Editor/EditorWrapper';
import EventFooter from './EventFooter';
import EventVersions from './EventVersions';
import EventResource from '../../resources/events';
/** Render author and time */
const AuthorAndTime = (props) => {