Skip to content

Commit

Permalink
[SDBELGA-758] Feature: Support creating Planning series with Event se…
Browse files Browse the repository at this point in the history
…ries (#1884)
  • Loading branch information
MarkLark86 authored Dec 5, 2023
1 parent 5501f4c commit c8aa073
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 44 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ 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,

### Assignments Config
* SLACK_BOT_TOKEN
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/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ export interface IPlanningConfig extends ISuperdeskGlobalConfig {
timeformat?: string;
allowed_coverage_link_types?: Array<string>;
autosave_timeout?: number;
default_create_planning_series_with_event_series?: boolean;
};
}

Expand Down
159 changes: 159 additions & 0 deletions server/features/recurring_event_and_planning.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
Feature: Recurring Events & Planning
Background: Initial setup
When we post to "/events"
"""
[{
"name": "Daily Club",
"dates": {
"start": "2024-11-21T12:00:00.000Z",
"end": "2024-11-21T14:00:00.000Z",
"tz": "Australia/Sydney",
"recurring_rule": {
"frequency": "DAILY",
"interval": 1,
"count": 3,
"endRepeatMode": "count"
}
}
}]
"""
Then we get OK response
Then we store "EVENT1" with first item
Then we store "EVENT2" with 2 item
Then we store "EVENT3" with 3 item
When we get "/events"
Then we get list with 3 items
"""
{"_items": [{
"_id": "#EVENT1._id#",
"recurrence_id": "#EVENT1.recurrence_id#",
"name": "Daily Club",
"dates": {"start": "2024-11-21T12:00:00+0000", "end": "2024-11-21T14:00:00+0000"}
}, {
"_id": "#EVENT2._id#",
"recurrence_id": "#EVENT1.recurrence_id#",
"name": "Daily Club",
"dates": {"start": "2024-11-22T12:00:00+0000", "end": "2024-11-22T14:00:00+0000"}
}, {
"_id": "#EVENT3._id#",
"recurrence_id": "#EVENT1.recurrence_id#",
"name": "Daily Club",
"dates": {"start": "2024-11-23T12:00:00+0000", "end": "2024-11-23T14:00:00+0000"}
}]}
"""

@auth
Scenario: Creates single plan for event series by default
When we post to "/planning"
"""
[{
"headline": "test headline",
"event_item": "#EVENT1._id#",
"planning_date": "2024-11-21T12:00:00.000Z",
"coverages": [{
"workflow_status": "draft",
"news_coverage_status": {"qcode": "ncostat:int"},
"planning": {
"headline": "test headline",
"slugline": "test slugline",
"g2_content_type": "text",
"scheduled": "2024-11-21T15:00:00.000Z"
}
}, {
"workflow_status": "draft",
"news_coverage_status": {"qcode": "ncostat:int"},
"planning": {
"headline": "test headline",
"slugline": "test slugline",
"g2_content_type": "picture",
"scheduled": "2024-11-21T16:00:00.000Z"
}
}]
}]
"""
Then we get OK response
When we get "/planning"
Then we get list with 1 items
"""
{"_items": [{
"guid": "__any_value__",
"type": "planning",
"headline": "test headline",
"planning_date": "2024-11-21T12:00:00+0000",
"event_item": "#EVENT1._id#",
"recurrence_id": "#EVENT1.recurrence_id#",
"coverages": [
{"planning": {"g2_content_type": "text", "scheduled": "2024-11-21T15:00:00+0000"}},
{"planning": {"g2_content_type": "picture", "scheduled": "2024-11-21T16:00:00+0000"}}
]
}]}
"""

@auth
Scenario: Create planning for each event in the series
When we post to "/planning?add_to_series=true"
"""
[{
"headline": "test headline",
"event_item": "#EVENT1._id#",
"planning_date": "2024-11-21T12:00:00.000Z",
"coverages": [{
"workflow_status": "draft",
"news_coverage_status": {"qcode": "ncostat:int"},
"planning": {
"headline": "test headline",
"slugline": "test slugline",
"g2_content_type": "text",
"scheduled": "2024-11-21T15:00:00.000Z"
}
}, {
"workflow_status": "draft",
"news_coverage_status": {"qcode": "ncostat:int"},
"planning": {
"headline": "test headline",
"slugline": "test slugline",
"g2_content_type": "picture",
"scheduled": "2024-11-21T16:00:00.000Z"
}
}]
}]
"""
Then we get OK response
When we get "/planning"
Then we get list with 3 items
"""
{"_items": [{
"guid": "__any_value__",
"type": "planning",
"headline": "test headline",
"planning_date": "2024-11-21T12:00:00+0000",
"event_item": "#EVENT1._id#",
"recurrence_id": "#EVENT1.recurrence_id#",
"coverages": [
{"planning": {"g2_content_type": "text", "scheduled": "2024-11-21T15:00:00+0000"}},
{"planning": {"g2_content_type": "picture", "scheduled": "2024-11-21T16:00:00+0000"}}
]
}, {
"guid": "__any_value__",
"type": "planning",
"headline": "test headline",
"planning_date": "2024-11-22T12:00:00+0000",
"event_item": "#EVENT2._id#",
"recurrence_id": "#EVENT1.recurrence_id#",
"coverages": [
{"planning": {"g2_content_type": "text", "scheduled": "2024-11-22T15:00:00+0000"}},
{"planning": {"g2_content_type": "picture", "scheduled": "2024-11-22T16:00:00+0000"}}
]
}, {
"guid": "__any_value__",
"type": "planning",
"headline": "test headline",
"planning_date": "2024-11-23T12:00:00+0000",
"event_item": "#EVENT3._id#",
"recurrence_id": "#EVENT1.recurrence_id#",
"coverages": [
{"planning": {"g2_content_type": "text", "scheduled": "2024-11-23T15:00:00+0000"}},
{"planning": {"g2_content_type": "picture", "scheduled": "2024-11-23T16:00:00+0000"}}
]
}]}
"""
4 changes: 4 additions & 0 deletions server/planning/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
get_planning_use_xmp_for_pic_slugline,
get_planning_allowed_coverage_link_types,
get_planning_auto_close_popup_editor,
get_config_default_create_planning_series_with_event_series,
)
from apps.common.components.utils import register_component
from .item_lock import LockService
Expand Down Expand Up @@ -235,6 +236,9 @@ def init_app(app):

app.client_config.setdefault("planning", {})
app.client_config["planning"]["allowed_coverage_link_types"] = get_planning_allowed_coverage_link_types(app)
app.client_config["planning"][
"default_create_planning_series_with_event_series"
] = get_config_default_create_planning_series_with_event_series(app)

# Set up Celery task options
if not app.config.get("CELERY_TASK_ROUTES"):
Expand Down
4 changes: 4 additions & 0 deletions server/planning/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ def get_notify_self_on_assignment(current_app=None):
return (current_app or app).config.get("PLANNING_SEND_NOTIFICATION_FOR_SELF_ASSIGNMENT", False)


def get_config_default_create_planning_series_with_event_series(current_app=None):
return (current_app or app).config.get("DEFAULT_CREATE_PLANNING_SERIES_WITH_EVENT_SERIES", False)


def remove_lock_information(item):
item.update({LOCK_USER: None, LOCK_SESSION: None, LOCK_TIME: None, LOCK_ACTION: None})

Expand Down
Loading

0 comments on commit c8aa073

Please sign in to comment.