1- # * Copyright (c) 2021, salesforce.com, inc.
2- # * All rights reserved.
3- # * SPDX-License-Identifier: BSD-3-Clause
4- # * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5- # *
6-
7- """"
8- Steps:
9-
10- 1) Retrieve Network, extract picassoSite (is the API Name of ExpBundle)
11- 2) Retrieve ExperienceBundle
12- 3) Update themes: loop over all *.json files in themes directory
13- - navigationMenuEditorRefresh
14-
15- """
161import os
2+ from pathlib import Path
173import re
18- from tempfile import mkstemp
19- from shutil import move
204import xml .etree .ElementTree as ET
215from cumulusci .core .exceptions import CumulusCIException
22- from os import path , fdopen , remove
236from dev .tasks .debugger import log_title
247from dev .tasks .sfdx import SfdxEtlWithNamespaceInjectionTask
258
269
27- class ReplaceThemeLayoutNavigationMenuTask (SfdxEtlWithNamespaceInjectionTask ):
28-
10+ class ExperienceBundleTask (SfdxEtlWithNamespaceInjectionTask ):
2911 task_options = {
3012 "network_name" : {"description" : ("Name of Network" ), "required" : True },
31- "navigation_menu" : {
32- "description" : "NavigationMenu API name that will replace the default Navigation Menus in the community theme layout." ,
33- "required" : True ,
34- },
3513 ** SfdxEtlWithNamespaceInjectionTask .task_options ,
3614 }
3715
16+ def _sfdx_extract (self ):
17+ # Create default package directory in temporary directory
18+ self ._temporary_package_directory = os .path .join (
19+ self ._temporary_directory , self ._default_package_directory
20+ )
21+
22+ self ._network_name = self .options .get ("network_name" )
23+
24+ # Retrieve the Network metadata.
25+ self ._retrieve_network ()
26+
27+ # Extract the ExperienceBundle API Name from the Network metadata.
28+ self ._set_experience_bundle_name ()
29+
30+ # Retrieve the ExperienceBundle metadata.
31+ self ._retrieve_experience_bundle ()
32+
33+ def _sfdx_transform (self ):
34+ pass
35+
36+ def _sfdx_load (self ):
37+ self ._deploy_experience_bundle ()
38+ self ._publish_network ()
39+
3840 def _retrieve_network (self ):
41+ self ._log_title ("Retrieving Network metadata" )
3942 result = self ._sfdx (
40- [
41- "force:source:retrieve" ,
42- '--metadata="Network:{}"' .format (self .options .get ("network_name" )),
43- ],
43+ ["force:source:retrieve" , f'--metadata="Network:{ self ._network_name } "' ],
4444 addTargetUsername = True ,
4545 )
4646
47- self .retrieved_file_paths = set ()
48- for inbound_file in result ["inboundFiles" ]:
49- self .retrieved_file_paths .add (inbound_file ["filePath" ])
50-
51- # raise CumulusCIException is no results found.
52- if not result ["inboundFiles" ]:
53- raise CumulusCIException (
54- 'Metadata not found: "Network:{}"' .format (
55- self .options .get ("network_name" )
56- )
57- )
47+ # Check if there were any errors retrieveing metadata
48+ self ._assert_sfdx_retrieved_successfully (result )
5849
5950 def _set_experience_bundle_name (self ):
6051 """
6152 1) Open Network file
6253 2) Extract picassoSite with regex.
6354 3) set self._experience_bundle_name = picassoSite
6455 """
65- basepath = path .dirname (self ._temporary_package_directory )
66- filepath = path .abspath (path .join (basepath , self .retrieved_file_paths .pop ()))
56+ network_path = os .path .join (
57+ self ._temporary_package_directory ,
58+ "main" ,
59+ "default" ,
60+ "networks" ,
61+ f"{ self ._network_name } .network-meta.xml" ,
62+ )
6763
68- tree = ET .parse (filepath )
64+ tree = ET .parse (network_path )
6965 root = tree .getroot ()
7066
7167 for elem in root .findall (
@@ -75,103 +71,112 @@ def _set_experience_bundle_name(self):
7571
7672 self ._experience_bundle_name = picasso_site
7773
74+ self ._log_title ("Extracting ExperienceBundle API Name from Network metadata" )
75+ self .logger .info (f"ExperienceBundle: { self ._experience_bundle_name } " )
76+
7877 def _retrieve_experience_bundle (self ):
78+ self ._log_title ("Retrieving ExperienceBundle metadata" )
7979 result = self ._sfdx (
8080 [
8181 "force:source:retrieve" ,
82- '--metadata="ExperienceBundle:{}"' . format ( self ._experience_bundle_name ) ,
82+ f '--metadata="ExperienceBundle:{ self ._experience_bundle_name } "' ,
8383 ],
8484 addTargetUsername = True ,
8585 )
8686
87- self .theme_file_paths = set ()
88-
89- for item in result ["inboundFiles" ]:
90- full_name = item ["fullName" ]
91- match = self ._experience_bundle_name + "/themes" in full_name
92- if match :
93- self .theme_file_paths .add (item ["filePath" ])
94-
95- # raise CumulusCIException is no results found.
96- if not result ["inboundFiles" ]:
97- raise CumulusCIException (
98- 'Metadata not found: "ExperienceBundle:{}"' .format (
99- self .options .get (self ._experience_bundle_name )
100- )
101- )
87+ # Check if there were any errors retrieveing metadata
88+ self ._assert_sfdx_retrieved_successfully (result )
10289
10390 def _deploy_experience_bundle (self ):
91+ self ._log_title ("Deploying ExperienceBundle metadata" )
92+
10493 result = self ._sfdx (
10594 [
10695 "force:source:deploy" ,
107- '--metadata="ExperienceBundle:{}"' . format ( self ._experience_bundle_name ) ,
96+ f '--metadata="ExperienceBundle:{ self ._experience_bundle_name } "' ,
10897 ],
10998 addTargetUsername = True ,
11099 )
111100
112101 # raise CumulusCIException is no results found.
113102 if not result ["deployedSource" ]:
114103 raise CumulusCIException (
115- 'Metadata not found: "ExperienceBundle:{}"' .format (
116- self .options .get (self ._experience_bundle_name )
117- )
104+ f'Metadata not found: "ExperienceBundle:{ self ._experience_bundle_name } "'
118105 )
119106
120- def _replace_theme_navigation_menus (self ):
121- """
122- Replaces default navigation with navigation menu specified in the option navigation_menu
123- """
124- basepath = path .dirname (self ._temporary_package_directory )
125- filepath = path .abspath (path .join (basepath , self .theme_file_paths .pop ()))
126-
127- fh , abs_path = mkstemp ()
128- log_title (
129- "Adding navigation menu: {} to community pages" .format (
130- self .options ["navigation_menu" ]
131- ),
132- self .logger .info ,
107+ def _publish_network (self ):
108+ self ._log_title ("Publishing Network" )
109+ self ._sfdx (
110+ ["force:community:publish" , f'--name="{ self ._network_name } "' ],
111+ addTargetUsername = True ,
133112 )
134- page_count = 0
135- with fdopen (fh , "w" ) as new_file :
136- with open (filepath ) as old_file :
137- substitution = '"navigationMenuEditorRefresh" : "{}"' .format (
138- self .options ["navigation_menu" ]
139- )
140- for line in old_file :
141- updated_line = re .sub (
142- r'("navigationMenuEditorRefresh" ?: ?.*$)' ,
143- substitution ,
144- line ,
145- )
146- new_file .write (updated_line )
147- if updated_line != line :
148- page_count += 1
149113
150- self .logger .info ("Number of Community Pages updated: {}" .format (page_count ))
114+ def _assert_sfdx_retrieved_successfully (self , sfdx_result ):
115+ errors = []
116+ for inbound_file in sfdx_result ["inboundFiles" ]:
117+ if inbound_file .get ("error" ):
118+ errors .append (inbound_file .get ("error" ))
151119
152- # Remove original file
153- remove (filepath )
154- # Move new file
155- move (abs_path , filepath )
120+ if errors :
121+ raise CumulusCIException ("; " .join (errors ))
122+
123+ def _log_title (self , title ):
124+ self .logger .info ("" )
125+ log_title (title , self .logger .info )
156126
157127 def _debug_temporary_directory (self ):
158128 self .logger .warn ("debugging temporary package directory" )
159129 self .logger .warn ("" )
160130 self .logger .warn (f"temporary directory: { self ._temporary_directory } " )
161131 self .list_files (self ._default_package_directory )
162132
163- def _sfdx_extract (self ):
164- # Create default package directory in temporary directory
165- self ._temporary_package_directory = os .path .join (
166- self ._temporary_directory , self ._default_package_directory
167- )
168- self ._retrieve_network ()
169- self ._set_experience_bundle_name ()
170133
171- self ._retrieve_experience_bundle ()
134+ class ReplaceThemeLayoutNavigationMenuTask (ExperienceBundleTask ):
135+
136+ task_options = {
137+ "navigation_menu" : {
138+ "description" : "NavigationMenu API name to set for all theme layouts." ,
139+ "required" : True ,
140+ },
141+ ** ExperienceBundleTask .task_options ,
142+ }
172143
173144 def _sfdx_transform (self ):
174- self ._replace_theme_navigation_menus ()
145+ self ._set_navigation_menu ()
146+
147+ def _set_navigation_menu (self ):
148+ self ._log_title ("Set navigation menu" )
149+ self .logger .info (
150+ 'Setting the navigation menu as "{}" for all "{}" ExperienceBundle themes:' .format (
151+ self .options ["navigation_menu" ],
152+ self ._experience_bundle_name ,
153+ )
154+ )
175155
176- def _sfdx_load (self ):
177- self ._deploy_experience_bundle ()
156+ themes_path = os .path .join (
157+ self ._temporary_package_directory ,
158+ "main" ,
159+ "default" ,
160+ "experiences" ,
161+ self ._experience_bundle_name ,
162+ "themes" ,
163+ )
164+
165+ for theme_file_dir_entry in os .scandir (themes_path ):
166+ if theme_file_dir_entry .is_file ():
167+ self .logger .info (f" { theme_file_dir_entry .name } " )
168+
169+ theme_file = Path (theme_file_dir_entry .path )
170+
171+ # Replace all "navigationMenuEditorRefresh" values with the navigation_menu option.
172+ # This should be safe since it seems only the forceCommunity:themeNav component has a navigationMenuEditorRefresh attribute.
173+ theme_file .write_text (
174+ re .sub (
175+ r'("navigationMenuEditorRefresh" ?: ?"\w*")' ,
176+ '"navigationMenuEditorRefresh" : "{}"' .format (
177+ self .options .get ("navigation_menu" )
178+ ),
179+ theme_file .read_text (),
180+ flags = re .M ,
181+ )
182+ )
0 commit comments