Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feature/multiple-events-in-plann…
Browse files Browse the repository at this point in the history
…ing' into feat-events-no-time
  • Loading branch information
petrjasek committed Dec 6, 2024
2 parents bd70d29 + a15abe9 commit 9a265c1
Show file tree
Hide file tree
Showing 104 changed files with 2,902 additions and 1,266 deletions.
3 changes: 3 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ module.exports = Object.assign({}, sharedConfigs, {
'camelcase': 0,
'no-prototype-builtins': 0, // allow hasOwnProperty
'react/prop-types': 0, // using interfaces

// can make functions harder to read; forces into rewriting the function to insert a debugger
'arrow-body-style': 0,
},
},
{
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/ci-client.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,22 @@ jobs:
- run: npm ci
- run: npm run test
- run: npm run lint

extension:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
- run: npm ci
- name: install
working-directory: client/planning-extension
run: npm ci
- name: compile
working-directory: client/planning-extension
run: npm run compile
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@ Below sections include the config options that can be defined in settings.py.
* PLANNING_JSON_ASSIGNED_INFO_EXTENDED
* Defaults to `false`
* If `true`, it will add to planning JSON output additional info for coverages like assigned desk name/email and assigned user name/email.
* ASSIGNMENT_MANUAL_REASSIGNMENT_ONLY
* Default: False (preserves the current behavior where automatic user assignment occurs)
* If true, Disables automatic user assignment for coverage, ensuring that assignments are updated only through explicit manual reassignment

### Authoring Config
* PLANNING_CHECK_FOR_ASSIGNMENT_ON_PUBLISH
Expand Down
117 changes: 107 additions & 10 deletions client/actions/events/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {get, isEqual, cloneDeep, pickBy, has, find, every, take} from 'lodash';
import {get, cloneDeep, has, find, every, take} from 'lodash';

import {planningApi} from '../../superdeskApi';
import {planningApi, superdeskApi} from '../../superdeskApi';
import {ISearchSpikeState, IEventSearchParams, IEventItem, IPlanningItem, IEventTemplate} from '../../interfaces';
import {appConfig} from 'appConfig';

Expand All @@ -9,6 +9,7 @@ import {
POST_STATE,
MAIN,
TO_BE_CONFIRMED_FIELD,
TEMP_ID_PREFIX,
} from '../../constants';
import * as selectors from '../../selectors';
import {
Expand All @@ -19,14 +20,15 @@ import {
isPublishedItemId,
isTemporaryId,
gettext,
getTimeZoneOffset,
} from '../../utils';

import planningApis from '../planning/api';
import eventsUi from './ui';
import main from '../main';
import {eventParamsToSearchParams} from '../../utils/search';
import {getRelatedEventIdsForPlanning} from '../../utils/planning';
import {planning} from '../../api/planning';
import * as actions from '../../actions';

/**
* Action dispatcher to load a series of recurring events into the local store.
Expand Down Expand Up @@ -480,11 +482,11 @@ function markEventPostponed(event: IEventItem, reason: string, actionedDate: str
};
}

const markEventHasPlannings = (event, planning) => ({
type: EVENTS.ACTIONS.MARK_EVENT_HAS_PLANNINGS,
const setEventPlannings = (event_id, planning_ids) => ({
type: EVENTS.ACTIONS.SET_EVENT_PLANNINGS,
payload: {
event_id: event,
planning_item: planning,
event_id,
planning_ids,
},
});

Expand Down Expand Up @@ -547,6 +549,90 @@ const uploadFiles = (event) => (
}
);

function updateLinkedPlanningsForEvent(
eventId: IEventItem['_id'],

/**
* these must be final values
* missing items will be linked, extra items unlinked
*/
associatedPlannings: Array<IPlanningItem>,
):Promise<void> {
return planningApi.events.getLinkedPlanningItems(eventId).then((currentlyLinked) => {
const currentLinkedIds = new Set(currentlyLinked.map((item) => item._id));

const toLink: Array<IPlanningItem> =
associatedPlannings.filter(({_id}) => currentLinkedIds.has(_id) !== true);

const toUnlink: Array<IPlanningItem> = currentlyLinked
.filter((item) => {
const createdAt = new Date(item._created);
const now = new Date();

const ageSeconds = (now.getTime() - createdAt.getTime()) / 1000;
const tooRecent = ageSeconds < 30;

if (tooRecent) {
/**
* This is a hack to workaround our existing "fake ID" workaround.
* In event editor it is possible to create a planning item and relate it to current event at once.
* It would happen only after saving, thus while it's not saved yet, we use a fake ID
* - which will not remain the same after saving.
* This function computes a list of planning items which have to be linked
* and which have to be unlinked based on a desired outcome
* which is that only items specified in {@link associatedPlannings} must remain linked.
* The problem arises that when item with a fake ID is saved, and ID changes,
* that item will immediately get unlinked by this function, because there is no way that
* the new ID could have been a part of {@link associatedPlannings}
* (which is computed before saving).
*/
return false;
} else {
const needToUnlink = associatedPlannings.find(({_id}) => _id === item._id) == null;

return needToUnlink;
}
});

return Promise.all(
[
...toLink.map((planningItem) => {
const linkType = planningItem._temporary?.link_type;

if (linkType == null) {
superdeskApi.utilities.logger.error(
new Error('linkType expected but not found'),
);

return Promise.resolve(planningItem);
}

const patch: Partial<IPlanningItem> = {
related_events: [
...(planningItem.related_events ?? []),
{_id: eventId, link_type: linkType},
],
};

return planning.update(planningItem, patch);
}),
...toUnlink.map((planningItem) => {
const patch: Partial<IPlanningItem> = {
related_events: (planningItem.related_events ?? [])
.filter((item) => item._id !== eventId),
};

return planning.update(planningItem, patch);
}),
],
).then((updatedPlanningItems) => {
planningApi.redux.store.dispatch<any>(planningApis.receivePlannings(updatedPlanningItems));

return null;
});
});
}

const save = (original, updates) => (
(dispatch) => {
let promise;
Expand All @@ -561,7 +647,7 @@ const save = (original, updates) => (
promise = Promise.resolve({});
}

return promise.then((originalEvent) => {
return promise.then((originalEvent): any => {
const originalItem = eventUtils.modifyForServer(cloneDeep(originalEvent), true);
const eventUpdates = eventUtils.getEventDiff(originalItem, updates);

Expand All @@ -574,9 +660,20 @@ const save = (original, updates) => (
EVENTS.UPDATE_METHODS[0].value :
eventUpdates.update_method?.value ?? eventUpdates.update_method;

return originalEvent?._id != null ?
const createOrUpdatePromise: Promise<Array<IEventItem>> = originalEvent?._id != null ?
planningApi.events.update(originalItem, eventUpdates) :
planningApi.events.create(eventUpdates);

return createOrUpdatePromise.then(([updatedEvent]: Array<IEventItem>) => {
if (updates.associated_plannings == null) {
return Promise.resolve([updatedEvent]);
}

return updateLinkedPlanningsForEvent(
updatedEvent._id,
updates.associated_plannings.filter(({_id}) => !_id.startsWith(TEMP_ID_PREFIX)),
).then(() => [updatedEvent]);
});
});
}
);
Expand Down Expand Up @@ -757,7 +854,7 @@ const self = {
silentlyFetchEventsById,
cancelEvent,
markEventCancelled,
markEventHasPlannings,
setEventPlannings,
rescheduleEvent,
updateEventTime,
markEventPostponed,
Expand Down
48 changes: 28 additions & 20 deletions client/actions/events/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,12 @@ const onEventDeleted = (e, data) => (
}
});

const onEventLinkUpdated = (e, data: IWebsocketMessageData['EVENT_LINK_UPDATED']) => (
(dispatch, getState) => {
dispatch(eventsApi.setEventPlannings(data.event, data.links));
dispatch(main.fetchItemHistory({_id: data.event, type: ITEM_TYPE.EVENT}));
});

// eslint-disable-next-line consistent-this
const self = {
onEventCreated,
Expand All @@ -363,6 +369,7 @@ const self = {
onEventPostChanged,
onEventExpired,
onEventDeleted,
onEventLinkUpdated,
};

export const planningEventTemplateEvents = {
Expand All @@ -386,27 +393,28 @@ export const planningEventTemplateEvents = {

// Map of notification name and Action Event to execute
self.events = {
'events:created': () => (self.onEventCreated),
'events:created:recurring': () => (self.onRecurringEventCreated),
'events:updated': () => (self.onEventUpdated),
'events:updated:recurring': () => (self.onEventUpdated),
'events:lock': () => (self.onEventLocked),
'events:unlock': () => (self.onEventUnlocked),
'events:spiked': () => (self.onEventSpiked),
'events:unspiked': () => (self.onEventUnspiked),
'events:cancel': () => (self.onEventCancelled),
'events:reschedule': () => (self.onEventScheduleChanged),
'events:reschedule:recurring': () => (self.onEventScheduleChanged),
'events:postpone': () => (self.onEventPostponed),
'events:posted': () => (self.onEventPostChanged),
'events:posted:recurring': () => (self.onEventPostChanged),
'events:unposted': () => (self.onEventPostChanged),
'events:unposted:recurring': () => (self.onEventPostChanged),
'events:update_time': () => (self.onEventScheduleChanged),
'events:update_time:recurring': () => (self.onEventScheduleChanged),
'events:update_repetitions:recurring': () => (self.onEventScheduleChanged),
'events:created': () => self.onEventCreated,
'events:created:recurring': () => self.onRecurringEventCreated,
'events:updated': () => self.onEventUpdated,
'events:updated:recurring': () => self.onEventUpdated,
'events:lock': () => self.onEventLocked,
'events:unlock': () => self.onEventUnlocked,
'events:spiked': () => self.onEventSpiked,
'events:unspiked': () => self.onEventUnspiked,
'events:cancel': () => self.onEventCancelled,
'events:reschedule': () => self.onEventScheduleChanged,
'events:reschedule:recurring': () => self.onEventScheduleChanged,
'events:postpone': () => self.onEventPostponed,
'events:posted': () => self.onEventPostChanged,
'events:posted:recurring': () => self.onEventPostChanged,
'events:unposted': () => self.onEventPostChanged,
'events:unposted:recurring': () => self.onEventPostChanged,
'events:update_time': () => self.onEventScheduleChanged,
'events:update_time:recurring': () => self.onEventScheduleChanged,
'events:update_repetitions:recurring': () => self.onEventScheduleChanged,
'events:expired': () => self.onEventExpired,
'events:delete': () => (self.onEventDeleted),
'events:delete': () => self.onEventDeleted,
'event:link_updated': () => self.onEventLinkUpdated,
...planningEventTemplateEvents,
};

Expand Down
5 changes: 1 addition & 4 deletions client/actions/main.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {get, isEmpty, isEqual, isNil, omit} from 'lodash';
import moment from 'moment';

import {appConfig as config} from 'appConfig';

const appConfig = config as IPlanningConfig;
import {appConfig} from 'appConfig';

import {IUser} from 'superdesk-api';
import {planningApi, superdeskApi} from '../superdeskApi';
Expand All @@ -21,7 +19,6 @@ import {
ITEM_TYPE,
IEventTemplate,
IEventItem,
IPlanningConfig,
} from '../interfaces';

import {
Expand Down
Loading

0 comments on commit 9a265c1

Please sign in to comment.