Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SDBELGA-758] Feature: Support creating Planning series with Event series #1884

Merged
merged 1 commit into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading