From 607e5201af1139d0aea0fac03588bd600c78a69f Mon Sep 17 00:00:00 2001 From: Bobby Wright Date: Tue, 31 Oct 2023 11:23:54 -0500 Subject: [PATCH 1/7] Add Metadata Deployment Utilities --- .../main/default/classes/MetadataDeploy.cls | 47 +++++++++++++++++++ .../classes/MetadataDeploy.cls-meta.xml | 5 ++ .../default/classes/MetadataDeployTests.cls | 10 ++++ .../classes/MetadataDeployTests.cls-meta.xml | 5 ++ .../main/default/classes/MetadataUtility.cls | 39 +++++++++++++++ .../classes/MetadataUtility.cls-meta.xml | 5 ++ 6 files changed, 111 insertions(+) create mode 100644 force-app/main/default/classes/MetadataDeploy.cls create mode 100644 force-app/main/default/classes/MetadataDeploy.cls-meta.xml create mode 100644 force-app/main/default/classes/MetadataDeployTests.cls create mode 100644 force-app/main/default/classes/MetadataDeployTests.cls-meta.xml create mode 100644 force-app/main/default/classes/MetadataUtility.cls create mode 100644 force-app/main/default/classes/MetadataUtility.cls-meta.xml diff --git a/force-app/main/default/classes/MetadataDeploy.cls b/force-app/main/default/classes/MetadataDeploy.cls new file mode 100644 index 0000000..2110d27 --- /dev/null +++ b/force-app/main/default/classes/MetadataDeploy.cls @@ -0,0 +1,47 @@ +/** + * Created by robertwright on 10/30/23. + */ + +public with sharing class MetadataDeploy implements Metadata.DeployCallback { + + public String resultMessage; + + public void handleResult(Metadata.DeployResult result,Metadata.DeployCallbackContext context) { + if(result.status === Metadata.DeployStatus.Succeeded) { + resultMessage = 'MetaData Deploy Succeeded'; + } else resultMessage = 'MetaData Deploy Failed'; + if(Test.isRunningTest()) System.debug(resultMessage); + } + + public static Id upsertMetaData(Metadata.DeployContainer deployContainer) { + MetadataDeploy callback = new MetadataDeploy(); + Id upsertId; + if(!Test.isRunningTest()) upsertId = Metadata.Operations.enqueueDeployment(deployContainer,callback); + return upsertId; + } + + + + private static Set entityRelationshipFields = new Set{ + 'Indicator_Bundle.sObject__c' + }; + + public static Metadata.CustomMetadata buildCustomMetadata(String fullName, String recordLabel, Map fieldValueMap) { + Metadata.CustomMetadata customMetadata = new Metadata.CustomMetadata(); + customMetadata.fullName = (fullName.length() > 40) ? fullName.substring(0,40) : fullName; + customMetadata.label = (recordLabel.length() > 40) ? recordLabel.substring(0,40) : recordLabel; + + for(String fieldName : fieldValueMap.keySet()) { + if(!fieldName.contains('__c')) continue; // We only pass in custom fields when deploying metadata + else if(entityRelationshipFields.contains(fieldName)) fieldName.replace('__r','__c'); + Metadata.CustomMetadataValue metadataField = new Metadata.CustomMetadataValue(); + metadataField.field = fieldName; + metadataField.value = fieldValueMap.get(fieldName); + customMetadata.values.add(metadataField); + } + + return customMetadata; + } + + +} \ No newline at end of file diff --git a/force-app/main/default/classes/MetadataDeploy.cls-meta.xml b/force-app/main/default/classes/MetadataDeploy.cls-meta.xml new file mode 100644 index 0000000..7a51829 --- /dev/null +++ b/force-app/main/default/classes/MetadataDeploy.cls-meta.xml @@ -0,0 +1,5 @@ + + + 58.0 + Active + diff --git a/force-app/main/default/classes/MetadataDeployTests.cls b/force-app/main/default/classes/MetadataDeployTests.cls new file mode 100644 index 0000000..1131fc6 --- /dev/null +++ b/force-app/main/default/classes/MetadataDeployTests.cls @@ -0,0 +1,10 @@ +/** + * Created by robertwright on 10/30/23. + */ + +@IsTest +private class MetadataDeployTests { + @IsTest + static void testBehavior() { + } +} \ No newline at end of file diff --git a/force-app/main/default/classes/MetadataDeployTests.cls-meta.xml b/force-app/main/default/classes/MetadataDeployTests.cls-meta.xml new file mode 100644 index 0000000..7a51829 --- /dev/null +++ b/force-app/main/default/classes/MetadataDeployTests.cls-meta.xml @@ -0,0 +1,5 @@ + + + 58.0 + Active + diff --git a/force-app/main/default/classes/MetadataUtility.cls b/force-app/main/default/classes/MetadataUtility.cls new file mode 100644 index 0000000..d6dadc0 --- /dev/null +++ b/force-app/main/default/classes/MetadataUtility.cls @@ -0,0 +1,39 @@ +/** + * Created by robertwright on 10/30/23. + */ + +public with sharing class MetadataUtility { + private static final String Label_Field = 'Label'; + private static final String QualifiedApiName_Field = 'QualifiedApiName'; + + private static Metadata.DeployContainer deployContainer; + + /**Combine the Object name with the QualifiedApiName**/ + private static String mergeFullName(String metadataName, String QualifiedApiName) { + return metadataName.replace('__mdt','')+'.'+QualifiedApiName; + } + + private static void buildMetaData(String sObjectName,Map metadataFieldValueMap) { + String QualifiedApiName = (String) metadataFieldValueMap.get(QualifiedApiName_Field); + String fullName = mergeFullName(sObjectName,QualifiedApiName); + String label = (String) metadataFieldValueMap.get(Label_Field); + if(String.isBlank(label)) label = 'Unnamed Metadata'; + Metadata.CustomMetadata customMetadata = MetadataDeploy.buildCustomMetadata(fullName,label,metadataFieldValueMap); + deployContainer.addMetadata(customMetadata); + } + + + private static Id deployMetadataRecords(List records,SObjectType sObjectType) { + deployContainer = new Metadata.DeployContainer(); // Build New Deployment Container + DescribeSObjectResult describeSObjectResult = sObjectType.getDescribe(); + for(SObject record : records) buildMetaData(describeSObjectResult.getName(), record.getPopulatedFieldsAsMap()); // Build Metadata Maps and Add to Deploy Container + Id deployId = MetadataDeploy.upsertMetaData(deployContainer); // Deploy Deploy Container + return deployId; + } + + @AuraEnabled + public static Id deployIndicatorBundles(List records) { + return deployMetadataRecords(records,Indicator_Bundle__mdt.getSObjectType()); + } + +} \ No newline at end of file diff --git a/force-app/main/default/classes/MetadataUtility.cls-meta.xml b/force-app/main/default/classes/MetadataUtility.cls-meta.xml new file mode 100644 index 0000000..fbbad0a --- /dev/null +++ b/force-app/main/default/classes/MetadataUtility.cls-meta.xml @@ -0,0 +1,5 @@ + + + 56.0 + Active + From d6ccbcda2d312d2704fa54eb8bf383d6dbf3bdf0 Mon Sep 17 00:00:00 2001 From: Bobby Wright Date: Tue, 31 Oct 2023 15:26:43 -0500 Subject: [PATCH 2/7] Add Indicator Edit Modal (This modal will be used for all editing it utilizes dynamic LWC instantiation) --- .../indicatorEditModal.html | 13 ++++++ .../indicatorEditModal/indicatorEditModal.js | 40 +++++++++++++++++++ .../indicatorEditModal.js-meta.xml | 10 +++++ 3 files changed, 63 insertions(+) create mode 100644 force-app/main/default/lwc/indicatorEditModal/indicatorEditModal.html create mode 100644 force-app/main/default/lwc/indicatorEditModal/indicatorEditModal.js create mode 100644 force-app/main/default/lwc/indicatorEditModal/indicatorEditModal.js-meta.xml diff --git a/force-app/main/default/lwc/indicatorEditModal/indicatorEditModal.html b/force-app/main/default/lwc/indicatorEditModal/indicatorEditModal.html new file mode 100644 index 0000000..25ac5c5 --- /dev/null +++ b/force-app/main/default/lwc/indicatorEditModal/indicatorEditModal.html @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/force-app/main/default/lwc/indicatorEditModal/indicatorEditModal.js b/force-app/main/default/lwc/indicatorEditModal/indicatorEditModal.js new file mode 100644 index 0000000..c4f1990 --- /dev/null +++ b/force-app/main/default/lwc/indicatorEditModal/indicatorEditModal.js @@ -0,0 +1,40 @@ +/** + * Created by robertwright on 10/31/23. + */ + +import {api} from 'lwc'; +import LightningModal from 'lightning/modal'; + +import Indicator_Bundle from "@salesforce/schema/Indicator_Bundle__mdt"; +import Indicator_Item from "@salesforce/schema/Indicator_Item__mdt"; +import Indicator_Item_Extension from "@salesforce/schema/Indicator_Item_Extension__mdt"; + +export default class IndicatorEditModal extends LightningModal { + componentConstructor; + componentProperties = {}; + + @api masterLabel; + @api objectApiName; + @api isNew = false; + + get headerLabel() { + return (this.isNew) ? `New ${this.masterLabel}` : `Update ${this.masterLabel}`; + } + + get componentToRender() { + switch (this.objectApiName) { + case Indicator_Bundle.objectApiName: + return "c/editIndicatorBundle"; + break; + default: + return "c/editIndicatorBundle"; + } + } + + connectedCallback() { + import(`${this.componentToRender}`) + .then(({ default: ctor }) => (this.componentConstructor = ctor)) + .catch((err) => console.log("Error importing component")); + } + +} \ No newline at end of file diff --git a/force-app/main/default/lwc/indicatorEditModal/indicatorEditModal.js-meta.xml b/force-app/main/default/lwc/indicatorEditModal/indicatorEditModal.js-meta.xml new file mode 100644 index 0000000..29f7cd2 --- /dev/null +++ b/force-app/main/default/lwc/indicatorEditModal/indicatorEditModal.js-meta.xml @@ -0,0 +1,10 @@ + + + 59.0 + Used to edit indicator records + false + Indicator Edit Modal + + lightning__dynamicComponent + + From 3b7408057e8fb1440f67a9a0ce94465e5061defe Mon Sep 17 00:00:00 2001 From: Bobby Wright Date: Tue, 31 Oct 2023 15:27:37 -0500 Subject: [PATCH 3/7] Add editIndicatorBundle LWC this is the primary lwc that will be used to create and edit Indicator Bundles --- .../editIndicatorBundle.css | 3 + .../editIndicatorBundle.html | 98 +++++++++ .../editIndicatorBundle.js | 195 ++++++++++++++++++ .../editIndicatorBundle.js-meta.xml | 7 + 4 files changed, 303 insertions(+) create mode 100644 force-app/main/default/lwc/editIndicatorBundle/editIndicatorBundle.css create mode 100644 force-app/main/default/lwc/editIndicatorBundle/editIndicatorBundle.html create mode 100644 force-app/main/default/lwc/editIndicatorBundle/editIndicatorBundle.js create mode 100644 force-app/main/default/lwc/editIndicatorBundle/editIndicatorBundle.js-meta.xml diff --git a/force-app/main/default/lwc/editIndicatorBundle/editIndicatorBundle.css b/force-app/main/default/lwc/editIndicatorBundle/editIndicatorBundle.css new file mode 100644 index 0000000..26d00e2 --- /dev/null +++ b/force-app/main/default/lwc/editIndicatorBundle/editIndicatorBundle.css @@ -0,0 +1,3 @@ +/** + * Created by robertwright on 10/31/23. + */ diff --git a/force-app/main/default/lwc/editIndicatorBundle/editIndicatorBundle.html b/force-app/main/default/lwc/editIndicatorBundle/editIndicatorBundle.html new file mode 100644 index 0000000..7836de1 --- /dev/null +++ b/force-app/main/default/lwc/editIndicatorBundle/editIndicatorBundle.html @@ -0,0 +1,98 @@ + + + + \ No newline at end of file diff --git a/force-app/main/default/lwc/editIndicatorBundle/editIndicatorBundle.js b/force-app/main/default/lwc/editIndicatorBundle/editIndicatorBundle.js new file mode 100644 index 0000000..e22289e --- /dev/null +++ b/force-app/main/default/lwc/editIndicatorBundle/editIndicatorBundle.js @@ -0,0 +1,195 @@ +/** + * Created by robertwright on 10/31/23. + */ + +import {LightningElement, wire, track} from 'lwc'; +import { getObjectInfo } from "lightning/uiObjectInfoApi"; +import deployIndicatorBundles from '@salesforce/apex/MetadataUtility.deployIndicatorBundles'; + +import Indicator_Bundle from "@salesforce/schema/Indicator_Bundle__mdt"; + +import Active_FIELD from "@salesforce/schema/Indicator_Bundle__mdt.Active__c"; +import Card_Icon_FIELD from "@salesforce/schema/Indicator_Bundle__mdt.Card_Icon__c"; +import Card_Icon_Background_FIELD from "@salesforce/schema/Indicator_Bundle__mdt.Card_Icon_Background__c"; +import Card_Icon_Foreground_FIELD from "@salesforce/schema/Indicator_Bundle__mdt.Card_Icon_Foreground__c"; +import Card_Text_FIELD from "@salesforce/schema/Indicator_Bundle__mdt.Card_Text__c"; +import Card_Title_FIELD from "@salesforce/schema/Indicator_Bundle__mdt.Card_Title__c"; +import Description_FIELD from "@salesforce/schema/Indicator_Bundle__mdt.Description__c"; +import sObject_FIELD from "@salesforce/schema/Indicator_Bundle__mdt.sObject__c"; +import {NavigationMixin} from "lightning/navigation"; + +export default class EditIndicatorBundle extends LightningElement { + + showSpinner = false; + get indicatorBundleObjectApiName() { + return Indicator_Bundle.objectApiName.replace('__c','__mdt') + } + + objectInfo = {}; + @wire(getObjectInfo, { objectApiName: '$indicatorBundleObjectApiName' }) setObjectInfo({ error, data }) { + if (error) { + let message = 'Unknown error'; + if (Array.isArray(error.body)) { + message = error.body.map(e => e.message).join(', '); + } else if (typeof error.body.message === 'string') { + message = error.body.message; + } + console.log(message); + } else if (data) { + console.log(data); + console.log(JSON.parse(JSON.stringify(data))); + this.objectInfo = data; + } + }; + + + @track indicator_Bundle = {}; + getdataType(fieldApiName) { + const fieldObject = this.objectInfo?.fields[fieldApiName] || {}; + switch (fieldObject.dataType) { + case 'String': return 'text'; + case 'Boolean': return 'checkbox-button'; + default: + return "text"; + } + } + get bundleInfo() { + return { + Label : { + fieldApiName : 'Label', + label: 'Label', + value: this.indicator_Bundle['Label'], + dataType: 'text' + }, + QualifiedApiName : { + fieldApiName : 'QualifiedApiName', + label: 'QualifiedApiName', + value: this.indicator_Bundle['QualifiedApiName'], + dataType: 'text' + }, + Active_FIELD : { + fieldApiName : Active_FIELD.fieldApiName, + label: this.objectInfo?.fields[Active_FIELD.fieldApiName]?.label, + inlineHelpText: this.objectInfo?.fields[Active_FIELD.fieldApiName]?.inlineHelpText, + value: this.indicator_Bundle[Active_FIELD.fieldApiName], + dataType: this.getdataType(Active_FIELD.fieldApiName), + required: this.objectInfo?.fields[Active_FIELD.fieldApiName]?.required + }, + Card_Icon_FIELD : { + fieldApiName : Card_Icon_FIELD.fieldApiName, + label: this.objectInfo?.fields[Card_Icon_FIELD.fieldApiName]?.label, + inlineHelpText: this.objectInfo?.fields[Card_Icon_FIELD.fieldApiName]?.inlineHelpText, + value: this.indicator_Bundle[Card_Icon_FIELD.fieldApiName], + dataType: this.getdataType(Card_Icon_FIELD.fieldApiName), + required: this.objectInfo?.fields[Card_Icon_FIELD.fieldApiName]?.required + }, + Card_Icon_Background_FIELD : { + fieldApiName : Card_Icon_Background_FIELD.fieldApiName, + label: this.objectInfo?.fields[Card_Icon_Background_FIELD.fieldApiName]?.label, + inlineHelpText: this.objectInfo?.fields[Card_Icon_Background_FIELD.fieldApiName]?.inlineHelpText, + value: this.indicator_Bundle[Card_Icon_Background_FIELD.fieldApiName], + dataType: this.getdataType(Card_Icon_Background_FIELD.fieldApiName), + required: this.objectInfo?.fields[Card_Icon_Background_FIELD.fieldApiName]?.required + }, + Card_Icon_Foreground_FIELD : { + fieldApiName : Card_Icon_Foreground_FIELD.fieldApiName, + label: this.objectInfo?.fields[Card_Icon_Foreground_FIELD.fieldApiName]?.label, + inlineHelpText: this.objectInfo?.fields[Card_Icon_Foreground_FIELD.fieldApiName]?.inlineHelpText, + value: this.indicator_Bundle[Card_Icon_Foreground_FIELD.fieldApiName], + dataType: this.getdataType(Card_Icon_Foreground_FIELD.fieldApiName), + required: this.objectInfo?.fields[Card_Icon_Foreground_FIELD.fieldApiName]?.required + }, + Card_Text_FIELD : { + fieldApiName : Card_Text_FIELD.fieldApiName, + label: this.objectInfo?.fields[Card_Text_FIELD.fieldApiName]?.label, + inlineHelpText: this.objectInfo?.fields[Card_Text_FIELD.fieldApiName]?.inlineHelpText, + value: this.indicator_Bundle[Card_Text_FIELD.fieldApiName], + dataType: this.getdataType(Card_Text_FIELD.fieldApiName), + required: this.objectInfo?.fields[Card_Text_FIELD.fieldApiName]?.required + }, + Card_Title_FIELD : { + fieldApiName : Card_Title_FIELD.fieldApiName, + label: this.objectInfo?.fields[Card_Title_FIELD.fieldApiName]?.label, + inlineHelpText: this.objectInfo?.fields[Card_Title_FIELD.fieldApiName]?.inlineHelpText, + value: this.indicator_Bundle[Card_Title_FIELD.fieldApiName], + dataType: this.getdataType(Card_Title_FIELD.fieldApiName), + required: this.objectInfo?.fields[Card_Title_FIELD.fieldApiName]?.required + }, + Description_FIELD : { + fieldApiName : Description_FIELD.fieldApiName, + label: this.objectInfo?.fields[Description_FIELD.fieldApiName]?.label, + inlineHelpText: this.objectInfo?.fields[Description_FIELD.fieldApiName]?.inlineHelpText, + value: this.indicator_Bundle[Description_FIELD.fieldApiName], + dataType: this.getdataType(Description_FIELD.fieldApiName), + required: this.objectInfo?.fields[Description_FIELD.fieldApiName]?.required + }, + sObject_FIELD : { + fieldApiName : sObject_FIELD.fieldApiName, + label: this.objectInfo?.fields[sObject_FIELD.fieldApiName]?.label, + inlineHelpText: this.objectInfo?.fields[sObject_FIELD.fieldApiName]?.inlineHelpText, + value: this.indicator_Bundle[sObject_FIELD.fieldApiName], + dataType: this.getdataType(sObject_FIELD.fieldApiName), + required: this.objectInfo?.fields[sObject_FIELD.fieldApiName]?.required + } + } + } + + + connectedCallback() { + console.log(this.indicatorBundleObjectApiName); + console.log(Active_FIELD); + console.log(Card_Icon_FIELD); + console.log(Card_Icon_Background_FIELD); + console.log(Card_Icon_Foreground_FIELD); + console.log(Card_Text_FIELD); + console.log(Card_Title_FIELD); + console.log(Description_FIELD); + console.log(sObject_FIELD); + } + + + replaceWhiteSpace(value) { + const returnValue = (value || '').replaceAll('_',' ') + .replaceAll('_',' ') + .replaceAll(/[\W_]+/g," ") + .trim() + .replaceAll(/ +/g,' ') + .replaceAll(' ','_'); + + return (returnValue.length > 40) ? returnValue.substring(0,40) : returnValue; + } + + handleLabelChange(event) { + const value = event.detail.value; + this.indicator_Bundle['Label'] = (value.length > 40) ? value.substring(0,40) : value; + this.indicator_Bundle['QualifiedApiName'] = this.replaceWhiteSpace(value); + } + handleChange(event) { + const value = event.detail.value; + const fieldName = (event.target.dataset || {}).fieldApiName; + this.indicator_Bundle[fieldName] = value; + } + handleSave(event) { + this.showSpinner = true; + this.saveMetaDataRecord(); + } + + + + saveMetaDataRecord() { + deployIndicatorBundles({records: [this.indicator_Bundle]}) + .then(result => { + console.log('deploymentId = result'); + }) + .catch(error => { + console.log(error); + }) + .finally(() => { + this.showSpinner = false; + }); + } + + + + +} \ No newline at end of file diff --git a/force-app/main/default/lwc/editIndicatorBundle/editIndicatorBundle.js-meta.xml b/force-app/main/default/lwc/editIndicatorBundle/editIndicatorBundle.js-meta.xml new file mode 100644 index 0000000..f20a711 --- /dev/null +++ b/force-app/main/default/lwc/editIndicatorBundle/editIndicatorBundle.js-meta.xml @@ -0,0 +1,7 @@ + + + 59.0 + Component is used to create and edit indicator bundles + false + Edit Indicator Bundle + From 2639835fb05999a6ed70e49e80bd10b9772450db Mon Sep 17 00:00:00 2001 From: Bobby Wright Date: Tue, 31 Oct 2023 15:28:10 -0500 Subject: [PATCH 4/7] Update configuration Manager component to call indicator edit modal upon click --- .../configurationManager.html | 43 ++++++++++--------- .../configurationManager.js | 26 ++++++++++- .../configurationManager.js-meta.xml | 2 +- 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/force-app/main/default/lwc/configurationManager/configurationManager.html b/force-app/main/default/lwc/configurationManager/configurationManager.html index 0fd987e..79908ec 100644 --- a/force-app/main/default/lwc/configurationManager/configurationManager.html +++ b/force-app/main/default/lwc/configurationManager/configurationManager.html @@ -6,44 +6,45 @@

Review existing Indicators or create new custom metadata records.

- - - + + +