cumulusci-plus 5.0.35__py3-none-any.whl → 5.0.45__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cumulusci/__about__.py +1 -1
- cumulusci/cli/cci.py +3 -2
- cumulusci/cli/task.py +9 -10
- cumulusci/cli/tests/test_org.py +5 -0
- cumulusci/cli/tests/test_task.py +34 -0
- cumulusci/core/config/__init__.py +1 -0
- cumulusci/core/config/org_config.py +2 -1
- cumulusci/core/config/project_config.py +12 -0
- cumulusci/core/config/scratch_org_config.py +12 -0
- cumulusci/core/config/sfdx_org_config.py +4 -1
- cumulusci/core/config/tests/test_config.py +1 -0
- cumulusci/core/dependencies/base.py +4 -0
- cumulusci/cumulusci.yml +18 -1
- cumulusci/schema/cumulusci.jsonschema.json +5 -0
- cumulusci/tasks/apex/testrunner.py +7 -4
- cumulusci/tasks/bulkdata/tests/test_select_utils.py +20 -0
- cumulusci/tasks/metadata_etl/__init__.py +2 -0
- cumulusci/tasks/metadata_etl/applications.py +256 -0
- cumulusci/tasks/metadata_etl/tests/test_applications.py +710 -0
- cumulusci/tasks/salesforce/insert_record.py +18 -19
- cumulusci/tasks/salesforce/tests/test_enable_prediction.py +4 -2
- cumulusci/tasks/salesforce/tests/test_update_external_auth_identity_provider.py +927 -0
- cumulusci/tasks/salesforce/tests/test_update_external_credential.py +523 -8
- cumulusci/tasks/salesforce/tests/test_update_record.py +512 -0
- cumulusci/tasks/salesforce/update_external_auth_identity_provider.py +551 -0
- cumulusci/tasks/salesforce/update_external_credential.py +89 -4
- cumulusci/tasks/salesforce/update_record.py +217 -0
- cumulusci/tasks/sfdmu/sfdmu.py +14 -1
- cumulusci/tasks/utility/credentialManager.py +58 -12
- cumulusci/tasks/utility/secretsToEnv.py +42 -11
- cumulusci/tasks/utility/tests/test_credentialManager.py +586 -0
- cumulusci/tasks/utility/tests/test_secretsToEnv.py +1240 -62
- cumulusci/utils/yaml/cumulusci_yml.py +1 -0
- {cumulusci_plus-5.0.35.dist-info → cumulusci_plus-5.0.45.dist-info}/METADATA +5 -7
- {cumulusci_plus-5.0.35.dist-info → cumulusci_plus-5.0.45.dist-info}/RECORD +39 -33
- {cumulusci_plus-5.0.35.dist-info → cumulusci_plus-5.0.45.dist-info}/WHEEL +1 -1
- {cumulusci_plus-5.0.35.dist-info → cumulusci_plus-5.0.45.dist-info}/entry_points.txt +0 -0
- {cumulusci_plus-5.0.35.dist-info → cumulusci_plus-5.0.45.dist-info}/licenses/AUTHORS.rst +0 -0
- {cumulusci_plus-5.0.35.dist-info → cumulusci_plus-5.0.45.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
from typing import List, Optional, Set
|
|
2
|
+
from urllib.parse import quote
|
|
3
|
+
|
|
4
|
+
from pydantic.v1 import BaseModel
|
|
5
|
+
|
|
6
|
+
from cumulusci.tasks.metadata_etl import MetadataSingleEntityTransformTask
|
|
7
|
+
from cumulusci.utils import inject_namespace
|
|
8
|
+
from cumulusci.utils.xml.metadata_tree import MetadataElement
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _inject_namespace(text: str, options: dict) -> str:
|
|
12
|
+
return inject_namespace(
|
|
13
|
+
"",
|
|
14
|
+
text,
|
|
15
|
+
namespace=options["namespace_inject"]
|
|
16
|
+
if not options.get("namespaced_org")
|
|
17
|
+
else "",
|
|
18
|
+
managed=options.get("managed") or False,
|
|
19
|
+
namespaced_org=options.get("namespaced_org"),
|
|
20
|
+
)[1]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ProfileActionOverrideOptions(BaseModel):
|
|
24
|
+
"""Options for a single profileActionOverride"""
|
|
25
|
+
|
|
26
|
+
action_name: str
|
|
27
|
+
content: str
|
|
28
|
+
form_factor: str
|
|
29
|
+
page_or_sobject_type: str
|
|
30
|
+
record_type: Optional[str]
|
|
31
|
+
type: str
|
|
32
|
+
profile: str
|
|
33
|
+
options: dict = {}
|
|
34
|
+
|
|
35
|
+
def namespace_inject(self, field_name: str) -> None:
|
|
36
|
+
setattr(
|
|
37
|
+
self, field_name, _inject_namespace(getattr(self, field_name), self.options)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AddProfileActionOverridesOptions(BaseModel):
|
|
42
|
+
"""Options container for profile action overrides"""
|
|
43
|
+
|
|
44
|
+
name: str
|
|
45
|
+
overrides: List[ProfileActionOverrideOptions]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class AddProfileActionOverrides(MetadataSingleEntityTransformTask):
|
|
49
|
+
"""
|
|
50
|
+
Inserts or updates profileActionOverrides in CustomApplication metadata.
|
|
51
|
+
If a profileActionOverride with the same actionName, pageOrSobjectType,
|
|
52
|
+
recordType, and profile already exists, it will be updated with a warning.
|
|
53
|
+
Otherwise, a new override will be added.
|
|
54
|
+
Task option details:
|
|
55
|
+
- applications: List of CustomApplications to modify
|
|
56
|
+
- name: API name of the CustomApplication to modify
|
|
57
|
+
- overrides: List of profile action overrides to add/update
|
|
58
|
+
- action_name: Action name (e.g., "View", "Edit", "New")
|
|
59
|
+
- content: Content reference (page/component API name)
|
|
60
|
+
- form_factor: Form factor (e.g., "Large", "Small")
|
|
61
|
+
- page_or_sobject_type: Page or SObject type
|
|
62
|
+
- record_type: Record type API name (optional)
|
|
63
|
+
- type: Override type (e.g., "Flexipage", "Visualforce", "LightningComponent")
|
|
64
|
+
- profile: Profile name or API name
|
|
65
|
+
Example Usage
|
|
66
|
+
-----------------------
|
|
67
|
+
.. code-block:: yaml
|
|
68
|
+
task: add_profile_action_overrides
|
|
69
|
+
options:
|
|
70
|
+
applications:
|
|
71
|
+
- name: "%%%NAMESPACE%%%CustomApplicationConsole"
|
|
72
|
+
overrides:
|
|
73
|
+
- action_name: View
|
|
74
|
+
content: "%%%NAMESPACED_ORG%%%AccountUserRecordPage"
|
|
75
|
+
form_factor: Large
|
|
76
|
+
page_or_sobject_type: Account
|
|
77
|
+
record_type: PersonAccount.User
|
|
78
|
+
type: Flexipage
|
|
79
|
+
profile: Admin
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
entity = "CustomApplication"
|
|
83
|
+
task_options = {
|
|
84
|
+
"applications": {
|
|
85
|
+
"description": "List of CustomApplications to modify. See task info for structure.",
|
|
86
|
+
"required": True,
|
|
87
|
+
},
|
|
88
|
+
**MetadataSingleEntityTransformTask.task_options,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
def _init_options(self, kwargs):
|
|
92
|
+
super()._init_options(kwargs)
|
|
93
|
+
|
|
94
|
+
# Validate options using Pydantic
|
|
95
|
+
self._validated_options: List[AddProfileActionOverridesOptions] = []
|
|
96
|
+
for application in self.options.get("applications"):
|
|
97
|
+
validated_options = AddProfileActionOverridesOptions(
|
|
98
|
+
name=quote(_inject_namespace(application.get("name"), self.options)),
|
|
99
|
+
overrides=application.get("overrides"),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
self._validated_options.append(validated_options)
|
|
103
|
+
|
|
104
|
+
self.api_names: Set[str] = set(
|
|
105
|
+
application.name for application in self._validated_options
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def _transform_entity(
|
|
109
|
+
self, metadata: MetadataElement, api_name: str
|
|
110
|
+
) -> Optional[MetadataElement]:
|
|
111
|
+
|
|
112
|
+
if not self._validated_options:
|
|
113
|
+
self.logger.warning("No applications to add profile action overrides for")
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
for application in self._validated_options:
|
|
117
|
+
if application.name != api_name:
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
for override_config in application.overrides:
|
|
121
|
+
self._add_or_update_override(
|
|
122
|
+
metadata, application.name, override_config
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return metadata
|
|
126
|
+
|
|
127
|
+
def _add_or_update_override(self, metadata, api_name, override_config):
|
|
128
|
+
"""Add or update a single profileActionOverride"""
|
|
129
|
+
override_config.options = self.options
|
|
130
|
+
|
|
131
|
+
# Inject namespace where needed
|
|
132
|
+
override_config.namespace_inject("content")
|
|
133
|
+
override_config.namespace_inject("page_or_sobject_type")
|
|
134
|
+
override_config.namespace_inject(
|
|
135
|
+
"record_type"
|
|
136
|
+
) if override_config.record_type else None
|
|
137
|
+
|
|
138
|
+
# Find existing override with same key fields
|
|
139
|
+
existing_override = self._find_existing_override(metadata, override_config)
|
|
140
|
+
|
|
141
|
+
if existing_override:
|
|
142
|
+
self.logger.warning(
|
|
143
|
+
f"Updating existing profileActionOverride for {override_config.profile}/{override_config.page_or_sobject_type}/{override_config.action_name} in {api_name}"
|
|
144
|
+
)
|
|
145
|
+
# Update the existing override
|
|
146
|
+
self._update_override_element(
|
|
147
|
+
existing_override,
|
|
148
|
+
override_config,
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
self.logger.info(
|
|
152
|
+
f"Adding profileActionOverride for {override_config.profile}/{override_config.page_or_sobject_type}/{override_config.action_name} to {api_name}"
|
|
153
|
+
)
|
|
154
|
+
# Create new override
|
|
155
|
+
self._create_new_override(metadata, override_config)
|
|
156
|
+
|
|
157
|
+
def _find_existing_override(self, metadata, override_config):
|
|
158
|
+
"""
|
|
159
|
+
Find an existing profileActionOverride that matches the key fields:
|
|
160
|
+
actionName, pageOrSobjectType, recordType, and profile
|
|
161
|
+
"""
|
|
162
|
+
for override_elem in metadata.findall("profileActionOverrides"):
|
|
163
|
+
elem_action = override_elem.find("actionName")
|
|
164
|
+
elem_page = override_elem.find("pageOrSobjectType")
|
|
165
|
+
elem_record_type = override_elem.find("recordType")
|
|
166
|
+
elem_profile = override_elem.find("profile")
|
|
167
|
+
|
|
168
|
+
# Match on all key fields
|
|
169
|
+
if (
|
|
170
|
+
elem_action
|
|
171
|
+
and elem_action.text == override_config.action_name
|
|
172
|
+
and elem_page
|
|
173
|
+
and elem_page.text == override_config.page_or_sobject_type
|
|
174
|
+
and elem_profile
|
|
175
|
+
and elem_profile.text == override_config.profile
|
|
176
|
+
):
|
|
177
|
+
# Handle recordType - both must be None or both must match
|
|
178
|
+
if override_config.record_type is None and elem_record_type is None:
|
|
179
|
+
return override_elem
|
|
180
|
+
elif (
|
|
181
|
+
override_config.record_type is not None
|
|
182
|
+
and elem_record_type is not None
|
|
183
|
+
and elem_record_type.text == override_config.record_type
|
|
184
|
+
):
|
|
185
|
+
return override_elem
|
|
186
|
+
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
def _update_override_element(
|
|
190
|
+
self,
|
|
191
|
+
override_elem,
|
|
192
|
+
override_config,
|
|
193
|
+
):
|
|
194
|
+
"""Update an existing profileActionOverride element"""
|
|
195
|
+
# Update each child element
|
|
196
|
+
# actionName
|
|
197
|
+
elem = override_elem.find("actionName")
|
|
198
|
+
if elem is not None:
|
|
199
|
+
elem.text = override_config.action_name
|
|
200
|
+
|
|
201
|
+
# content
|
|
202
|
+
elem = override_elem.find("content")
|
|
203
|
+
if elem is not None:
|
|
204
|
+
elem.text = override_config.content
|
|
205
|
+
|
|
206
|
+
# formFactor
|
|
207
|
+
elem = override_elem.find("formFactor")
|
|
208
|
+
if elem is not None:
|
|
209
|
+
elem.text = override_config.form_factor
|
|
210
|
+
|
|
211
|
+
# pageOrSobjectType
|
|
212
|
+
elem = override_elem.find("pageOrSobjectType")
|
|
213
|
+
if elem is not None:
|
|
214
|
+
elem.text = override_config.page_or_sobject_type
|
|
215
|
+
|
|
216
|
+
# recordType (optional)
|
|
217
|
+
elem = override_elem.find("recordType")
|
|
218
|
+
if override_config.record_type:
|
|
219
|
+
if elem is not None:
|
|
220
|
+
elem.text = override_config.record_type
|
|
221
|
+
elif elem is not None:
|
|
222
|
+
# Remove recordType if it exists but shouldn't
|
|
223
|
+
override_elem.remove(elem)
|
|
224
|
+
|
|
225
|
+
# type
|
|
226
|
+
elem = override_elem.find("type")
|
|
227
|
+
if elem is not None:
|
|
228
|
+
elem.text = override_config.type
|
|
229
|
+
|
|
230
|
+
# profile
|
|
231
|
+
elem = override_elem.find("profile")
|
|
232
|
+
if elem is not None:
|
|
233
|
+
elem.text = override_config.profile
|
|
234
|
+
|
|
235
|
+
def _create_new_override(
|
|
236
|
+
self,
|
|
237
|
+
metadata,
|
|
238
|
+
override_config,
|
|
239
|
+
):
|
|
240
|
+
"""Create a new profileActionOverride element with proper ordering"""
|
|
241
|
+
override_elem = metadata.append("profileActionOverrides")
|
|
242
|
+
|
|
243
|
+
# Add elements in the correct order per Salesforce metadata API
|
|
244
|
+
override_elem.append("actionName", text=override_config.action_name)
|
|
245
|
+
override_elem.append("content", text=override_config.content)
|
|
246
|
+
override_elem.append("formFactor", text=override_config.form_factor)
|
|
247
|
+
override_elem.append(
|
|
248
|
+
"pageOrSobjectType", text=override_config.page_or_sobject_type
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# recordType is optional
|
|
252
|
+
if override_config.record_type:
|
|
253
|
+
override_elem.append("recordType", text=override_config.record_type)
|
|
254
|
+
|
|
255
|
+
override_elem.append("type", text=override_config.type)
|
|
256
|
+
override_elem.append("profile", text=override_config.profile)
|