diff --git a/parsons/action_builder/action_builder.py b/parsons/action_builder/action_builder.py index 43fd70f1a6..da6fb7c59b 100644 --- a/parsons/action_builder/action_builder.py +++ b/parsons/action_builder/action_builder.py @@ -289,6 +289,87 @@ def add_section_field_values_to_record( identifier=identifier, data=data, campaign=campaign ) + def remove_tagging( + self, + identifier=None, + tag_id=None, + tag_name=None, + tagging_id=None, + campaign=None, + ): + """ + Remove one or more tags (i.e. custom field value) from an existing entity or connection + record in Action Builder. The basis for this end point is the combination of the tag's + interact ID and that of the specific tagging. The tag ID can usually be determined from + the tag's name, and the tagging ID can be derived if the identifier of the entity or + connection record is supplied instead. + `Args:` + identifier: str + Optional. The unique identifier for an entity or connection record being updated. + If omitted, `tagging_id` must be provided. + tag_id: str + Optional. The unique identifier for the tag being removed. If omitted, `tag_name` + must be provided. + tag_name: str + Optional. The exact name of the tag being removed. May result in an error if + multiple tags (in different fields/sections) have the same name. If omitted, + `tag_id` must be provided. + tagging_id: str + Optional. The unique identifier for the specific application of the tag to an + individual entity or connection record. If omitted, `identifier` must be provided. + campaign: str + Optional. The 36-character "interact ID" of the campaign whose data is to be + retrieved or edited. Not necessary if supplied when instantiating the class. + `Returns:` + API response JSON which contains `{'message': 'Tag has been removed from Taggable + Logbook'}` if successful. + """ + + if {tag_name, tag_id} == {None}: + raise ValueError("Please supply a tag_name or tag_id!") + + if {identifier, tagging_id} == {None}: + raise ValueError( + "Please supply an entity or connection identifier, or a tagging id!" + ) + + campaign = self._campaign_check(campaign) + endpoint = "tags/{}/taggings" + + if tag_name and {tag_id, tagging_id} == {None}: + tag_data = self.get_tag_by_name(tag_name, campaign=campaign) + tag_count = tag_data.num_rows + + if tag_count > 1: + error_msg = f"Found {tag_count} tags with this name. " + error_msg += "Try with using the unique interact ID" + raise ValueError(error_msg) + + tag_id = tag_data["identifiers"][0][0].split(":")[1] + logger.info(f"Tag {tag_name} has ID {tag_id}") + + if tagging_id and not tag_id: + raise ValueError("Cannot search based on tagging ID alone.") + + if tag_id and not tagging_id: + taggings = self._get_all_records(self.campaign, endpoint.format(tag_id)) + taggings_filtered = taggings.select_rows( + lambda row: identifier + in row["_links"]["action_builder:connection"]["href"] + if row["item_type"] == "connection" + else identifier in row["osdi:person"]["href"] + ) + tagging_id = [ + x.split(":")[1] + for x in taggings_filtered["identifiers"][0] + if "action_builder" in x + ][0] + + logger.info(f"Removing tag {tag_id} from {identifier or tagging_id}") + return self.api.delete_request( + f"campaigns/{campaign}/{endpoint.format(tag_id)}/{tagging_id}" + ) + def upsert_connection(self, identifiers, tag_data=None, campaign=None): """ Load or update a connection record in Action Builder between two existing entity records. diff --git a/test/test_action_builder/test_action_builder.py b/test/test_action_builder/test_action_builder.py index 79b1ae89bf..57fcdfcb45 100644 --- a/test/test_action_builder/test_action_builder.py +++ b/test/test_action_builder/test_action_builder.py @@ -207,6 +207,12 @@ def setUp(self, m): f"action_builder:{self.fake_entity_id}" ] + self.fake_tag_id = "fake_tag_id" + self.fake_tagging_id = "fake_tagging_id" + self.fake_remove_tag_resp = { + "message": "Tag has been removed from Taggable Logbook" + } + self.fake_connection = {"person_id": "fake-entity-id-2"} @requests_mock.Mocker() @@ -362,6 +368,17 @@ def test_add_section_field_values_to_record(self, m): ) self.assertEqual(add_tags_response, self.fake_tagging) + @requests_mock.Mocker() + def test_remove_tagging(self, m): + m.delete( + f"{self.api_url}/tags/{self.fake_tag_id}/taggings/{self.fake_tagging_id}", + json=self.fake_remove_tag_resp, + ) + remove_tag_resp = self.bldr.remove_tagging( + tag_id=self.fake_tag_id, tagging_id=self.fake_tagging_id + ) + self.assertEqual(remove_tag_resp, self.fake_remove_tag_resp) + def connect_callback(self, request, context): # Internal method for returning constructed connection data to test