diff --git a/sfdx/force-app/main/default/classes/ConfigExportVFCont_Test.cls b/sfdx/force-app/main/default/classes/ConfigExportVFCont_Test.cls index 2b5576640f..2d4209449a 100644 --- a/sfdx/force-app/main/default/classes/ConfigExportVFCont_Test.cls +++ b/sfdx/force-app/main/default/classes/ConfigExportVFCont_Test.cls @@ -1,7 +1,39 @@ /** - * temp empty until jmater-c's branch goes in + * Created by jmather-c on 5/1/23. */ - @IsTest - public with sharing class ConfigExportVFCont_Test { - } \ No newline at end of file +@IsTest +public with sharing class ConfigExportVFCont_Test { + @IsTest + static public void TestGetConfig() { + Setup_Connection_Data__mdt testSetupData = test_setupAssistant.getTestStripeConnectionKey(); + test_setupAssistant.setTestGlobalKey(testSetupData); + test_setupAssistant.insertTestConnectedRecord(); + Test.setMock(HttpCalloutMock.class, new test_setupAssistant.UnifiedConfigMock()); + ConfigExportVFController controller = new ConfigExportVFController(); + + Test.startTest(); + String response = controller.getConfig(); + Map resultsMap = (Map)JSON.deserializeUntyped(response); + Test.stopTest(); + + Map allMappingConfigurations = (Map)resultsMap.get('allMappingConfigurations'); + Map default_mappings = (Map)allMappingConfigurations.get('default_mappings'); + Map field_defaults = (Map)allMappingConfigurations.get('field_defaults'); + Map field_mappings = (Map)allMappingConfigurations.get('field_mappings'); + Map required_mappings = (Map)allMappingConfigurations.get('required_mappings'); + + System.assertNotEquals(allMappingConfigurations, null); + System.assertEquals(default_mappings, null); + System.assertEquals(required_mappings, null); + System.assertNotEquals(field_defaults, null); + System.assertNotEquals(field_mappings, null); + System.assertEquals('2 days', (String)resultsMap.get('sync_record_retention')); + System.assertEquals('yesterday', (String)resultsMap.get('sync_start_date')); + + System.assertEquals('testOrderFilter', (String)resultsMap.get('order_filter')); + System.assertEquals('testAccountFilter', (String)resultsMap.get('account_filter')); + System.assertEquals('testProduct2Filter', (String)resultsMap.get('product_filter')); + System.assertEquals('testPricebookEntryFilter', (String)resultsMap.get('pricebook_entry_filter')); + } +} diff --git a/sfdx/force-app/main/default/classes/ConfigExportVFController.cls b/sfdx/force-app/main/default/classes/ConfigExportVFController.cls new file mode 100644 index 0000000000..f268c191d6 --- /dev/null +++ b/sfdx/force-app/main/default/classes/ConfigExportVFController.cls @@ -0,0 +1,38 @@ +/** + * Created by jmather-c on 4/27/23. + */ + +public with sharing class ConfigExportVFController { + ConfigPayloadHelper payloadHelper = new ConfigPayloadHelper(); + + public String getConfig() { + Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); + Boolean isConnected = utilities.isConnected(stripeConnectRec); + responseData rd = new responseData(); + + if (isConnected == false) { + rd.put('error', 'Unable to connect to remote system.'); + return rd.getResultsJsonString(); + } + + try { + //construct call out to rubys configuration endpoint + String route = constants.RUBY_SERVICE_BASE_URI + '/v1/configuration'; + HttpResponse response = utilities.makeCallout(route,'GET'); + + //if the call out is successful pull out all mapping objects and add to list + Map responseBody; + if(response.getStatusCode() == 200) { + responseBody = (Map)JSON.deserializeUntyped(response.getBody()); + payloadHelper.extractSettingsData(responseBody, rd, false); + payloadHelper.extractFieldMappingData(responseBody, rd, false); + payloadHelper.extractFilterSettingsData(responseBody, rd); + } else { + errorLogger.create('getConfiguration', String.valueOf(response.getStatusCode()), (String)response.getStatus(), 'Failed to get mapping configuration from ruby service.'); + } + } catch (Exception e) { + rd.addError(e); + } + return rd.getResultsJsonString(); + } +} \ No newline at end of file diff --git a/sfdx/force-app/main/default/classes/ConfigExportVFController.cls-meta.xml b/sfdx/force-app/main/default/classes/ConfigExportVFController.cls-meta.xml new file mode 100644 index 0000000000..40d67933d0 --- /dev/null +++ b/sfdx/force-app/main/default/classes/ConfigExportVFController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 54.0 + Active + diff --git a/sfdx/force-app/main/default/classes/ConfigPayloadHelper.cls b/sfdx/force-app/main/default/classes/ConfigPayloadHelper.cls new file mode 100644 index 0000000000..c20d0f8add --- /dev/null +++ b/sfdx/force-app/main/default/classes/ConfigPayloadHelper.cls @@ -0,0 +1,338 @@ +/** + * Created by jmather-c on 4/26/23. + */ + +public with sharing class ConfigPayloadHelper { + @TestVisible + Debug_Helper debugger = new Debug_Helper(); + + /** + * If a variable is prefixed with INPUT_ or OUTPUT_ it means + * that it is only used for that portion of the translation. + * + * No prefix indicates that it is used in both the input and output. + */ + + public static final String INPUT_CONFIG_HASH = 'configuration_hash'; + public static final String OUTPUT_CONFIG_HASH = 'configurationHash'; + + public static final String INPUT_CONNECTION_STATUS = 'connection_status'; + + public static final String FIELD_MAPPINGS = 'field_mappings'; + public static final String DEFAULT_MAPPINGS = 'default_mappings'; + public static final String REQUIRED_MAPPINGS = 'required_mappings'; + public static final String FIELD_DEFAULTS = 'field_defaults'; + public static final String OUTPUT_ALL_MAPPING_CONFIGS = 'allMappingConfigurations'; + public static final List MAPPING_KEYS = new List { + FIELD_MAPPINGS, + DEFAULT_MAPPINGS, + REQUIRED_MAPPINGS, + FIELD_DEFAULTS + }; + + public static final String INPUT_HIDDEN_MAPPER_FIELDS = 'hidden_mapper_fields'; + public static final String OUTPUT_HIDDEN_MAPPER_FIELDS = 'hiddenMapperFields'; + + public static final String INPUT_FILTERS = 'filters'; + public static final String INPUT_SETTINGS = 'settings'; + public static final String ENABLED = 'enabled'; + public static final String INPUT_HIDDEN_SYNC_PREFS_FIELDS = 'hidden_sync_pref_fields'; + public static final String OUTPUT_DEFAULT_CURRENCY = 'default_currency'; + public static final String OUTPUT_IS_CPQ_INSTALLED = 'isCpqInstalled'; + public static final String OUTPUT_HIDDEN_SYNC_PREFS_FIELDS = 'hiddenSyncPrefsFields'; + public static final String SYNC_RECORD_RETENTION = 'sync_record_retention'; + + public static final List SETTINGS_STRING_FIELDS = new List { + 'sync_record_retention', + 'sync_start_date', + 'api_percentage_limit', + 'cpq_term_unit', + 'cpq_prorate_precision' + }; + + public static final List SETTINGS_BOOLEAN_FIELDS = new List { + 'polling_enabled' + }; + + public static final List CONNECTION_READ_ONLY_FIELDS = new List { + 'stripe_account_id', + 'last_synced' + }; + + Map IOMap_FilterFields = new Map { + 'Product2' => 'product_filter', + 'Order' => 'order_filter', + 'Account' => 'account_filter', + 'PricebookEntry' => 'pricebook_entry_filter' + }; + + Map IOMap_Config = new Map { + INPUT_CONFIG_HASH => OUTPUT_CONFIG_HASH, + INPUT_HIDDEN_SYNC_PREFS_FIELDS => OUTPUT_HIDDEN_SYNC_PREFS_FIELDS, + INPUT_HIDDEN_MAPPER_FIELDS => OUTPUT_HIDDEN_MAPPER_FIELDS + }; + + public void extractConfigHash(Map configPayload, responseData response) { + String mappedKey = IOMap_Config.get(INPUT_CONFIG_HASH); + response.put(mappedKey, (String)configPayload.get(INPUT_CONFIG_HASH)); + } + + public Boolean extractConnectionStatus(Map configPayload, Stripe_Connection__c connection) { + //get connection status values from response + Map connectionStatus = (Map)configPayload.get(INPUT_CONNECTION_STATUS); + connection.Stripe_Connected__c = (Boolean)connectionStatus.get(constants.SYSTEM_STRIPE); + connection.Salesforce_Connected__c = (Boolean)connectionStatus.get(constants.SYSTEM_SALESFORCE); + return connection.Salesforce_Connected__c && connection.Stripe_Connected__c; + } + + public void extractFilterSettingsData(Map configPayload, responseData response) { + Map settings = (Map)configPayload.get(INPUT_SETTINGS); + Map filters = (Map)settings.get(INPUT_FILTERS); + + for (String key : IOMap_FilterFields.keySet()) { + response.put(IOMap_FilterFields.get(key), String.valueOf(filters.get(key))); + } + } + + public void extractSettingsData(Map configPayload, responseData response, Boolean showInternalFields) { + List hiddenSyncPrefFields = getHiddenSyncPrefFields(configPayload); + Map settings = (Map)configPayload.get(INPUT_SETTINGS); + + //gets non read only settings fields from response for frontend + for (String stringField : SETTINGS_STRING_FIELDS) { + // skip hidden field values + if (hiddenSyncPrefFields.contains(stringField)) { + continue; + } + response.put(stringField, String.valueOf(settings.get(stringField))); + } + + //gets boolean connection fields from response for frontend + for (String booleanField : SETTINGS_BOOLEAN_FIELDS) { + if (settings.get(booleanField) == null) { + response.put(booleanField, false); + } else { + response.put(booleanField, Boolean.valueOf(settings.get(booleanField))); + } + } + + if (showInternalFields) { + response.put(OUTPUT_DEFAULT_CURRENCY, UserInfo.getDefaultCurrency()); + response.put(OUTPUT_IS_CPQ_INSTALLED, utilities.isCpqEnabled()); + + //gets read only connection fields from response for frontend + Map connectionStatus = (Map)configPayload.get(INPUT_CONNECTION_STATUS); + for(String readOnlyField : CONNECTION_READ_ONLY_FIELDS) { + response.put(readOnlyField, String.valueOf(connectionStatus.get(readOnlyField))); + } + + String mappedKey = IOMap_Config.get(INPUT_HIDDEN_SYNC_PREFS_FIELDS); + response.put(mappedKey, hiddenSyncPrefFields); + response.put(ENABLED, (Boolean)configPayload.get(ENABLED)); + } + } + + public void extractFieldMappingData(Map configPayload, responseData response, Boolean showInternalFields) { + List hiddenMapperFields = getHiddenMapperFields(configPayload); + + //used to store configuration maps from response + Map allMappingConfigurations = new Map(); + + Map valueMap; + for (String key : MAPPING_KEYS) { + valueMap = (Map)configPayload.get(key); + if (valueMap != null && !valueMap.isEmpty()) { + allMappingConfigurations.put(key, valueMap); + } + } + + HiddenMapperFieldAnalysis analysis = new HiddenMapperFieldAnalysis(hiddenMapperFields); + stripHiddenMapperFields(analysis, (Map) allMappingConfigurations.get(FIELD_DEFAULTS)); + stripHiddenMapperFields(analysis, (Map) allMappingConfigurations.get(FIELD_MAPPINGS)); + + if (showInternalFields) { + String mappedKey = IOMap_Config.get(INPUT_HIDDEN_MAPPER_FIELDS); + response.put(mappedKey, hiddenMapperFields); + } else { + allMappingConfigurations.remove(DEFAULT_MAPPINGS); + allMappingConfigurations.remove(REQUIRED_MAPPINGS); + } + + response.put(OUTPUT_ALL_MAPPING_CONFIGS, allMappingConfigurations); + } + + /** + * This method strips the fields defined by the analysis from the given data map. + * + * @param analysis the analysis object containing the processed lists of fields to remove + * @param data the data to strip fields from + */ + @TestVisible + private void stripHiddenMapperFields(HiddenMapperFieldAnalysis analysis, Map data) { + stripMapFields(data, analysis.topLevelRemovals); + Set allKeys = new Set(analysis.subLevelRemovals.keySet()); + allKeys.addAll(analysis.partialLevelRemovals.keySet()); + + for (String keyField : allKeys) { + if (data.containsKey(keyField) == false) { + continue; + } + + Map subData = (Map) data.get(keyField); + stripAllMapFields(subData, analysis.subLevelRemovals.get(keyField), analysis.partialLevelRemovals.get(keyField)); + } + } + + /** + * This just coordinates the calls to stripMapFields and stripPartialMapFields. + * + * @param data underlying object data + * @param fields full field names + * @param partials partial match field names + */ + @TestVisible + private void stripAllMapFields(Map data, List fields, List partials) { + stripMapFields(data, fields); + stripPartialMapFields(data, partials); + } + + /** + * Removes the specified fields from the data map. + * + * @param data underlying object data + * @param fields full field names + */ + @TestVisible + private void stripMapFields(Map data, List fields) { + if (fields == null || fields.isEmpty()) { + return; + } + + for (String field : fields) { + if (data.containsKey(field)) { + data.remove(field); + } + } + } + + /** + * Removes the specified partial match fields from the data map. + * + * @param data underlying object data + * @param partials partial match field names + */ + @TestVisible + private void stripPartialMapFields(Map data, List partials) { + if (partials == null || partials.isEmpty()) { + return; + } + + List regexPartials = new List(); + for (String partial : partials) { + regexPartials.add(Pattern.quote(partial + '.')); + } + + debugger.debug('ConfigPayloadHelper.stripPartialMapFields.regexPartials', regexPartials); + + String regex = '^(' + String.join(regexPartials, '|') + ')'; + + debugger.debug('ConfigPayloadHelper.stripPartialMapFields.regex', regex); + + Pattern partialPattern = Pattern.compile(regex); + for (String key : data.keySet()) { + Boolean match = partialPattern.matcher(key).find(); + debugger.debug('ConfigPayloadHelper.stripPartialMapFields.check', key); + debugger.debug('ConfigPayloadHelper.stripPartialMapFields.matches', match); + + if (match) { + data.remove(key); + } + } + } + + @TestVisible + private List getHiddenSyncPrefFields(Map configPayload) { + List hiddenSyncPrefFields = getListFieldFromPayload(INPUT_HIDDEN_SYNC_PREFS_FIELDS, configPayload, false); + if (hiddenSyncPrefFields == null) { + return new List { 'api_percentage_limit', 'cpq_term_unit' }; + } else { + return hiddenSyncPrefFields; + } + } + + @TestVisible + private List getHiddenMapperFields(Map configPayload) { + List hiddenMapperFields = getListFieldFromPayload(INPUT_HIDDEN_MAPPER_FIELDS, configPayload, false); + if (hiddenMapperFields == null) { + return new List { 'coupon', 'subscription_schedule.prebilling', 'customer.standard.coupon' }; + } else { + return hiddenMapperFields; + } + } + + public List getListFieldFromPayload(String listKey, Map payload) { + return getListFieldFromPayload(listKey, payload, true); + } + + public List getListFieldFromPayload(String listKey, Map payload, Boolean ensureList) { + List respFields; + List respList = new List(); + + try { + respFields = (List) payload.get(listKey); + + if (respFields == null) { + return ensureList ? new List() : null; + } + + for (Object obj : respFields) { + respList.add(String.valueOf(obj)); + } + } catch (Exception e) { + // data is not in the format we expect... + return ensureList ? new List() : null; + } + + return respList; + } + + @TestVisible + class HiddenMapperFieldAnalysis { + public List topLevelRemovals = new List(); + public Map> partialLevelRemovals = new Map>(); + public Map> subLevelRemovals = new Map>(); + + /** + * This class analyzes the hidden mapper fields and determines which fields, or patterns of fields, + * need to be removed from the payload. The naming convention of the fields is a little sporadic, so + * we need to be able to handle a few different scenarios. See the unit tests for examples. + * + * @param hiddenMapperFields the list of hidden mapper fields to analyze for permutations required for removal + */ + public HiddenMapperFieldAnalysis(List hiddenMapperFields) { + for (String hiddenField : hiddenMapperFields) { + if (hiddenField.contains('.')) { + Integer pos = hiddenField.indexOf('.'); + String key = hiddenField.substring(0, pos); + String val = hiddenField.substring(pos + 1); + + if (val.contains('.')) { + if (subLevelRemovals.containsKey(key)) { + subLevelRemovals.get(key).add(val); + } else { + subLevelRemovals.put(key, new List { val }); + } + } else { + if (partialLevelRemovals.containsKey(key)) { + partialLevelRemovals.get(key).add(val); + } else { + partialLevelRemovals.put(key, new List { val }); + } + } + } else { + topLevelRemovals.add(hiddenField); + } + } + } + } +} \ No newline at end of file diff --git a/sfdx/force-app/main/default/classes/ConfigPayloadHelper.cls-meta.xml b/sfdx/force-app/main/default/classes/ConfigPayloadHelper.cls-meta.xml new file mode 100644 index 0000000000..40d67933d0 --- /dev/null +++ b/sfdx/force-app/main/default/classes/ConfigPayloadHelper.cls-meta.xml @@ -0,0 +1,5 @@ + + + 54.0 + Active + diff --git a/sfdx/force-app/main/default/classes/ConfigPayloadHelper_Test.cls b/sfdx/force-app/main/default/classes/ConfigPayloadHelper_Test.cls index 710d06ae03..3dc0de7fa7 100644 --- a/sfdx/force-app/main/default/classes/ConfigPayloadHelper_Test.cls +++ b/sfdx/force-app/main/default/classes/ConfigPayloadHelper_Test.cls @@ -1,7 +1,99 @@ /** - * temp empty until jmater-c's branch goes in + * Created by jmather-c on 4/26/23. */ - @IsTest - public with sharing class ConfigPayloadHelper_Test { - } \ No newline at end of file +@IsTest +public with sharing class ConfigPayloadHelper_Test { + @IsTest + static void testStripHiddenMapperData() { + String jsonData = '{"foo": {"bar.baz": "biz"}, "customer": {"address.state": "MI", "address.country": "USA"}, "coupon": {}}'; + Map data = (Map) JSON.deserializeUntyped(jsonData); + Map customer = (Map) data.get('customer'); + Map foo = (Map) data.get('foo'); + List hiddenMapperFields = new List { 'coupon', 'foo.bar', 'customer.address.country' }; + ConfigPayloadHelper.HiddenMapperFieldAnalysis analysis = + new ConfigPayloadHelper.HiddenMapperFieldAnalysis(hiddenMapperFields); + + ConfigPayloadHelper helper = new ConfigPayloadHelper(); + + System.assert(data.containsKey('coupon'), 'coupon key exists'); + System.assert(data.containsKey('customer'), 'customer key exists'); + System.assert(customer.containsKey('address.country'), 'customer.address.country key exists'); + + helper.stripHiddenMapperFields(analysis, data); + + System.assert(false == data.containsKey('coupon'), 'coupon key does not exists'); + System.assert(data.containsKey('customer'), 'customer key exists'); + System.assert(false == customer.containsKey('address.country'), 'customer.address.country key does not exists'); + System.assert(data.containsKey('foo'), 'foo key exists'); + System.assert(false == foo.containsKey('bar.baz'), 'foo.bar.baz key does not exists'); + } + + @IsTest + static void testStripPartialMapFields() { + Map data = new Map { + 'a.a' => 'a', + 'a.b' => 'b', + 'ab.a' => 'c' + }; + List partialFields = new List { 'a' }; + ConfigPayloadHelper helper = new ConfigPayloadHelper(); + + helper.debugger = new Debug_Helper(true); + + helper.stripPartialMapFields(data, null); + helper.stripPartialMapFields(data, partialFields); + + System.assert(false == data.containsKey('a.a'), 'a.a key does not exists'); + System.assert(false == data.containsKey('a.b'), 'a.b key does not exists'); + System.assert(data.containsKey('ab.a'), 'b.a key does not exists'); + } + + @IsTest + static void testStripMapFields() { + Map data = new Map { + 'b.a' => 'c', + 'b.b' => 'd', + 'c.a' => 'e' + }; + List fullFields = new List { 'b.a' }; + ConfigPayloadHelper helper = new ConfigPayloadHelper(); + + helper.stripMapFields(data, null); + helper.stripMapFields(data, fullFields); + + System.assert(false == data.containsKey('b.a'), 'b.a key does not exists'); + System.assert(data.containsKey('b.b'), 'b.b key exists'); + System.assert(data.containsKey('c.a'), 'c.a key exists'); + } + + @IsTest + static void testGetListFieldFromPayload() { + ConfigPayloadHelper helper = new ConfigPayloadHelper(); + Map responseBody = new Map { + 'empty' => new List(), + 'some' => new List { 'A', 'B' }, + 'broken' => 'foo' + }; + + List nullRes = helper.getListFieldFromPayload('null', responseBody); + List nullResRaw = helper.getListFieldFromPayload('null', responseBody, false); + List someRes = helper.getListFieldFromPayload('some', responseBody); + List emptyRes = helper.getListFieldFromPayload('empty', responseBody); + List brokenRes = helper.getListFieldFromPayload('broken', responseBody, false); + + System.assertNotEquals(null, nullRes, 'null ref with ensured list is not null'); + System.assertEquals(0, nullRes.size(), 'null ref with ensure list is empty'); + + System.assertEquals(null, nullResRaw, 'null ref without ensure list is null'); + + System.assertNotEquals(null, emptyRes, 'empty ref is not null'); + System.assertEquals(0, emptyRes.size(), 'empty ref is empty'); + + System.assertNotEquals(null, someRes, 'some ref is not null'); + System.assertEquals(2, someRes.size(), 'some ref is not empty'); + + System.assertEquals(null, brokenRes, 'broken ref is null'); + } + +} diff --git a/sfdx/force-app/main/default/classes/constants.cls b/sfdx/force-app/main/default/classes/constants.cls index 1ad0b5b071..8d0d33d78c 100644 --- a/sfdx/force-app/main/default/classes/constants.cls +++ b/sfdx/force-app/main/default/classes/constants.cls @@ -1,49 +1,55 @@ /*** Stripe Toolbelt v2.0.1 ***/ public with sharing class constants { - public string ORG_DOMAIN { get; set; } + public String ORG_DOMAIN { get; set; } // Namespaces //fetches the namespace of the package in case we need to prefix it any where in apex for CRUD or accessing anything that will be namespaced in a package - private static FINAL List RAW_NAMESPACE = String.valueOf(constants.class).split('[.]', 2); - public static FINAL String NAMESPACE = RAW_NAMESPACE.size() > 1 ? RAW_NAMESPACE[0] : 'c'; + private static final List RAW_NAMESPACE = String.valueOf(constants.class).split('[.]', 2); + public static final String NAMESPACE = RAW_NAMESPACE.size() > 1 ? RAW_NAMESPACE[0] : 'c'; // if empty we are outside of a package - public static FINAL String NAMESPACE_API = NAMESPACE == 'c' ? '' : NAMESPACE + '__'; + public static final String NAMESPACE_API = NAMESPACE == 'c' ? '' : NAMESPACE + '__'; // Sandbox identifcation // TODO ORG_ID generation should move to where the instance type stuff is managed and should be passed to sentry - private static FINAL Organization ORG = [SELECT Id FROM Organization LIMIT 1]; - public static FINAL String ORG_ID = ORG.Id; - private static FINAL List SANDBOX_INSTANCE_TYPES = new List{ + private static final Organization ORG = [SELECT Id FROM Organization LIMIT 1]; + public static final String ORG_ID = ORG.Id; + private static final List SANDBOX_INSTANCE_TYPES = new List{ Sentry_Environment.InstanceType.SANDBOX, Sentry_Environment.InstanceType.SCRATCH_ORG }; - public static FINAL Boolean IS_SANDBOX = SANDBOX_INSTANCE_TYPES.contains(Sentry_Environment.getInstanceType()); + public static final Boolean IS_SANDBOX = SANDBOX_INSTANCE_TYPES.contains(Sentry_Environment.getInstanceType()); // oAuth callout urls - public static FINAL String SALESFORCE_INSTANCE_URI = System.URL.getSalesforceBaseURL().toExternalForm(); - public static FINAL String SALESFORCE_BASE_URI = 'https://' + (IS_SANDBOX ? 'test' : 'login') + '.salesforce.com'; - public static FINAL String SALESFORCE_OAUTH_URI = SALESFORCE_BASE_URI + '/services/oauth2'; + public static final String SALESFORCE_INSTANCE_URI = System.Url.getSalesforceBaseUrl().toExternalForm(); + public static final String SALESFORCE_BASE_URI = 'https://' + (IS_SANDBOX ? 'test' : 'login') + '.salesforce.com'; + public static final String SALESFORCE_OAUTH_URI = SALESFORCE_BASE_URI + '/services/oauth2'; //Stripe Ruby URIS - public static FINAL String RUBY_SERVICE_BASE_URI = 'https://salesforce.suitesync.io'; + public static final String RUBY_SERVICE_BASE_URI = 'https://salesforce.suitesync.io'; + public static final String RUBY_SERVICE_CONFIG_URI = '/v1/configuration'; + public static final String RUBY_SERVICE_TRANSLATE_URI = '/v1/translate'; + public static final String RUBY_SERVICE_TRANSLATE_ALL_URI = '/v1/translate_all'; //Setup Data Record Name - public static FINAL String SETUP_DATA_RECORD_NAME = 'SetupData'; - public static FINAL String PACKAGED_PERMISSION_SET_NAME = 'Stripe Connector Integration User'; + public static final String SETUP_DATA_RECORD_NAME = 'SetupData'; + public static final String PACKAGED_PERMISSION_SET_NAME = 'Stripe Connector Integration User'; public static final String FIELD_CURRENCY_ISO_CODE = 'CurrencyIsoCode'; public static final String RESOLUTION_STATUS_ERROR = 'Error'; - // this seemingly-useless code is used for the bootstrap process in the setup.page - public String getNamespace() { - return constants.NAMESPACE; - } + public static final String SYSTEM_STRIPE = 'stripe'; + public static final String SYSTEM_SALESFORCE = 'salesforce'; public constants() { // empty } + + // this seemingly-useless code is used for the bootstrap process in the setup.page + public String getNamespace() { + return constants.NAMESPACE; + } } diff --git a/sfdx/force-app/main/default/classes/responseData.cls b/sfdx/force-app/main/default/classes/responseData.cls index 92def1dcfb..271d1e894b 100644 --- a/sfdx/force-app/main/default/classes/responseData.cls +++ b/sfdx/force-app/main/default/classes/responseData.cls @@ -4,6 +4,9 @@ public with sharing class responseData { public Map results; public String error; + @TestVisible + private Debug_Helper debugger = new Debug_Helper(); + public responseData() { this.isSuccess = true; this.results = new Map(); @@ -12,6 +15,8 @@ public with sharing class responseData { public void addError(Exception e) { if(!Test.isRunningTest()) { Sentry.record(e); + } else { + this.debugger.debug('responseData.addError(Exception)', e); } this.error = e.getMessage(); @@ -24,12 +29,33 @@ public with sharing class responseData { } public void put(String key, Object val){ - results.put(key, val); + this.results.put(key, val); + } + + public String getString(String key) { + return String.valueOf(this.results.get(key)); + } + + public String getResultsJsonString() { + if (this.error != null) { + this.results.put('error', this.error); + } + + return getSanitizedJsonString(this.results); } - //removes extra underscores that are typically appended to the end of the namespace public String getJsonString() { - String jsonString = JSON.serialize(this); + Map response = new Map { + 'isSuccess' => this.isSuccess, + 'error' => this.error, + 'results' => this.results + }; + return getSanitizedJsonString(response); + } + + //removes extra underscores that are typically appended to the end of the namespace + public String getSanitizedJsonString(Object obj) { + String jsonString = JSON.serialize(obj); return jsonString.replaceAll('"' + constants.NAMESPACE_API + '([a-zA-Z0-9_]+?__(c|r))":', '"$1":'); } } diff --git a/sfdx/force-app/main/default/classes/setupAssistant.cls b/sfdx/force-app/main/default/classes/setupAssistant.cls index 83c4ae0abf..77e06f44df 100644 --- a/sfdx/force-app/main/default/classes/setupAssistant.cls +++ b/sfdx/force-app/main/default/classes/setupAssistant.cls @@ -1,16 +1,27 @@ public with sharing class setupAssistant { + @TestVisible + private static ConfigPayloadHelper payloadHelper = new ConfigPayloadHelper(); + + @TestVisible + private static Debug_Helper debugger = new Debug_Helper(); + + static Boolean isConnected() { + Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); + + return utilities.isConnected(stripeConnectRec); + } + + @AuraEnabled(Cacheable=true) + public static String getExportableConfigDownloadUrl() { + return Page.ConfigExportDownload.getUrl(); + } + // gets steps completed from setup data to manage/presist states @AuraEnabled public static String getSetupData() { responseData rd = new responseData(); - Boolean isConnected = true; + Boolean isConnected = isConnected(); try { - Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); - - if(stripeConnectRec.Id == null || !stripeConnectRec.Salesforce_Connected__c || !stripeConnectRec.Stripe_Connected__c) { - isConnected = false; - } - List setupDataQuery = [ SELECT Id, Steps_Completed__c, isSetupComplete__c FROM Setup_Data__c @@ -105,7 +116,7 @@ public with sharing class setupAssistant { } // make callout to ruby services configuration endpoint to see if the org is connected to Stripe and Salesforce - String route = constants.RUBY_SERVICE_BASE_URI + '/v1/configuration'; + String route = utilities.getPlatformConfigEndpoint(); HttpResponse response = utilities.makeCallout(route, 'GET'); Map errorBody; @@ -128,41 +139,26 @@ public with sharing class setupAssistant { */ responseBody = (Map)JSON.deserializeUntyped(response.getBody()); - //get connection status values from response - Map connectionStatus = (Map)responseBody.get('connection_status'); - stripeConnectRec.Stripe_Connected__c = (Boolean)connectionStatus.get('stripe'); - stripeConnectRec.Salesforce_Connected__c = (Boolean)connectionStatus.get('salesforce'); + payloadHelper.extractConnectionStatus(responseBody, stripeConnectRec); // TODO why do we care if it's a connected callback? Why can't we just return the same data regardless? if(isConnectedCallback) { - // TODO why do we need distinct strings here for the status? Can't we just pass a boolean? - if(!(Boolean)connectionStatus.get('salesforce')) { - rd.put('isSalesforceConnected','salesforceDisconnected'); - } else { - rd.put('isSalesforceConnected','salesforceConnected'); - } + // why do we need distinct strings here for the status? Can't we just pass a boolean? + // because of the 'freshConnection' value in the condition below. + String status = stripeConnectRec.Salesforce_Connected__c ? 'salesforceConnected' : 'salesforceDisconnected'; + rd.put('isSalesforceConnected', status); - // TODO same Q here: why can't we just use a boolean here? - if(!(Boolean)connectionStatus.get('stripe')) { - rd.put('isStripeConnected','stripeDisconnected'); - } else { - rd.put('isStripeConnected','stripeConnected'); - } + status = stripeConnectRec.Stripe_Connected__c ? 'stripeConnected' : 'stripeDisconnected'; + rd.put('isStripeConnected', status); } else { /*if the response shows they are connected we will udpate the Setup Connection custom setting record to signify they are connected We return a connection status of freshConnection to determine if we should show a toast message of success or not*/ - if(systemConnected == 'stripe') { - if (!(Boolean)connectionStatus.get('stripe')) { - rd.put('isStripeConnected','stripeDisconnected'); - } else { - rd.put('isStripeConnected','freshConnection'); - } - } else if(systemConnected == 'salesforce') { - if (!(Boolean)connectionStatus.get('salesforce')) { - rd.put('isSalesforceConnected','salesforceDisconnected'); - } else { - rd.put('isSalesforceConnected','freshConnection'); - } + if(systemConnected == constants.SYSTEM_STRIPE) { + String status = stripeConnectRec.Stripe_Connected__c ? 'freshConnection' : 'stripeDisconnected'; + rd.put('isStripeConnected', status); + } else if(systemConnected == constants.SYSTEM_SALESFORCE) { + String status = stripeConnectRec.Stripe_Connected__c ? 'freshConnection' : 'salesforceDisconnected'; + rd.put('isSalesforceConnected', status); } } @@ -181,11 +177,10 @@ public with sharing class setupAssistant { responseData rd = new responseData(); Boolean isConnected = false; try { - Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); + isConnected = isConnected(); - if(stripeConnectRec.Id != null && stripeConnectRec.Salesforce_Connected__c && stripeConnectRec.Stripe_Connected__c) { - isConnected = true; - if(isConnectedCallback == true) { + if (isConnected) { + if (isConnectedCallback == true) { //builds a map of fields lists by object Map> fieldListByObjectMap = new Map>{ 'OrderItem' => utilities.getListOfFieldsByObject('OrderItem'), @@ -267,50 +262,24 @@ public with sharing class setupAssistant { responseData rd = new responseData(); Boolean isConnected = false; try { - Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); + isConnected = isConnected(); - if(stripeConnectRec.Id != null && stripeConnectRec.Salesforce_Connected__c && stripeConnectRec.Stripe_Connected__c) { + if (isConnected) { //used to store configuration maps from response Map allMappingConfigurations = new Map(); //construct call out to rubys configuration endpoint - isConnected = true; - String route = constants.RUBY_SERVICE_BASE_URI + '/v1/configuration'; + String route = utilities.getPlatformConfigEndpoint(); HttpResponse response = utilities.makeCallout(route,'GET'); //if the call out is successful pull out all mapping objects and add to list Map responseBody; - if(response.getStatusCode() == 200) { + if (response.getStatusCode() == 200) { responseBody = (Map)JSON.deserializeUntyped(response.getBody()); - rd.put('configurationHash', (String)responseBody.get('configuration_hash')); - Map fieldMappingsMap = (Map)responseBody.get('field_mappings'); - if (fieldMappingsMap != null && !fieldMappingsMap.isEmpty()) { - allMappingConfigurations.put('field_mappings', fieldMappingsMap); - } - - Map defaultMappingsMap = (Map)responseBody.get('default_mappings'); - if (defaultMappingsMap != null && !defaultMappingsMap.isEmpty()) { - allMappingConfigurations.put('default_mappings', defaultMappingsMap); - } - - Map requiredMappingsMap = (Map)responseBody.get('required_mappings'); - if (requiredMappingsMap != null && !requiredMappingsMap.isEmpty()) { - allMappingConfigurations.put('required_mappings', requiredMappingsMap); - } - - Map fieldDefaultsMappingsMap = (Map)responseBody.get('field_defaults'); - if (fieldDefaultsMappingsMap != null && !fieldDefaultsMappingsMap.isEmpty()) { - allMappingConfigurations.put('field_defaults', fieldDefaultsMappingsMap); - } - rd.put('allMappingConfigurations', allMappingConfigurations); + payloadHelper.extractConfigHash(responseBody, rd); - List hiddenMapperFields = getListFieldFromPayload('hidden_mapper_fields', responseBody, false); - if (hiddenMapperFields == null) { - rd.put('hiddenMapperFields', new List { 'coupon', 'subscription_schedule.prebilling', 'customer.standard.coupon' }); - } else { - rd.put('hiddenMapperFields', hiddenMapperFields); - } + payloadHelper.extractFieldMappingData(responseBody, rd, true); } else { errorLogger.create('getMappingConfigurations', String.valueOf(response.getStatusCode()), (String)response.getStatus(), 'Failed to get mapping configuration from ruby service.'); } @@ -327,9 +296,8 @@ public with sharing class setupAssistant { public static String saveMappingConfigurations(String jsonMappingConfigurationsObject) { responseData rd = new responseData(); try { - Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); - if(stripeConnectRec.Id != null && stripeConnectRec.Salesforce_Connected__c && stripeConnectRec.Stripe_Connected__c) { - String route = constants.RUBY_SERVICE_BASE_URI + '/v1/configuration'; + if(isConnected()) { + String route = utilities.getPlatformConfigEndpoint(); HttpResponse response = utilities.makeCallout(route, 'PUT', jsonMappingConfigurationsObject); rd = utilities.validateCalloutResponse(response, 'saveMappingConfigurations'); } @@ -347,13 +315,13 @@ public with sharing class setupAssistant { rd.put('isSandbox', constants.IS_SANDBOX); Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); - if(stripeConnectRec.Id == null || !stripeConnectRec.Salesforce_Connected__c || !stripeConnectRec.Stripe_Connected__c) { + if(utilities.isConnected(stripeConnectRec) == false) { rd.put('isConnected', false); return rd.getJsonString(); } //make callout to ruby services configuration endpoint to get stored stripe setting values - String route = constants.RUBY_SERVICE_BASE_URI + '/v1/configuration'; + String route = utilities.getPlatformConfigEndpoint(); HttpResponse response = utilities.makeCallout(route, 'GET'); Map errorBody; @@ -361,78 +329,29 @@ public with sharing class setupAssistant { /*if we get a 200 response code from the ruby service we will check the connection status object in the payload to determine weather the user successfully connected to stripe or not*/ - if(response.getStatusCode() == 200) { + if (response.getStatusCode() == 200) { responseBody = (Map)JSON.deserializeUntyped(response.getBody()); - //get connection status values from response - Map connectionStatus = (Map)responseBody.get('connection_status'); + payloadHelper.extractConnectionStatus(responseBody, stripeConnectRec); //ensure we are still connected - if(!(Boolean)connectionStatus.get('stripe') || !(Boolean)connectionStatus.get('salesforce')) { + Boolean isConnected = stripeConnectRec.Stripe_Connected__c && stripeConnectRec.Salesforce_Connected__c; + if (isConnected == false) { rd.put('isConnected', false); - stripeConnectRec.Stripe_Connected__c = (Boolean)connectionStatus.get('stripe'); - stripeConnectRec.Salesforce_Connected__c = (Boolean)connectionStatus.get('salesforce'); upsert stripeConnectRec; return rd.getJsonString(); } - - rd.put('enabled', (Boolean)responseBody.get('enabled')); - rd.put('configurationHash', (String)responseBody.get('configuration_hash')); - rd.put('default_currency', UserInfo.getDefaultCurrency()); - - Map settings = (Map)responseBody.get('settings'); - - List settingsFields = new List { - 'sync_record_retention', - 'sync_start_date', - 'api_percentage_limit', - 'cpq_term_unit', - 'cpq_prorate_precision' - }; - - List booleanFields = new List { - 'polling_enabled' - }; - List connectionReadOnlyFields = new List { - 'stripe_account_id', - 'last_synced' - }; - - //gets non read only settings fields from response for frontend - for(String settingsField : settingsFields) { - rd.put(settingsField, String.valueOf(settings.get(settingsField))); - if (settingsField == 'sync_record_retention') { - rd.put(settingsField, String.valueOf(settings.get(settingsField))); - stripeConnectRec.Sync_Record_Retention__c = String.valueOf(settings.get(settingsField)); - upsert stripeConnectRec; - } - } - - //gets boolean connection fields from response for frontend - for(String booleanField : booleanFields) { - if (settings.get(booleanField) == null) { - rd.put(booleanField, false); - } else { - rd.put(booleanField, Boolean.valueOf(settings.get(booleanField))); - } - } - - //gets read only connection fields from response for frontend - for(String readOnlyField : connectionReadOnlyFields) { - rd.put(readOnlyField, String.valueOf(connectionStatus.get(readOnlyField))); - } - - List hiddenSyncPrefFields = getListFieldFromPayload('hidden_sync_pref_fields', responseBody, false); - if (hiddenSyncPrefFields == null) { - rd.put('hiddenSyncPrefsFields', new List { 'api-percentage-limit', 'cpq-term-unit' }); - } else { - rd.put('hiddenSyncPrefsFields', hiddenSyncPrefFields); + payloadHelper.extractConfigHash(responseBody, rd); + payloadHelper.extractSettingsData(responseBody, rd, true); + String recRetention = rd.getString(ConfigPayloadHelper.SYNC_RECORD_RETENTION); + if (stripeConnectRec.Sync_Record_Retention__c != recRetention) { + stripeConnectRec.Sync_Record_Retention__c = recRetention; + upsert stripeConnectRec; } } rd.put('isConnected', true); - rd.put('isCpqInstalled', utilities.isCpqEnabled()); } catch (Exception e) { rd.addError(e); } @@ -440,34 +359,6 @@ public with sharing class setupAssistant { return rd.getJsonString(); } - @TestVisible - private static List getListFieldFromPayload(String listKey, Map responseBody) { - return getListFieldFromPayload(listKey, responseBody, true); - } - - @TestVisible - private static List getListFieldFromPayload(String listKey, Map responseBody, Boolean ensureList) { - List respFields; - List respList = new List(); - - try { - respFields = (List) responseBody.get(listKey); - - if (respFields == null) { - return ensureList ? new List() : null; - } - - for (Object obj : respFields) { - respList.add(String.valueOf(obj)); - } - } catch (Exception e) { - // data is not in the format we expect... - return ensureList ? new List() : null; - } - - return respList; - } - //gets multi currency options if it is enablec in the org @AuraEnabled public static String getMulticurrencySelectionOptions() { @@ -511,31 +402,38 @@ public with sharing class setupAssistant { public static String saveSyncPreferences(String defaultCurrency, String syncRecordRetention, String syncStartDate, String apiPercentageLimit, String cpqTermUnit, String cpqProratePrecision, Boolean pollingEnabled, String configurationHash) { responseData rd = new responseData(); try { - Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); - if(stripeConnectRec.Id != null) { + if (isConnected()) { + Map settings = new Map { + 'polling_enabled' => pollingEnabled, + 'sync_start_date' => syncStartDate, + 'sync_record_retention' => syncRecordRetention, + 'default_currency' => UserInfo.getDefaultCurrency(), + 'cpq_term_unit' => cpqTermUnit, + 'cpq_prorate_precision' => cpqProratePrecision + }; + + if (apiPercentageLimit != null) { + settings.put('api_percentage_limit', apiPercentageLimit); + } + + if (cpqTermUnit != null) { + settings.put('cpq_term_unit', cpqTermUnit); + } + Map resquestBody = new Map { 'configuration_hash' => configurationHash, - 'settings' => new Map { - 'polling_enabled' => pollingEnabled, - 'api_percentage_limit' => apiPercentageLimit, - 'sync_start_date' => syncStartDate, - 'sync_record_retention' => syncRecordRetention, - 'default_currency' => UserInfo.getDefaultCurrency(), - 'multicurrency_enabled' => UserInfo.isMultiCurrencyOrganization(), - 'cpq_term_unit' => cpqTermUnit, - 'cpq_prorate_precision' => cpqProratePrecision - } + 'settings' => settings }; - String route = constants.RUBY_SERVICE_BASE_URI + '/v1/configuration'; + String route = utilities.getPlatformConfigEndpoint(); HttpResponse response = utilities.makeCallout(route, 'PUT', JSON.serialize(resquestBody)); rd = utilities.validateCalloutResponse(response, 'saveSyncPreferences'); } - } catch (Exception e) { + } catch (Exception e) { rd.addError(e); - } - return rd.getJsonString(); + } + return rd.getJsonString(); } //activates polling in the ruby service @@ -543,8 +441,7 @@ public with sharing class setupAssistant { public static String activatePolling(String syncStartDate, Boolean isConfigEnabled, String configurationHash) { responseData rd = new responseData(); try { - Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); - if(stripeConnectRec.Id != null) { + if (isConnected()) { Map resquestBody = new Map { 'enabled' => isConfigEnabled, 'configuration_hash' => configurationHash, @@ -554,7 +451,7 @@ public with sharing class setupAssistant { } }; - String route = constants.RUBY_SERVICE_BASE_URI + '/v1/configuration'; + String route = utilities.getPlatformConfigEndpoint(); HttpResponse response = utilities.makeCallout(route, 'PUT', JSON.serialize(resquestBody)); rd = utilities.validateCalloutResponse(response, 'activatePolling'); } @@ -570,9 +467,7 @@ public with sharing class setupAssistant { responseData rd = new responseData(); Boolean isSyncRecordDispactched = false; try { - Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); - - if(stripeConnectRec.Id != null && stripeConnectRec.Salesforce_Connected__c && stripeConnectRec.Stripe_Connected__c) { + if (isConnected()) { //get sync record information for ruby service List syncRecordList = [SELECT Id, Primary_Record_ID__c, Secondary_Record_ID__c, Resolution_Status__c, Secondary_Object_Type__c, Primary_Object_Type__c FROM Sync_Record__c @@ -588,7 +483,7 @@ public with sharing class setupAssistant { Sync_Record__c recordToSync = syncRecordList[0]; - String route = constants.RUBY_SERVICE_BASE_URI + '/v1/translate'; + String route = utilities.getPlatformTranslateEndpoint(); Map body = new Map{ 'object_type' => (String)recordToSync.Primary_Object_Type__c, @@ -599,7 +494,7 @@ public with sharing class setupAssistant { HttpResponse response = utilities.makeCallout(route, 'POST', JSON.serialize(body)); - if(response.getStatusCode() == 200) { + if (response.getStatusCode() == 200) { isSyncRecordDispactched = true; } else { rd.put('isSyncRecordDispactched', false); @@ -615,59 +510,43 @@ public with sharing class setupAssistant { //gets all saved filter settings @AuraEnabled - public static String getFilterSettings() { - responseData rd = new responseData(); - try { + public static String getFilterSettings() { + responseData rd = new responseData(); + try { Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); - if (stripeConnectRec.Id == null || !stripeConnectRec.Salesforce_Connected__c || !stripeConnectRec.Stripe_Connected__c) { + if (utilities.isConnected(stripeConnectRec) == false) { rd.put('isConnected', false); return rd.getJsonString(); } - //make callout to ruby services configuration endpoint to get stored stripe setting values - String route = constants.RUBY_SERVICE_BASE_URI + '/v1/configuration'; + String route = utilities.getPlatformConfigEndpoint(); HttpResponse response = utilities.makeCallout(route, 'GET'); - if(response.getStatusCode() != 200) { + if (response.getStatusCode() != 200) { errorLogger.create('getFilterSettings', String.valueOf(response.getStatusCode()), (String)response.getStatus(), ''); rd.put('isConnected', false); return rd.getJsonString(); } + Map responseBody = (Map)JSON.deserializeUntyped(response.getBody()); //get connection status values from response - Map connectionStatus = (Map)responseBody.get('connection_status'); + Boolean isConnected = payloadHelper.extractConnectionStatus(responseBody, stripeConnectRec); //ensure we are still connected - if(!(Boolean)connectionStatus.get('stripe') || !(Boolean)connectionStatus.get('salesforce')) { + if (isConnected == false) { rd.put('isConnected', false); - stripeConnectRec.Stripe_Connected__c = (Boolean)connectionStatus.get('stripe'); - stripeConnectRec.Salesforce_Connected__c = (Boolean)connectionStatus.get('salesforce'); upsert stripeConnectRec; return rd.getJsonString(); } - Map settings = (Map)responseBody.get('settings'); - - Map filters = (Map)settings.get('filters'); - - List filterFields = new List { - 'Product2', - 'Order', - 'Account', - 'PricebookEntry' - }; - - //gets filter fields and values for front end - for(String filterField : filterFields) { - rd.put(filterField, String.valueOf(filters.get(filterField))); - } - + payloadHelper.extractFilterSettingsData(responseBody, rd); } catch (Exception e) { rd.addError(e); } + rd.put('isConnected', true); return rd.getJsonString(); } @@ -716,7 +595,7 @@ public with sharing class setupAssistant { } Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); - if (stripeConnectRec.Id == null || !stripeConnectRec.Salesforce_Connected__c || !stripeConnectRec.Stripe_Connected__c) { + if (utilities.isConnected(stripeConnectRec) == false) { rd.put('isFiltersSaved', false); rd.put('isConnected', false); return rd.getJsonString(); @@ -727,7 +606,7 @@ public with sharing class setupAssistant { 'filters' => queryFilterMap } }; - String route = constants.RUBY_SERVICE_BASE_URI + '/v1/configuration'; + String route = utilities.getPlatformConfigEndpoint(); HttpResponse response = utilities.makeCallout(route, 'PUT', JSON.serialize(resquestBody)); if(response.getStatusCode() != 200) { @@ -747,22 +626,29 @@ public with sharing class setupAssistant { //sends object name for all records syncing to the ruby service @AuraEnabled public static String syncAllRecords(String objectType) { + debugger.debug('setupAssistant.syncAllRecords', 'objectType: ' + objectType); responseData rd = new responseData(); try { Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); - if (stripeConnectRec.Id == null || !stripeConnectRec.Salesforce_Connected__c || !stripeConnectRec.Stripe_Connected__c) { + if (utilities.isConnected(stripeConnectRec) == false) { rd.put('isConnected', false); return rd.getJsonString(); } - String route = constants.RUBY_SERVICE_BASE_URI + '/v1/translate_all'; + debugger.debug('setupAssistant.syncAllRecords', 'is connected'); + + String route = utilities.getPlatformTranslateAllEndpoint(); Map body = new Map{ 'object_type' => objectType }; - HttpResponse response = utilities.makeCallout(route, 'POST', JSON.serialize(body)); + debugger.debug('setupAssistant.syncAllRecords', 'route: ' + route); + debugger.debug('setupAssistant.syncAllRecords', 'body: ' + JSON.serialize(body)); + HttpResponse response = utilities.makeCallout(route, 'POST', JSON.serialize(body)); + + debugger.debug('setupAssistant.syncAllRecords', 'Got response status code: ' + response.getStatusCode()); if (response.getStatusCode() != 200) { rd.put('syncAllRecordsDispatched', false); @@ -806,8 +692,15 @@ public with sharing class setupAssistant { return rd.getJsonString(); } + Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); + + if (utilities.isConnected(stripeConnectRec) == false) { + rd.put('isConnected', false); + return rd.getJsonString(); + } + //here is where we need to validate if its record or not - String route = constants.RUBY_SERVICE_BASE_URI + '/v1/translate'; + String route = utilities.getPlatformTranslateEndpoint(); Map body = new Map { 'object_type' => objectName, diff --git a/sfdx/force-app/main/default/classes/test_setupAssistant.cls b/sfdx/force-app/main/default/classes/test_setupAssistant.cls index cfa8a5f932..9c851c8c24 100644 --- a/sfdx/force-app/main/default/classes/test_setupAssistant.cls +++ b/sfdx/force-app/main/default/classes/test_setupAssistant.cls @@ -1,32 +1,6 @@ @isTest public with sharing class test_setupAssistant { - @IsTest - static void testGetListFieldFromPayload() { - Map responseBody = new Map { - 'empty' => new List(), - 'some' => new List { 'A', 'B' }, - 'broken' => 'foo' - }; - - List nullRes = setupAssistant.getListFieldFromPayload('null', responseBody); - List nullResRaw = setupAssistant.getListFieldFromPayload('null', responseBody, false); - List someRes = setupAssistant.getListFieldFromPayload('some', responseBody); - List emptyRes = setupAssistant.getListFieldFromPayload('empty', responseBody); - List brokenRes = setupAssistant.getListFieldFromPayload('broken', responseBody, false); - - System.assertNotEquals(null, nullRes, 'null ref with ensured list is not null'); - System.assertEquals(0, nullRes.size(), 'null ref with ensure list is empty'); - - System.assertEquals(null, nullResRaw, 'null ref without ensure list is null'); - - System.assertNotEquals(null, emptyRes, 'empty ref is not null'); - System.assertEquals(0, emptyRes.size(), 'empty ref is empty'); - - System.assertNotEquals(null, someRes, 'some ref is not null'); - System.assertEquals(2, someRes.size(), 'some ref is not empty'); - - System.assertEquals(null, brokenRes, 'broken ref is null'); - } + static Debug_Helper debugger = new Debug_Helper(); @isTest static public void testSetupAssistant_getSetupData() { @@ -257,7 +231,7 @@ public with sharing class test_setupAssistant { System.assertEquals((Boolean)responseMap.get('isSuccess'), false); } - @isTest + @IsTest static public void testSetupAssistant_getMappingConfigurations() { Setup_Connection_Data__mdt testSetupData = getTestStripeConnectionKey(); setTestGlobalKey(testSetupData); @@ -365,8 +339,6 @@ public with sharing class test_setupAssistant { System.assertEquals('USD', (String)resultsMap.get('default_currency')); System.assertEquals('2 days', (String)resultsMap.get('sync_record_retention')); System.assertEquals('yesterday', (String)resultsMap.get('sync_start_date')); - System.assertEquals('1000515', (String)resultsMap.get('api_percentage_limit')); - System.assertEquals('monthly', (String)resultsMap.get('cpq_term_unit')); System.assertEquals('month', (String)resultsMap.get('cpq_prorate_precision')); System.assertEquals(true, (Boolean)resultsMap.get('enabled')); System.assertEquals('1234', (String)resultsMap.get('configurationHash')); @@ -515,9 +487,10 @@ public with sharing class test_setupAssistant { Map resultsMap = (Map)responseMap.get('results'); - System.assertEquals('testOrderFilter', (String)resultsMap.get('Order')); - System.assertEquals('testAccountFilter', (String)resultsMap.get('Account')); - System.assertEquals('testProduct2Filter', (String)resultsMap.get('Product2')); + System.assertEquals('testOrderFilter', (String)resultsMap.get('order_filter')); + System.assertEquals('testAccountFilter', (String)resultsMap.get('account_filter')); + System.assertEquals('testProduct2Filter', (String)resultsMap.get('product_filter')); + System.assertEquals('testPricebookEntryFilter', (String)resultsMap.get('pricebook_entry_filter')); } @isTest @@ -567,13 +540,19 @@ public with sharing class test_setupAssistant { Test.startTest(); Test.setMock(HttpCalloutMock.class, new stripeSuccessMock()); String response = setupAssistant.syncAllRecords('Product2'); - Map responseMap = (Map)JSON.deserializeUntyped(response); Test.stopTest(); - System.assert((Boolean)responseMap.get('isSuccess'), responseMap.get('error')); + debugger.debug('testSetupAssistant_syncAllRecords', 'got test response: ' + response); + Map responseMap = (Map)JSON.deserializeUntyped(response); + + debugger.debug('testSetupAssistant_syncAllRecords', 'response map: ' + responseMap); + System.assert((Boolean)responseMap.get('isSuccess'), 'Is success'); + System.assertEquals(null, responseMap.get('error'), 'No error provided'); Map resultsMap = (Map)responseMap.get('results'); - System.assertEquals(true, (Boolean)resultsMap.get('syncAllRecordsDispatched')); + debugger.debug('testSetupAssistant_syncAllRecords', 'results map: ' + resultsMap); + System.assert((Boolean)resultsMap.get('isConnected'), 'Is connected'); + System.assertEquals(true, (Boolean)resultsMap.get('syncAllRecordsDispatched'), 'all records dispatched'); } public static Setup_Connection_Data__mdt getTestStripeConnectionKey() { @@ -668,10 +647,16 @@ public with sharing class test_setupAssistant { 'filters' => new Map { 'Product2' => 'testProduct2Filter', 'Order' => 'testOrderFilter', - 'Account' => 'testAccountFilter' + 'Account' => 'testAccountFilter', + 'PricebookEntry' => 'testPricebookEntryFilter' } }); + responseBody.put('hidden_sync_pref_fields', new List { + 'api_percentage_limit', + 'cpq_term_unit' + }); + res.setBody(Json.serialize(responseBody)); res.setStatusCode(200); } @@ -688,127 +673,148 @@ public with sharing class test_setupAssistant { } } - private class stripeSuccessMock implements HttpCalloutMock { - public Integer calls = 0; - public HttpResponse respond(HttpRequest req) { - calls++; + private class stripeSuccessMock implements HttpCalloutMock { + public Integer calls = 0; + public HttpResponse respond(HttpRequest req) { + calls++; - HttpResponse res = new HttpResponse(); - System.assertEquals(req.getHeader('Salesforce-Account-Id'), (String)constants.ORG_ID); - System.assertEquals(req.getHeader('Salesforce-Type'), String.valueOf(Sentry_Environment.getInstanceType())); - System.assertEquals(req.getHeader('Salesforce-Package-Namespace'), constants.NAMESPACE); - if(utilities.isPackagedEnvironment()) { - System.assertEquals(req.getHeader('Salesforce-Package-Id'), (String)utilities.getPackageVersionString()); - } - Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); + HttpResponse res = new HttpResponse(); + System.assertEquals(req.getHeader('Salesforce-Account-Id'), (String)constants.ORG_ID); + System.assertEquals(req.getHeader('Salesforce-Type'), String.valueOf(Sentry_Environment.getInstanceType())); + System.assertEquals(req.getHeader('Salesforce-Package-Namespace'), constants.NAMESPACE); + if (utilities.isPackagedEnvironment()) { + System.assertEquals(req.getHeader('Salesforce-Package-Id'), (String)utilities.getPackageVersionString()); + } + Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); - if (req.getEndpoint().endsWith('configuration')) { - System.assertEquals(req.getHeader('Salesforce-Key'), (String)stripeConnectRec.API_Key__c); - Map fieldMap = new Map(); - Map testFieldMap = new Map(); - testFieldMap.put('testFieldMapping', 'test'); - fieldMap.put('field_mappings', (Object)testFieldMap); - - Map defaultMap = new Map(); - Map testdefaultMap = new Map(); - testdefaultMap.put('testDefaultMap', 'test'); - defaultMap.put('default_mappings',(Object)testdefaultMap); - - Map requiredMapping = new Map(); - Map testRequiredMapping = new Map(); - testRequiredMapping.put('testRequiredMapping', 'test'); - requiredMapping.put('required_mappings', (Object)testRequiredMapping); - - Map fieldDefaultMap = new Map(); - Map testFieldDefaultMap = new Map(); - testFieldDefaultMap.put('testFieldDefaultMap', 'test'); - fieldDefaultMap.put('field_defaults', (Object)testFieldDefaultMap); - - Map responseBody = new Map(); - responseBody.put('configuration_hash', '1234'); - responseBody.put('field_mappings', (Object)fieldMap); - responseBody.put('default_mappings', (Object)defaultMap); - responseBody.put('required_mappings', (Object)requiredMapping); - responseBody.put('field_defaults', (Object)fieldDefaultMap); - - res.setBody(Json.serialize(responseBody)); - res.setStatusCode(200); - } else if(req.getEndpoint().endsWith('translate')) { - System.assertEquals(req.getHeader('Salesforce-Key'), (String)stripeConnectRec.API_Key__c); - Map responseObject = new Map(); - - Map responseBody = new Map(); - responseBody.put('retry_status', (Object)responseObject); - - res.setBody(Json.serialize(responseBody)); - res.setStatusCode(200); - } else if(req.getEndpoint().endsWith('openapi.json')) { - System.assertEquals(req.getHeader('Salesforce-Key'), ''); - List testFormattedFieldsList = new List { - 'testField1', - 'testField2', - 'testField3', - 'testField4', - 'testField5' - }; - Map responseBody = new Map(); - responseBody.put('formattedStripeCustomerFields', (Object)testFormattedFieldsList); - responseBody.put('formattedStripeProductItemFields', (Object)testFormattedFieldsList); - responseBody.put('formattedStripeSubscriptionFields', (Object)testFormattedFieldsList); - responseBody.put('formattedStripeSubscriptionItemFields', (Object)testFormattedFieldsList); - responseBody.put('formattedStripeSubscriptionSchedulePhaseFields', (Object)testFormattedFieldsList); - responseBody.put('formattedStripePriceFields', (Object)testFormattedFieldsList); - res.setBody(Json.serialize(responseBody)); - res.setStatusCode(200); - } else if(req.getEndpoint().endsWith('translate_all')) { - res.setStatusCode(200); - } + if (req.getEndpoint().endsWith('configuration')) { + System.assertEquals(req.getHeader('Salesforce-Key'), (String)stripeConnectRec.API_Key__c); + Map fieldMap = new Map(); + Map testFieldMap = new Map(); + testFieldMap.put('testFieldMapping', 'test'); + fieldMap.put('field_mappings', (Object)testFieldMap); + + Map defaultMap = new Map(); + Map testdefaultMap = new Map(); + testdefaultMap.put('testDefaultMap', 'test'); + defaultMap.put('default_mappings',(Object)testdefaultMap); + + Map requiredMapping = new Map(); + Map testRequiredMapping = new Map(); + testRequiredMapping.put('testRequiredMapping', 'test'); + requiredMapping.put('required_mappings', (Object)testRequiredMapping); + + Map fieldDefaultMap = new Map(); + Map testFieldDefaultMap = new Map(); + testFieldDefaultMap.put('testFieldDefaultMap', 'test'); + fieldDefaultMap.put('field_defaults', (Object)testFieldDefaultMap); - return res; - } - } + Map responseBody = new Map(); + responseBody.put('configuration_hash', '1234'); + responseBody.put('field_mappings', (Object)fieldMap); + responseBody.put('default_mappings', (Object)defaultMap); + responseBody.put('required_mappings', (Object)requiredMapping); + responseBody.put('field_defaults', (Object)fieldDefaultMap); - private class stripeNotConnectedMock implements HttpCalloutMock { - public HttpResponse respond(HttpRequest req) { - HttpResponse res = new HttpResponse(); - if(req.getEndpoint().endsWith('configuration')) { - - Map responseBody = new Map(); - responseBody.put('connection_status', (Object)new Map{ - 'salesforce' => false, - 'stripe' => false, - 'last_synced' => 'test', - 'stripe_account_id' => 1234 - }); - responseBody.put('settings', (Object)new Map{ - 'default_currency' => 'USD', - 'sync_record_retention' => '2 days', - 'sync_start_date' => 'yesterday', - 'api_percentage_limit' => 1000515, - 'cpq_term_unit' => 'monthly', - 'filters' => new Map { - 'Product2' => 'testProduct2Filter', - 'Order' => 'testOrderFilter', - 'Account' => 'testAccountFilter' - } - }); - - res.setBody(Json.serialize(responseBody)); - res.setStatusCode(200); - } - return res; + res.setBody(JSON.serialize(responseBody)); + res.setStatusCode(200); + } else if(req.getEndpoint().endsWith('translate')) { + System.assertEquals(req.getHeader('Salesforce-Key'), (String)stripeConnectRec.API_Key__c); + Map responseObject = new Map(); + + Map responseBody = new Map(); + responseBody.put('retry_status', (Object)responseObject); + + res.setBody(JSON.serialize(responseBody)); + res.setStatusCode(200); + } else if(req.getEndpoint().endsWith('openapi.json')) { + System.assertEquals(req.getHeader('Salesforce-Key'), ''); + List testFormattedFieldsList = new List { + 'testField1', + 'testField2', + 'testField3', + 'testField4', + 'testField5' + }; + Map responseBody = new Map(); + responseBody.put('formattedStripeCustomerFields', (Object)testFormattedFieldsList); + responseBody.put('formattedStripeProductItemFields', (Object)testFormattedFieldsList); + responseBody.put('formattedStripeSubscriptionFields', (Object)testFormattedFieldsList); + responseBody.put('formattedStripeSubscriptionItemFields', (Object)testFormattedFieldsList); + responseBody.put('formattedStripeSubscriptionSchedulePhaseFields', (Object)testFormattedFieldsList); + responseBody.put('formattedStripePriceFields', (Object)testFormattedFieldsList); + res.setBody(JSON.serialize(responseBody)); + res.setStatusCode(200); + } else if(req.getEndpoint().endsWith('translate_all')) { + res.setStatusCode(200); } + + return res; } + } - private class stripeConnectFailMock implements HttpCalloutMock { - public HttpResponse respond(HttpRequest req) { - HttpResponse res = new HttpResponse(); + private class stripeNotConnectedMock implements HttpCalloutMock { + public HttpResponse respond(HttpRequest req) { + HttpResponse res = new HttpResponse(); + if(req.getEndpoint().endsWith('configuration')) { Map responseBody = new Map(); + responseBody.put('connection_status', (Object)new Map{ + 'salesforce' => false, + 'stripe' => false, + 'last_synced' => 'test', + 'stripe_account_id' => 1234 + }); + responseBody.put('settings', (Object)new Map{ + 'default_currency' => 'USD', + 'sync_record_retention' => '2 days', + 'sync_start_date' => 'yesterday', + 'api_percentage_limit' => 1000515, + 'cpq_term_unit' => 'monthly', + 'filters' => new Map { + 'Product2' => 'testProduct2Filter', + 'Order' => 'testOrderFilter', + 'Account' => 'testAccountFilter' + } + }); + res.setBody(Json.serialize(responseBody)); - res.setStatusCode(500); - return res; + res.setStatusCode(200); } + return res; } + } + + private class stripeConnectFailMock implements HttpCalloutMock { + public HttpResponse respond(HttpRequest req) { + HttpResponse res = new HttpResponse(); + + Map responseBody = new Map(); + res.setBody(Json.serialize(responseBody)); + res.setStatusCode(500); + return res; + } + } + + public class UnifiedConfigMock implements HttpCalloutMock { + public HttpResponse respond(HttpRequest req) { + stripeConnectionSuccessMock mock1 = new stripeConnectionSuccessMock(); + stripeSuccessMock mock2 = new stripeSuccessMock(); + + HttpResponse res1 = mock1.respond(req); + HttpResponse res2 = mock2.respond(req); + + Map body1 = (Map) JSON.deserializeUntyped(res1.getBody()); + Map body2 = (Map) JSON.deserializeUntyped(res2.getBody()); + + for (String key : body1.keySet()) { + body2.put(key, body1.get(key)); + } + + res2.setBody(JSON.serialize(body2)); + + return res2; + } + } } diff --git a/sfdx/force-app/main/default/classes/utilities.cls b/sfdx/force-app/main/default/classes/utilities.cls index 65e02227be..21336522fe 100644 --- a/sfdx/force-app/main/default/classes/utilities.cls +++ b/sfdx/force-app/main/default/classes/utilities.cls @@ -1,10 +1,33 @@ public with sharing class utilities { + @TestVisible + static ConfigPayloadHelper configPayloadHelper = new ConfigPayloadHelper(); + + @TestVisible + static Debug_Helper debugger = new Debug_Helper(); + public class SetupIsNotCompleteException extends Exception {} + // used a method to prepare for an eventual future where the base uri is configurable. + public static String getPlatformConfigEndpoint() { + return constants.RUBY_SERVICE_BASE_URI + constants.RUBY_SERVICE_CONFIG_URI; + } + + public static String getPlatformTranslateEndpoint() { + return constants.RUBY_SERVICE_BASE_URI + constants.RUBY_SERVICE_TRANSLATE_URI; + } + + public static String getPlatformTranslateAllEndpoint() { + return constants.RUBY_SERVICE_BASE_URI + constants.RUBY_SERVICE_TRANSLATE_ALL_URI; + } + public static Boolean isPackagedEnvironment() { return !String.isEmpty(constants.NAMESPACE_API); } + public static Boolean isConnected(Stripe_Connection__c stripeConnectRec) { + return stripeConnectRec != null && stripeConnectRec.Id != null && stripeConnectRec.Salesforce_Connected__c && stripeConnectRec.Stripe_Connected__c; + } + @AuraEnabled public static String getPackageVersion() { responseData rd = new responseData(); @@ -268,7 +291,12 @@ public with sharing class utilities { return makeCallout(endPoint, method, null, headers); } - public static HttpResponse makeCallout(String endPoint, String method, String body, Map headers){ + public static HttpResponse makeCallout(String endPoint, String method, String body, Map headers) { + debugger.debug('utilities.makeCallout', 'endpoint: ' + endPoint); + debugger.debug('utilities.makeCallout', 'method: ' + method); + debugger.debug('utilities.makeCallout', 'body: ' + body); + debugger.debug('utilities.makeCallout', 'headers: ' + headers); + Http httpMethod = new Http(); HttpRequest request = new HttpRequest(); @@ -283,6 +311,7 @@ public with sharing class utilities { request.setHeader('Content-Type', 'application/json'); request.setHeader('Accept', 'application/json'); request.setBody(body); + debugger.debug('utilities.makeCallout', 'Set body with default body headers'); } /* these standard headers will be add to the request if none are provided @@ -299,19 +328,24 @@ public with sharing class utilities { request.setHeader('Salesforce-Package-Id', '0.0.0.0'); } + debugger.debug('utilities.makeCallout', 'Set default headers'); + /* this only happens in the case the key is passed in after generating the package level api key, otherwise we will use the organization-specific api key */ if(headers != null) { + debugger.debug('utilities.makeCallout', 'Set custom headers'); request.setHeader('Salesforce-Key', headers.get('Salesforce-Key')); + debugger.debug('utilities.makeCallout', 'Calling endpoint'); HttpResponse response = httpMethod.send(request); return response; } Stripe_Connection__c stripeConnectRec = Stripe_Connection__c.getOrgDefaults(); - if(stripeConnectRec.Id == null || stripeConnectRec.API_Key__c == null) { + if (stripeConnectRec.Id == null || stripeConnectRec.API_Key__c == null) { + debugger.debug('utilities.makeCallout', 'Missing Stripe_Connection__c.Id or API_Key__c'); /* This error should only be thrown in a scratch org when unmangaed key is deployed to an org and the custom metadata record containing the global key was not created @@ -320,8 +354,9 @@ public with sharing class utilities { throw new SetupIsNotCompleteException('Connector is not setup correctly, missing metadata.'); } + debugger.debug('utilities.makeCallout', 'Setting default API key header'); request.setHeader('Salesforce-Key', (String)stripeConnectRec.API_Key__c); - + debugger.debug('utilities.makeCallout', 'Calling endpoint'); HttpResponse response = httpMethod.send(request); return response; } @@ -334,7 +369,7 @@ public with sharing class utilities { rd.put('isConfigSaved', true); if (actionTaken == 'saveSyncPreferences' || actionTaken == 'saveMappingConfigurations' || actionTaken == 'activatePolling') { Map responseBody = (Map) JSON.deserializeUntyped(response.getBody()); - rd.put('configurationHash', (String)responseBody.get('configuration_hash')); + configPayloadHelper.extractConfigHash(responseBody, rd); } return rd; } diff --git a/sfdx/force-app/main/default/lwc/dataMappingStep/dataMappingStep.js b/sfdx/force-app/main/default/lwc/dataMappingStep/dataMappingStep.js index 5d062a0f6b..63b2143e95 100644 --- a/sfdx/force-app/main/default/lwc/dataMappingStep/dataMappingStep.js +++ b/sfdx/force-app/main/default/lwc/dataMappingStep/dataMappingStep.js @@ -412,6 +412,7 @@ export default class DataMappingStep extends LightningElement { sfValueType: 'default' }; Object.assign(this.activeObjectFields[targetSectionIndex].fields[parseInt(targetFieldIndex)], updatedSelection); + this.valueChange(); } } diff --git a/sfdx/force-app/main/default/lwc/debugger/debugger.js b/sfdx/force-app/main/default/lwc/debugger/debugger.js new file mode 100644 index 0000000000..3df707e86e --- /dev/null +++ b/sfdx/force-app/main/default/lwc/debugger/debugger.js @@ -0,0 +1,57 @@ +/** + * Created by jmather-c on 6/1/23. + */ + +class Debugger { + static debug = false; + + /** + * @param {...*} var_args + */ + static log(var_args) { + this.exec(console.log, arguments); + } + + /** + * @param {...*} var_args + */ + static error(var_args) { + this.exec(console.error, arguments); + } + + /** + * @param {...*} var_args + */ + static warn(var_args) { + this.exec(console.warn, arguments); + } + + static exec(target, args) { + if (this.debug) { + target.apply(console, this.sanitize(args)); + } + } + + /** + * + * @param {*[]} data + * @return {*[]} + */ + static sanitize(data) { + const ret = []; + for (let i = 0; i < data.length; i++) { + try { + if (data[i] instanceof Proxy) { + ret.push(JSON.parse(JSON.stringify(data[i]))); + } else { + ret.push(data[i]); + } + } catch (e) { + ret.push(data[i]); + } + } + return ret; + } +} + +export {Debugger} \ No newline at end of file diff --git a/sfdx/force-app/main/default/lwc/debugger/debugger.js-meta.xml b/sfdx/force-app/main/default/lwc/debugger/debugger.js-meta.xml new file mode 100644 index 0000000000..07df4b80fc --- /dev/null +++ b/sfdx/force-app/main/default/lwc/debugger/debugger.js-meta.xml @@ -0,0 +1,7 @@ + + + 54.0 + Debugger + false + Debugger + diff --git a/sfdx/force-app/main/default/lwc/pollingStep/pollingStep.js b/sfdx/force-app/main/default/lwc/pollingStep/pollingStep.js index 8e6df6854b..9edbc87c92 100644 --- a/sfdx/force-app/main/default/lwc/pollingStep/pollingStep.js +++ b/sfdx/force-app/main/default/lwc/pollingStep/pollingStep.js @@ -6,6 +6,7 @@ import activatePolling from '@salesforce/apex/setupAssistant.activatePolling'; import getSyncPreferences from '@salesforce/apex/setupAssistant.getSyncPreferences'; import { LightningElement, api, track } from 'lwc'; import LightningConfirm from 'lightning/confirm'; +import { Debugger } from 'c/debugger'; /** @typedef {Object} ResponseData * @property {boolean} polling_enabled @@ -99,6 +100,7 @@ export default class PollingStep extends LightningElement { } // console.log(syncPrefs); + Debugger.log('pollingStep', 'initPageStateData', syncPrefs); this.pollingEnabled = syncPrefs.polling_enabled; this.lastSynced = syncPrefs.last_synced; diff --git a/sfdx/force-app/main/default/lwc/setup/setup.js b/sfdx/force-app/main/default/lwc/setup/setup.js index 4bbd199acf..7fe4f5cf28 100644 --- a/sfdx/force-app/main/default/lwc/setup/setup.js +++ b/sfdx/force-app/main/default/lwc/setup/setup.js @@ -281,8 +281,10 @@ export default class FirstTimeSetup extends LightningElement { } next(e) { + console.log('got next', e); this.contentLoading = true; this.stepName = this.steps[this.activeStepIndex].name; + console.log('processing step', this.stepName); if(this.stepName === 'C-DATA-MAPPING-STEP') { return this.template.querySelector('c-data-mapping-step').saveDataMappings(); } else if(this.stepName === 'C-SYNC-PREFERENCES-STEP') { diff --git a/sfdx/force-app/main/default/lwc/step/step.html b/sfdx/force-app/main/default/lwc/step/step.html index 16ee2d6f5b..1fba51fe3d 100644 --- a/sfdx/force-app/main/default/lwc/step/step.html +++ b/sfdx/force-app/main/default/lwc/step/step.html @@ -59,6 +59,7 @@

+
diff --git a/sfdx/force-app/main/default/lwc/step/step.js b/sfdx/force-app/main/default/lwc/step/step.js index c14aafd285..2e8102db0f 100644 --- a/sfdx/force-app/main/default/lwc/step/step.js +++ b/sfdx/force-app/main/default/lwc/step/step.js @@ -1,4 +1,7 @@ -import { LightningElement, api, track } from 'lwc'; +import { LightningElement, api, track, wire } from 'lwc'; +import getExportableConfigDownloadUrl from '@salesforce/apex/setupAssistant.getExportableConfigDownloadUrl'; +import LightningConfirm from 'lightning/confirm'; +import LightningAlert from "lightning/alert"; export default class SetupStep extends LightningElement { @api stepName = ''; @@ -14,6 +17,31 @@ export default class SetupStep extends LightningElement { @api loading = false; @track _showIntro; @track useStandardFooter = true; + @track exportLabel = 'Export Config'; + @wire(getExportableConfigDownloadUrl) + exportableConfigDownloadUrl + + async exportAction() { + if (this.exportDisabled) { + const result = await LightningAlert.open({ + message: 'There are unsaved changes on this page. Please save and then export, or refresh the page to discard changes.', + theme: 'texture', + label: 'Unsaved Changes', // this is the header text + }); + + return; + } + window.open(this.exportableConfigDownloadUrl.data, '"_blank"'); + } + + get exportTitle() { + return this.exportDisabled ? 'Download a copy of the previously saved config' : 'Download a copy of the current config'; + } + + // follow the opposite of next disabled, so we're clear what config is downloading. + get exportDisabled() { + return !this.saveDisabled || !this.exportableConfigDownloadUrl || !this.exportableConfigDownloadUrl.data; + } @api get showIntro() { @@ -37,6 +65,7 @@ export default class SetupStep extends LightningElement { } next() { + console.log('sending next event'); this.dispatchEvent(new CustomEvent('next', { bubbles: true, composed: true diff --git a/sfdx/force-app/main/default/lwc/syncPreferencesStep/syncPreferencesStep.html b/sfdx/force-app/main/default/lwc/syncPreferencesStep/syncPreferencesStep.html index c435825894..919bb7512c 100644 --- a/sfdx/force-app/main/default/lwc/syncPreferencesStep/syncPreferencesStep.html +++ b/sfdx/force-app/main/default/lwc/syncPreferencesStep/syncPreferencesStep.html @@ -102,7 +102,7 @@

- +

@@ -120,7 +120,7 @@

- +

@@ -160,7 +160,7 @@

- +

@@ -179,7 +179,7 @@

- +

@@ -198,7 +198,7 @@

- +

@@ -218,7 +218,7 @@

- +

diff --git a/sfdx/force-app/main/default/lwc/syncPreferencesStep/syncPreferencesStep.js b/sfdx/force-app/main/default/lwc/syncPreferencesStep/syncPreferencesStep.js index cc5d0f7aee..d0971a2895 100644 --- a/sfdx/force-app/main/default/lwc/syncPreferencesStep/syncPreferencesStep.js +++ b/sfdx/force-app/main/default/lwc/syncPreferencesStep/syncPreferencesStep.js @@ -8,6 +8,7 @@ import syncAllRecords from '@salesforce/apex/setupAssistant.syncAllRecords'; import { LightningElement, api, track} from 'lwc'; import { getErrorMessage } from 'c/utils' import LightningConfirm from 'lightning/confirm'; +import { Debugger } from 'c/debugger'; const POLLING_FIRST_ENABLE_ALERT_TITLE = 'Are you sure?'; const POLLING_FIRST_ENABLE_ALERT_TEMPLATE = `You are activating live syncing for your integration, meaning we will begin creating Stripe subscriptions for all Salesforce orders activated on or after SYNC_START_DATE. You can pause this in the future in the 'Sync Preferences' menu.` @@ -180,8 +181,8 @@ export default class SyncPreferencesStep extends LightningElement { if (responseData.results.sync_start_date && responseData.results.sync_start_date !== "0") { this.syncStartDate = new Date(responseData.results.sync_start_date * 1000).toISOString(); } - this.apiPercentageLimit = responseData.results.api_percentage_limit; - this.cpqTermUnit = responseData.results.cpq_term_unit; + this.apiPercentageLimit = responseData.results.api_percentage_limit || null; + this.cpqTermUnit = responseData.results.cpq_term_unit || null; this.cpqProratePrecision = responseData.results.cpq_prorate_precision || 'month'; this.isCpqInstalled = responseData.results.isCpqInstalled; this.isSandbox = responseData.results.isSandbox; @@ -204,14 +205,15 @@ export default class SyncPreferencesStep extends LightningElement { const filterSettings = await getFilterSettings(); const filterSettingsResponseData = JSON.parse(filterSettings); + if(!filterSettingsResponseData.isSuccess && filterSettingsResponseData.error) { this.showToast(filterSettingsResponseData.error, 'error', 'sticky'); } - this.orderFilter = filterSettingsResponseData.results.Order; - this.accountFilter = filterSettingsResponseData.results.Account; - this.productFilter = filterSettingsResponseData.results.Product2; - this.priceBookFilter = filterSettingsResponseData.results.PricebookEntry; + this.orderFilter = filterSettingsResponseData.results.order_filter || ''; + this.accountFilter = filterSettingsResponseData.results.account_filter || ''; + this.productFilter = filterSettingsResponseData.results.product_filter || ''; + this.priceBookFilter = filterSettingsResponseData.results.pricebook_entry_filter || ''; } catch (error) { let errorMessage = getErrorMessage(error); this.showToast(errorMessage, 'error', 'sticky'); @@ -355,6 +357,8 @@ export default class SyncPreferencesStep extends LightningElement { } @api async saveModifiedSyncPreferences() { + Debugger.log('saveModifiedSyncPreferences', 'enter', this.pollingEnabledChanged, this.hasSynced, this.pollingEnabled); + let saveSuccess = false; try { // confirm they wish to pause polling @@ -366,6 +370,7 @@ export default class SyncPreferencesStep extends LightningElement { }); if (result === false) { + Debugger.log('saveModifiedSyncPreferences', 'user cancelled disabling polling'); this.dispatchEvent(new CustomEvent('savecomplete', { detail: { saveSuccess: saveSuccess, @@ -380,6 +385,7 @@ export default class SyncPreferencesStep extends LightningElement { if (this.pollingEnabledChanged && this.hasSynced === false && this.pollingEnabled) { const startDateEle = this.template.querySelector('[data-id="syncStartDate"]'); if (startDateEle.checkValidity() === false) { + Debugger.log('saveModifiedSyncPreferences', 'invalid sync start date'); startDateEle.reportValidity(); this.showToast("A Sync Start Date must be set.", 'error', 'sticky'); return; @@ -394,6 +400,7 @@ export default class SyncPreferencesStep extends LightningElement { }); if (result === false) { + Debugger.log('saveModifiedSyncPreferences', 'user cancelled enabling polling'); this.dispatchEvent(new CustomEvent('savecomplete', { detail: { saveSuccess: saveSuccess, @@ -407,19 +414,24 @@ export default class SyncPreferencesStep extends LightningElement { this.lastSynced = 'Scheduled'; } - if((this.apiPercentageLimit < 100 && this.apiPercentageLimit > 0) && (this.syncRecordRetention < 1000000 && this.syncRecordRetention > 100)) { - const updatedSyncPreferences = await saveSyncPreferences({ + const validApiPercentageLimit = this.apiPercentageLimit === null || (this.apiPercentageLimit > 0 && this.apiPercentageLimit < 100); + const validSyncRecordRetention = this.syncRecordRetention < 1000000 && this.syncRecordRetention > 100; + Debugger.log('saveModifiedSyncPreferences', 'before call check', {validApiPercentageLimit, validSyncRecordRetention}); + if (validApiPercentageLimit && validSyncRecordRetention) { + const toPersist = { pollingEnabled: this.pollingEnabled, - cpqTermUnit: this.cpqTermUnit, - cpqProratePrecision: this.cpqProratePrecision, defaultCurrency: this.defaultCurrency, syncRecordRetention: this.syncRecordRetention, syncStartDate: (new Date(this.syncStartDate).getTime() / 1000), apiPercentageLimit: this.apiPercentageLimit, - configurationHash: this.configurationHash - }); + cpqTermUnit: this.cpqTermUnit, + cpqProratePrecision: this.cpqProratePrecision, + configurationHash: this.configurationHash, + }; + const updatedSyncPreferences = await saveSyncPreferences(toPersist); const savedSyncPreferencesResponseData = JSON.parse(updatedSyncPreferences); if(savedSyncPreferencesResponseData.isSuccess) { + Debugger.log('saveModifiedSyncPreferences', 'sync preferences saved') this.configurationHash = savedSyncPreferencesResponseData.results.configurationHash; this.pollingEnabledInitialValue = this.pollingEnabled; // Reset object filter validation messages @@ -436,11 +448,13 @@ export default class SyncPreferencesStep extends LightningElement { const filterResponseData = JSON.parse(updatedFilterSettings); if (filterResponseData.isSuccess ) { if (filterResponseData.results.isFiltersSaved) { + Debugger.log('saveModifiedSyncPreferences', 'filters saved'); saveSuccess = true this.showToast('Changes were successfully saved', 'success'); } else { if(filterResponseData.results.isValidationError) { const listOfExceptions = filterResponseData.results.ValidationErrors; + Debugger.log('saveModifiedSyncPreferences', 'filters validation error', listOfExceptions); for (let i = 0; i < listOfExceptions.length; i++) { let objectWithError = listOfExceptions[i].Object; let exceptionThrown = listOfExceptions[i].Error; @@ -459,18 +473,22 @@ export default class SyncPreferencesStep extends LightningElement { } } } else { + Debugger.log('saveModifiedSyncPreferences', 'filters save error', filterResponseData.error); this.showToast(filterResponseData.error, 'error', 'sticky'); } } } } else { + Debugger.log('saveModifiedSyncPreferences', 'sync preferences save error', savedSyncPreferencesResponseData.error); this.showToast(savedSyncPreferencesResponseData.error, 'error', 'sticky'); } } } catch (error) { + Debugger.log('saveModifiedSyncPreferences', 'error', error); let errorMessage = getErrorMessage(error); this.showToast(errorMessage, 'error', 'sticky'); } finally { + Debugger.log('saveModifiedSyncPreferences', 'save complete'); this.dispatchEvent(new CustomEvent('savecomplete', { detail: { saveSuccess: saveSuccess, diff --git a/sfdx/force-app/main/default/pages/ConfigExportDownload.page b/sfdx/force-app/main/default/pages/ConfigExportDownload.page new file mode 100644 index 0000000000..7778cf7ba5 --- /dev/null +++ b/sfdx/force-app/main/default/pages/ConfigExportDownload.page @@ -0,0 +1,13 @@ + + +{!config} \ No newline at end of file diff --git a/sfdx/force-app/main/default/pages/ConfigExportDownload.page-meta.xml b/sfdx/force-app/main/default/pages/ConfigExportDownload.page-meta.xml new file mode 100644 index 0000000000..fa985cbc17 --- /dev/null +++ b/sfdx/force-app/main/default/pages/ConfigExportDownload.page-meta.xml @@ -0,0 +1,5 @@ + + + 54.0 + +