Commit 3e53ab65 authored by Alejandro De Maria Antolinos's avatar Alejandro De Maria Antolinos Committed by Marjolaine Bodin
Browse files

Massive Removal of outdated issues. We need to review from scracth all the...

Massive Removal of outdated issues. We need to review from scracth all the components of the data portal: datasets, logbook and sample tracking

Removing outdate issues It closes #130, closes #132, closes #137, closes #138, closes #139, closes #140, closes #147, closes #155, closes #177, closes #179, closes #186, closes #192, closes #197, closes #198, closes #210, closes #215, closes #218, closes #221, closes #222, closes #225 @228, closes #229, closes #232, closes #236, closes #242, closes #243, closes #244, closes #246, closes #254, closes #256, closes #265, closes #266, closes #268, closes #272, closes #274, closes #275, closes #276, closes #279, closes #339, closes #347, closes #350, closes #351, closes #377, closes #387, closes #390, closes #400, closes #401, closes #413, closes #414, closes #415, closes #417, closes #418, closes #421, closes #428, closes #430, closes #463, closes #469, closes #481
parent c7b8313e
......@@ -14,7 +14,8 @@ export function getEventURL(
sortOrder,
sortBy,
types,
format
format,
search
) {
const params = new URLSearchParams();
params.set('investigationId', investigationId);
......@@ -24,7 +25,7 @@ export function getEventURL(
if (types) params.set('types', types);
if (skip) params.set('skip', skip);
if (format) params.set('format', format);
if (search) params.set('search', search);
return `${ICATPLUS.server}/logbook/${sessionId}/event?${params.toString()}`;
}
......
......@@ -107,7 +107,7 @@ class EventVersionItem extends React.Component {
function buildTheDisplay(event, type, mostRecent) {
return (
<Well bsSize="small" style={{ marginBottom: 5, cursor: 'pointer' }}>
<b> {event.username} </b>
<b> {event.fullName} </b>
{type === 'creation' ? 'created on ' : ''}
{type === 'edition' ? 'edited on ' : ''}
{moment(event.creationDate).format('MMMM DD HH:mm')}
......
......@@ -13,7 +13,7 @@ import EventTextBox from './EventTextBox';
*/
function Event(props) {
let events = [props.event];
const { isReleased } = props;
const { isReleased, search } = props;
const history = useHistory();
const query = useQuery();
......@@ -32,14 +32,14 @@ function Event(props) {
};
const getTimeComponent = (event) => {
const page = event.meta.search
? event.meta.search.page
: event.meta.page.currentPage;
return (
<a
id={event._id}
href={`events?page=${event.meta.page.currentPage}#${event._id}`}
href={`events?page=${page}#${event._id}`}
style={{ fontWeight: 'bold' }}
title={moment(getOriginalEvent(event).creationDate).format(
moment.HTML5_FMT.DATETIME_LOCAL_SECONDS
)}
class="text-muted"
>
{moment(getOriginalEvent(event).creationDate).format(
moment.HTML5_FMT.TIME_SECONDS
......@@ -53,7 +53,7 @@ function Event(props) {
};
return events.map((event, index) => (
<tr key={index} style={{ backgroundColor: '#f0f0f6' }}>
<tr id={event._id} key={index} style={{ backgroundColor: '#f0f0f6' }}>
<td
style={{ width: 16 }}
className={styles.borderTopSeparatorBetweenEvents}
......@@ -86,7 +86,7 @@ function Event(props) {
}}
>
<div style={{ marginLeft: 5, backgroundColor: 'white' }}>
<EventTextBox event={event} />
<EventTextBox event={event} search={search} />
{event.events && collapsed && (
<Label
......
......@@ -76,7 +76,7 @@ function getItems(events, automaticCollapsing) {
* The list of the all events
*/
function EventList(props) {
const { events, isReleased, automaticCollapsing } = props;
const { events, isReleased, search, automaticCollapsing } = props;
if (!events) {
return null;
......@@ -119,6 +119,7 @@ function EventList(props) {
return (
<Event
search={search}
key={index}
event={event}
isReleased={isReleased}
......
......@@ -22,7 +22,7 @@ import LazyLoadedText from './LazyLoadedText';
* For notifications, the box could render 2 texts: the original text and the last comment if it exists
* */
function EventTextBox(props) {
const { event } = props;
const { event, search } = props;
const htmlText = getText(event.content, 'html');
let text = convertImagesToThumbnails(
......@@ -42,7 +42,7 @@ function EventTextBox(props) {
if (event.type === ANNOTATION) {
return (
<div className={getTextColor(EVENT_CATEGORY_COMMENT)}>
<LazyLoadedText text={text} />
<LazyLoadedText text={text} search={search} />
</div>
);
}
......@@ -51,14 +51,8 @@ function EventTextBox(props) {
if (!event.previousVersionEvent) {
return (
<div className={getTextColor(event.category)}>
<pre
className={
event.category.toLowerCase() === EVENT_CATEGORY_COMMANDLINE
? undefined
: 'whitePre'
}
>
<LazyLoadedText text={text} />
<pre className="whitePre">
<LazyLoadedText text={text} search={search} />
</pre>
</div>
);
......@@ -79,11 +73,11 @@ function EventTextBox(props) {
: 'whitePre'
}
>
<LazyLoadedText text={originalText} />
<LazyLoadedText text={originalText} search={search} />
</pre>
</div>
<div className={getTextColor(EVENT_CATEGORY_COMMENT)}>
<LazyLoadedText text={text} />
<LazyLoadedText text={text} search={search} />
</div>
</>
);
......@@ -105,13 +99,13 @@ function getTextColor(category) {
}
if (category.toLowerCase() === EVENT_CATEGORY_INFO) {
return '';
return 'primary';
}
if (category.toLowerCase() === EVENT_CATEGORY_DEBUG) {
return 'text-muted';
}
return 'text-primary';
return '';
}
EventTextBox.propTypes = {
......
......@@ -6,20 +6,41 @@ import LazyLoad from 'react-lazyload';
* least an `img` tag. When there are no images, the text is rendered as such.
*/
function LazyLoadedText(props) {
const { text } = props;
const { search } = props;
let { text } = props;
const withImages = text.indexOf('img') !== -1;
/* If search is enabled then the search is highlighted but not in the images as the base64 code can get corrupted */
if (search && !withImages) {
text = text.replace(
new RegExp(search, 'g'),
`<span style="background-color:yellow;">${search}</span>`
);
}
if (!text) {
return <div />;
}
if (text.indexOf('img') !== -1) {
if (!withImages) {
return (
<LazyLoad once>
<div dangerouslySetInnerHTML={{ __html: text }} />
<div
dangerouslySetInnerHTML={{
__html: text,
}}
/>
</LazyLoad>
);
}
return <div dangerouslySetInnerHTML={{ __html: text }} />;
return (
<div
dangerouslySetInnerHTML={{
__html: text,
}}
/>
);
}
LazyLoadedText.propTypes = {
......
......@@ -2,8 +2,16 @@ 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 {
Nav,
Navbar,
NavItem,
Well,
Glyphicon,
FormControl,
InputGroup,
Button,
} from 'react-bootstrap';
import { getEventURL } from '../../../api/icat-plus/logbook';
import { NEW_EVENT_VISIBLE } from '../../../constants/eventTypes';
import LogbookPager from '../LogbookPager';
......@@ -69,13 +77,17 @@ function EventListMenu(props) {
}
};
const onSearch = (data) => {
if (data?.length > 0) {
query.set('search', data[0].search);
history.push({ search: query.toString() });
}
const onSearch = (value) => {
query.set('search', value);
query.delete('page');
history.push({ search: query.toString() });
};
const onKeyUpValue = (event) => {
if (event.keyCode === 13) {
onSearch(event.target.value);
}
};
return (
<Navbar
fluid
......@@ -189,37 +201,28 @@ function EventListMenu(props) {
</NavItem>
)}
<NavItem className="logbookNavItem">
<div className="hidden-xs hidden-sm hidden-md">
<LogbookPager
activePage={activePage}
eventCount={eventCountBySelectionFilter}
<NavItem>
<InputGroup style={{ marginTop: '-7px' }}>
<InputGroup.Button>
<Button bsStyle="primary">
<Glyphicon glyph="search" />
</Button>
</InputGroup.Button>
<FormControl
onKeyUp={onKeyUpValue}
type="search"
placeholder="Search"
/>
</div>
</InputGroup>
</NavItem>
</Nav>
<Nav pullRight>
<NavItem
eventKey={6}
className="logbookNavItem logbookNavItem--combo"
>
<div className="comboSearchContainer">
<ComboSearch
onSearch={onSearch}
selectData={[
{ value: 'Everywhere', text: 'Everywhere' },
{ value: 'category', text: 'Category' },
{ value: 'Creation date', text: 'Creation date' },
]}
datePickerCriteria="Creation date"
/>
</div>
<NavItem>
<Well
style={{
display: 'inline-block',
padding: 4,
padding: 6,
marginBottom: 0,
marginTop: '-7px',
marginLeft: '-30px',
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
color: '#666',
......@@ -229,6 +232,17 @@ function EventListMenu(props) {
</Well>
</NavItem>
</Nav>
<Nav pullRight>
<NavItem className="logbookNavItem" pullRight>
<div className="hidden-xs hidden-sm hidden-md">
<LogbookPager
activePage={activePage}
eventCount={eventCountBySelectionFilter}
/>
</div>
</NavItem>
</Nav>
</Navbar.Collapse>
{isSettingsDisplayed && (
<SettingLogbookMenuPanel
......
......@@ -36,7 +36,7 @@ const AuthorAndTime = (props) => {
<small style={{ color: 'white' }}>
{' '}
<em>
@{props.event.username} -{' '}
@{props.event.fullName} -{' '}
<TimeAgo date={props.event.creationDate} />
<OverlayTrigger
trigger="click"
......
......@@ -28,10 +28,10 @@ const UI = {
},
logbook: {
/** Number of logbook events to display per page. EVENTS_PER_PAGE should be lower than EVENTS_PER_DOWNLOAD */
EVENTS_PER_PAGE: 40,
EVENTS_PER_PAGE: 100,
/** Maximum number of logbook events downloaded from the server. This enables to store a larger set of
events than those required for a single page thus reducing HTTP requests to the server. */
EVENTS_PER_DOWNLOAD: 40,
EVENTS_PER_DOWNLOAD: 100,
/* the field used to sort events. Most of the time 'creationDate' is used. Possible values: 'creationDate', '_id', 'createdAt', 'updatedAt' */
SORT_EVENTS_BY: '_id',
/* the order the events sorted by SORT_EVENTS_BY will be ordered. Possible values: 1 (for ascending order), -1 (for descending order)*/
......
import React, { useEffect } from 'react';
import React, { useEffect, useRef } from 'react';
import { Col, Grid, Row, Alert } from 'react-bootstrap';
import { useParams } from 'react-router';
import { useDispatch, useSelector } from 'react-redux';
......@@ -19,17 +19,32 @@ function DatasetsPage() {
const sessionId = useSelector((state) => state.user.sessionId);
const datasets = useSelector((state) => state.datasets);
const breadcrumbsList = useSelector((state) => state.breadcrumbsList);
const currentBreadcrumbsList = useRef(); // work around stale breadcrumbsList reference
currentBreadcrumbsList.current = breadcrumbsList;
const dispatch = useDispatch();
/* eslint-disable react-hooks/exhaustive-deps */
useEffect(() => {
dispatch(fetchDatasetsByInvestigationId(sessionId, investigationId));
if (investigation) {
dispatch(setInvestigationBreadCrumbs(investigation));
dispatch(
setInvestigationBreadCrumbs(
investigation,
currentBreadcrumbsList.current
)
);
}
// `investigation` object can be duplicated in store, so we use `investigation.id` as effect dependency
}, [dispatch, investigationId, sessionId, investigation?.id]); // eslint-disable-line react-hooks/exhaustive-deps
}, [
dispatch,
investigationId,
sessionId,
investigation?.id,
currentBreadcrumbsList,
]);
if (!investigation) {
return <PageNotFound />;
......
import React, { useEffect } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import { useFetcher, useResource } from 'rest-hooks';
import { Col, Grid, Row } from 'react-bootstrap';
import { Col, Grid, Row, Alert } from 'react-bootstrap';
import { useParams } from 'react-router';
import { useDispatch, useSelector } from 'react-redux';
import moment from 'moment';
......@@ -25,6 +25,8 @@ const {
} = UI.logbook;
function EventsPage() {
const [loading, setLoading] = useState(false);
const dispatch = useDispatch();
const { investigationId } = useParams();
const user = useSelector((state) => state.user);
......@@ -32,6 +34,7 @@ function EventsPage() {
const query = useQuery();
const edit = query.get('edit');
const search = query.get('search');
const page = query.get('page') || 1;
const { categoryTypes } = useSelector((state) => state.logbook);
......@@ -48,12 +51,17 @@ function EventsPage() {
(state) => state.logbook.isSortingLatestEventsFirst
);
const breadcrumbsList = useSelector((state) => state.breadcrumbsList);
const currentBreadcrumbsList = useRef(); // work around stale breadcrumbsList reference
currentBreadcrumbsList.current = breadcrumbsList;
const fetchingParams = {
investigationId,
skip: EVENTS_PER_PAGE * (page - 1),
limit: EVENTS_PER_DOWNLOAD,
sortBy: SORT_EVENTS_BY,
sortOrder: SORTING_ORDER,
search,
types: categoryTypes?.map((cat) =>
cat.category ? `${cat.type}-${cat.category}` : `${cat.type}`
),
......@@ -62,8 +70,10 @@ function EventsPage() {
const events = useResource(EventResource.listShape(), fetchingParams);
const refecth = useFetcher(EventResource.listShape());
const refetchEvent = () => {
refecth(fetchingParams);
const refetchEvent = async () => {
setLoading(true);
await refecth(fetchingParams);
setLoading(false);
};
const editEvent = events.find((event) => event._id === edit);
......@@ -73,14 +83,22 @@ function EventsPage() {
// Getting total number of events
let totalEventCount = events.length;
if (events && events.length > 0) {
totalEventCount = events[0].meta.page.total;
totalEventCount =
events[0].meta.search != null
? events[0].meta.search.total
: events[0].meta.page.total;
}
useEffect(() => {
if (investigation) {
dispatch(setInvestigationBreadCrumbs(investigation));
dispatch(
setInvestigationBreadCrumbs(
investigation,
currentBreadcrumbsList.current
)
);
}
}, [dispatch, investigation]);
}, [dispatch, investigation, currentBreadcrumbsList]);
const isReleased =
!!investigation.releaseDate && moment().isAfter(investigation.releaseDate);
......@@ -129,8 +147,9 @@ function EventsPage() {
automaticRefresh={automaticRefresh}
isSortingLatestEventsFirst={isSortingLatestEventsFirst}
/>
{loading && <Alert bsStyle="info">Updating new events</Alert>}
<EventList
search={search}
events={events}
isReleased={isReleased}
automaticCollapsing={automaticCollapsing}
......@@ -140,9 +159,7 @@ function EventsPage() {
</Grid>
<LogbookPager
activePage={page}
eventCount={
events ? (events.length > 0 ? events[0].meta.page.total : 0) : 0
}
eventCount={totalEventCount}
isCentered={true}
/>
</>
......
import React, { useEffect } from 'react';
import React, { useEffect, useRef } from 'react';
import { Col, Grid, Row } from 'react-bootstrap';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router';
import ShippingPanel from '../components/Shipping/ShippingPanel';
import TabContainerMenu from '../components/TabContainerMenu/TabContainerMenu';
......@@ -16,12 +16,20 @@ function ShippingPage() {
id: investigationId,
});
const dispatch = useDispatch();
const breadcrumbsList = useSelector((state) => state.breadcrumbsList);
const currentBreadcrumbsList = useRef(); // work around stale breadcrumbsList reference
currentBreadcrumbsList.current = breadcrumbsList;
useEffect(() => {
if (investigation) {
dispatch(setInvestigationBreadCrumbs(investigation));
dispatch(
setInvestigationBreadCrumbs(
investigation,
currentBreadcrumbsList.current
)
);
}
}, [dispatch, investigation]);
}, [dispatch, investigation, currentBreadcrumbsList]); // eslint-disable-line react-hooks/exhaustive-deps
if (!investigation) {
return <PageNotFound />;
......
......@@ -2,7 +2,9 @@ import { INVESTIGATION_DATE_FORMAT } from '../constants';
import moment from 'moment';
import { replaceOrAppendBreadCrumb } from '../actions/breadcrumbs';
export function setInvestigationBreadCrumbs(investigation) {
export function setInvestigationBreadCrumbs(investigation, breadcrumbsList) {
const investigationBreadCrumb = 'INVESTIGATION';
function getDateFormat(date) {
return date ? moment(date).format(INVESTIGATION_DATE_FORMAT) : '';
}
......@@ -13,11 +15,32 @@ export function setInvestigationBreadCrumbs(investigation) {
name: `${getDateFormat(investigation.startDate)}-${getDateFormat(
investigation.endDate
)} ${investigation.title}`,
type: investigationBreadCrumb,
};
}
function isLastItemInvestigation(items) {
const nbItems = items.length;
return (
nbItems > 0 &&
items[nbItems - 1].type &&
items[nbItems - 1].type === investigationBreadCrumb
);
}
function getLevelInBreadCrumb(breadcrumbsList) {
const items =
breadcrumbsList && breadcrumbsList.items ? breadcrumbsList.items : [];
const level = isLastItemInvestigation(items)
? items.length - 1
: items.length;
return level;
}
const level = getLevelInBreadCrumb(breadcrumbsList);
return replaceOrAppendBreadCrumb(
getInvestigationBreadCrumb(investigation),
1
level
);
}
......@@ -22,6 +22,9 @@ export function useScrollToHash(props) {
if (hash) {
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element) {
element.setAttribute('style', 'border:1px solid blue');
}
if (element) {
element.scrollIntoView({ block: 'start', behavior: 'instant' });
window.scrollBy(0, offsetY);
......
......@@ -16,8 +16,16 @@ export default class EventResource extends Resource {
static listUrl(params) {
const { sessionId } = store.getState().user;
const { investigationId, skip, limit, sortOrder, sortBy, types } = params;
const {
investigationId,
skip,
limit,