Skip to content

Commit

Permalink
feat: add metadata entity transform for global value sets
Browse files Browse the repository at this point in the history
  • Loading branch information
rossreicks committed Dec 17, 2024
1 parent 534210c commit f8c596d
Show file tree
Hide file tree
Showing 4 changed files with 586 additions and 216 deletions.
8 changes: 8 additions & 0 deletions cumulusci/cumulusci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ tasks:
class_path: cumulusci.tasks.metadata_etl.AddValueSetEntries
options:
namespace_inject: $project_config.project__package__namespace
entity: StandardValueSet
add_global_value_set_entries:
group: Metadata Transformations
description: Adds specified picklist entries to a Global Value Set.
class_path: cumulusci.tasks.metadata_etl.AddValueSetEntries
options:
namespace_inject: $project_config.project__package__namespace
entity: GlobalValueSet
add_picklist_entries:
group: Metadata Transformations
description: Adds specified picklist entries to a custom picklist field.
Expand Down
147 changes: 131 additions & 16 deletions cumulusci/tasks/metadata_etl/tests/test_value_sets.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from cumulusci.utils.xml import lxml_parse_string, metadata_tree

MD = "{%s}" % "http://soap.sforce.com/2006/04/metadata"
VALUESET_XML = b"""<?xml version="1.0" encoding="UTF-8"?>
STANDARD_VALUESET_XML = b"""<?xml version="1.0" encoding="UTF-8"?>
<StandardValueSet xmlns="http://soap.sforce.com/2006/04/metadata">
<sorted>false</sorted>
<standardValue>
Expand All @@ -28,29 +28,46 @@
</StandardValueSet>
"""

GLOBAL_VALUESET_XML = b"""<?xml version="1.0" encoding="UTF-8"?>
<GlobalValueSet xmlns="http://soap.sforce.com/2006/04/metadata">
<sorted>false</sorted>
<masterLabel>Test</masterLabel>
<customValue>
<fullName>Value</fullName>
<default>true</default>
<label>Value</label>
</customValue>
<customValue>
<fullName>Other</fullName>
<default>false</default>
<label>Other</label>
</customValue>
</GlobalValueSet>
"""

class TestAddValueSetEntries:
class TestAddValueSetEntriesForStandardValueSet:
def test_adds_entry(self):
task = create_task(
AddValueSetEntries,
{
"managed": True,
"api_version": "47.0",
"api_names": "bar,foo",
"entity": "StandardValueSet",
"entries": [
{"fullName": "Test", "label": "Label", "group": "Schedule"},
{"fullName": "Test_2", "label": "Label 2", "default": "true"},
],
},
)

tree = lxml_parse_string(VALUESET_XML)
tree = lxml_parse_string(STANDARD_VALUESET_XML)

assert len(tree.findall(f".//{MD}standardValue[{MD}fullName='Test']")) == 0
assert len(tree.findall(f".//{MD}standardValue[{MD}fullName='Test_2']")) == 0

result = task._transform_entity(
metadata_tree.fromstring(VALUESET_XML), "ValueSet"
metadata_tree.fromstring(STANDARD_VALUESET_XML), "ValueSet"
)

entry = result._element.findall(f".//{MD}standardValue[{MD}fullName='Test']")
Expand Down Expand Up @@ -91,10 +108,11 @@ def test_adds_entry__opportunitystage(self):
"probability": 100,
}
],
"entity": "StandardValueSet",
},
)

tree = metadata_tree.fromstring(VALUESET_XML)
tree = metadata_tree.fromstring(STANDARD_VALUESET_XML)

assert len(tree.findall(f".//{MD}standardValue[{MD}fullName='Test']")) == 0
assert len(tree.findall(f".//{MD}standardValue[{MD}fullName='Test_2']")) == 0
Expand Down Expand Up @@ -130,16 +148,17 @@ def test_adds_entry__casestatus(self):
"api_version": "47.0",
"api_names": "CaseStatus",
"entries": [{"fullName": "Test", "label": "Label", "closed": True}],
"entity": "StandardValueSet",
},
)

tree = lxml_parse_string(VALUESET_XML)
tree = lxml_parse_string(STANDARD_VALUESET_XML)

assert len(tree.findall(f".//{MD}standardValue[{MD}fullName='Test']")) == 0
assert len(tree.findall(f".//{MD}standardValue[{MD}fullName='Test_2']")) == 0

result = task._transform_entity(
metadata_tree.fromstring(VALUESET_XML), "CaseStatus"
metadata_tree.fromstring(STANDARD_VALUESET_XML), "CaseStatus"
)

entry = result._element.findall(f".//{MD}standardValue[{MD}fullName='Test']")
Expand All @@ -162,16 +181,17 @@ def test_adds_entry__leadStatus(self):
"api_version": "47.0",
"api_names": "LeadStatus",
"entries": [{"fullName": "Test", "label": "Label", "converted": True}],
"entity": "StandardValueSet",
},
)

tree = lxml_parse_string(VALUESET_XML)
tree = lxml_parse_string(STANDARD_VALUESET_XML)

assert len(tree.findall(f".//{MD}standardValue[{MD}fullName='Test']")) == 0
assert len(tree.findall(f".//{MD}standardValue[{MD}fullName='Test_2']")) == 0

result = task._transform_entity(
metadata_tree.fromstring(VALUESET_XML), "LeadStatus"
metadata_tree.fromstring(STANDARD_VALUESET_XML), "LeadStatus"
)

entry = result._element.findall(f".//{MD}standardValue[{MD}fullName='Test']")
Expand All @@ -194,14 +214,15 @@ def test_does_not_add_existing_entry(self):
"api_version": "47.0",
"api_names": "bar,foo",
"entries": [{"fullName": "Value", "label": "Label"}],
"entity": "StandardValueSet",
},
)

tree = lxml_parse_string(VALUESET_XML)
tree = lxml_parse_string(STANDARD_VALUESET_XML)

assert len(tree.findall(f".//{MD}standardValue[{MD}fullName='Value']")) == 1

metadata = metadata_tree.fromstring(VALUESET_XML)
metadata = metadata_tree.fromstring(STANDARD_VALUESET_XML)
task._transform_entity(metadata, "ValueSet")

assert len(tree.findall(f".//{MD}standardValue[{MD}fullName='Value']")) == 1
Expand All @@ -221,9 +242,10 @@ def test_raises_exception_missing_values(self, entry):
"api_version": "47.0",
"api_names": "bar,foo",
"entries": [entry],
"entity": "StandardValueSet",
},
)
tree = metadata_tree.fromstring(VALUESET_XML)
tree = metadata_tree.fromstring(STANDARD_VALUESET_XML)

with pytest.raises(TaskOptionsError, match=FULL_NAME_AND_LABEL_ERR):
task._transform_entity(tree, "ValueSet")
Expand All @@ -236,9 +258,10 @@ def test_raises_exception_missing_values__opportunitystage(self):
"api_version": "47.0",
"api_names": "OpportunityStage",
"entries": [{"fullName": "Value", "label": "Value"}],
"entity": "StandardValueSet",
},
)
tree = metadata_tree.fromstring(VALUESET_XML)
tree = metadata_tree.fromstring(STANDARD_VALUESET_XML)

with pytest.raises(TaskOptionsError, match=OPP_STAGE_ERR):
task._transform_entity(tree, "OpportunityStage")
Expand All @@ -251,9 +274,10 @@ def test_raises_exception_missing_values__casestatus(self):
"api_version": "47.0",
"api_names": "CaseStatus",
"entries": [{"fullName": "Value", "label": "Value"}],
"entity": "StandardValueSet",
},
)
tree = metadata_tree.fromstring(VALUESET_XML)
tree = metadata_tree.fromstring(STANDARD_VALUESET_XML)

with pytest.raises(TaskOptionsError, match=CASE_STATUS_ERR):
task._transform_entity(tree, "CaseStatus")
Expand All @@ -265,10 +289,11 @@ def test_raises_exception_missing_values__leadstatus(self):
"managed": True,
"api_version": "47.0",
"api_names": "LeadStatus",
"entity": "StandardValueSet",
"entries": [{"fullName": "Value", "label": "Value"}],
},
)
tree = metadata_tree.fromstring(VALUESET_XML)
tree = metadata_tree.fromstring(STANDARD_VALUESET_XML)

with pytest.raises(TaskOptionsError, match=LEAD_STATUS_ERR):
task._transform_entity(tree, "LeadStatus")
Expand All @@ -280,6 +305,7 @@ def test_adds_correct_number_of_values(self):
"managed": True,
"api_version": "47.0",
"api_names": "bar,foo",
"entity": "StandardValueSet",
"entries": [
{"fullName": "Test", "label": "Label"},
{"fullName": "Test_2", "label": "Label 2"},
Expand All @@ -288,11 +314,100 @@ def test_adds_correct_number_of_values(self):
},
)

mdtree = metadata_tree.fromstring(VALUESET_XML)
mdtree = metadata_tree.fromstring(STANDARD_VALUESET_XML)
xml_tree = mdtree._element

assert len(xml_tree.findall(f".//{MD}standardValue")) == 2

task._transform_entity(mdtree, "ValueSet")

assert len(xml_tree.findall(f".//{MD}standardValue")) == 4

class TestAddValueSetEntriesForGlobalValueSet:
def test_adds_entry(self):
task = create_task(
AddValueSetEntries,
{
"managed": True,
"api_version": "47.0",
"api_names": "bar,foo",
"entity": "GlobalValueSet",
"entries": [
{"fullName": "Test", "label": "Label", "group": "Schedule"},
{"fullName": "Test_2", "label": "Label 2", "default": "true"},
],
},
)

tree = lxml_parse_string(GLOBAL_VALUESET_XML)

assert len(tree.findall(f".//{MD}customValue[{MD}fullName='Test']")) == 0
assert len(tree.findall(f".//{MD}customValue[{MD}fullName='Test_2']")) == 0

result = task._transform_entity(
metadata_tree.fromstring(GLOBAL_VALUESET_XML), "GlobalValueSet"
)

entry = result._element.findall(f".//{MD}customValue[{MD}fullName='Test']")
assert len(entry) == 1
label = entry[0].findall(f".//{MD}label")
assert len(label) == 1
assert label[0].text == "Label"
default = entry[0].findall(f".//{MD}default")
assert len(default) == 1
assert default[0].text == "false"

entry = result._element.findall(f".//{MD}customValue[{MD}fullName='Test_2']")
assert len(entry) == 1
label = entry[0].findall(f".//{MD}label")
assert len(label) == 1
assert label[0].text == "Label 2"
default = entry[0].findall(f".//{MD}default")
assert len(default) == 1
assert default[0].text == "false"

def test_adds_correct_number_of_values(self):
task = create_task(
AddValueSetEntries,
{
"managed": True,
"api_version": "47.0",
"api_names": "bar,foo",
"entity": "GlobalValueSet",
"entries": [
{"fullName": "Test", "label": "Label"},
{"fullName": "Test_2", "label": "Label 2"},
{"fullName": "Other", "label": "Duplicate"},
],
},
)

mdtree = metadata_tree.fromstring(GLOBAL_VALUESET_XML)
xml_tree = mdtree._element

assert len(xml_tree.findall(f".//{MD}customValue")) == 2

task._transform_entity(mdtree, "GlobalValueSet")

assert len(xml_tree.findall(f".//{MD}customValue")) == 4

def test_does_not_add_existing_entry(self):
task = create_task(
AddValueSetEntries,
{
"managed": True,
"api_version": "47.0",
"api_names": "bar,foo",
"entries": [{"fullName": "Value", "label": "Label"}],
"entity": "GlobalValueSet",
},
)

tree = lxml_parse_string(GLOBAL_VALUESET_XML)

assert len(tree.findall(f".//{MD}customValue[{MD}fullName='Value']")) == 1

metadata = metadata_tree.fromstring(GLOBAL_VALUESET_XML)
task._transform_entity(metadata, "GlobalValueSet")

assert len(tree.findall(f".//{MD}customValue[{MD}fullName='Value']")) == 1
51 changes: 49 additions & 2 deletions cumulusci/tasks/metadata_etl/value_sets.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class AddValueSetEntries(MetadataSingleEntityTransformTask):
Example Usage
-----------------------
Add entries to a Standard Value Set.
.. code-block:: yaml
task: add_standard_value_set_entries
Expand All @@ -32,9 +32,20 @@ class AddValueSetEntries(MetadataSingleEntityTransformTask):
ui_options:
name: Add values to Case Origin picklist
Add entries to a Global Value Set.
.. code-block:: yaml
task: add_global_value_set_entries
options:
api_names: CaseOrigin
entries:
- fullName: New Account
label: New Account
- fullName: Questionable Contact
label: Questionable Contact
"""

