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

[STTNHUB-374] Unlink content on Event/Planning sttinstruct:remove signal #118

Merged
merged 5 commits into from
Nov 8, 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
125 changes: 125 additions & 0 deletions server/features/ingest_planning.feature
Original file line number Diff line number Diff line change
Expand Up @@ -495,3 +495,128 @@ Feature: Ingest STT Planning items
}
]}
"""

@auth
@stt_cvs
@stt_providers
Scenario: Unlinks content from planning on remove signal
When we fetch from "STTNewsML" ingest "stt_newsml_link_content.xml" using routing_scheme
"""
#routing_schemes._id#
"""
When we fetch from "STTPlanningML" ingest "planning_ml_link_content.xml"
When we get "/assignments"
Then we get list with 1 items
Then we store "assignment" with first item
When we get "/planning"
Then we get list with 1 items
"""
{"_items": [{
"_id": "urn:newsml:stt.fi:437036",
"coverages": [{
"coverage_id": "ID_TEXT_120123822",
"workflow_status": "active",
"assigned_to": {
"assignment_id": "#assignment._id#",
"desk": "#desks._id#",
"user": null,
"state": "completed",
"priority": 6
}
}]
}]}
"""
When we get "published"
Then we get list with 1 items
"""
{"_items": [{
"uri": "urn:newsml:stt.fi:101801633",
"assignment_id": "#assignment._id#"
}]}
"""
When we fetch from "STTPlanningML" ingest "planning_ml_link_content_delete.xml"
When we get "/assignments"
Then we get list with 0 items
When we get "/planning"
Then we get list with 1 items
"""
{"_items": [{
"_id": "urn:newsml:stt.fi:437036",
"coverages": [{
"coverage_id": "ID_TEXT_120123822",
"assigned_to": "__empty__",
"workflow_status": "draft"
}]
}]}
"""
When we get "published"
Then we get list with 1 items
"""
{"_items": [{
"uri": "urn:newsml:stt.fi:101801633",
"assignment_id": null
}]}
"""

@auth
@stt_cvs
@stt_providers
Scenario: Unlinks content from event on remove signal
When we fetch from "STTNewsML" ingest "stt_newsml_link_content.xml" using routing_scheme
"""
#routing_schemes._id#
"""
When we fetch from "STTEventsML" ingest "events_ml_259431.xml"
When we fetch from "STTPlanningML" ingest "planning_ml_437036_link_content_and_event.xml"
When we get "/assignments"
Then we get list with 1 items
Then we store "assignment" with first item
When we get "/planning"
Then we get list with 1 items
"""
{"_items": [{
"_id": "urn:newsml:stt.fi:437036",
"coverages": [{
"coverage_id": "ID_TEXT_120123822",
"workflow_status": "active",
"assigned_to": {
"assignment_id": "#assignment._id#",
"desk": "#desks._id#",
"user": null,
"state": "completed",
"priority": 6
}
}]
}]}
"""
When we get "published"
Then we get list with 1 items
"""
{"_items": [{
"uri": "urn:newsml:stt.fi:101801633",
"assignment_id": "#assignment._id#"
}]}
"""
When we fetch from "STTEventsML" ingest "events_ml_259431_delete.xml"
When we get "/assignments"
Then we get list with 0 items
When we get "/planning"
Then we get list with 1 items
"""
{"_items": [{
"_id": "urn:newsml:stt.fi:437036",
"coverages": [{
"coverage_id": "ID_TEXT_120123822",
"assigned_to": "__empty__",
"workflow_status": "draft"
}]
}]}
"""
When we get "published"
Then we get list with 1 items
"""
{"_items": [{
"uri": "urn:newsml:stt.fi:101801633",
"assignment_id": null
}]}
"""
83 changes: 79 additions & 4 deletions server/stt/common.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
from typing import Dict, Any
from typing import Dict, Any, Union
import logging
from copy import deepcopy

from xml.etree.ElementTree import Element
from eve.utils import config

from superdesk import get_resource_service
from superdesk.metadata.item import ITEM_TYPE, ITEM_STATE
from planning.common import WORKFLOW_STATE, POST_STATE, update_post_item
from planning.common import WORKFLOW_STATE, POST_STATE, update_post_item, update_assignment_on_link_unlink


logger = logging.getLogger(__name__)


def planning_xml_contains_remove_signal(xml: Element) -> bool:
Expand All @@ -17,9 +22,19 @@ def planning_xml_contains_remove_signal(xml: Element) -> bool:
return False


def unpost_or_spike_event_or_planning(item: Dict[str, Any]):
def unpost_or_spike_event_or_planning(item: Dict[str, Any]) -> None:
item_resource = "events" if item.get(ITEM_TYPE) == "event" else "planning"
original: Dict[str, Any] = get_resource_service(item_resource).find_one(req=None, _id=item["guid"]) or {}
original: Union[Dict[str, Any], None] = get_resource_service(item_resource).find_one(req=None, _id=item["guid"])

if not original:
logger.error("Failed to spike/cancel ingested item: item not found", extra={"item_id": item["guid"]})
return

# Wrap ``unlink_item_from_all_content`` in a try...except, so if it fails the item is still spiked/cancelled
try:
unlink_item_from_all_content(original)
except Exception:
logger.exception("Failed to unlink content from item", extra={"item_id": item["guid"]})

if not original.get("pubstatus") and original.get(ITEM_STATE) in [
WORKFLOW_STATE.INGESTED,
Expand All @@ -32,6 +47,66 @@ def unpost_or_spike_event_or_planning(item: Dict[str, Any]):
update_post_item({"pubstatus": POST_STATE.CANCELLED, "_etag": original["_etag"]}, original)


def unlink_item_from_all_content(item: Dict[str, Any]) -> None:
"""Attempts to unlink all content/assignments from the provided item

