From a26b628295bfa8fbd9c4103cef286bea30df424c Mon Sep 17 00:00:00 2001 From: Jeffrey de Looff Date: Tue, 31 Oct 2023 16:38:43 +0100 Subject: [PATCH 1/4] feat: adds TargetRequests with content and data callback --- .../target/RCTAEPTargetDataBridge.java | 34 +++++++++++++ .../target/RCTAEPTargetMapUtil.java | 3 +- .../target/RCTAEPTargetModule.java | 6 +++ .../src/AEPTargetRequestObjectDataBridge.h | 6 +++ .../src/AEPTargetRequestObjectDataBridge.m | 19 +++++++ packages/target/ios/src/RCTAEPTarget.m | 20 ++++++++ packages/target/ts/index.ts | 2 + .../ts/models/TargetRequestObjectWithData.ts | 50 +++++++++++++++++++ 8 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 packages/target/ts/models/TargetRequestObjectWithData.ts diff --git a/packages/target/android/src/main/java/com/adobe/marketing/mobile/reactnative/target/RCTAEPTargetDataBridge.java b/packages/target/android/src/main/java/com/adobe/marketing/mobile/reactnative/target/RCTAEPTargetDataBridge.java index cd0dea3e..07b22540 100644 --- a/packages/target/android/src/main/java/com/adobe/marketing/mobile/reactnative/target/RCTAEPTargetDataBridge.java +++ b/packages/target/android/src/main/java/com/adobe/marketing/mobile/reactnative/target/RCTAEPTargetDataBridge.java @@ -11,7 +11,11 @@ package com.adobe.marketing.mobile.reactnative.target; +import android.util.Log; + import com.adobe.marketing.mobile.AdobeCallback; +import com.adobe.marketing.mobile.AdobeError; +import com.adobe.marketing.mobile.target.AdobeTargetDetailedCallback; import com.adobe.marketing.mobile.target.TargetOrder; import com.adobe.marketing.mobile.target.TargetParameters; import com.adobe.marketing.mobile.target.TargetPrefetch; @@ -20,7 +24,9 @@ import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -65,6 +71,34 @@ public void call(String content) { }); } + public static TargetRequest mapToRequestWithData(ReadableMap map, final Callback successCallback) { + if (map == null) { + return null; + } + + TargetParameters parameters = mapToParameters(getNullableMap(map, TARGET_PARAMETERS_KEY)); + return new TargetRequest(getNullableString(map, NAME_KEY), parameters, getNullableString(map, DEFAULT_CONTENT_KEY), new AdobeTargetDetailedCallback() { + + @Override + public void call(String content, Map data) { + try { + // wrapped data in HashMap to ensure it is mutable + WritableMap metadata = data != null ? RCTAEPTargetMapUtil.toWritableMap(new HashMap<>(data)) : null; + + successCallback.invoke(null, content, metadata); + } + catch (Exception exception) { + Log.e("RCTAEPTargetDataBridge", "Could not parse data", exception); + } + } + + @Override + public void fail(AdobeError adobeError) { + successCallback.invoke(adobeError, null, null); + } + }); + } + public static TargetParameters mapToParameters(ReadableMap map) { if (map == null) { return null; diff --git a/packages/target/android/src/main/java/com/adobe/marketing/mobile/reactnative/target/RCTAEPTargetMapUtil.java b/packages/target/android/src/main/java/com/adobe/marketing/mobile/reactnative/target/RCTAEPTargetMapUtil.java index 3f0200de..711e1e0d 100644 --- a/packages/target/android/src/main/java/com/adobe/marketing/mobile/reactnative/target/RCTAEPTargetMapUtil.java +++ b/packages/target/android/src/main/java/com/adobe/marketing/mobile/reactnative/target/RCTAEPTargetMapUtil.java @@ -107,7 +107,8 @@ public static WritableMap toWritableMap(Map map) { } else if (value instanceof String) { writableMap.putString((String) pair.getKey(), (String) value); } else if (value instanceof Map) { - writableMap.putMap((String) pair.getKey(), RCTAEPTargetMapUtil.toWritableMap((Map) value)); + // wrapped value in HashMap to ensure it is mutable + writableMap.putMap((String) pair.getKey(), RCTAEPTargetMapUtil.toWritableMap((new HashMap<>((Map) value)))); } else if (value.getClass() != null && value.getClass().isArray()) { writableMap.putArray((String) pair.getKey(), RCTAEPTargetArrayUtil.toWritableArray((Object[]) value)); } diff --git a/packages/target/android/src/main/java/com/adobe/marketing/mobile/reactnative/target/RCTAEPTargetModule.java b/packages/target/android/src/main/java/com/adobe/marketing/mobile/reactnative/target/RCTAEPTargetModule.java index 28baed3f..0e1bae1c 100644 --- a/packages/target/android/src/main/java/com/adobe/marketing/mobile/reactnative/target/RCTAEPTargetModule.java +++ b/packages/target/android/src/main/java/com/adobe/marketing/mobile/reactnative/target/RCTAEPTargetModule.java @@ -182,4 +182,10 @@ public void registerTargetRequests(ReadableMap requestMap, Callback successCallb registeredTargetRequests.put(requestMap.getString(REQUEST_ID_KEY), request); } +@ReactMethod + public void registerTargetRequestsWithData(ReadableMap requestMap, Callback successCallback) { + TargetRequest request = RCTAEPTargetDataBridge.mapToRequestWithData(requestMap, successCallback); + registeredTargetRequests.put(requestMap.getString(REQUEST_ID_KEY), request); + } + } diff --git a/packages/target/ios/src/AEPTargetRequestObjectDataBridge.h b/packages/target/ios/src/AEPTargetRequestObjectDataBridge.h index dbd28fbf..0a7e7c77 100644 --- a/packages/target/ios/src/AEPTargetRequestObjectDataBridge.h +++ b/packages/target/ios/src/AEPTargetRequestObjectDataBridge.h @@ -13,6 +13,8 @@ @import AEPTarget; #import "AEPTargetRequestObjectDataBridge.h" +typedef void (^TargetRequestCallbackWithData)(NSString * _Nullable, NSDictionary * _Nullable); + @interface AEPTargetRequestObject (RCTBridge) + (AEPTargetRequestObject *) @@ -20,4 +22,8 @@ callback:(nullable void (^)( NSString *__nullable content))callback; ++ (AEPTargetRequestObject *) + targetRequestObjectWithDataFromDict:(NSDictionary *)dict + contentWithDataCallback:(TargetRequestCallbackWithData)contentWithDataCallback; + @end diff --git a/packages/target/ios/src/AEPTargetRequestObjectDataBridge.m b/packages/target/ios/src/AEPTargetRequestObjectDataBridge.m index 07423b92..c9480d2e 100644 --- a/packages/target/ios/src/AEPTargetRequestObjectDataBridge.m +++ b/packages/target/ios/src/AEPTargetRequestObjectDataBridge.m @@ -33,4 +33,23 @@ @implementation AEPTargetRequestObject (RCTBridge) initWithMboxName:dict[REQUEST_NAME_KEY] defaultContent:dict[DEFAULT_CONTENT_KEY] targetParameters:parameters contentCallback:callback]; } ++ (AEPTargetRequestObject *) + targetRequestObjectWithDataFromDict:(NSDictionary *)dict + contentWithDataCallback: (TargetRequestCallbackWithData) contentWithDataCallback { + + if (!dict || [dict isEqual:[NSNull null]]) { + return nil; + } + + AEPTargetParameters *parameters = [AEPTargetParameters + targetParametersFromDict:dict[REQUEST_PARAMETERS_KEY]]; + + return [[AEPTargetRequestObject alloc] + initWithMboxName:dict[REQUEST_NAME_KEY] + defaultContent:dict[DEFAULT_CONTENT_KEY] + targetParameters:parameters + contentWithDataCallback: contentWithDataCallback + ]; +} + @end diff --git a/packages/target/ios/src/RCTAEPTarget.m b/packages/target/ios/src/RCTAEPTarget.m index 3cae605f..4547f493 100644 --- a/packages/target/ios/src/RCTAEPTarget.m +++ b/packages/target/ios/src/RCTAEPTarget.m @@ -166,4 +166,24 @@ - (dispatch_queue_t)methodQueue { _registeredTargetRequests[requestDict[@"id"]] = obj; } +RCT_EXPORT_METHOD(registerTargetRequestsWithData + : (nonnull NSDictionary *)requestDict callback + : (RCTResponseSenderBlock)callback) { + + TargetRequestCallbackWithData contentWithDataCallback = ^void(NSString * _Nullable content, NSDictionary * _Nullable data) { + NSDictionary *test = [[NSDictionary alloc] initWithDictionary: data]; + callback(@[ [NSNull null], content, test]); + }; + + AEPTargetRequestObject *obj = [AEPTargetRequestObject + targetRequestObjectWithDataFromDict:requestDict + contentWithDataCallback: contentWithDataCallback]; + + if (!_registeredTargetRequests) { + _registeredTargetRequests = [NSMutableDictionary dictionary]; + } + + _registeredTargetRequests[requestDict[@"id"]] = obj; +} + @end diff --git a/packages/target/ts/index.ts b/packages/target/ts/index.ts index 8a088624..2ba9cb98 100644 --- a/packages/target/ts/index.ts +++ b/packages/target/ts/index.ts @@ -15,6 +15,7 @@ import TargetParameters from './models/TargetParameters'; import TargetPrefetchObject from './models/TargetPrefetchObject'; import TargetProduct from './models/TargetProduct'; import TargetRequestObject from './models/TargetRequestObject'; +import TargetRequestObjectWithData from './models/TargetRequestObjectWithData'; export { // Native models @@ -23,6 +24,7 @@ export { TargetPrefetchObject, TargetProduct, TargetRequestObject, + TargetRequestObjectWithData, // Native modules Target }; diff --git a/packages/target/ts/models/TargetRequestObjectWithData.ts b/packages/target/ts/models/TargetRequestObjectWithData.ts new file mode 100644 index 00000000..02bde62c --- /dev/null +++ b/packages/target/ts/models/TargetRequestObjectWithData.ts @@ -0,0 +1,50 @@ +/* +Copyright 2022 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +import TargetPrefetchObject from './TargetPrefetchObject'; +import TargetParameters from './TargetParameters'; +import { NativeModules } from 'react-native'; + +export type TargetDataCallback = ( + error: Error | null, + content: string | null, + data: Record +) => void + +interface ITargetRequests { + registerTargetRequestsWithData: ( + requestMap: TargetRequestObjectWithData, + callback: TargetDataCallback + ) => void +} + +const RCTTarget: ITargetRequests = NativeModules.AEPTarget + +class TargetRequestObjectWithData extends TargetPrefetchObject { + defaultContent: string + id: string + + constructor( + name: string, + targetParameters: TargetParameters, + defaultContent: string, + callback: TargetDataCallback + ) { + super(name, targetParameters) + + this.defaultContent = defaultContent + this.id = '_' + Math.random().toString(36).substr(2, 9) + + RCTTarget.registerTargetRequestsWithData(this, callback) + } +} + +export default TargetRequestObjectWithData; \ No newline at end of file From 1654bc9ec4e87657f432818299236cb10275e60f Mon Sep 17 00:00:00 2001 From: Jeffrey de Looff Date: Wed, 1 Nov 2023 11:48:33 +0100 Subject: [PATCH 2/4] test: adds use cases for TargetRequestObjectWithData --- packages/target/__tests__/TargetTests.ts | 85 ++++++++++++++++++++++-- tests/jest/setup.ts | 3 +- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/packages/target/__tests__/TargetTests.ts b/packages/target/__tests__/TargetTests.ts index 6f548768..7ffbea7e 100644 --- a/packages/target/__tests__/TargetTests.ts +++ b/packages/target/__tests__/TargetTests.ts @@ -13,6 +13,9 @@ import { NativeModules } from 'react-native'; import Target from '../ts/Target'; import TargetPrefetchObject from '../ts/models/TargetPrefetchObject'; import TargetRequestObject from '../ts/models/TargetRequestObject'; +import TargetRequestObjectWithData, { + TargetDataCallback, +} from '../ts/models/TargetRequestObjectWithData'; import TargetOrder from '../ts/models/TargetOrder'; import TargetProduct from '../ts/models/TargetProduct'; import TargetParameters from '../ts/models/TargetParameters'; @@ -133,6 +136,80 @@ describe('Target', () => { expect(spy).toHaveBeenCalledWith(locationRequests, parameters); }); + test('retrieveLocationContent is called with correct parameters and metadata', async () => { + const spy = jest.spyOn(NativeModules.AEPTarget, 'retrieveLocationContent'); + + const metadata = { + responseTokens: { + 'activity.id': '42', + 'activity.name': 'Adobe test', + 'experience.id': '0', + 'experience.name': 'Adobe test', + }, + }; + + NativeModules.AEPTarget.registerTargetRequestsWithData = jest.fn( + (_, callback: TargetDataCallback) => { + console.log('registering callback'); + callback(null, 'content', metadata); + } + ); + + var mboxParameters1 = { status: 'platinum' }; + var purchaseIDs = ['34', '125']; + + var targetOrder = new TargetOrder('ADCKKIM', 344.3, purchaseIDs); + var targetProduct = new TargetProduct('24D3412', 'Books'); + var parameters1 = new TargetParameters(mboxParameters1, null, null, null); + var request1 = new TargetRequestObjectWithData( + 'mboxName2', + parameters1, + 'defaultContent1', + (error, content, data) => { + if (error) { + console.error(error); + } else { + console.log('Adobe content and metadata:', content, data); + } + } + ); + + var parameters2 = new TargetParameters( + mboxParameters1, + { profileParameters: 'parameterValue' }, + targetProduct, + targetOrder + ); + var request2 = new TargetRequestObjectWithData( + 'mboxName2', + parameters2, + 'defaultContent2', + (error, content, data) => { + if (error) { + console.error(error); + } else { + console.log('Adobe content and metadata:', content, data); + } + } + ); + + var locationRequests = [request1, request2]; + var profileParameters1 = { ageGroup: '20-32' }; + + var parameters = new TargetParameters( + { parameters: 'parametervalue' }, + profileParameters1, + targetProduct, + targetOrder + ); + await Target.retrieveLocationContent(locationRequests, parameters); + + expect(spy).toHaveBeenCalledWith(locationRequests, parameters); + expect( + NativeModules.AEPTarget.registerTargetRequestsWithData + ).toHaveBeenCalled(); + }); + test('displayedLocations is called with correct parameters', async () => { const spy = jest.spyOn(NativeModules.AEPTarget, 'displayedLocations'); var purchaseIDs = ['34', '125']; @@ -176,8 +253,7 @@ describe('Target', () => { expect(spy).toHaveBeenCalledWith('locationName', parameters); }); - test('prefetchContent is called with correct parameters', async () => { - + test('prefetchContent is called with correct parameters', async () => { const spy = jest.spyOn(NativeModules.AEPTarget, 'prefetchContent'); var mboxParameters1 = { status: 'platinum' }; var purchaseIDs = ['34', '125']; @@ -205,11 +281,10 @@ describe('Target', () => { targetOrder ); - - await Target.prefetchContent(prefetchList, parameters) + await Target.prefetchContent(prefetchList, parameters) .then((success) => console.log(success)) .catch((err) => console.log(err)); expect(spy).toHaveBeenCalledWith(prefetchList, parameters); - }); + }); }); diff --git a/tests/jest/setup.ts b/tests/jest/setup.ts index c850c865..dd2ac018 100644 --- a/tests/jest/setup.ts +++ b/tests/jest/setup.ts @@ -128,7 +128,8 @@ jest.doMock('react-native', () => { prefetchContent: jest.fn(() => new Promise(resolve => resolve(''))), displayedLocations: jest.fn(), clickedLocation: jest.fn(), - registerTargetRequests: jest.fn() + registerTargetRequests: jest.fn(), + registerTargetRequestsWithData: jest.fn() }, AEPPlaces: { extensionVersion: jest.fn(() => new Promise(resolve => resolve(''))), From d391e72617951894cebf1699a17204975bba7230 Mon Sep 17 00:00:00 2001 From: Jeffrey de Looff Date: Wed, 1 Nov 2023 11:48:52 +0100 Subject: [PATCH 3/4] style: updates formatting --- .../ts/models/TargetRequestObjectWithData.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/target/ts/models/TargetRequestObjectWithData.ts b/packages/target/ts/models/TargetRequestObjectWithData.ts index 02bde62c..0132a3c1 100644 --- a/packages/target/ts/models/TargetRequestObjectWithData.ts +++ b/packages/target/ts/models/TargetRequestObjectWithData.ts @@ -17,20 +17,20 @@ export type TargetDataCallback = ( error: Error | null, content: string | null, data: Record -) => void +) => void; interface ITargetRequests { registerTargetRequestsWithData: ( requestMap: TargetRequestObjectWithData, callback: TargetDataCallback - ) => void + ) => void; } -const RCTTarget: ITargetRequests = NativeModules.AEPTarget +const RCTTarget: ITargetRequests = NativeModules.AEPTarget; class TargetRequestObjectWithData extends TargetPrefetchObject { - defaultContent: string - id: string + defaultContent: string; + id: string; constructor( name: string, @@ -38,13 +38,13 @@ class TargetRequestObjectWithData extends TargetPrefetchObject { defaultContent: string, callback: TargetDataCallback ) { - super(name, targetParameters) + super(name, targetParameters); - this.defaultContent = defaultContent - this.id = '_' + Math.random().toString(36).substr(2, 9) + this.defaultContent = defaultContent; + this.id = "_" + Math.random().toString(36).substr(2, 9); - RCTTarget.registerTargetRequestsWithData(this, callback) + RCTTarget.registerTargetRequestsWithData(this, callback); } } -export default TargetRequestObjectWithData; \ No newline at end of file +export default TargetRequestObjectWithData; From c0951d9d83e0e6ab476266f4ad029185577d4bd1 Mon Sep 17 00:00:00 2001 From: Jeffrey de Looff Date: Wed, 1 Nov 2023 14:28:16 +0100 Subject: [PATCH 4/4] docs: adds example for TargetRequestObjectWithData --- packages/target/README.md | 42 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/packages/target/README.md b/packages/target/README.md index c73e6969..8aa80b27 100644 --- a/packages/target/README.md +++ b/packages/target/README.md @@ -261,6 +261,48 @@ var parameters = new TargetParameters( Target.retrieveLocationContent(locationRequests, parameters); ``` +### Load Target requests with metadata: + +**Syntax** + +```typescript +retrieveLocationContent(Array, ): void +``` + +**Example** + +```typescript +var mboxParameters = { status: "platinum" }; +var purchaseIDs = ["34", "125"]; + +var targetOrder = new TargetOrder("ADCKKIM", 344.3, purchaseIDs); +var targetProduct = new TargetProduct("24D3412", "Books"); +var parameters = new TargetParameters(mboxParameters, null, null, null); +var requestWithData = new TargetRequestObjectWithData( + "mboxNameWithData", + parameters, + "defaultContent", + (error, content, data) => { + if (error) { + console.error(error); + } else { + console.log("Adobe content and data:", content, data); + } + } +); + +var locationRequests = [requestWithData]; +var profileParameters = { ageGroup: "20-32" }; + +var parameters = new TargetParameters( + { parameters: "parametervalue" }, + profileParameters, + targetProduct, + targetOrder +); +Target.retrieveLocationContent(locationRequests, parameters); +``` + ### Using the prefetch APIs: **Syntax**