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

Commit 6013b2fe authored by Alejandro De Maria Antolinos's avatar Alejandro De Maria Antolinos

Merge branch '487-feat-filter_event_category' into 'milestone-logbook-search-settings'

Resolve "Add menu options to change the visibility and the formatting of the logbook"

See merge request !509
parents ff4f5bf0 1764c55f
Pipeline #46630 passed with stage
in 3 minutes and 4 seconds
......@@ -16,7 +16,7 @@ import { NEW_EVENT_VISIBLE } from '../../../constants/eventTypes';
import LogbookPager from '../LogbookPager';
import EventListMenuButton from './EventListMenuButton';
import NewlyAvailableEventsDialogue from './NewlyAvailableEventsDialogue';
import SettingLogbookMenuPanel from './SettingLogbookMenuPanel.js';
import SettingLogbookMenuPanel from './SettingLogbookMenuPanel';
/**
* The menu displayed above the event list
......@@ -135,7 +135,7 @@ function EventListMenu(props) {
<MenuItem eventKey={3.1} onSelect={() => setViewSettings()}>
<Checkbox
checked={isSettingsDisplayed}
onClick={(e) => {
onChange={(e) => {
setViewSettings();
e.stopPropagation();
}}
......@@ -179,7 +179,7 @@ function EventListMenu(props) {
>
<Checkbox
checked={isAutorefreshEnabled}
onClick={(e) => {
onChange={(e) => {
onAutoRefreshMenuItemClicked();
e.stopPropagation();
}}
......
......@@ -6,21 +6,29 @@ import {
EVENT_CATEGORY_INFO,
ANNOTATION,
NOTIFICATION,
EVENT_CATEGORY_COMMENT,
EVENT_CATEGORY_DEBUG,
} from '../../../constants/eventTypes';
import { useDispatch, useSelector } from 'react-redux';
import styles from './EventFilterPanel.module.css';
import { useDispatch } from 'react-redux';
import styles from './SettingLogbookMenuPanel.module.css';
import { setCategoryTypes, unsetCategoryTypes } from '../../../actions/logbook';
const checkBoxes = [
{
name: 'showComments',
label: 'Comments',
value: [{ type: ANNOTATION }],
value: [
{ type: ANNOTATION },
{ type: NOTIFICATION, category: EVENT_CATEGORY_COMMENT },
],
},
{
name: 'showInformation',
label: 'Information',
value: [{ type: NOTIFICATION, category: EVENT_CATEGORY_INFO }],
value: [
{ type: NOTIFICATION, category: EVENT_CATEGORY_INFO },
{ type: NOTIFICATION, category: EVENT_CATEGORY_DEBUG },
],
},
{
name: 'showError',
......@@ -37,8 +45,7 @@ const checkBoxes = [
/*
* React component which renders a panel showing the filter that can be applied on events
*/
export default function SettingLogbookMenuPanel() {
const categoryTypes = useSelector((state) => state.logbook.categoryTypes);
export default function SettingLogbookMenuPanel(props) {
const dispatch = useDispatch();
const getValueByName = (name) =>
......@@ -49,8 +56,8 @@ export default function SettingLogbookMenuPanel() {
let found = true;
values.forEach((value) => {
if (
categoryTypes &&
categoryTypes.find(
props.categoryTypes &&
props.categoryTypes.find(
(ct) => ct.type === value.type && ct.category === value.category
) == null
) {
......@@ -72,14 +79,14 @@ export default function SettingLogbookMenuPanel() {
<Grid>
<Row>
{checkBoxes.map((checkbox) => (
<Row>
<Row key={checkbox.name}>
<Col md={2} sm={6} xs={6}>
<Checkbox
className={styles.returnCheckbox}
name={checkbox.name}
checked={isChecked(checkbox.value)}
value={checkbox.value}
onClick={(e) => {
onChange={(e) => {
if (e.target.checked) {
dispatch(
setCategoryTypes(getValueByName(e.target.name))
......
......@@ -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: 4,
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: 4,
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)*/
......
......@@ -16,13 +16,7 @@ import UserMessage from '../../components/UserMessage';
*/
function EventsProvider(props) {
const { children, investigationId, sortingFilter } = props;
const {
searchCriteria,
forceReload,
filterType,
filterCategory,
page,
} = props;
const { searchCriteria, forceReload, categoryTypes, page } = props;
const [downloadedEvents, setDownloadedEvents] = useState({}); //{ isFetching: bool, events: [], startIndex: Number, endIndex: Number, isError: bool}
......@@ -38,8 +32,7 @@ function EventsProvider(props) {
investigationId,
sortingFilter,
searchCriteria,
filterType,
filterCategory,
categoryTypes,
forceReload,
]);
......@@ -48,17 +41,16 @@ function EventsProvider(props) {
return;
}
const selectionFilter = buildSearchFilter(
searchCriteria,
filterType,
filterCategory
);
const selectionFilter = buildSearchFilter(searchCriteria, categoryTypes);
setDownloadedEvents({ isFetching: true });
getEvents(selectionFilter, sortingFilter, skip, investigationId)
.then(({ data }) => {
// Filter notifications that have been annotated which annotation is empty
if (filterType.length === 1 && filterType[0] === ANNOTATION) {
if (
categoryTypes.length === 1 &&
categoryTypes[0].type === ANNOTATION
) {
// debugger;
data = data.filter(
(event) =>
......@@ -83,8 +75,7 @@ function EventsProvider(props) {
skip,
sortingFilter,
searchCriteria,
filterType,
filterCategory,
categoryTypes,
isDownloaded,
isFetching,
isError,
......@@ -127,8 +118,7 @@ EventsProvider.propTypes = {
/** search criteria */
searchCriteria: PropTypes.array,
/** filter criteria, by type and by category */
filterType: PropTypes.array,
filterCategory: PropTypes.array,
categoryTypes: PropTypes.array,
/** Whether the events must be forced reloaded. (when no others props changed such as page, sorting filter, searchCriteria, ...) */
forceReload: PropTypes.bool,
};
......
......@@ -17,7 +17,6 @@ import PublicEventPanel from '../../components/Logbook/PublicEventPanel';
import UserMessage from '../../components/UserMessage';
import UI from '../../config/ui';
import {
ANNOTATION,
LOGBOOK_CONTEXT_NAME_PROPOSAL,
NEW_EVENT_INVISIBLE,
NEW_EVENT_VISIBLE,
......@@ -66,8 +65,6 @@ export class LogbookContainerClass extends React.Component {
sortingFilter: { [UI.logbook.SORT_EVENTS_BY]: UI.logbook.SORTING_ORDER }, // current sorting flter
searchCriteria: [],
forceReloadEventList: false,
filterType: [ANNOTATION],
filterCategory: [],
};
this.createEvent = this.createEvent.bind(this);
......@@ -99,8 +96,6 @@ export class LogbookContainerClass extends React.Component {
page,
errorMessage,
forceReloadEventList,
filterType,
filterCategory,
categoryTypes,
} = this.state;
......@@ -131,7 +126,7 @@ export class LogbookContainerClass extends React.Component {
onCallbackSuccess={this.onNewEventCountReceived}
periodicCallback={(onSuccess, onFailure) => {
this.getEventCountBySelectionFilter(
buildSearchFilter(searchCriteria, filterType, filterCategory)
buildSearchFilter(searchCriteria, categoryTypes)
).then(
(eventCount) => onSuccess(eventCount),
(error) => onFailure(error)
......@@ -168,8 +163,7 @@ export class LogbookContainerClass extends React.Component {
selectionFilter={getSelectionFiltersForMongoQuery(
searchCriteria,
sortingFilter,
filterType,
filterCategory
categoryTypes
)}
sessionId={user.sessionId}
setNewEventVisibility={this.setNewEventVisibility}
......@@ -233,8 +227,7 @@ export class LogbookContainerClass extends React.Component {
<EventsProvider
page={page}
searchCriteria={searchCriteria}
filterType={filterType}
filterCategory={filterCategory}
categoryTypes={categoryTypes}
sortingFilter={sortingFilter}
forceReload={forceReloadEventList}
investigationId={investigation.id}
......@@ -271,7 +264,7 @@ export class LogbookContainerClass extends React.Component {
*/
componentDidMount() {
const { releaseDate } = this.props.investigation;
const { searchCriteria, filterType, filterCategory } = this.state;
const { searchCriteria, categoryTypes } = this.state;
const isReleased = !!releaseDate && moment().isAfter(releaseDate);
......@@ -281,7 +274,7 @@ export class LogbookContainerClass extends React.Component {
});
this.getEventCountBySelectionFilter(
buildSearchFilter(searchCriteria, filterType, filterCategory)
buildSearchFilter(searchCriteria, categoryTypes)
).then((eventCount) => {
this.setState({
allEventsCountAtLastRefresh: parseInt(eventCount, 10),
......@@ -300,14 +293,14 @@ export class LogbookContainerClass extends React.Component {
this.setState({ forceReloadEventList: false });
}
/** TODO: it is needed probably something more robust
/** TODO: it is needed probably something more robust */
if (
this.props.categoryTypes &&
prevProps.categoryTypes.length !== this.props.eventCategories.length
prevProps.categoryTypes &&
prevProps.categoryTypes.length !== this.props.categoryTypes.length
) {
//this.filterEvents(this.props.eventTypes, this.props.eventCategories);
this.filterEvents(this.props.categoryTypes);
}
*/
}
/** Get the height percentage value for the panels for new and edition of an event */
......@@ -375,11 +368,7 @@ export class LogbookContainerClass extends React.Component {
this.setState({ searchCriteria, page: 1 });
this.getEventCountBySelectionFilter(
buildSearchFilter(
searchCriteria,
this.state.filterType,
this.state.filterCategory
)
buildSearchFilter(searchCriteria, this.state.categoryTypes)
).then((eventCount) => {
this.setState({ eventCountBySelectionFilter: parseInt(eventCount, 10) });
});
......@@ -419,21 +408,14 @@ export class LogbookContainerClass extends React.Component {
this.setState({ isShowingPublicEventUI: false });
}
filterEvents(filterTypes, filterCategories) {
filterEvents(categoryType) {
this.setState({
filterType: filterTypes,
filterCategory: filterCategories,
categoryTypes: categoryType,
page: 1,
});
console.log(filterTypes);
console.log(filterCategories);
this.getEventCountBySelectionFilter(
buildSearchFilter(
this.state.searchCriteria,
filterTypes,
filterCategories
)
buildSearchFilter(this.state.searchCriteria, categoryType)
).then((eventCount) => {
this.setState({
allEventsCountAtLastRefresh: parseInt(eventCount, 10),
......@@ -446,12 +428,7 @@ export class LogbookContainerClass extends React.Component {
* Function triggered whne the user clicks in the menu to change to event sorting order
*/
flipEventsSortingOrder() {
const {
sortingFilter,
searchCriteria,
filterType,
filterCategory,
} = this.state;
const { sortingFilter, searchCriteria, categoryTypes } = this.state;
const newSortingFilter = {};
const sortingCriteria = Object.keys(sortingFilter)[0];
newSortingFilter[sortingCriteria] = getSortingOrder(sortingFilter) * -1;
......@@ -459,7 +436,7 @@ export class LogbookContainerClass extends React.Component {
this.setState({ sortingFilter: newSortingFilter });
this.getEventCountBySelectionFilter(
buildSearchFilter(searchCriteria, filterType, filterCategory)
buildSearchFilter(searchCriteria, categoryTypes)
).then((eventCount) => {
this.setState({ eventCountBySelectionFilter: parseInt(eventCount) });
});
......@@ -543,7 +520,7 @@ export class LogbookContainerClass extends React.Component {
* @param {*} event event to be created or updated
*/
onSaveButtonClicked(event) {
const { searchCriteria, filterType, filterCategory } = this.state;
const { searchCriteria, categoryTypes } = this.state;
if (this.state.event) {
//editing an event
this.updateEvent(event).then(() => {
......@@ -563,7 +540,7 @@ export class LogbookContainerClass extends React.Component {
});
this.getEventCountBySelectionFilter(
buildSearchFilter(searchCriteria, filterType, filterCategory)
buildSearchFilter(searchCriteria, categoryTypes)
).then((eventCount) =>
this.setState({
eventCountBySelectionFilter: parseInt(eventCount),
......@@ -571,7 +548,7 @@ export class LogbookContainerClass extends React.Component {
);
this.getEventCountBySelectionFilter(
buildSearchFilter([], filterType, filterCategory)
buildSearchFilter([], categoryTypes)
).then((eventCount) =>
this.setState({
allEventsCountAtLastRefresh: parseInt(eventCount),
......
......@@ -10,13 +10,12 @@ const EQUALITY_FILTER_TYPE = 'equalityFilterType';
* Get selecton filters combining find, sort selection filters for mongoDB query
* @param {*} findCriteria what will be the find parameter in mongo query
* @param {*} sortCriteria what will be the sort parameter in mongo query
* @param {*} view current logbook view
* @param {*} filterTypeCategory array of filter on event type/category
*/
export function getSelectionFiltersForMongoQuery(
findCriteria,
sortCriteria,
filterType,
filterCategory
filterTypeCategory
) {
if (!findCriteria) {
findCriteria = [];
......@@ -26,7 +25,7 @@ export function getSelectionFiltersForMongoQuery(
}
return {
find: buildSearchFilter(findCriteria, filterType, filterCategory),
find: buildSearchFilter(findCriteria, filterTypeCategory),
sort: sortCriteria,
};
}
......@@ -45,15 +44,14 @@ export function getSelectionFilterForAllAnnotationsAndNotifications() {
/**
* builds the selection filter based on filter on events type / category and user search criteria
* @param {*} criteria search criteria as provided by the combosearch component.
* @param {*} filterType array of filter on event type
* @param {*} filterCategory array of filter on event category
* @param {*} filterTypeCategory array of filter on event type/category
* @returns {Object} selection filter query to be used
*/
export function buildSearchFilter(criteria, filterType, filterCategory) {
export function buildSearchFilter(criteria, filterTypeCategory) {
const filter = {};
const andExpressions = [];
// the first part of the AND filter which selects annotation and notification systematically
andExpressions.push(buildEventFilter(filterType, filterCategory));
andExpressions.push(buildEventFilter(filterTypeCategory));
const userSpecificFilters = getUserSpecificSelectionFilters(criteria);
if (userSpecificFilters) {
......@@ -65,95 +63,63 @@ export function buildSearchFilter(criteria, filterType, filterCategory) {
/**
* builds the selection filter based on filter on events type / category
* @param {*} filterType array of filter on event type
* @param {*} filterCategory array of filter on event category
* @param {*} filterTypeCategory array of filter on event type/category
* @returns {Object} selection filter query to be used
*/
export function buildEventFilter(filterType, filterCategory) {
export function buildEventFilter(filterTypeCategory) {
const filter = {};
if (
isArrayUndefinedOrEmpty(filterType) ||
(isFilterOnlyNotification(filterType) &&
isArrayUndefinedOrEmpty(filterCategory))
) {
if (isArrayUndefinedOrEmpty(filterTypeCategory)) {
return buildNoTypeFilter();
}
const orFilter = [];
Array.prototype.forEach.call(filterType, (filter) => {
if (filter === ANNOTATION) {
orFilter.push(buildEventFilterForUserComment());
}
if (filter === NOTIFICATION) {
orFilter.push(buildEventFilterForNotifcation(filterCategory));
}
Array.prototype.forEach.call(filterTypeCategory, (filter) => {
orFilter.push(buildSingleEventFilter(filter));
});
filter.$or = orFilter;
return filter;
}
function isFilterOnlyNotification(filterType) {
return filterType.length === 1 && filterType[0] === NOTIFICATION;
}
function isArrayUndefinedOrEmpty(array) {
return !array || array.length === 0;
}
function buildNoTypeFilter() {
export function buildSingleEventFilter(singleFilter) {
if (singleFilter.type === ANNOTATION && !singleFilter.category) {
return buildEventFilterForUserComment();
}
const filter = {};
const andExpressions = [];
const filterItem = popSingleFilter(
{},
{ criteria: 'type', search: 'undefined' },
{ criteria: 'type', search: singleFilter.type },
EQUALITY_FILTER_TYPE
);
andExpressions.push(filterItem);
if (singleFilter.category) {
const filterItem = popSingleFilter(
{},
{ criteria: 'category', search: singleFilter.category },
EQUALITY_FILTER_TYPE
);
andExpressions.push(filterItem);
}
filter.$and = andExpressions;
return filter;
}
/**
* builds the selection filter for notification events
* @param {*} filterCategory array of filter on event category
* @returns {Object} selection filter query to be used
*/
export function buildEventFilterForNotifcation(filterCategory) {
function isArrayUndefinedOrEmpty(array) {
return !array || array.length === 0;
}
function buildNoTypeFilter() {
const filter = {};
if (!filterCategory || filterCategory.length === 0) {
return filter;
}
const andExpressions = [];
const filterItem = popSingleFilter(
{},
{ criteria: 'type', search: NOTIFICATION },
{ criteria: 'type', search: 'undefined' },
EQUALITY_FILTER_TYPE
);
andExpressions.push(filterItem);
andExpressions.push(buildEventCategoryFilter(filterCategory));
filter.$and = andExpressions;
return filter;
}
/**
* builds the selection filter based on category
* @param {*} filterCategory array of filter on event category
* @returns {Object} selection filter query to be used
*/
export function buildEventCategoryFilter(filterCategory) {
const filter = {};
const orFilter = [];
Array.prototype.forEach.call(filterCategory, (filter) => {
const orFilterItem = popSingleFilter(
{},
{ criteria: 'category', search: filter },
EQUALITY_FILTER_TYPE
);
orFilter.push(orFilterItem);
});
filter.$or = orFilter;
return filter;
}
/**
* builds the selection filter for annotation events + notification with previous events
* @returns {Object} selection filter query to be used
......
......@@ -5,8 +5,8 @@ import {
} from '../constants/actionTypes';
import {
ANNOTATION,
EVENT_CATEGORY_COMMENT,
EVENT_CATEGORY_ERROR,
EVENT_CATEGORY_INFO,
NOTIFICATION,
} from '../constants/eventTypes';
......@@ -18,7 +18,7 @@ const initialState = {
},
categoryTypes: [
{ type: ANNOTATION },
{ type: NOTIFICATION, category: EVENT_CATEGORY_INFO },
{ type: NOTIFICATION, category: EVENT_CATEGORY_COMMENT },
{ type: NOTIFICATION, category: EVENT_CATEGORY_ERROR },
],
};
......
......@@ -161,13 +161,7 @@ describe('EventsProviderUnitTests', () => {
*/
function getWrapper(props) {
let { forceReload, investigationId, page, setErrorMessage } = props;
let {
sessionId,
searchCriteria,
filterType,
filterCategory,
sortingFilter,
} = props;
let { sessionId, searchCriteria, categoryTypes, sortingFilter } = props;
if (props) {
page = page || 1;
......@@ -177,8 +171,7 @@ function getWrapper(props) {
sortingFilter = sortingFilter || { _id: -1 };
forceReload = forceReload || false;
searchCriteria = searchCriteria || [];
filterType = filterType || [ANNOTATION];
filterCategory = filterCategory || [];
categoryTypes = categoryTypes || [{ type: ANNOTATION }];
const FakeEventList = function () {
return <div> </div>;
......@@ -198,8 +191,7 @@ function getWrapper(props) {
sortingFilter={sortingFilter}
setErrorMessage={setErrorMessage}
searchCriteria={searchCriteria}
filterType={filterType}
filterCategory={filterCategory}
categoryTypes={categoryTypes}
forceReload={forceReload}
>
{(events) => <FakeEventList events={events} />}
......
......@@ -42,8 +42,7 @@ describe('selectionFilterHelper', () => {
getSelectionFiltersForMongoQuery(
element.criteria,
{ [element.SORT_EVENTS_BY]: element.SORTING_ORDER },
element.filterType,
element.filterCategory
element.filterTypeCategory
)
).toEqual(element.expected);
next();
......@@ -67,16 +66,16 @@ describe('selectionFilterHelper', () => {
});
});
describe('buildEventCategoryFilter', () => {
describe('buildSingleEventFilter', () => {
describe('handle errors', () => {
it.each(
resources.buildEventCategoryFilter,
resources.buildSingleEventFilter,
'[note: %s ]',
['description'],
(element, next) => {
const buildEventCategoryFilter = require('../../containers/Logbook/SelectionFilterHelper')
.buildEventCategoryFilter;
expect(buildEventCategoryFilter(element.filterCategory)).toEqual(
const buildSingleEventFilter = require('../../containers/Logbook/SelectionFilterHelper')
.buildSingleEventFilter;
expect(buildSingleEventFilter(element.filter)).toEqual(
element.expected
);
next();
......@@ -85,24 +84,6 @@ describe('selectionFilterHelper', () => {
});
});
describe('buildEventFilterForNotifcation', () => {
describe('handle errors', () => {
it.each(
resources.buildEventFilterForNotifcation,
'[note: %s ]',
['description'],
(element, next) => {
const buildEventFilterForNotifcation = require('../../containers/Logbook/SelectionFilterHelper')
.buildEventFilterForNotifcation;
expect(
buildEventFilterForNotifcation(element.filterCategory)
).toEqual(element.expected);
next();
}
);
});
});
describe('buildEventFilter', () => {
describe('handle errors', () => {
it.each(
......@@ -112,9 +93,9 @@ describe('selectionF