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

feat: add metadata entity transform for global value sets #3862

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
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