Skip to content

Commit

Permalink
[SDESK-7166] fix(ingest): Assignment ID lost when updating from Plann…
Browse files Browse the repository at this point in the history
…ingML (#1906)

* [SDESK-7166] fix(ingest): Assignment ID lost when updating from PlanningML
  • Loading branch information
MarkLark86 authored Feb 2, 2024
1 parent 582acf7 commit 234037f
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 31 deletions.
11 changes: 10 additions & 1 deletion server/planning/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, Tuple
from typing import NamedTuple, Dict, Any, Set, Optional

import re
import time
Expand All @@ -30,6 +30,8 @@
import json
from bson import ObjectId

from planning.types import Planning, Coverage

ITEM_STATE = "state"
ITEM_EXPIRY = "expiry"

Expand Down Expand Up @@ -838,3 +840,10 @@ def update_ingest_on_patch(updates: Dict[str, Any], original: Dict[str, Any]):
# The local version has been published
# and no change to ``pubstatus`` on ingested item
updates.pop("state")


def get_coverage_from_planning(planning_item: Planning, coverage_id: str) -> Optional[Coverage]:
return next(
(coverage for coverage in planning_item.get("coverages") or [] if coverage.get("coverage_id") == coverage_id),
None,
)
95 changes: 65 additions & 30 deletions server/planning/feed_parsers/superdesk_planning_xml.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from typing import Optional
import logging
from eve.utils import config
from flask import current_app as app

from superdesk import get_resource_service
from superdesk.io.feed_parsers import NewsMLTwoFeedParser
import pytz
from xml.etree.ElementTree import Element
Expand All @@ -14,8 +16,10 @@
from superdesk.utc import utcnow, utc_to_local
from superdesk.errors import ParserError

from planning.types import Planning
from planning.common import (
get_coverage_status_from_cv,
get_coverage_from_planning,
get_default_coverage_status_qcode_on_ingest,
WORKFLOW_STATE,
POST_STATE,
Expand Down Expand Up @@ -78,30 +82,35 @@ def get_item_id(self, tree: Element) -> str:
def parse(self, tree: Element, provider=None):
self.root = tree
self.set_missing_voc_policy()
planning_service = get_resource_service("planning")

try:
guid = self.get_item_id(tree)
original: Optional[Planning] = planning_service.find_one(req=None, _id=guid)
item = self.parse_item(tree, original)
return [item] if item is not None else []
except Exception as ex:
raise ParserError.parseMessageError(ex, provider)

item = {
GUID_FIELD: guid,
ITEM_TYPE: CONTENT_TYPE.PLANNING,
"state": CONTENT_STATE.INGESTED,
"_id": guid,
}
def parse_item(self, tree: Element, original: Optional[Planning]) -> Optional[Planning]:
guid = (original or {}).get("_id") or self.get_item_id(tree)
item = {
GUID_FIELD: guid,
ITEM_TYPE: CONTENT_TYPE.PLANNING,
"state": CONTENT_STATE.INGESTED,
"_id": guid,
}

self.parse_item_meta(tree, item)
self.parse_content_meta(tree, item)
self.parse_news_coverage_set(tree, item)
self.parse_news_coverage_status(tree, item)
self.parse_item_meta(tree, item)
self.parse_content_meta(tree, item)
self.parse_news_coverage_set(tree, item, original)
self.parse_news_coverage_status(tree, item)

upgrade_rich_text_fields(item, "planning")
for coverage in item.get("coverages") or []:
upgrade_rich_text_fields(coverage.get("planning") or {}, "coverage")
upgrade_rich_text_fields(item, "planning")
for coverage in item.get("coverages") or []:
upgrade_rich_text_fields(coverage.get("planning") or {}, "coverage")

return [item]

except Exception as ex:
raise ParserError.parseMessageError(ex, provider)
return item

def parse_item_meta(self, tree, item):
"""Parse itemMeta tag
Expand Down Expand Up @@ -175,17 +184,26 @@ def parse_news_coverage_status(self, tree, item):

# Get the ``news_coverage_status.qcode`` to use
assert_el = tree.find(self.qname("assert"))
status_qcode = get_default_coverage_status_qcode_on_ingest()
ingest_status_qcode = None
if assert_el is not None:
news_coverage_status = assert_el.find(self.qname("newsCoverageStatus")).get("qcode")
if news_coverage_status:
status_qcode = news_coverage_status
ingest_status_qcode = news_coverage_status

# Now assign the ``news_coverage_status`` to all coverages of this Planning item
coverage_status = get_coverage_status_from_cv(status_qcode)
default_status_qcode = get_default_coverage_status_qcode_on_ingest()
default_coverage_status = get_coverage_status_from_cv(default_status_qcode)
default_coverage_status.pop("is_active", None)

coverage_status = get_coverage_status_from_cv(ingest_status_qcode or default_status_qcode)
coverage_status.pop("is_active", None)
for coverage in item["coverages"]:
coverage["news_coverage_status"] = coverage_status
if ingest_status_qcode is not None:
# Status was provided by ingest, use that
coverage["news_coverage_status"] = coverage_status
elif coverage.get("news_coverage_status") is None:
# Status wasn't provided in ingest, and coverage has no current value, use default instead
coverage["news_coverage_status"] = default_coverage_status

def parse_genre(self, planning_elt, planning):
"""Parse Genre tag
Expand Down Expand Up @@ -225,38 +243,55 @@ def parse_coverage_planning(self, news_coverage_elt, item):

return None

def get_coverage_details(self, news_coverage_elt, item):
def get_coverage_details(self, news_coverage_elt: Element, item: Planning, original: Optional[Planning]):
"""Process the Coverage element and optionally return the coverage details
If ``None`` is returned, this coverage is not added to the Planning item
"""

coverage_id = news_coverage_elt.get("id")
planning = self.parse_coverage_planning(news_coverage_elt, item)
if coverage_id is None:
logger.warning("Unable to process coverage details, no coverage id found in ingest source")
return None

planning = self.parse_coverage_planning(news_coverage_elt, item)
if planning is None:
logger.warning(f"Failed to process coverage '{coverage_id}', planning details not found")
return None

modified = news_coverage_elt.get("modified")
return {
coverage_details = {
"coverage_id": coverage_id,
"workflow_status": WORKFLOW_STATE.DRAFT,
"firstcreated": item["firstcreated"],
"versioncreated": self.datetime(modified) if modified else item["firstcreated"],
"planning": planning,
}

def parse_news_coverage_set(self, tree, item):
"""Parse newsCoverageSet tag
original_coverage = get_coverage_from_planning(original, coverage_id) if original else None
if original_coverage is not None:
direct_copy_fields = {
"workflow_status",
"news_coverage_status",
"previous_status",
"assigned_to",
"flags",
"original_creator",
"version_creator",
}
for field in direct_copy_fields:
if field in original_coverage:
coverage_details[field] = original_coverage[field]

return coverage_details

def parse_news_coverage_set(self, tree: Element, item: Planning, original: Optional[Planning]):
"""Parse newsCoverageSet tag"""

:param tree: tree
:param item: planning item
"""
item["coverages"] = []
news_coverage_set = tree.find(self.qname("newsCoverageSet"))
if news_coverage_set is not None:
for news_coverage_elt in news_coverage_set.findall(self.qname("newsCoverage")):
coverage = self.get_coverage_details(news_coverage_elt, item)
coverage = self.get_coverage_details(news_coverage_elt, item, original)
if coverage is not None:
item["coverages"].append(coverage)

0 comments on commit 234037f

Please sign in to comment.