Performs the following actions:
* If this is an Event, re-runs this function with any linked Planning items
* Removes ``assignment_id`` from content linked to this item, using 'archived', 'published' or 'archive' collection
* Deletes all items in ``delivery`` collection, that match any coverage in the Planning item
* Deletes all items in ``assignments`` collection, that match the Planning item's ID
* Updates the Planning item's coverages, to remove ``assigned_to`` field and set ``workflow_status`` to ``DRAFT``

The above actions are performed directly to avoid validation logic in the Planning module. As some of the services,
such as Assignments service, assumes an unlink is being performed from the front-end and not via ingest.
So instead we directly delete the items from their respective collections.
"""

item_id = item["_id"]
planning_service = get_resource_service("planning")
if item.get(ITEM_TYPE) == "event":
for planning_item in planning_service.find(where={"event_item": item_id}):
unlink_item_from_all_content(planning_item)
else:
delivery_service = get_resource_service("delivery")
archive_service = get_resource_service("search")

coverages = deepcopy(item.get("coverages") or [])
if not len(coverages):
# No coverages on this Planning item, no need to continue
return

for coverage in coverages:
# Remove assignee information and set state to DRAFT
coverage.pop("assigned_to", None)
coverage["workflow_status"] = WORKFLOW_STATE.DRAFT

for content_link in delivery_service.find(where={"coverage_id": coverage["coverage_id"]}):
content_id = content_link.get("item_id")
if not content_id:
# Content ID not on this delivery, no need to unlink
continue

content_item = archive_service.find_one(req=None, _id=content_id)
if not content_item or not content_item.get("assignment_id"):
# Either content not found, or does not contain the ``assignment_id``
# Nothing to do for this one
continue

# Update the content item to remove the ``assignment_id``
update_assignment_on_link_unlink(None, content_item)

# Delete all delivery entries for this Planning item
delivery_service.delete_action(lookup={"planning_id": item_id})

# Delete all assignments for this Planning item directly
# Note: skips ``on_delete`` and ``on_deleted`` hooks, due to validation issues
get_resource_service("assignments").delete(lookup={"planning_item": item_id})

# Update the Planning item, to update its coverage assignee and workflow_status
planning_service.system_update(item_id, {"coverages": coverages}, item)


def remove_date_portion_from_id(item_id: str) -> str:
"""Removes the date portion from an ingested Event or Planning ID

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<planningItem
xmlns="http://iptc.org/std/nar/2006-10-01/"
xmlns:stt="http://www.stt-lehtikuva.fi/NewsML"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://iptc.org/std/nar/2006-10-01/ http://www.iptc.org/std/NewsML-G2/2.12/specification/NewsML-G2_2.12-spec-All-Power.xsd http://www.stt-lehtikuva.fi/NewsML http://www.stt-lehtikuva.fi/newsml/schema/STT-Lehtikuva_NewsML_G2.xsd"
guid="urn:newsml:stt.fi:20220330:437036"
version="2"
standard="NewsML-G2"
standardversion="2.12"
conformance="power"
xml:lang="fi"
>
<catalogRef href="http://www.iptc.org/std/catalog/catalog.IPTC-G2-Standards_18.xml"/>
<catalogRef href="http://www.stt-lehtikuva.fi/newsml/doc/stt-NewsCodesCatalog_1.xml"/>
<itemMeta>
<itemClass qcode="plinat:newscoverage"/>
<provider literal="STT"/>
<versionCreated>2022-03-22T14:54:59+02:00</versionCreated>
<pubStatus qcode="stat:usable"/>
<edNote role="sttdescription:additionalinfo">Miten taistelut etenevät? Millaisia kansainvälisiä reaktioita syntyy? Entä miten tilanne Venäjällä elää? Seuraamme päivän tapahtumia ja tarkennamme paketointia.</edNote>
</itemMeta>
<contentMeta>
<contentCreated>2022-03-22T14:54:59+02:00</contentCreated>
<contentModified>2022-03-22T14:54:59+02:00</contentModified>
<headline>Miten tilanne Ukrainan sodan ympärillä ja Ukrainassa kehittyy?</headline>
<description role="drol:summary"/>
<subject qcode="stt-topics:437036">
<related rel="sttrel:assigneddate" value="2022-03-30" valuedatatype="Date"/>
</subject>
<subject type="cpnat:department" qcode="sttdepartment:14">
<name>Ulkomaat</name>
</subject>
</contentMeta>
<assert qcode="stt-topics:437036">
<newsCoverageStatus qcode="ncostat:int"/>
</assert>
<newsCoverageSet>
<newsCoverage id="ID_EVENT_259431" modified="2022-03-30T09:25:05+02:00">
<planning>
<g2contentType>application/vnd.iptc.g2.newsitem+xml</g2contentType>
<itemClass qcode="ninat:text"/>
<headline>SDP:n puoluevaltuuston kokous</headline>
<description>Puoluevaltuuston kokouksen avaa klo 13.00 puoluevaltuuston puheenjohtaja, kuntaministeri Sirpa Paatero. Poliittisen tilannekatsauksen pitää SDP:n puheenjohtaja, pääministeri Sanna Marin. Tämän jälkeen kokous jatkuu suljettuna.</description>
<subject type="cpnat:event" qcode="urn:newsml:stt.fi:20220402:259431">
<name>SDP:n puoluevaltuuston kokous</name>
</subject>
</planning>
</newsCoverage>
<newsCoverage id="ID_TEXT_120123822">
<planning>
<g2contentType>application/vnd.iptc.g2.newsitem+xml</g2contentType>
<itemClass qcode="ninat:text"/>
<headline>UKRAINA // Yön seurantaa</headline>
<description/>
<subject qcode="stt-topics:437036" type="cpnat:abstract"/>
<genre qcode="sttgenre:1">
<name>Pääjuttu</name>
<narrower qcode="sttversion:3"/>
<narrower qcode="sttversion:4"/>
<narrower qcode="sttversion:6"/>
<narrower qcode="sttversion:7"/>
<related rel="sttrel:plannedlength" value="3150"/>
</genre>
</planning>
<delivery>
<deliveredItemRef guidref="urn:newsml:stt.fi:20171219:101801633">
<modified>2022-03-30T09:15:40+02:00</modified>
<version/>
</deliveredItemRef>
</delivery>
</newsCoverage>
<newsCoverage id="ID_WORKREQUEST_159700">
<planning>
<g2contentType>application/vnd.iptc.g2.newsitem+xml</g2contentType>
<itemClass qcode="ninat:picture"/>
<headline>Miten tilanne Ukrainan sodan ympärillä ja Ukrainassa kehittyy?</headline>
<subject type="ninat:text" qcode="urn:newsml:stt.fi:20220330000000:159700">
<stt:workstartdate>2022-03-30T00:00:00+02:00</stt:workstartdate>
<stt:workenddate>2022-03-30T00:00:00+02:00</stt:workenddate>
</subject>
<dateline>Ukraina</dateline>
<genre qcode="sttimage:27">
<name>Kv. kuvaa</name>
</genre>
</planning>
</newsCoverage>
</newsCoverageSet>
</planningItem>
Loading
Loading