Skip to content

Commit

Permalink
[STT-7] fix(ui): Update UI due to changes with Planning RelatedEvents…
Browse files Browse the repository at this point in the history
… schema (#1996)

* Bump coverage from 7.5.1 to 7.5.3 (#1994)

Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.5.1 to 7.5.3.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](nedbat/coveragepy@7.5.1...7.5.3)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Update interfaces

* Update redux reducers

* Update redux selectors

* Update redux actions

* Update websocket notification handling

* Update internal PlanningAPI

* Update UI components

* Update utils

* Update package-lock.json

* fix unit test

* Add comment to IPlanningRelatedEventLinkType interface, update variable name

* merging `release/2.7` to develop (#1998)

* [SDBELGA-778] fix: Show recurring Planning update method on Ignore|Cancel|Save modal (#1930)

* [SDBELGA-778] fix: Show recurring Planning update method on Ignore|Cancel|Save modal
Also improve look and feel when rendering Event & Planning metadata in the modal

* fix(ci): Use superdesk/release/2.7 branch for test instance

* fix extension build error

* add new should_udpate func [SDCP-742] (#1963)

* add new should_udpate func [SDCP-742]

* update params

* refactore logic

* Onclusive parse new key for event status mapping [SDCP-749] (#1970)

* Onclusive parse new key for event status mapping [SDCP-749]

* address comment

* Fix content profile closing after canceling a groups' creation (#1964)

* implement onclusive reingesting (#1971)

SDCP-751

* drop angular-history (#1933)

* drop angular-history

* fix test build

* sync redux versions with client core

* update package-lock

* update events during ingest if name/subject changes

SDCP-751

* allow removing contact email/phone via update (#1979)

SDCP-762

* set expiry when parsing onclusive events (#1995)

* set expiry when parsing onclusive events

CPCN-825

* fix mypy

* bump version

* update ui framework version (#1997)

* fix ci

* Set version to 2.8.0-dev

---------

Co-authored-by: MarkLark86 <[email protected]>
Co-authored-by: Ketan <[email protected]>
Co-authored-by: Konstantin Markov <[email protected]>

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Petr Jašek <[email protected]>
Co-authored-by: Ketan <[email protected]>
Co-authored-by: Konstantin Markov <[email protected]>
  • Loading branch information
5 people authored Jun 19, 2024
1 parent ed7c3f3 commit 5a0b815
Show file tree
Hide file tree
Showing 65 changed files with 1,443 additions and 827 deletions.
12 changes: 10 additions & 2 deletions client/actions/agenda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as selectors from '../selectors';
import {cloneDeep, pick, get, sortBy, findIndex} from 'lodash';
import {Moment} from 'moment';

import {IEventItem, IPlanningItem, IAgenda} from '../interfaces';
import {IEventItem, IPlanningItem, IAgenda, IPlanningRelatedEventLink} from '../interfaces';
import {planningApi} from '../superdeskApi';

import {AGENDA, MODALS, EVENTS} from '../constants';
Expand Down Expand Up @@ -243,11 +243,19 @@ const addEventToCurrentAgenda = (
export function convertEventToPlanningItem(event: IEventItem): Partial<IPlanningItem> {
const defaultPlace = selectors.general.defaultPlaceList(planningApi.redux.store.getState());
const defaultValues = planningUtils.defaultPlanningValues(null, defaultPlace);
const eventLink: IPlanningRelatedEventLink = {
_id: event._id,
link_type: 'primary',
};

if (event.recurrence_id != null) {
eventLink.recurrence_id = event.recurrence_id;
}

let newPlanningItem: Partial<IPlanningItem> = {
...defaultValues,
type: 'planning',
event_item: event._id,
related_events: [eventLink],
planning_date: event._sortDate || event.dates?.start,
place: event.place || defaultPlace,
subject: event.subject,
Expand Down
10 changes: 6 additions & 4 deletions client/actions/events/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import planningApis from '../planning/api';
import eventsUi from './ui';
import main from '../main';
import {eventParamsToSearchParams} from '../../utils/search';
import {getRelatedEventIdsForPlanning} from '../../utils/planning';

/**
* Action dispatcher to load a series of recurring events into the local store.
Expand Down Expand Up @@ -213,7 +214,7 @@ function loadEventDataForAction(
_relatedPlannings: loadEveryRecurringPlanning ?
items.plannings :
items.plannings.filter(
(item) => item.event_item === event._id
(item) => getRelatedEventIdsForPlanning(item, 'primary').includes(event._id)
),
}));
}
Expand Down Expand Up @@ -482,7 +483,7 @@ function markEventPostponed(event: IEventItem, reason: string, actionedDate: str
const markEventHasPlannings = (event, planning) => ({
type: EVENTS.ACTIONS.MARK_EVENT_HAS_PLANNINGS,
payload: {
event_item: event,
event_id: event,
planning_item: planning,
},
});
Expand Down Expand Up @@ -569,8 +570,9 @@ const save = (original, updates) => (
) {
delete eventUpdates.dates;
}
eventUpdates.update_method = get(eventUpdates, 'update_method.value') ||
EVENTS.UPDATE_METHODS[0].value;
eventUpdates.update_method = eventUpdates.update_method == null ?
EVENTS.UPDATE_METHODS[0].value :
eventUpdates.update_method?.value ?? eventUpdates.update_method;

return originalEvent?._id != null ?
planningApi.events.update(originalItem, eventUpdates) :
Expand Down
9 changes: 6 additions & 3 deletions client/actions/events/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import {
timeUtils,
getItemId,
isItemPublic,
stringUtils,
} from '../../utils';
import {convertStringFields} from '../../utils/strings';
import {getRelatedEventIdsForPlanning} from '../../utils/planning';

/**
* Action Dispatcher to fetch events from the server
Expand Down Expand Up @@ -590,13 +590,15 @@ const openEventPostModal = (
let promise = Promise.resolve(original);

if (planningItem) {
const primaryEventIds = getRelatedEventIdsForPlanning(planningItem, 'primary');

// Actually posting a planning item
if (!planningItem.event_item || !planningItem.recurrence_id) {
if (primaryEventIds.length === 0 || !planningItem.recurrence_id) {
// Adhoc planning item or does not belong to recurring series
return dispatch(planningAction()).then((p) => Promise.resolve(p));
}

promise = dispatch(eventsApi.fetchById(planningItem.event_item, {force: true, loadPlanning: false}));
promise = dispatch(eventsApi.fetchById(primaryEventIds[0], {force: true, loadPlanning: false}));
}

return promise.then((fetchedEvent) => {
Expand Down Expand Up @@ -928,6 +930,7 @@ const save = (original, updates, confirmation, unlockOnClose) => (
{
actionType: 'save',
unlockOnClose: unlockOnClose,
large: true,
}
));
}
Expand Down
7 changes: 4 additions & 3 deletions client/actions/eventsPlanning/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import eventsApi from '../events/api';
import planningApi from '../planning/api';
import {EVENTS_PLANNING, MAIN, ITEM_TYPE, MODALS} from '../../constants';
import * as selectors from '../../selectors';
import {getItemType, dispatchUtils, getErrorMessage} from '../../utils';
import {getItemType, dispatchUtils, getErrorMessage, gettext} from '../../utils';
import {getRelatedEventIdsForPlanning} from '../../utils/planning';
import main from '../main';
import {gettext} from '../../utils';
import {showModal} from '../index';

/**
Expand Down Expand Up @@ -90,7 +90,8 @@ const refetchPlanning = (planningId) => (
(dispatch, getState) => {
const storedPlannings = selectors.planning.storedPlannings(getState());
const plan = get(storedPlannings, planningId);
const eventId = get(plan, 'event_item');
const relatedEventIds = getRelatedEventIdsForPlanning(plan, 'primary');
const eventId = relatedEventIds.length > 0 ? relatedEventIds[0] : undefined;
const events = selectors.eventsPlanning.getRelatedPlanningsList(getState()) || {};

if (!selectors.main.isEventsPlanningView(getState()) || !eventId ||
Expand Down
8 changes: 2 additions & 6 deletions client/actions/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -716,11 +716,7 @@ const openIgnoreCancelSaveModal = ({
const storedItems = itemType === ITEM_TYPE.EVENT ?
selectors.events.storedEvents(getState()) :
selectors.planning.storedPlannings(getState());

const item = {
...get(storedItems, itemId) || {},
...autosaveData,
};
const item = get(storedItems, itemId) || {};

if (!isExistingItem(item)) {
delete item._id;
Expand Down Expand Up @@ -749,7 +745,7 @@ const openIgnoreCancelSaveModal = ({
modalType: MODALS.IGNORE_CANCEL_SAVE,
modalProps: {
item: itemWithAssociatedData,
itemType: itemType,
updates: autosaveData,
onCancel: onCancel,
onIgnore: onIgnore,
onSave: onSave,
Expand Down
24 changes: 10 additions & 14 deletions client/actions/planning/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from '../../constants';
import main from '../main';
import {planningParamsToSearchParams} from '../../utils/search';
import {getRelatedEventIdsForPlanning} from '../../utils/planning';

/**
* Action dispatcher that marks a Planning item as spiked
Expand Down Expand Up @@ -181,24 +182,19 @@ const refetch = (page = 1, plannings = []) => (
* @param {Array} plannings - An array of Planning items
* @return Promise
*/
const fetchPlanningsEvents = (plannings) => (
const fetchPlanningsEvents = (plannings: Array<IPlanningItem>) => (
(dispatch, getState) => {
const loadedEvents = selectors.events.storedEvents(getState());
const linkedEvents = plannings
.map((p) => p.event_item)
.filter((eid) => (
eid && !has(loadedEvents, eid)
));

// load missing events, if there are any
if (get(linkedEvents, 'length', 0) > 0) {
return dispatch(actions.events.api.silentlyFetchEventsById(
linkedEvents,
'both'
));
}
const linkedEventIds = plannings
.map((plan) => getRelatedEventIdsForPlanning(plan, 'primary'))
.flat()
.filter((eventId) => loadedEvents[eventId] == null);

return Promise.resolve([]);
// load missing events, if there are any
return linkedEventIds.length > 0 ?
dispatch(actions.events.api.silentlyFetchEventsById(linkedEventIds, 'both')) :
Promise.resolve([]);
}
);

Expand Down
4 changes: 3 additions & 1 deletion client/actions/planning/featuredPlanning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,15 @@ function movePlanningToUnselectedList(item: IPlanningItem) {
function getAndUpdateStoredPlanningItem(itemId: IPlanningItem['_id']) {
return (dispatch, getState) => {
if (selectors.featuredPlanning.inUse(getState())) {
planningApi.planning.getById(itemId, false, true).then((item) => {
return planningApi.planning.getById(itemId, false, true).then((item) => {
dispatch({
type: FEATURED_PLANNING.ACTIONS.UPDATE_PLANNING_AND_LISTS,
payload: item,
});
});
}

return Promise.resolve();
};
}

Expand Down
116 changes: 62 additions & 54 deletions client/actions/planning/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,86 +20,89 @@ import eventsPlanning from '../eventsPlanning';
* @param {object} _e - Event object
* @param {object} data - Planning and User IDs
*/
const onPlanningCreated = (_e, data) => (
const onPlanningCreated = (_e: {}, data: IWebsocketMessageData['PLANNING_CREATED']) => (
(dispatch, getState) => {
// If this planning item was created by this user in AddToPlanning Modal
// Then ignore this notification
if (selectors.general.sessionId(getState()) === data.session && (
if (data.item == null) {
return Promise.resolve();
} else if (selectors.general.sessionId(getState()) === data.session && (
selectors.general.modalType(getState()) === MODALS.ADD_TO_PLANNING ||
selectors.general.previousModalType(getState()) === MODALS.ADD_TO_PLANNING
)) {
return;
// If this planning item was created by this user in AddToPlanning Modal
// Then ignore this notification
return Promise.resolve();
}

if (get(data, 'item')) {
if (get(data, 'event_item', null) !== null) {
dispatch(events.api.markEventHasPlannings(
data.event_item,
data.item
));
dispatch(main.fetchItemHistory({_id: data.event_item, type: ITEM_TYPE.EVENT}));
}

dispatch(main.setUnsetLoadingIndicator(true));
return dispatch(planning.ui.scheduleRefetch())
.then(() => dispatch(eventsPlanning.ui.scheduleRefetch()))
.finally(() => dispatch(main.setUnsetLoadingIndicator(false)));
// Update Redux store to mark Event's to have Planning items
for (let eventId of data.event_ids) {
dispatch(events.api.markEventHasPlannings(eventId, data.item));
dispatch(main.fetchItemHistory({_id: eventId, type: ITEM_TYPE.EVENT}));
}

return Promise.resolve();
dispatch(main.setUnsetLoadingIndicator(true));
return dispatch(planning.ui.scheduleRefetch())
.then(() => dispatch(eventsPlanning.ui.scheduleRefetch()))
.finally(() => dispatch(main.setUnsetLoadingIndicator(false)));
}
);

/**
* WS Action when a Planning item gets updated, spiked or unspiked
* If the Planning Item is not loaded, silently discard this notification
* @param {object} _e - Event object
* @param {object} data - Planning and User IDs
*/
const onPlanningUpdated = (_e, data) => (
const onPlanningUpdated = (_e: {}, data: IWebsocketMessageData['PLANNING_UPDATED']) => (
(dispatch, getState) => {
// If this planning item was update by this user in AddToPlanning Modal
// Then ignore this notification
if (selectors.general.sessionId(getState()) === data.session && (
if (data.item == null) {
return Promise.resolve();
} else if (selectors.general.sessionId(getState()) === data.session && (
selectors.general.modalType(getState()) === MODALS.ADD_TO_PLANNING ||
selectors.general.previousModalType(getState()) === MODALS.ADD_TO_PLANNING
)) {
return;
// If this planning item was update by this user in AddToPlanning Modal
// Then ignore this notification
return Promise.resolve();
}

if (get(data, 'item')) {
dispatch(planning.ui.scheduleRefetch())
.then((results) => {
if (selectors.general.currentWorkspace(getState()) === WORKSPACE.ASSIGNMENTS) {
const selectedItems = selectors.multiSelect.selectedPlannings(getState());
const currentPreviewId = selectors.main.previewId(getState());

const loadedFromRefetch = selectedItems.indexOf(data.item) !== -1 &&
!get(results, '[0]._items').find((plan) => plan._id === data.item);

if (!loadedFromRefetch && currentPreviewId === data.item) {
dispatch(planning.api.fetchById(data.item, {force: true}));
}
// Update Redux store to mark Event's to have Planning items
for (let eventId of data.event_ids) {
dispatch(events.api.markEventHasPlannings(eventId, data.item));
dispatch(main.fetchItemHistory({_id: eventId, type: ITEM_TYPE.EVENT}));
}

const promises = [];

promises.push(dispatch(planning.ui.scheduleRefetch())
.then((results) => {
if (selectors.general.currentWorkspace(getState()) === WORKSPACE.ASSIGNMENTS) {
const selectedItems = selectors.multiSelect.selectedPlannings(getState());
const currentPreviewId = selectors.main.previewId(getState());

const loadedFromRefetch = selectedItems.indexOf(data.item) !== -1 &&
!get(results, '[0]._items').find((plan) => plan._id === data.item);

if (!loadedFromRefetch && currentPreviewId === data.item) {
dispatch(planning.api.fetchById(data.item, {force: true}));
}
}

dispatch(eventsPlanning.ui.scheduleRefetch());
});
dispatch(eventsPlanning.ui.scheduleRefetch());
}));

if (get(data, 'added_agendas.length', 0) > 0 || get(data, 'removed_agendas.length', 0) > 0) {
dispatch(fetchAgendas());
}
dispatch(main.fetchItemHistory({_id: data.item, type: ITEM_TYPE.PLANNING}));
dispatch(udpateAssignment(data.item));
dispatch(planning.featuredPlanning.getAndUpdateStoredPlanningItem(data.item));
if (data.added_agendas.length > 0 || data.removed_agendas.length > 0) {
promises.push(dispatch(fetchAgendas()));
}

return Promise.resolve();
promises.push(dispatch(main.fetchItemHistory({_id: data.item, type: ITEM_TYPE.PLANNING})));
promises.push(dispatch(udpateAssignment(data.item)));
promises.push(dispatch(planning.featuredPlanning.getAndUpdateStoredPlanningItem(data.item)));

return Promise.all(promises);
}
);

const onPlanningLocked = (e, data) => (
const onPlanningLocked = (e: {}, data: IWebsocketMessageData['ITEM_LOCKED']) => (
(dispatch, getState) => {
if (get(data, 'item')) {
if (data.item != null) {
planningApi.locks.setItemAsLocked(data);

const sessionId = selectors.general.session(getState()).sessionId;
Expand Down Expand Up @@ -158,8 +161,6 @@ function onPlanningUnlocked(_e: {}, data: IWebsocketMessageData['ITEM_UNLOCKED']
}

planningItem = {
event_item: get(data, 'event_item') || null,
recurrence_id: get(data, 'recurrence_id') || null,
...planningItem,
_id: data.item,
lock_action: null,
Expand Down Expand Up @@ -301,13 +302,20 @@ const udpateAssignment = (planningId) => (
}

const planningItem = selectors.planning.storedPlannings(getState())[planningId];
const promises = [];

get(planningItem, 'coverages', []).forEach((cov) => {
if (get(cov, 'assigned_to.assignment_id')) {
dispatch(assignments.api.fetchAssignmentById(cov.assigned_to.assignment_id, true));
dispatch(assignments.api.fetchAssignmentHistory({_id: cov.assigned_to.assignment_id}));
promises.push(
dispatch(assignments.api.fetchAssignmentById(cov.assigned_to.assignment_id, true))
);
promises.push(
dispatch(assignments.api.fetchAssignmentHistory({_id: cov.assigned_to.assignment_id}))
);
}
});

return Promise.all(promises);
}
);

Expand Down
Loading

0 comments on commit 5a0b815

Please sign in to comment.