GitLab will be upgraded on June 23th evening. During the upgrade the service will be unavailable, sorry for the inconvenience.

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

Merge branch 'issue_493' into 'master'

#493

Closes #493, #492, and #489

See merge request !503
parents 39400fdc 93e8c55b
Pipeline #46402 passed with stage
in 4 minutes and 28 seconds
This diff is collapsed.
......@@ -185,9 +185,11 @@ function App() {
<MintSelectionPage />
</Route>
<Route exact path="/manager/stats/data">
<DataStatisticsPage />
</Route>
{user.isAdministrator && (
<Route exact path="/manager/stats/data">
<DataStatisticsPage />
</Route>
)}
<Route exact path="/login">
<Redirect
......
......@@ -41,11 +41,15 @@ class Event extends React.Component {
const getTimeComponent = (event) => {
return (
<p style={{ fontWeight: 'bold' }}>
<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
)}
</p>
</a>
);
};
return events.map((event, index) => (
......
import { cloneDeep } from 'lodash-es';
import moment from 'moment';
import PropTypes from 'prop-types';
import React, { Profiler } from 'react';
import React from 'react';
import { Table } from 'react-bootstrap';
import { EVENT_CATEGORY_COMMANDLINE } from '../../../constants/eventTypes';
import { getOriginalEvent } from '../../../helpers/eventHelpers';
......@@ -88,54 +88,46 @@ function EventList(props) {
return (
<>
<Profiler
id="language"
onRender={(id, phase, actualDuration) => {
console.log({ id, phase, actualDuration });
console.log(actualDuration);
}}
>
<Table responsive style={{ border: 0 }}>
<tbody>
{getItems(events).map((event, index) => {
if (event.type === 'date') {
return (
<tr
key={index}
<Table responsive style={{ border: 0 }}>
<tbody>
{getItems(events).map((event, index) => {
if (event.type === 'date') {
return (
<tr
key={index}
style={{
backgroundColor: '#f0f0f6',
color: 'gray',
height: 50,
}}
>
<td
id={event.anchor}
style={{
backgroundColor: '#f0f0f6',
color: 'gray',
height: 50,
textAlign: 'center',
fontSize: 18,
fontWeight: 'bold',
}}
colSpan={6}
>
<td
id={event.anchor}
style={{
textAlign: 'center',
fontSize: 18,
fontWeight: 'bold',
}}
colSpan={6}
>
{event.text}
<br />
</td>
</tr>
);
}
return (
<Event
key={index}
event={event}
logbookContext={props.logbookContext}
onEventClicked={props.onEventClicked}
/>
{event.text}
<br />
</td>
</tr>
);
})}
</tbody>
</Table>
</Profiler>
}
return (
<Event
key={index}
event={event}
logbookContext={props.logbookContext}
onEventClicked={props.onEventClicked}
/>
);
})}
</tbody>
</Table>
</>
);
}
......
import PropTypes from 'prop-types';
import React from 'react';
import { useHistory } from 'react-router';
import PropTypes from 'prop-types';
import ReactPaginate from 'react-paginate';
import UI from '../../config/ui';
/* This class handles the display of pages for the logbook */
class LogbookPager extends React.Component {
constructor(props) {
super(props);
this.getTotalPageNumber = this.getTotalPageNumber.bind(this);
}
render() {
if (this.getTotalPageNumber() <= 1) {
return null;
}
if (this.props.eventCount === 0) {
return null;
/**
* Get the number of pages requested to display the data
*/
const getTotalPageNumber = (eventCount) => {
if (UI.logbook.EVENTS_PER_PAGE) {
if (eventCount < UI.logbook.EVENTS_PER_PAGE) {
return 1;
}
return Math.ceil(eventCount / UI.logbook.EVENTS_PER_PAGE);
}
// if in case of error, displays all items on the same page
console.log(
'[ERROR] Could not get the number of events per page. Check the config file.'
);
return 1;
};
const styleForCentering = {
position: 'absolute',
left: '50%',
marginLeft: '-125px',
width: 250,
};
/* This class handles the display of pages for the logbook */
function LogbookPager(props) {
const history = useHistory();
const { eventCount = 0, isCentered, activePage } = props;
return (
<div style={this.props.isCentered ? styleForCentering : null}>
<ReactPaginate
forcePage={this.props.activePage - 1}
pageCount={this.getTotalPageNumber()}
pageRangeDisplayed={1}
previousLabel="<"
nextLabel=">"
marginPagesDisplayed={1}
containerClassName={'pagination pagination-sm margin-top-bottom-0'}
subContainerClassName={'pages pagination pagination-sm'}
activeClassName={'active'}
onPageChange={(data) => this.props.onPageClicked(data.selected + 1)}
/>
</div>
);
if (getTotalPageNumber(eventCount) <= 1 || eventCount === 0) {
return null;
}
/**
* Get the number of pages requested to display the data
*/
getTotalPageNumber() {
if (UI.logbook.EVENTS_PER_PAGE) {
if (this.props.eventCount < UI.logbook.EVENTS_PER_PAGE) {
return 1;
}
return Math.ceil(this.props.eventCount / UI.logbook.EVENTS_PER_PAGE);
}
// if in case of error, displays all items on the same page
console.log(
'[ERROR] Could not get the number of events per page. Check the config file.'
);
return 1;
}
const styleForCentering = {
position: 'absolute',
left: '50%',
marginLeft: '-125px',
width: 250,
};
return (
<div style={isCentered ? styleForCentering : null}>
<ReactPaginate
forcePage={activePage - 1}
pageCount={getTotalPageNumber(eventCount)}
pageRangeDisplayed={1}
previousLabel="<"
nextLabel=">"
marginPagesDisplayed={1}
containerClassName={'pagination pagination-sm margin-top-bottom-0'}
subContainerClassName={'pages pagination pagination-sm'}
activeClassName={'active'}
onPageChange={(data) => {
history.push(`${window.location.pathname}?page=${data.selected + 1}`);
}}
/>
</div>
);
}
LogbookPager.propTypes = {
......
......@@ -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: 2000,
EVENTS_PER_PAGE: 1000,
/** 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: 2000,
EVENTS_PER_DOWNLOAD: 1000,
/* 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)*/
......
......@@ -8,12 +8,17 @@ import PageNotFound from './PageNotFound';
import { useResource } from 'rest-hooks';
import InvestigationResource from '../resources/investigation';
import { setInvestigationBreadCrumbs } from './investigation-breadcrumbs';
import { useScrollToHash, useQuery } from '../helpers/hooks';
function EventsPage() {
const { investigationId } = useParams();
const investigation = useResource(InvestigationResource.detailShape(), {
id: investigationId,
});
const query = useQuery();
const page = query.get('page') || 1;
useScrollToHash({ milliseconds: 100, attempts: 100 });
const dispatch = useDispatch();
......@@ -32,7 +37,7 @@ function EventsPage() {
<Row>
<Col sm={12}>
<TabContainerMenu />
<LogbookContainer investigation={investigation} />
<LogbookContainer investigation={investigation} page={page} />
</Col>
</Row>
</Grid>
......
......@@ -40,8 +40,6 @@ function EventsProvider(props) {
getEvents(selectionFilter, sortingFilter, skip, investigationId)
.then(({ data }) => {
console.log(view);
// Filter notifications that have been annotated which annotation is empty
if (view === DOC_VIEW) {
// debugger;
......
......@@ -42,8 +42,10 @@ export class LogbookContainerClass extends React.Component {
constructor(props) {
super(props);
const { page = 1 } = props;
this.state = {
page: 1,
page,
eventCountBySelectionFilter: 0, // count of events for the current selection filter found in the logbook since last refresh
allEventsCountAtLastRefresh: 0, // count of all events found in the logbook since last refresh
......@@ -279,8 +281,13 @@ export class LogbookContainerClass extends React.Component {
});
}
componentDidUpdate() {
componentDidUpdate(prevProps) {
const { forceReloadEventList } = this.state;
if (this.props.page !== prevProps.page) {
this.setState({ page: this.props.page });
return;
}
if (forceReloadEventList) {
this.setState({ forceReloadEventList: false });
}
......
import { useLocation } from 'react-router';
import { useEffect } from 'react';
export function useQuery() {
return new URLSearchParams(useLocation().search);
}
/**
* Checks every milisseconds if there is an element which id is the hash location until a defined number of attempts
* @param {*} props
*/
export function useScrollToHash(props) {
const { milliseconds = 100, offsetY = -70 } = props;
let { attempts = 10 } = props;
useEffect(() => {
const intervalId = setInterval(() => {
const { hash } = window.location;
if (attempts < 0) {
clearInterval(intervalId);
}
if (hash) {
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element) {
element.scrollIntoView({ block: 'start', behavior: 'instant' });
window.scrollBy(0, offsetY);
clearInterval(intervalId);
}
attempts--;
}
}, milliseconds);
}, [attempts, milliseconds, offsetY]);
}
......@@ -18,7 +18,6 @@ import { CacheProvider } from 'rest-hooks';
import App from './App';
import { unregister } from './registerServiceWorker';
import { persistor, store } from './store';
import ScrollToTop from './components/ScrollToTop';
import { BrowserRouter } from 'react-router-dom';
import keycloak from './keycloak';
import { checkExpirationTime, matchAuthStateToSSO } from './helpers/auth';
......@@ -47,7 +46,6 @@ function renderApp() {
<Provider store={store}>
<PersistGate onBeforeLift={prepareAuth} persistor={persistor}>
<BrowserRouter>
<ScrollToTop />
<App />
</BrowserRouter>
</PersistGate>
......
......@@ -57,6 +57,7 @@ export function createEventByAttributes(attributesObj) {
? attributesObj.updatedAt
: '2018-01-01T00:00:01.000Z',
username: attributesObj.username ? attributesObj.username : null,
meta: { page: { currentPage: 1, total: 5, totalPages: 1 } },
};
}
......@@ -72,6 +73,7 @@ export function createEventsWithContentEqualsId(firstId, lastId) {
events.push(
createEventByAttributes({
_id: String(index),
meta: { page: { currentPage: 1, total: 5, totalPages: 1 } },
content: [
{ format: 'plainText', text: String(index) },
{ format: 'html', text: `<p>${String(index)}</p>` },
......@@ -94,6 +96,7 @@ export function createEventsWithEmptyContent(count) {
events.push(
createEventByAttributes({
_id: String(i),
meta: { page: { currentPage: 1, total: 5, totalPages: 1 } },
content: [
{ format: 'plainText', text: '' },
{ format: 'html', text: '<p></p>' },
......
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import React from 'react';
import Event from '../../components/Logbook/List/Event';
const resources = require('./resources/event.resource.js');
require('it-each')({ testPerIteration: true });
beforeEach(() => {
Enzyme.configure({ adapter: new Adapter() });
});
describe('EventUnitTests', () => {
describe('Rendering', () => {
it.each(
resources.renderinglinkToEditEvent,
'[It renders a link to edit: %s ]',
['description'],
(element, next) => {
expect(
getWrapper({
event: element.event,
logbookContext: element.logbookContext,
})
.find('Glyphicon')
.prop('glyph')
).toEqual(element.expected);
next();
}
);
it.each(
resources.renderingEventTextBox,
'[ It renders a EventTextBox for %s ]',
['description'],
(element, next) => {
const wrapper = getWrapper({ event: element.event });
expect(wrapper.find('EventTextBox').prop('event')).toEqual(
element.event
);
next();
}
);
});
});
function getWrapper(params) {
if (params) {
params.logbookContext = params.logbookContext
? params.logbookContext
: { isReleased: false };
params.onEventClicked = params.onEventClicked
? params.onEventClicked
: () => null;
return Enzyme.shallow(
<Event
event={params.event}
logbookContext={params.logbookContext}
onEventClicked={params.onEventClicked}
/>
);
}
}
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import React from 'react';
import getUIConfiguration from '../uiConfig';
require('it-each')({ testPerIteration: true });
const resources = require('./resources/logbookPager.resource');
/* code executed before each test */
beforeEach(() => {
Enzyme.configure({ adapter: new Adapter() });
jest.resetModules();
});
describe('LogbookPagerUnitTests', () => {
describe('rendering', () => {
it.each(resources.rendering, '%s', ['description'], (element, next) => {
if (element.expected === null) {
//mock the configuration file. Since mockGetUIConfiguration is out of scope of jest.mock(), it must start with 'mock'.
const mockGetUIConfiguration = getUIConfiguration({
EVENTS_PER_PAGE: element.EVENTS_PER_PAGE,
});
jest.mock('../../config/ui', () => mockGetUIConfiguration);
expect(getWrapper(element.eventCount).get(0)).toBeNull();
}
next();
});
});
describe('getTotalPageNumber', () => {
it.each(
resources.getTotalPageNumber,
'%s',
['description'],
(element, next) => {
//mock the configuration file. Since mockGetUIConfiguration is out of scope of jest.mock(), it must start with 'mock'.
const mockGetUIConfiguration = getUIConfiguration({
EVENTS_PER_PAGE: element.EVENTS_PER_PAGE,
});
jest.mock('../../config/ui', () => mockGetUIConfiguration);
expect(
getWrapper(element.eventCount).instance().getTotalPageNumber()
).toBe(element.expected);
next();
}
);
});
});
function getWrapper(eventCount) {
const LogbookPager = require('../../components/Logbook/LogbookPager').default;
return Enzyme.shallow(
<LogbookPager
eventCount={eventCount}
isFloatingRight
onPageClicked={() => null}
/>
);
}
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