Skip to content

Commit

Permalink
Initial versions
Browse files Browse the repository at this point in the history
  • Loading branch information
sudo-buddy committed Feb 7, 2024
1 parent ce3cab1 commit edd84a8
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 18 deletions.
18 changes: 11 additions & 7 deletions packages/spacecat-shared-aa-api-client/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
import { fetch } from './utils.js';
import { stringToUint8Array } from './helpers.js';

const AA_SCOPE = 'openid,AdobeID,additional_info.projectedProductContext';
const IMS_URL = 'https://ims-na1.adobelogin.com/ims/token/v3';
const AA_URL = 'https://analytics-collection.adobe.io/aa/collect/v1';
function createBoundary() {
return `----WebKitFormBoundary${Math.random().toString(36).substring(2)}`;
}
Expand Down Expand Up @@ -41,7 +44,7 @@ export default class AAAPIClient {
#domain;

constructor(config) {
['IMS_URL', 'AA_CLIENT_ID', 'AA_CLIENT_SECRET', 'AA_SCOPES', 'AA_DOMAIN'].forEach((key) => {
['AA_CLIENT_ID', 'AA_CLIENT_SECRET', 'AA_DOMAIN'].forEach((key) => {
if (!config[key]) {
throw new Error(`Missing required config: ${key}`);
}
Expand All @@ -51,12 +54,12 @@ export default class AAAPIClient {
this.#domain = config.AA_DOMAIN;
}

static create(context) {
static async create(context) {
if (context.aaApiClient) {
return context.aaApiClient;
}
context.aaApiClient = new AAAPIClient({ ...context.env });
context.aaApiClient.#getIMSAccessToken();
await context.aaApiClient.#getIMSAccessToken();
return context.aaApiClient;
}

Expand All @@ -79,8 +82,9 @@ export default class AAAPIClient {

async #getIMSAccessToken() {
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
const body = `client_id=${this.#config.AA_CLIENT_ID}&client_secret=${this.#config.AA_CLIENT_SECRET}&grant_type=client_credentials&scope=${this.#config.AA_SCOPES}`;
this.#token = await this.#post(this.#config.IMS_URL, headers, body);
const body = `client_id=${this.#config.AA_CLIENT_ID}&client_secret=${this.#config.AA_CLIENT_SECRET}&grant_type=client_credentials&scope=${AA_SCOPE}`;
const token = await this.#post(IMS_URL, headers, body);
this.#token = token.access_token;
return this.#token;
}

Expand All @@ -95,7 +99,7 @@ export default class AAAPIClient {
};
const multipartBody = createMultipartBody(archiveBuffer, zipPath, boundary);

return this.#post(`${this.#config.AA_URL}/events/validate`, headers, multipartBody);
return this.#post(`${AA_URL}/events/validate`, headers, multipartBody);
}

async ingestEvents(archiveBuffer, zipPath) {
Expand All @@ -108,6 +112,6 @@ export default class AAAPIClient {
'Content-Type': `multipart/form-data; boundary=${boundary}`,
};
const multipartBody = createMultipartBody(archiveBuffer, zipPath, boundary);
return this.#post(`${this.#config.AA_URL}/events`, headers, multipartBody);
return this.#post(`${AA_URL}/events`, headers, multipartBody);
}
}
95 changes: 95 additions & 0 deletions packages/spacecat-shared-aa-api-client/src/rumtoaamapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2024 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 { EVARS_KEY, EVENTS_KEY } from './helpers.js';

export default class RUMToAAMapper {
#config;

constructor(config) {
this.#config = config;
}

static getFieldValueOrDefault(field, defaultValue, data) {
if (field in data) {
return data[field];
}
return defaultValue || '';
}

static formatAsCSV(entries) {
if (!entries || entries.length === 0) {
return '';
}

const headerFields = Object.keys(entries[0]);

const rows = entries.map((row) => headerFields.map((fieldName) => JSON.stringify(row[fieldName])).join(','));

rows.unshift(headerFields.join(','));

return rows.join('\r\n');
}

mapRUMPageViewsToAA(rumData, date, timezone) {
const rumPagesList = rumData?.results?.data;
if (!rumPagesList || rumPagesList.length === 0) {
return [];
}

return rumPagesList.map((rumPageView) => {
const mapping = this.#config.rum2aaMapping;

const aaPageData = {};

Object.keys(mapping).forEach((key) => {
const keyMapping = mapping[key];

if (key === EVARS_KEY) {
Object.keys(keyMapping).forEach((evarKey) => {
const { rumField: field, default: defaultVal } = keyMapping[evarKey];
aaPageData[evarKey] = RUMToAAMapper
.getFieldValueOrDefault(field, defaultVal, rumPageView);
});
} else if (key === EVENTS_KEY) {
const events = [];
Object.keys(keyMapping).forEach((eventKey) => {
const { rumField: field, default: defaultVal } = keyMapping[eventKey];
const fieldValue = RUMToAAMapper.getFieldValueOrDefault(field, defaultVal, rumPageView);
if (fieldValue) {
events.push(`${eventKey}=${fieldValue}`);
}
});

aaPageData[key] = events.length > 0 ? events.join(',') : '';
} else {
const { rumField: field, default: defaultVal } = keyMapping;
const fieldValue = RUMToAAMapper.getFieldValueOrDefault(field, defaultVal, rumPageView);

if (fieldValue) {
aaPageData[key] = fieldValue;
}
}
});

if (timezone === 'UTC') {
aaPageData.timestamp = `${date}T00:00:00Z`;
} else if (timezone.startsWith('UTC')) {
const offset = timezone.replace('UTC', '');
aaPageData.timestamp = `${date}T00:00:00${offset}`;
} else {
aaPageData.timestamp = `${date}T00:00:00`;
}

return aaPageData;
}).filter((aaPageData) => !!aaPageData[EVENTS_KEY]); // Skip record is no metric will be set
}
}
26 changes: 15 additions & 11 deletions packages/spacecat-shared-aa-api-client/test/aa-api-client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,28 @@ describe('Adobe Analytics api client', () => {
beforeEach(() => {
context = {
env: {
IMS_URL: 'https://ims.com',
AA_CLIENT_ID: 'test',
AA_CLIENT_SECRET: 'secret',
AA_SCOPES: 'test',
AA_CLIENT_SECRET: 'test',
AA_DOMAIN: 'test',
},
};
});
afterEach('clean each', () => {
nock.cleanAll();
});

it('does not create a new instance if previously initialized', async () => {
const aaApiClient = AAAPIClient.create({ aaApiClient: 'hebele', env: context.env });
expect(aaApiClient).to.equal('hebele');
});

it('rejects when one of the AA parameter missing', async () => {
expect(() => AAAPIClient.create(context)).to.throw('AA API Client needs a IMS_URL, AA_CLIENT_ID, AA_CLIENT_SECRET, AA_SCOPES, AA_DMAIN keys to be set');
it('call validateFileFormat with valid file', async () => {
nock('https://ims-na1.adobelogin.com')
.post('/ims/token/v3')
.reply(200, { access_token: 'test' });
const aaApiClient = await AAAPIClient.create(context);
const file = {
name: 'test.zip',
buffer: Buffer.from('test'),
};
nock('https://analytics-collection.adobe.io')
.post('/aa/collect/v1/events/validate')
.reply(204, {});
const result = await aaApiClient.validateFileFormat(file);
expect(result).to.throw;
});
});

0 comments on commit edd84a8

Please sign in to comment.