Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: enhancement: onboard user API for onesignal #3457

Closed
wants to merge 12 commits into from
21 changes: 21 additions & 0 deletions src/v0/destinations/one_signal/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { getMappingConfig } = require('../../util');

const BASE_URL = 'https://onesignal.com/api/v1';
const BASE_URL_V2 = 'https://api.onesignal.com/apps/{{app_id}}/users';

const ENDPOINTS = {
IDENTIFY: {
Expand All @@ -16,13 +17,33 @@ const ENDPOINTS = {

const ConfigCategory = {
IDENTIFY: { name: 'OneSignalIdentifyConfig', endpoint: '/players' },
IDENTIFY_V2: { name: 'OneSignalIdentifyConfigV2' },
SUBSCRIPTION: { name: 'OneSignalSubscriptionConfig' },
};

const mappingConfig = getMappingConfig(ConfigCategory, __dirname);

// Used for User Model (V2)
const deviceTypesV2Enums = [
'iOSPush',
'email',
'sms',
'AndroidPush',
'HuaweiPush',
'FireOSPush',
'WindowsPush',
'macOSPush',
'ChromeExtensionPush',
'ChromePush',
'SafariLegacyPush',
'FirefoxPush',
'SafariPush',
];
module.exports = {
BASE_URL,
BASE_URL_V2,
ENDPOINTS,
ConfigCategory,
mappingConfig,
deviceTypesV2Enums,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
[
{ "sourceKeys": "context.locale", "destKey": "properties.laguage", "required": false },
{ "sourceKeys": "context.ip", "destKey": "properties.ip", "required": false },
{ "sourceKeys": "context.timezone", "destKey": "properties.timezone_id", "required": false },
{ "sourceKeys": "context.location.latitude", "destKey": "properties.lat", "required": false },
{ "sourceKeys": "context.location.longitude", "destKey": "properties.long", "required": false },
{
"sourceKeys": "createdAt",
"destKey": "properties.created_at",
"sourceFromGenericMap": true,
"metadata": {
"type": "secondTimestamp"
},
"required": false
},
{
"sourceKeys": "createdAt",
"destKey": "properties.last_active",
"sourceFromGenericMap": true,
"metadata": {
"type": "secondTimestamp"
},
"required": false
},
{
"sourceKeys": [
"traits.country",
"context.traits.country",
"traits.address.country",
"context.traits.address.country"
],
"destKey": "properties.country",
"required": false
},
{
"sourceKeys": [
"traits.firstActive",
"context.traits.firstActive",
"traits.first_active",
"context.traits.first_active"
],
"metadata": {
"type": "secondTimestamp"
},
"destKey": "properties.first_active",
"required": false
},
{
"sourceKeys": "userIdOnly",
"destKey": "identity.external_id",
"sourceFromGenericMap": true,
"required": false
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{ "sourceKeys": "enabled", "destKey": "enabled", "required": false },
{ "sourceKeys": "notification_types", "destKey": "notification_types", "required": false },
{ "sourceKeys": "session_time", "destKey": "session_time", "required": false },
{ "sourceKeys": "session_count", "destKey": "session_count", "required": false },
{ "sourceKeys": "app_version", "destKey": "app_version", "required": false },
{ "sourceKeys": "test_type", "destKey": "test_type", "required": false }
]
11 changes: 9 additions & 2 deletions src/v0/destinations/one_signal/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const {
TransformationError,
InstrumentationError,
} = require('@rudderstack/integrations-lib');
const { process: processV2 } = require('./transformV2');
const { EventType } = require('../../../constants');
const { ConfigCategory, mappingConfig, BASE_URL, ENDPOINTS } = require('./config');
const {
Expand All @@ -17,7 +18,7 @@ const {
isDefinedAndNotNullAndNotEmpty,
defaultPutRequestConfig,
} = require('../../util');
const { populateDeviceType, populateTags } = require('./util');
const { populateDeviceType, populateTags } = require('./utils');
const { JSON_MIME_TYPE } = require('../../util/constant');

const responseBuilder = (payload, endpoint, eventType) => {
Expand Down Expand Up @@ -186,10 +187,16 @@ const groupResponseBuilder = (message, { Config }) => {
};

const processEvent = (message, destination) => {
const { Config } = destination;
const { version, appId } = Config;
if (version === 'V2') {
// This version is used to direct the request to user centric model
return processV2(message, destination);
}
if (!message.type) {
throw new InstrumentationError('Event type is required');
}
if (!destination.Config.appId) {
if (!appId) {
throw new ConfigurationError('appId is a required field');
}
const messageType = message.type.toLowerCase();
Expand Down
158 changes: 158 additions & 0 deletions src/v0/destinations/one_signal/transformV2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
const get = require('get-value');
const {
ConfigurationError,
TransformationError,
InstrumentationError,
} = require('@rudderstack/integrations-lib');
const { EventType } = require('../../../constants');
const { ConfigCategory, mappingConfig, BASE_URL_V2 } = require('./config');
const {
defaultRequestConfig,
getFieldValueFromMessage,
constructPayload,
defaultPostRequestConfig,
removeUndefinedAndNullValues,
} = require('../../util');
const {
populateTags,
getProductPurchasesDetails,
getSubscriptions,
getOneSignalAliases,
} = require('./utils');
const { JSON_MIME_TYPE } = require('../../util/constant');

const responseBuilder = (payload, Config) => {
const { appId } = Config;
if (payload) {
const response = defaultRequestConfig();
response.endpoint = `${BASE_URL_V2.replace('{{app_id}}', appId)}`;
response.headers = {
Accept: JSON_MIME_TYPE,
'Content-Type': JSON_MIME_TYPE,
};
response.method = defaultPostRequestConfig.requestMethod;
response.body.JSON = removeUndefinedAndNullValues(payload);
return response;
}
throw new TransformationError('Payload could not be populated due to wrong input');

Check warning on line 37 in src/v0/destinations/one_signal/transformV2.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/one_signal/transformV2.js#L37

Added line #L37 was not covered by tests
};

/**
* This function is used for creating response for identify call, to create a new user or update an existing user.
* a responseArray for creating/updating user is being prepared.
* If the value of emailDeviceType/smsDeviceType(toggle in dashboard) is true, separate responses will also be created
* for new subscriptions to be added to user with email/sms as token.
* @param {*} message
* @param {*} param1
* @returns
*/
const identifyResponseBuilder = (message, { Config }) => {
// Populating the tags
const tags = populateTags(message);

const payload = constructPayload(message, mappingConfig[ConfigCategory.IDENTIFY_V2.name]);
if (!payload?.identity?.external_id) {
const alias = getOneSignalAliases(message);
if (Object.keys(alias).length === 0) {
throw new InstrumentationError('userId or any other alias is required for identify');
}
payload.identity = alias;
}
// Following check is to intialise properties object in case we don't get properties object from construct payload
if (!payload.properties) {
payload.properties = {};

Check warning on line 63 in src/v0/destinations/one_signal/transformV2.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/one_signal/transformV2.js#L63

Added line #L63 was not covered by tests
}
payload.subscriptions = getSubscriptions(message, Config);
payload.properties.tags = tags;
return responseBuilder(removeUndefinedAndNullValues(payload), Config);
};

/**
* This function is used to build the response for track call and Group call.
* It is used to edit the OneSignal tags using external_id.
* It edits tags[event] as true for track call
* @param {*} message
* @param {*} param1
* @returns
*/
const trackOrGroupResponseBuilder = (message, { Config }, msgtype) => {
const { eventAsTags, allowedProperties } = Config;
const event = get(message, 'event');
const groupId = getFieldValueFromMessage(message, 'groupId');
// validation and adding tags for track and group call respectively
const tags = {};
const payload = { properties: {} };
if (msgtype === EventType.TRACK) {
if (!event) {
throw new InstrumentationError('Event is not present in the input payloads');
}
/* Populating event as true in tags.
eg. tags: {
"event_name": true
}
*/
tags[event] = true;
payload.properties.purchases = getProductPurchasesDetails(message);
}
if (msgtype === EventType.GROUP) {
if (!groupId) {
throw new InstrumentationError('groupId is required for group events');
}
tags.groupId = groupId;
}

const externalUserId = getFieldValueFromMessage(message, 'userIdOnly');
if (!externalUserId) {
const alias = getOneSignalAliases(message);
if (Object.keys(alias).length === 0) {
throw new InstrumentationError('userId or any other alias is required for track and group');
}
payload.identity = alias;
} else {
payload.identity = {
external_id: externalUserId,
};
}

// Populating tags using allowed properties(from dashboard)
const properties = get(message, 'properties');
if (properties && allowedProperties && Array.isArray(allowedProperties)) {
allowedProperties.forEach((item) => {
if (typeof properties[item.propertyName] === 'string') {
const tagName = eventAsTags ? `${event}_${[item.propertyName]}` : item.propertyName;
tags[tagName] = properties[item.propertyName];
}
});
}
payload.properties.tags = tags;
return responseBuilder(removeUndefinedAndNullValues(payload), Config);
};

const processEvent = (message, destination) => {
if (!message.type) {
throw new InstrumentationError('Event type is required');
}
if (!destination.Config.appId) {
throw new ConfigurationError('appId is a required field');

Check warning on line 136 in src/v0/destinations/one_signal/transformV2.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/one_signal/transformV2.js#L136

Added line #L136 was not covered by tests
}
const messageType = message.type.toLowerCase();
let response;
switch (messageType) {
case EventType.IDENTIFY:
response = identifyResponseBuilder(message, destination);
break;
case EventType.TRACK:
response = trackOrGroupResponseBuilder(message, destination, EventType.TRACK);
break;
case EventType.GROUP:
response = trackOrGroupResponseBuilder(message, destination, EventType.GROUP);
break;
default:
throw new InstrumentationError(`Message type ${messageType} is not supported`);
}
return response;
};

const process = (message, destination) => processEvent(message, destination);

module.exports = { process };
75 changes: 0 additions & 75 deletions src/v0/destinations/one_signal/util.js

This file was deleted.

Loading
Loading