diff --git a/client/utils/history.tsx b/client/utils/history.tsx
index f3c2aaaca..794d4fe21 100644
--- a/client/utils/history.tsx
+++ b/client/utils/history.tsx
@@ -61,7 +61,7 @@ const getHistoryRowElement = (text, historyItem, users) => {
if (text) {
return (
-
{text}{gettext(' by ')}
+
{text}{historyItem.user_id != null ? gettext(' by ') : null}
{self.getDisplayUser(historyItem.user_id, users)}
@@ -74,50 +74,20 @@ const getPostedHistoryElement = (index, historyItems, users) => {
const historyItem = historyItems[index];
const itemType = 'event_id' in historyItem ? gettext('Event ') : gettext('Planning ');
- for (let i = index - 1; i >= 0; i--) {
- const item = historyItems[i];
-
- if (item.operation !== HISTORY_OPERATIONS.POST ||
- [HISTORY_OPERATIONS.SPIKED, HISTORY_OPERATIONS.UNSPIKED].includes(historyItem.operation)) {
- continue;
- }
-
- if (get(item, 'update.pubstatus') === POST_STATE.USABLE) {
- // Current history item happened when the item was in posted state
-
- if (get(historyItem, 'update.pubstatus') === POST_STATE.USABLE &&
- historyItem.operation !== HISTORY_OPERATIONS.EDITED) {
- // If it is an edit and update operation don't show as a separate item
- return;
- }
-
- if (get(historyItem, 'update.pubstatus') !== POST_STATE.CANCELLED) {
- text = gettext('Updated');
- break;
- }
-
- if (get(historyItem, 'update.pubstatus') === POST_STATE.CANCELLED) {
- text = itemType + gettext('unposted');
- break;
- }
- } else if (get(historyItem, 'update.pubstatus') === POST_STATE.USABLE) {
- // Posted when the item was in unposted state
- text = itemType + gettext('re-posted');
- break;
- }
+ if (historyItem.operation !== HISTORY_OPERATIONS.POST &&
+ historyItem.operation !== HISTORY_OPERATIONS.EVENTS_CANCEL &&
+ historyItem.operation !== HISTORY_OPERATIONS.PLANNING_CANCEL
+ ) {
+ return; // not post operation
}
- // Item posted for the first time
- if (!text && historyItem.operation === HISTORY_OPERATIONS.POST) {
- text = itemType + gettext('posted');
+ text = itemType + gettext('posted');
+
+ if (get(historyItem, 'update.pubstatus') === POST_STATE.CANCELLED) {
+ text = itemType + gettext('unposted');
}
- return text && text.includes('unposted') ? (
-
- {self.getHistoryRowElement(gettext('Updated'), historyItem, users)}
- {self.getHistoryRowElement(text, historyItem, users)}
-
- ) : self.getHistoryRowElement(text, historyItem, users);
+ return self.getHistoryRowElement(text, historyItem, users);
};
// eslint-disable-next-line consistent-this
diff --git a/server/planning/common.py b/server/planning/common.py
index d98e04793..25cd0fabc 100644
--- a/server/planning/common.py
+++ b/server/planning/common.py
@@ -8,7 +8,7 @@
# AUTHORS and LICENSE files distributed with this source code, or
# at https://www.sourcefabric.org/superdesk/license
-from typing import NamedTuple, Dict, Any, Set, Optional
+from typing import NamedTuple, Dict, Any, Set, Optional, Union
import re
import time
@@ -30,7 +30,7 @@
import json
from bson import ObjectId
-from planning.types import Planning, Coverage
+from planning.types import Planning, Coverage, Event
ITEM_STATE = "state"
ITEM_EXPIRY = "expiry"
@@ -379,6 +379,8 @@ def update_post_item(updates, original):
# Save&Post or Save&Unpost
if updates.get("pubstatus"):
pub_status = updates["pubstatus"]
+ elif updates.get("ingest_pubstatus"):
+ pub_status = updates["ingest_pubstatus"]
elif original.get("pubstatus") == POST_STATE.USABLE:
# From item actions
pub_status = POST_STATE.USABLE
@@ -854,7 +856,7 @@ def update_ingest_on_patch(updates: Dict[str, Any], original: Dict[str, Any]):
# The local version has not been published yet
# So remove the provided ``pubstatus``
updates.pop("pubstatus", None)
- elif original.get("pubstatus") == updates.get("pubstatus"):
+ elif original.get("pubstatus") == updates.get("ingest_pubstatus") or original.get("pubstatus"):
# The local version has been published
# and no change to ``pubstatus`` on ingested item
updates.pop("state")
@@ -865,3 +867,8 @@ def get_coverage_from_planning(planning_item: Planning, coverage_id: str) -> Opt
(coverage for coverage in planning_item.get("coverages") or [] if coverage.get("coverage_id") == coverage_id),
None,
)
+
+
+def prepare_ingested_item_for_storage(doc: Union[Event, Planning]) -> None:
+ doc.setdefault("state", "ingested")
+ doc["ingest_pubstatus"] = doc.pop("pubstatus", "usable") # pubstatus is set when posted
diff --git a/server/planning/events/events.py b/server/planning/events/events.py
index 4c6bec981..2fc13aa97 100644
--- a/server/planning/events/events.py
+++ b/server/planning/events/events.py
@@ -54,6 +54,7 @@
get_max_recurrent_events,
WORKFLOW_STATE,
ITEM_STATE,
+ prepare_ingested_item_for_storage,
remove_lock_information,
format_address,
update_post_item,
@@ -135,6 +136,7 @@ def post_in_mongo(self, docs, **kwargs):
"""Post an ingested item(s)"""
for doc in docs:
+ prepare_ingested_item_for_storage(doc)
self._resolve_defaults(doc)
set_ingest_version_datetime(doc)
@@ -146,6 +148,9 @@ def post_in_mongo(self, docs, **kwargs):
def patch_in_mongo(self, id, document, original) -> Optional[Dict[str, Any]]:
"""Patch an ingested item onto an existing item locally"""
+ prepare_ingested_item_for_storage(document)
+ events_history = get_resource_service("events_history")
+ events_history.on_item_updated(document, original, "ingested")
set_planning_schedule(document)
update_ingest_on_patch(document, original)
diff --git a/server/planning/events/events_schema.py b/server/planning/events/events_schema.py
index d3b5bf9e3..07ff3dc2a 100644
--- a/server/planning/events/events_schema.py
+++ b/server/planning/events/events_schema.py
@@ -64,6 +64,7 @@
"ingest_provider_sequence": metadata_schema["ingest_provider_sequence"],
"ingest_firstcreated": metadata_schema["versioncreated"],
"ingest_versioncreated": metadata_schema["versioncreated"],
+ "ingest_pubstatus": metadata_schema["pubstatus"],
"event_created": {"type": "datetime"},
"event_lastmodified": {"type": "datetime"},
# Event Details
diff --git a/server/planning/events/events_tests.py b/server/planning/events/events_tests.py
index 46229ce71..78778cfd0 100644
--- a/server/planning/events/events_tests.py
+++ b/server/planning/events/events_tests.py
@@ -157,7 +157,7 @@ def test_create_cancelled_event(self):
event = service.find_one(req=None, guid="test")
assert event is not None
- assert event["pubstatus"] == "cancelled"
+ assert event["ingest_pubstatus"] == "cancelled"
class EventLocationFormatAddress(TestCase):
diff --git a/server/planning/io/ingest_rule_handler.py b/server/planning/io/ingest_rule_handler.py
index 0a0462210..6f10948e9 100644
--- a/server/planning/io/ingest_rule_handler.py
+++ b/server/planning/io/ingest_rule_handler.py
@@ -74,15 +74,18 @@ def apply_rule(self, rule: Dict[str, Any], ingest_item: Dict[str, Any], routing_
if updates is not None:
ingest_item.update(updates)
-
- if attributes.get("autopost", False):
+ elif attributes.get("autopost", False):
self.process_autopost(ingest_item)
def _is_original_posted(self, ingest_item: Dict[str, Any]):
service = get_resource_service("events" if ingest_item[ITEM_TYPE] == CONTENT_TYPE.EVENT else "planning")
original = service.find_one(req=None, _id=ingest_item.get(config.ID_FIELD))
- return original is not None and original.get("pubstatus") in [POST_STATE.USABLE, POST_STATE.CANCELLED]
+ return (
+ original is not None
+ and original.get("pubstatus") in [POST_STATE.USABLE, POST_STATE.CANCELLED]
+ and original["pubstatus"] == ingest_item.get("ingest_pubstatus")
+ )
def add_event_calendars(self, ingest_item: Dict[str, Any], attributes: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Add Event Calendars from Routing Rule Action onto the ingested item"""
@@ -164,7 +167,6 @@ def add_planning_agendas(self, ingest_item: Dict[str, Any], attributes: Dict[str
def process_autopost(self, ingest_item: Dict[str, Any]):
"""Automatically post this item"""
-
if self._is_original_posted(ingest_item):
# No need to autopost this item
# As the original is already posted
@@ -174,7 +176,7 @@ def process_autopost(self, ingest_item: Dict[str, Any]):
item_id = ingest_item.get(config.ID_FIELD)
update_post_item(
{
- "pubstatus": ingest_item.get("pubstatus") or POST_STATE.USABLE,
+ "pubstatus": ingest_item.get("ingest_pubstatus") or POST_STATE.USABLE,
"_etag": ingest_item.get("_etag"),
},
ingest_item,
diff --git a/server/planning/io/ingest_rule_handler_test.py b/server/planning/io/ingest_rule_handler_test.py
index 98a246133..e41c87114 100644
--- a/server/planning/io/ingest_rule_handler_test.py
+++ b/server/planning/io/ingest_rule_handler_test.py
@@ -9,6 +9,8 @@
# at https://www.sourcefabric.org/superdesk/license
from bson import ObjectId
+from datetime import datetime
+from superdesk import get_resource_service
from superdesk.metadata.item import ITEM_TYPE, CONTENT_TYPE
from .ingest_rule_handler import PlanningRoutingRuleHandler
from planning.tests import TestCase
@@ -30,6 +32,8 @@
},
}
+AUTOPOST_RULE = {"actions": {"extra": {"autopost": True}}}
+
class IngestRuleHandlerTestCase(TestCase):
calendars = [
@@ -44,10 +48,11 @@ class IngestRuleHandlerTestCase(TestCase):
{
"_id": "event1",
"dates": {
- "start": "2022-07-02T14:00:00+0000",
- "end": "2022-07-03T14:00:00+0000",
+ "start": datetime.fromisoformat("2022-07-02T14:00:00+00:00"),
+ "end": datetime.fromisoformat("2022-07-03T14:00:00+00:00"),
},
"type": "event",
+ "pubstatus": "usable",
},
{
"_id": "event2",
@@ -92,7 +97,7 @@ def test_adds_event_calendars(self):
}
],
)
- event = self.event_items[0]
+ event = self.event_items[0].copy()
self.app.data.insert("events", [event])
original = self.app.data.find_one("events", req=None, _id=event["_id"])
@@ -115,7 +120,7 @@ def test_skips_disabled_and_existing_calendars(self):
}
],
)
- event = self.event_items[1]
+ event = self.event_items[1].copy()
self.app.data.insert("events", [event])
original = self.app.data.find_one("events", req=None, _id=event["_id"])
@@ -159,3 +164,53 @@ def test_skips_disabled_and_existing_agendas(self):
self.assertEqual(len(updated["agendas"]), 1)
self.assertEqual(updated["agendas"][0], self.agendas[0]["_id"])
+
+ def test_autopost(self):
+ event = self.event_items[0].copy()
+ events_service = get_resource_service("events")
+ events_service.post_in_mongo([event])
+
+ history = self.get_event_history()
+ assert len(history) == 1
+ assert history[0]["operation"] == "ingested"
+
+ self.handler.apply_rule(AUTOPOST_RULE, event, {})
+
+ history = self.get_event_history()
+ assert len(history) == 2
+ assert history[-1]["operation"] == "post"
+
+ original = events_service.find_one(req=None, _id=event["_id"])
+ assert original["pubstatus"] == "usable"
+
+ event["pubstatus"] = "cancelled"
+ event["versioncreated"] = datetime.now()
+ events_service.patch_in_mongo(event["_id"], event, original)
+
+ self.handler.apply_rule(AUTOPOST_RULE, event, {})
+
+ history = self.get_event_history()
+ assert len(history) == 4
+ assert history[-2]["operation"] == "ingested"
+ assert history[-1]["operation"] == "post"
+
+ original = events_service.find_one(req=None, _id=event["_id"])
+ assert original["pubstatus"] == "cancelled"
+
+ def test_autopost_cancelled(self):
+ event = self.event_items[0].copy()
+ event["pubstatus"] = "cancelled"
+ events_service = get_resource_service("events")
+ events_service.post_in_mongo([event])
+
+ self.handler.apply_rule(AUTOPOST_RULE, event, {})
+
+ history = self.get_event_history()
+ assert len(history) == 2
+ assert history[-1]["operation"] == "post"
+
+ original = events_service.find_one(req=None, _id=event["_id"])
+ assert original["pubstatus"] == "cancelled"
+
+ def get_event_history(self):
+ return list(self.app.data.find_all("events_history"))
diff --git a/server/planning/planning/planning.py b/server/planning/planning/planning.py
index 14de66af6..0a3a2dac2 100644
--- a/server/planning/planning/planning.py
+++ b/server/planning/planning/planning.py
@@ -38,6 +38,7 @@
get_coverage_status_from_cv,
WORKFLOW_STATE,
ASSIGNMENT_WORKFLOW_STATE,
+ prepare_ingested_item_for_storage,
update_post_item,
get_coverage_type_name,
set_original_creator,
@@ -91,8 +92,8 @@ def post_in_mongo(self, docs, **kwargs):
"""Post an ingested item(s)"""
for doc in docs:
+ prepare_ingested_item_for_storage(doc)
self._resolve_defaults(doc)
- doc.pop("pubstatus", None)
set_ingest_version_datetime(doc)
self.on_create(docs)
@@ -105,7 +106,7 @@ def post_in_mongo(self, docs, **kwargs):
def patch_in_mongo(self, id, document, original):
"""Patch an ingested item onto an existing item locally"""
-
+ prepare_ingested_item_for_storage(document)
update_ingest_on_patch(document, original)
response = self.backend.update_in_mongo(self.datasource, id, document, original)
self.on_updated(document, original, from_ingest=True)
@@ -1813,6 +1814,7 @@ def _iter_recurring_plannings_to_update(self, updates, original, update_method):
"ingest_provider_sequence": metadata_schema["ingest_provider_sequence"],
"ingest_firstcreated": metadata_schema["versioncreated"],
"ingest_versioncreated": metadata_schema["versioncreated"],
+ "ingest_pubstatus": metadata_schema["pubstatus"],
# Agenda Item details
"agendas": {
"type": "list",