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

[SDESK-7166] fix(ingest): Assignment ID lost when updating from PlanningML #1906

Merged
merged 2 commits into from
Feb 2, 2024
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
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)
Loading