Skip to content

Commit

Permalink
[SDESK-4027] Pre-built filters for events and planning view.
Browse files Browse the repository at this point in the history
  • Loading branch information
Mayur Dhamanwala committed Apr 8, 2019
1 parent c0e0903 commit f458f78
Show file tree
Hide file tree
Showing 49 changed files with 2,018 additions and 113 deletions.
22 changes: 5 additions & 17 deletions client/actions/events/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import eventsUi from './ui';
import main from '../main';
import planningApi from '../planning/api';
import {get} from 'lodash';
import {gettext, dispatchUtils, getErrorMessage, isItemLockedForEditing} from '../../utils';
import {gettext, dispatchUtils, getErrorMessage} from '../../utils';
import eventsPlanning from '../eventsPlanning';

/**
Expand Down Expand Up @@ -275,22 +275,10 @@ const onRecurringEventCreated = (_e, data) => (
const onEventUpdated = (_e, data) => (
(dispatch, getState) => {
if (data && data.item) {
const storedEvent = selectors.events.storedEvents(getState())[data.item];
const currentEditId = selectors.forms.currentItemId(getState());

if (get(data, 'recurrence_id') ||
get(data, 'item') !== currentEditId ||
!isItemLockedForEditing(
storedEvent,
selectors.general.session(getState()),
selectors.locks.getLockedItems(getState())
)
) {
dispatch(main.setUnsetLoadingIndicator(true));
dispatch(eventsUi.scheduleRefetch(get(data, 'recurrence_id') ? [data.item] : []))
.then(() => dispatch(eventsPlanning.ui.scheduleRefetch()))
.finally(() => dispatch(main.setUnsetLoadingIndicator(false)));
}
dispatch(main.setUnsetLoadingIndicator(true));
dispatch(eventsUi.scheduleRefetch(get(data, 'recurrence_id') ? [data.item] : []))
.then(() => dispatch(eventsPlanning.ui.scheduleRefetch()))
.finally(() => dispatch(main.setUnsetLoadingIndicator(false)));

dispatch(fetchItemHistoryOnRecurringNotitication(data));
}
Expand Down
6 changes: 3 additions & 3 deletions client/actions/events/tests/notifications_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,7 @@ describe('actions.events.notifications', () => {
restoreSinonStub(eventsPlanningUi.scheduleRefetch);
});

it('onEventUpdated does not call scheduleRefetch if item is being edited', (done) => {
it('onEventUpdated does call scheduleRefetch if item is being edited', (done) => {
store.initialState.events.events['e1'] = {
lock_action: 'edit',
lock_user: 'ident1',
Expand All @@ -735,8 +735,8 @@ describe('actions.events.notifications', () => {

return store.test(done, eventsNotifications.onEventUpdated({}, {item: data.events[0]._id}))
.then(() => {
expect(eventsUi.scheduleRefetch.callCount).toBe(0);
expect(eventsPlanningUi.scheduleRefetch.callCount).toBe(0);
expect(eventsUi.scheduleRefetch.callCount).toBe(1);
expect(eventsPlanningUi.scheduleRefetch.callCount).toBe(1);
done();
})
.catch(done.fail);
Expand Down
52 changes: 48 additions & 4 deletions client/actions/eventsPlanning/api.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {get} from 'lodash';
import {MAIN, SPIKED_STATE} from '../../constants';
import {planningUtils, eventUtils, getDateTimeElasticFormat} from '../../utils';
import {get, pickBy, isEqual} from 'lodash';
import {EVENTS_PLANNING, MAIN, SPIKED_STATE} from '../../constants';
import {planningUtils, eventUtils, getDateTimeElasticFormat, getTimeZoneOffset} from '../../utils';
import * as selectors from '../../selectors';
import main from '../main';

Expand All @@ -15,6 +15,8 @@ const query = (
spikeState = SPIKED_STATE.NOT_SPIKED,
page = 1,
maxResults = MAIN.PAGE_SIZE,
calendars = [],
agendas = [],
},
storeTotal = false
) => (
Expand All @@ -38,6 +40,9 @@ const query = (
end_date: get(advancedSearch, 'dates.end') ?
getDateTimeElasticFormat(get(advancedSearch, 'dates.end')) : null,
start_of_week: selectors.config.getStartOfWeek(getState()),
calendars: get(calendars, 'length', 0) > 0 ? JSON.stringify((calendars || []).map((c) => c.qcode)) : null,
agendas: get(agendas, 'length', 0) > 0 ? JSON.stringify((agendas || []).map((a) => a._id)) : null,
tz_offset: getTimeZoneOffset(),
page: page,
max_results: maxResults,
};
Expand Down Expand Up @@ -71,7 +76,7 @@ const query = (
* It achieves this by performing a fetch using the params from
* the store value `planning.lastRequestParams`
*/
const refetch = (page = 1, items = []) => (
const refetch = (page = 1, items = [], updateFilter = false) => (
(dispatch, getState) => {
const prevParams = selectors.main.lastRequestParams(getState());
let currentPage = page;
Expand All @@ -80,6 +85,19 @@ const refetch = (page = 1, items = []) => (
currentPage,
};

if (updateFilter) {
const filterId = selectors.eventsPlanning.currentFilter(getState());
const filters = selectors.eventsPlanning.combinedViewFilters(getState());
const filter = filterId !== EVENTS_PLANNING.FILTER.ALL_EVENTS_PLANNING ?
filters.find((f) => f._id === filterId) : {};

params = {
...params,
agendas: get(filter, 'agendas', []),
calendars: get(filter, 'calendars', []),
};
}

return dispatch(self.query(params, true))
.then((result) => {
const totalItems = items.concat(result);
Expand All @@ -93,10 +111,36 @@ const refetch = (page = 1, items = []) => (
}
);

/**
* Saves the combined view filter
* @param filter
*/
const saveFilter = (filter) => (
(dispatch, getState, {api}) => {
let original = {};

if (filter._id) {
// existing filter
const filters = selectors.eventsPlanning.combinedViewFilters(getState());

original = filters.find((f) => f._id === filter._id);
}

// remove all properties starting with _
// and updates that are the same as original
let diff = pickBy(filter, (v, k) => (
!k.startsWith('_') && !isEqual(filter[k], original[k])
));

return api('events_planning_filters').save(original, diff);
}
);

// eslint-disable-next-line consistent-this
const self = {
query,
refetch,
saveFilter,
};

export default self;
2 changes: 2 additions & 0 deletions client/actions/eventsPlanning/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import ui from './ui';
import api from './api';
import notifications from './notifications';

export default {
api,
ui,
notifications,
};
64 changes: 64 additions & 0 deletions client/actions/eventsPlanning/notifications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import eventsPlanningUi from './ui';
import * as selectors from '../../selectors';
import {gettext} from '../../utils';
import {EVENTS_PLANNING, MAIN} from '../../constants';

const onEventPlaningFilterCreatedOrUpdated = (_e, data) => (
(dispatch, getState, {notify}) => {
if (data && data.item) {
return dispatch(eventsPlanningUi.fetchFilterById(data.item))
.then(() => {
const currentFilter = selectors.eventsPlanning.currentFilter(getState());
const currentView = selectors.main.activeFilter(getState());

if (currentFilter === data.item && MAIN.FILTERS.COMBINED === currentView) {
notify.warning(
gettext('The Event and Planning filter you were viewing is modified!')
);
return dispatch(eventsPlanningUi.scheduleRefetch(true));
}
return Promise.resolve();
});
}
}
);


const onEventPlaningFilterDeleted = (_e, data) => (
(dispatch, getState, {notify}) => {
if (data && data.item) {
return dispatch(eventsPlanningUi.fetchFilters())
.then(() => {
const currentFilter = selectors.eventsPlanning.currentFilter(getState());
const currentView = selectors.main.activeFilter(getState());

if (currentFilter === data.item && MAIN.FILTERS.COMBINED === currentView) {
dispatch(eventsPlanningUi.storeFilter(EVENTS_PLANNING.FILTER.ALL_EVENTS_PLANNING));

notify.warning(
gettext('The Event and Planning filter you were viewing is deleted!')
);

return dispatch(eventsPlanningUi.scheduleRefetch(true));
}
return Promise.resolve();
});
}
}
);

// eslint-disable-next-line consistent-this
const self = {
onEventPlaningFilterCreatedOrUpdated,
onEventPlaningFilterDeleted,
};


// Map of notification name and Action Event to execute
self.events = {
'event_planning_filters:created': () => (self.onEventPlaningFilterCreatedOrUpdated),
'event_planning_filters:updated': () => (self.onEventPlaningFilterCreatedOrUpdated),
'event_planning_filters:deleted': () => (self.onEventPlaningFilterDeleted),
};

export default self;
159 changes: 159 additions & 0 deletions client/actions/eventsPlanning/tests/notifications_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import sinon from 'sinon';
import {getTestActionStore, restoreSinonStub} from '../../../utils/testUtils';
import {registerNotifications} from '../../../utils';
import notifications from '../notifications';
import eventsPlanningUi from '../ui';
import {MAIN} from '../../../constants';


describe('actions.eventsplanning.notifications', () => {
let store;
let data;
let services;

beforeEach(() => {
store = getTestActionStore();
data = store.data;
services = store.services;
store.init();
});

describe('websocket', () => {
const delay = 0;
let $rootScope;

beforeEach(inject((_$rootScope_) => {
sinon.stub(notifications, 'onEventPlaningFilterCreatedOrUpdated').callsFake(
() => (Promise.resolve())
);

sinon.stub(notifications, 'onEventPlaningFilterDeleted').callsFake(
() => (Promise.resolve())
);

$rootScope = _$rootScope_;
registerNotifications($rootScope, store);
$rootScope.$digest();
}));

afterEach(() => {
restoreSinonStub(notifications.onEventPlaningFilterCreatedOrUpdated);
restoreSinonStub(notifications.onEventPlaningFilterDeleted);
});

it('on filter created', (done) => {
$rootScope.$broadcast('event_planning_filters:created', {item: 'finance', user: 'user1'});

setTimeout(() => {
expect(notifications.onEventPlaningFilterCreatedOrUpdated.callCount).toBe(1);
expect(notifications.onEventPlaningFilterCreatedOrUpdated.args[0][1]).toEqual(
{item: 'finance', user: 'user1'}
);
done();
}, delay);
});

it('on filter updated', (done) => {
$rootScope.$broadcast('event_planning_filters:updated', {item: 'finance', user: 'user1'});

setTimeout(() => {
expect(notifications.onEventPlaningFilterCreatedOrUpdated.callCount).toBe(1);
expect(notifications.onEventPlaningFilterCreatedOrUpdated.args[0][1]).toEqual(
{item: 'finance', user: 'user1'}
);
done();
}, delay);
});

it('on filter deleted', (done) => {
$rootScope.$broadcast('event_planning_filters:deleted', {item: 'finance', user: 'user1'});

setTimeout(() => {
expect(notifications.onEventPlaningFilterDeleted.callCount).toBe(1);
expect(notifications.onEventPlaningFilterDeleted.args[0][1]).toEqual(
{item: 'finance', user: 'user1'}
);
done();
}, delay);
});
});


describe('on filter create', () => {
beforeEach(() => {
sinon.stub(eventsPlanningUi, 'fetchFilterById').callsFake(
() => Promise.resolve(data.events_planning_filters[0])
);
sinon.stub(eventsPlanningUi, 'scheduleRefetch').callsFake(
() => Promise.resolve([])
);
sinon.stub(eventsPlanningUi, 'fetchFilters').callsFake(
() => Promise.resolve([])
);
});

afterEach(() => {
restoreSinonStub(eventsPlanningUi.fetchFilterById);
restoreSinonStub(eventsPlanningUi.scheduleRefetch);
restoreSinonStub(eventsPlanningUi.fetchFilters);
});

it('create new filter', (done) => {
store.test(done, notifications.onEventPlaningFilterCreatedOrUpdated({}, {item: 'finance', user: 'user1'}))
.then(() => {
expect(eventsPlanningUi.fetchFilterById.callCount).toBe(1);
expect(eventsPlanningUi.scheduleRefetch.callCount).toBe(0);
expect(services.notify.warning.callCount).toBe(0);
done();
})
.catch(done.fail);
});

it('update filter', (done) => {
store.initialState.eventsPlanning.currentFilter = 'finance';
store.initialState.main.filter = MAIN.FILTERS.COMBINED;
store.test(done, notifications.onEventPlaningFilterCreatedOrUpdated({}, {item: 'finance', user: 'user1'}))
.then(() => {
expect(eventsPlanningUi.fetchFilterById.callCount).toBe(1);
expect(eventsPlanningUi.scheduleRefetch.callCount).toBe(1);
expect(eventsPlanningUi.scheduleRefetch.args[0][0]).toBe(true);
expect(services.notify.warning.callCount).toBe(1);
expect(services.notify.warning.args[0][0]).toEqual(
'The Event and Planning filter you were viewing is modified!'
);
done();
})
.catch(done.fail);
});

it('delete filter with current filter different', (done) => {
store.initialState.eventsPlanning.currentFilter = 'xxx';
store.initialState.main.filter = MAIN.FILTERS.COMBINED;
store.test(done, notifications.onEventPlaningFilterDeleted({}, {item: 'finance', user: 'user1'}))
.then(() => {
expect(eventsPlanningUi.fetchFilters.callCount).toBe(1);
expect(eventsPlanningUi.scheduleRefetch.callCount).toBe(0);
expect(services.notify.warning.callCount).toBe(0);
done();
})
.catch(done.fail);
});

it('delete filter with current filter different', (done) => {
store.initialState.eventsPlanning.currentFilter = 'finance';
store.initialState.main.filter = MAIN.FILTERS.COMBINED;
store.test(done, notifications.onEventPlaningFilterDeleted({}, {item: 'finance', user: 'user1'}))
.then(() => {
expect(eventsPlanningUi.fetchFilters.callCount).toBe(1);
expect(eventsPlanningUi.scheduleRefetch.callCount).toBe(1);
expect(eventsPlanningUi.scheduleRefetch.args[0][0]).toBe(true);
expect(services.notify.warning.callCount).toBe(1);
expect(services.notify.warning.args[0][0]).toEqual(
'The Event and Planning filter you were viewing is deleted!'
);
done();
})
.catch(done.fail);
});
});
});

0 comments on commit f458f78

Please sign in to comment.