entity = "StandardValueSet"
entity = None
task_options = {
**MetadataSingleEntityTransformTask.task_options,
"entries": {
Expand All @@ -50,9 +61,26 @@ class AddValueSetEntries(MetadataSingleEntityTransformTask):
"such as 'OpportunityStage', 'AccountType', 'CaseStatus', 'LeadStatus'",
"required": True,
},
"entity": {
"description": "The type of value set to affect. Defaults to 'StandardValueSet'.",
"required": False,
},
}

def _init_options(self, kwargs):
super()._init_options(kwargs)

self.entity = self.options.get("entity", "StandardValueSet")

def _transform_entity(self, metadata: MetadataElement, api_name: str):
if self.entity == "StandardValueSet":
return self._transform_standard_value_set(metadata, api_name)
elif self.entity == "GlobalValueSet":
return self._transform_global_value_set(metadata, api_name)
else:
raise TaskOptionsError(f"Invalid entity type: {self.entity}")

def _transform_standard_value_set(self, metadata: MetadataElement, api_name: str):
for entry in self.options.get("entries", []):
if "fullName" not in entry or "label" not in entry:
raise TaskOptionsError(FULL_NAME_AND_LABEL_ERR)
Expand Down Expand Up @@ -103,4 +131,23 @@ def _transform_entity(self, metadata: MetadataElement, api_name: str):
for entry_key in entry:
if entry_key not in ["fullName", "label", "default"]:
elem.append(entry_key, str(entry[entry_key]))

return metadata

def _transform_global_value_set(self, metadata: MetadataElement, api_name: str):
for entry in self.options.get("entries", []):
if "fullName" not in entry or "label" not in entry:
raise TaskOptionsError(FULL_NAME_AND_LABEL_ERR)

existing_entry = metadata.findall(
"customValue", fullName=entry["fullName"]
)

if not existing_entry:
# Entry doesn't exist. Insert it.
elem = metadata.append(tag="customValue")
elem.append("fullName", text=entry["fullName"])
elem.append("label", text=entry["label"])
elem.append("default", text="false")

return metadata
Loading

0 comments on commit f8c596d

Please sign in to comment.