Skip to content

Commit

Permalink
Merge branch 'develop' into authoring-react-post-broadcasting
Browse files Browse the repository at this point in the history
  • Loading branch information
thecalcc committed Jan 17, 2024
2 parents 24c6003 + 65434dc commit b548e0a
Show file tree
Hide file tree
Showing 39 changed files with 2,280 additions and 173 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,24 @@ Below sections include the config options that can be defined in settings.py.
* PLANNING_DEFAULT_COVERAGE_STATUS_ON_INGEST:
* Default: 'ncostat:int' - Coverage Planned
* The default CV qcode for populating planning.coverage[x].news_coverage_status on ingest
* DEFAULT_CREATE_PLANNING_SERIES_WITH_EVENT_SERIES
* Default: False
* If true, will default to creating series of Planning items with a recurring series of Events,
* SYNC_EVENT_FIELDS_TO_PLANNING
* Default: ""
* Comma separated list of Planning & Coverage fields to keep in sync with the associated Event
* Supported Fields:
* slugline
* internal_note
* name
* place (list CVs)
* subject (list CVs, exclude items with scheme)
* custom_vocabularies (list CVs, inside subject with scheme)
* anpa_category (list CVs)
* ednote
* language (includes `languages` if multilingual is enabled)
* definition_short (copies to Planning item's `Description Text`)
* priority

### Assignments Config
* SLACK_BOT_TOKEN
Expand All @@ -176,6 +194,9 @@ Below sections include the config options that can be defined in settings.py.
* PLANNING_SEND_NOTIFICATION_FOR_SELF_ASSIGNMENT
* Defaults to false
* If true, sends a notification to a user on creating an assignment that is assigned to themselves
* 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.

### Authoring Config
* PLANNING_CHECK_FOR_ASSIGNMENT_ON_PUBLISH
Expand Down
2 changes: 1 addition & 1 deletion client/actions/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ import {
shouldUnLockItem,
timeUtils
} from '../utils';
import {hideModal, locks, showModal} from './';
import {hideModal, showModal} from './';
import {fetchSelectedAgendaPlannings} from './agenda';
import eventsPlanningUi from './eventsPlanning/ui';

Expand Down
116 changes: 73 additions & 43 deletions client/api/events.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import {
FILTER_TYPE,
IEventItem,
IPlanningAPI, IPlanningItem,
IPlanningAPI,
ISearchAPIParams,
ISearchParams,
ISearchSpikeState,
LOCK_STATE
IPlanningConfig,
} from '../interfaces';
import {appConfig as config} from 'appConfig';
import {IRestApiResponse} from 'superdesk-api';
import {planningApi, superdeskApi} from '../superdeskApi';
import {EVENTS, TEMP_ID_PREFIX} from '../constants';

import {arrayToString, convertCommonParams, cvsToString, searchRaw, searchRawGetAll} from './search';
import {eventUtils, planningUtils} from '../utils';
import {eventUtils} from '../utils';
import {eventProfile, eventSearchProfile} from '../selectors/forms';
import * as actions from '../actions';

const appConfig = config as IPlanningConfig;

function convertEventParams(params: ISearchParams): Partial<ISearchAPIParams> {
return {
reference: params.reference,
Expand Down Expand Up @@ -118,62 +121,89 @@ function getEventSearchProfile() {
return eventSearchProfile(planningApi.redux.store.getState());
}

function createOrUpdatePlannings(
event: IEventItem,
items: Array<Partial<IPlanningItem>>
): Promise<Array<IPlanningItem>> {
return Promise.all(
items.map(
(updates) => (
updates._id.startsWith(TEMP_ID_PREFIX) ?
planningApi.planning.createFromEvent(event, updates) :
planningApi.planning.getById(updates._id)
.then((original) => (
planningApi.planning.update(original, updates)
))
)
)
)
.then((newOrUpdatedItems) => {
newOrUpdatedItems.forEach(planningUtils.modifyForClient);

return newOrUpdatedItems;
});
}

function create(updates: Partial<IEventItem>): Promise<Array<IEventItem>> {
return superdeskApi.dataApi.create<IEventItem | IRestApiResponse<IEventItem>>('events', {
const url = appConfig.planning.default_create_planning_series_with_event_series === true ?
'events?add_to_series=true' :
'events';

return superdeskApi.dataApi.create<IEventItem | IRestApiResponse<IEventItem>>(url, {
...updates,
associated_plannings: undefined,
embedded_planning: updates.associated_plannings.map((planning) => ({
coverages: planning.coverages.map((coverage) => ({
coverage_id: coverage.coverage_id,
g2_content_type: coverage.planning.g2_content_type,
desk: coverage.assigned_to.desk,
user: coverage.assigned_to.user,
language: coverage.planning.language,
news_coverage_status: coverage.news_coverage_status.qcode,
scheduled: coverage.planning.scheduled,
genre: coverage.planning.genre?.qcode,
slugline: coverage.planning.slugline,
ednote: coverage.planning.ednote,
internal_note: coverage.planning.internal_note,
})),
})),
})
.then((response) => {
const events: Array<IEventItem> = modifySaveResponseForClient(response);

return createOrUpdatePlannings(events[0], updates.associated_plannings ?? [])
.then((plannings) => {
// Make sure to update the Redux Store with the latest Planning items
// So that the Editor can set the state with these latest items
planningApi.redux.store.dispatch<any>(actions.planning.api.receivePlannings(plannings));
const events = modifySaveResponseForClient(response);

return events;
});
return planningApi.planning.searchGetAll({
recurrence_id: events[0].recurrence_id,
event_item: events[0].recurrence_id != null ? null : events.map((event) => event._id),
spike_state: 'both',
only_future: false,
}).then((planningItems) => {
// Make sure to update the Redux Store with the latest Planning items
// So that the Editor can set the state with these latest items
planningApi.redux.store.dispatch<any>(actions.planning.api.receivePlannings(planningItems));

return events;
});
})
.catch((error) => {
console.error(error);

return Promise.reject(error);
});
}

function update(original: IEventItem, updates: Partial<IEventItem>): Promise<Array<IEventItem>> {
return superdeskApi.dataApi.patch<any>('events', original, {
return superdeskApi.dataApi.patch<IEventItem>('events', original, {
...updates,
associated_plannings: undefined,
embedded_planning: updates.associated_plannings.map((planning) => ({
planning_id: planning._id.startsWith(TEMP_ID_PREFIX) ? undefined : planning._id,
coverages: planning.coverages.map((coverage) => ({
coverage_id: coverage.coverage_id,
g2_content_type: coverage.planning.g2_content_type,
desk: coverage.assigned_to.desk,
user: coverage.assigned_to.user,
language: coverage.planning.language,
news_coverage_status: coverage.news_coverage_status.qcode,
scheduled: coverage.planning.scheduled,
genre: coverage.planning.genre?.qcode,
slugline: coverage.planning.slugline,
ednote: coverage.planning.ednote,
internal_note: coverage.planning.internal_note,
})),
})),
})
.then((response) => {
const events = modifySaveResponseForClient(response);

return createOrUpdatePlannings(events[0], updates.associated_plannings ?? [])
.then((plannings) => {
planningApi.redux.store.dispatch<any>(actions.planning.api.receivePlannings(plannings));

return events;
});
return planningApi.planning.searchGetAll({
recurrence_id: events[0].recurrence_id,
event_item: events[0].recurrence_id != null ? null : events.map((event) => event._id),
spike_state: 'both',
only_future: false,
}).then((planningItems) => {
// Make sure to update the Redux Store with the latest Planning items
// So that the Editor can set the state with these latest items
planningApi.redux.store.dispatch<any>(actions.planning.api.receivePlannings(planningItems));

return events;
});
});
}

Expand Down
19 changes: 17 additions & 2 deletions client/api/locks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,15 @@ function loadLockedItems(types?: Array<'events_and_planning' | 'featured_plannin
}
}

const lockedItemIds = lockUtils.getLockedItemIds(locks);

if (lockedItemIds.length === 0) {
return Promise.resolve();
}

// Make sure that all items that are locked are loaded into the store
return planningApi.combined.searchGetAll({
item_ids: lockUtils.getLockedItemIds(locks),
item_ids: lockedItemIds,
only_future: false,
include_killed: true,
spike_state: 'draft',
Expand Down Expand Up @@ -228,7 +234,16 @@ function unlockItem<T extends IAssignmentOrPlanningItem>(item: T, reloadLocksIfN
}
}

const lockedItemId = currentLock.item_id;
let lockedItemId: string;

if (item.type === 'event' && item.recurrence_id === currentLock.item_id) {
lockedItemId = item._id;
} else if (item.type === 'planning' && item.recurrence_id === currentLock.item_id) {
lockedItemId = item.event_item;
} else {
lockedItemId = currentLock.item_id;
}

const resource = getLockResourceName(currentLock.item_type);
const endpoint = `${resource}/${lockedItemId}/unlock`;

Expand Down
49 changes: 27 additions & 22 deletions client/api/planning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import {
ISearchAPIParams,
ISearchParams,
ISearchSpikeState,
IPlanningConfig,
} from '../interfaces';
import {appConfig as config} from 'appConfig';

import {arrayToString, convertCommonParams, searchRaw, searchRawGetAll, cvsToString} from './search';
import {planningApi, superdeskApi} from '../superdeskApi';
import {IRestApiResponse} from 'superdesk-api';
Expand All @@ -20,6 +23,8 @@ import {PLANNING} from '../constants';
import * as selectors from '../selectors';
import * as actions from '../actions';

const appConfig = config as IPlanningConfig;

function convertPlanningParams(params: ISearchParams): Partial<ISearchAPIParams> {
return {
agendas: arrayToString(params.agendas),
Expand Down Expand Up @@ -140,14 +145,11 @@ function getPlanningSearchProfile() {
return planningSearchProfile(planningApi.redux.store.getState());
}

function create(updates: Partial<IPlanningItem>): Promise<IPlanningItem> {
// If the Planning item has coverages, then we need to create the Planning first
// before saving the coverages
// As Assignments are created and require a Planning ID
return !updates.coverages?.length ?
superdeskApi.dataApi.create<IPlanningItem>('planning', updates) :
superdeskApi.dataApi.create<IPlanningItem>('planning', {...updates, coverages: []})
.then((item) => update(item, updates));
function create(updates: Partial<IPlanningItem>, addToSeries?: boolean): Promise<IPlanningItem> {
return superdeskApi.dataApi.create<IPlanningItem>(
addToSeries === true ? 'planning?add_to_series=true' : 'planning',
updates
);
}

function update(original: IPlanningItem, updates: Partial<IPlanningItem>): Promise<IPlanningItem> {
Expand All @@ -159,20 +161,23 @@ function update(original: IPlanningItem, updates: Partial<IPlanningItem>): Promi
}

function createFromEvent(event: IEventItem, updates: Partial<IPlanningItem>): Promise<IPlanningItem> {
return create(planningUtils.modifyForServer({
slugline: event.slugline,
planning_date: event._sortDate ?? event.dates.start,
internal_note: event.internal_note,
name: event.name,
place: event.place,
subject: event.subject,
anpa_category: event.anpa_category,
description_text: event.definition_short,
ednote: event.ednote,
language: event.language,
...updates,
event_item: event._id,
}));
return create(
planningUtils.modifyForServer({
slugline: event.slugline,
planning_date: event._sortDate ?? event.dates.start,
internal_note: event.internal_note,
name: event.name,
place: event.place,
subject: event.subject,
anpa_category: event.anpa_category,
description_text: event.definition_short,
ednote: event.ednote,
language: event.language,
...updates,
event_item: event._id,
}),
appConfig.planning.default_create_planning_series_with_event_series === true,
);
}

function setDefaultValues(
Expand Down
1 change: 1 addition & 0 deletions client/api/ui/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ function changeFilterId(id: ISearchFilter['_id'], params: ICombinedEventOrPlanni
type: EVENTS_PLANNING.ACTIONS.SELECT_EVENTS_PLANNING_FILTER,
payload: id,
});
urlParams.setString('eventsPlanningFilter', id);
}
return reloadList(params);
}
Expand Down
10 changes: 9 additions & 1 deletion client/apps/Planning/PlanningList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
IContactItem,
SORT_FIELD,
ICommonSearchParams,
ISearchFilter,
} from '../../interfaces';

import * as actions from '../../actions';
Expand Down Expand Up @@ -61,6 +62,7 @@ interface IProps {
listViewType: LIST_VIEW_TYPE;
sortField: SORT_FIELD;
currentSearch: ICommonSearchParams<IEventOrPlanningItem>;
searchFilters: Array<ISearchFilter>;

openPreview(item: IEventOrPlanningItem): void;
edit(item: IEventOrPlanningItem): void;
Expand Down Expand Up @@ -94,7 +96,8 @@ const mapStateToProps = (state) => ({
contacts: selectors.general.contactsById(state),
listViewType: selectors.main.getCurrentListViewType(state),
sortField: selectors.main.getCurrentSortField(state),
currentSearch: selectors.main.currentSearch(state)
currentSearch: selectors.main.currentSearch(state),
searchFilters: selectors.eventsPlanning.combinedViewFilters(state),
});

const mapDispatchToProps = (dispatch) => ({
Expand Down Expand Up @@ -140,6 +143,10 @@ export class PlanningListComponent extends React.PureComponent<IProps> {
}

render() {
const activeSearchFilter = this.props.searchFilters.filter((filter) =>
filter.item_type === FILTER_TYPE[this.props.activeFilter] &&
filter._id === this.props.currentSearch?.filter_id);

const {
groups,
agendas,
Expand Down Expand Up @@ -211,6 +218,7 @@ export class PlanningListComponent extends React.PureComponent<IProps> {
sortField={sortField}
indexItems
searchParams={currentSearch.advancedSearch}
searchFilterParams={activeSearchFilter[0]?.params}
/>
</React.Fragment>
);
Expand Down
Loading

0 comments on commit b548e0a

Please sign in to comment.