Skip to content

Commit 2ee3bec

Browse files
refactor(google_adwords_remarketing_lists): migrate to TypeScript (#5037)
## Summary - Migrate all `google_adwords_remarketing_lists` destination files from JavaScript to TypeScript (`.js` → `.ts`) - Add `types.ts` with type definitions for the destination - Replace `require`/`module.exports` with ES module `import`/`export` syntax - Add type annotations to functions, variables, and config objects ## Test plan - [ ] Verify existing unit tests pass (`util.test.ts`) - [ ] Verify integration/component tests for google_adwords_remarketing_lists pass - [ ] Verify TypeScript compilation succeeds with no errors 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 55e7417 commit 2ee3bec

File tree

7 files changed

+280
-123
lines changed

7 files changed

+280
-123
lines changed

src/v0/destinations/google_adwords_remarketing_lists/config.js renamed to src/v0/destinations/google_adwords_remarketing_lists/config.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { getMappingConfig } = require('../../util');
1+
import { getMappingConfig } from '../../util';
22

33
const API_VERSION = 'v22';
44

@@ -9,32 +9,36 @@ const CONFIG_CATEGORIES = {
99
ADDRESSINFO: { type: 'addressInfo', name: 'addressInfo' },
1010
};
1111
const ADDRESS_INFO_ATTRIBUTES = ['firstName', 'lastName', 'country', 'postalCode'];
12-
const attributeMapping = {
12+
const attributeMapping: Record<string, string> = {
1313
email: 'hashedEmail',
1414
phone: 'hashedPhoneNumber',
1515
};
1616
const hashAttributes = ['email', 'phone', 'firstName', 'lastName'];
1717
const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname);
18-
const TYPEOFLIST = Object.freeze({
18+
const TYPEOFLIST: Readonly<Record<string, string>> = Object.freeze({
1919
userID: 'thirdPartyUserId',
2020
mobileDeviceID: 'mobileId',
2121
});
2222

23-
const consentConfigMap = {
23+
const consentConfigMap: Record<string, string> = {
2424
personalizationConsent: 'adPersonalization',
2525
userDataConsent: 'adUserData',
2626
};
2727

28-
module.exports = {
28+
const offlineDataJobsMapping = MAPPING_CONFIG[CONFIG_CATEGORIES.AUDIENCE_LIST.name];
29+
const addressInfoMapping = MAPPING_CONFIG[CONFIG_CATEGORIES.ADDRESSINFO.name];
30+
const destType = 'google_adwords_remarketing_lists';
31+
32+
export {
2933
API_VERSION,
3034
OFFLINE_USER_DATA_JOBS_ENDPOINT,
3135
BASE_ENDPOINT,
3236
TYPEOFLIST,
3337
attributeMapping,
3438
hashAttributes,
35-
offlineDataJobsMapping: MAPPING_CONFIG[CONFIG_CATEGORIES.AUDIENCE_LIST.name],
36-
addressInfoMapping: MAPPING_CONFIG[CONFIG_CATEGORIES.ADDRESSINFO.name],
39+
offlineDataJobsMapping,
40+
addressInfoMapping,
3741
ADDRESS_INFO_ATTRIBUTES,
3842
consentConfigMap,
39-
destType: 'google_adwords_remarketing_lists',
43+
destType,
4044
};

src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js renamed to src/v0/destinations/google_adwords_remarketing_lists/networkHandler.ts

Lines changed: 85 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
const { NetworkError } = require('@rudderstack/integrations-lib');
2-
const get = require('get-value');
3-
const { prepareProxyRequest, handleHttpRequest } = require('../../../adapters/network');
4-
const { isHttpStatusSuccess } = require('../../util/index');
5-
const {
6-
processAxiosResponse,
7-
getDynamicErrorType,
8-
} = require('../../../adapters/utils/networkUtils');
9-
const tags = require('../../util/tags');
10-
const { getAuthErrCategory } = require('../../util/googleUtils');
11-
const { getDeveloperToken } = require('../../util/googleUtils');
1+
import { NetworkError } from '@rudderstack/integrations-lib';
2+
import get from 'get-value';
3+
import { prepareProxyRequest, handleHttpRequest } from '../../../adapters/network';
4+
import { isHttpStatusSuccess } from '../../util/index';
5+
import { processAxiosResponse, getDynamicErrorType } from '../../../adapters/utils/networkUtils';
6+
import tags from '../../util/tags';
7+
import { getAuthErrCategory, getDeveloperToken } from '../../util/googleUtils';
8+
import type { OfflineDataJobPayload } from './types';
129
/**
1310
* This function helps to create a offlineUserDataJobs
1411
* @param endpoint
@@ -20,9 +17,21 @@ const { getDeveloperToken } = require('../../util/googleUtils');
2017
* ref: https://developers.google.com/google-ads/api/rest/reference/rest/v15/CustomerMatchUserListMetadata
2118
*/
2219

23-
const createJob = async ({ endpoint, headers, method, params, metadata }) => {
20+
const createJob = async ({
21+
endpoint,
22+
headers,
23+
method,
24+
params,
25+
metadata,
26+
}: {
27+
endpoint: string;
28+
headers: Record<string, string>;
29+
method: string;
30+
params: { customerId: string; listId: string; consent: Record<string, string> };
31+
metadata: unknown;
32+
}) => {
2433
const jobCreatingUrl = `${endpoint}:create`;
25-
const customerMatchUserListMetadata = {
34+
const customerMatchUserListMetadata: Record<string, unknown> = {
2635
userList: `customers/${params.customerId}/userLists/${params.listId}`,
2736
};
2837
if (Object.keys(params.consent).length > 0) {
@@ -58,7 +67,21 @@ const createJob = async ({ endpoint, headers, method, params, metadata }) => {
5867
* @param body
5968
*/
6069

61-
const addUserToJob = async ({ endpoint, headers, method, jobId, body, metadata }) => {
70+
const addUserToJob = async ({
71+
endpoint,
72+
headers,
73+
method,
74+
jobId,
75+
body,
76+
metadata,
77+
}: {
78+
endpoint: string;
79+
headers: Record<string, string>;
80+
method: string;
81+
jobId: string;
82+
body: { JSON: OfflineDataJobPayload };
83+
metadata: unknown;
84+
}) => {
6285
const jobAddingUrl = `${endpoint}/${jobId}:addOperations`;
6386
const secondRequest = {
6487
url: jobAddingUrl,
@@ -84,7 +107,19 @@ const addUserToJob = async ({ endpoint, headers, method, jobId, body, metadata }
84107
* @param method
85108
* @param jobId
86109
*/
87-
const runTheJob = async ({ endpoint, headers, method, jobId, metadata }) => {
110+
const runTheJob = async ({
111+
endpoint,
112+
headers,
113+
method,
114+
jobId,
115+
metadata,
116+
}: {
117+
endpoint: string;
118+
headers: Record<string, string>;
119+
method: string;
120+
jobId: string;
121+
metadata: unknown;
122+
}) => {
88123
const jobRunningUrl = `${endpoint}/${jobId}:run`;
89124
const thirdRequest = {
90125
url: jobRunningUrl,
@@ -108,7 +143,14 @@ const runTheJob = async ({ endpoint, headers, method, jobId, metadata }) => {
108143
* @param {*} request
109144
* @returns
110145
*/
111-
const gaAudienceProxyRequest = async (request) => {
146+
const gaAudienceProxyRequest = async (request: {
147+
body: { JSON: OfflineDataJobPayload };
148+
method: string;
149+
params: { customerId: string; listId: string; consent: Record<string, string> };
150+
endpoint: string;
151+
metadata: unknown;
152+
headers: Record<string, string>;
153+
}) => {
112154
const { body, method, params, endpoint, metadata } = request;
113155
const { headers } = request;
114156

@@ -129,11 +171,18 @@ const gaAudienceProxyRequest = async (request) => {
129171
}
130172

131173
// step2: putting users into the job
132-
let jobId;
174+
let jobId: string | undefined;
133175
if (firstResponse?.response?.data?.resourceName)
134176
// eslint-disable-next-line prefer-destructuring
135177
jobId = firstResponse.response.data.resourceName.split('/')[3];
136-
const secondResponse = await addUserToJob({ endpoint, headers, method, jobId, body, metadata });
178+
const secondResponse = await addUserToJob({
179+
endpoint,
180+
headers,
181+
method,
182+
jobId: jobId!,
183+
body,
184+
metadata,
185+
});
137186
if (!secondResponse.success && !isHttpStatusSuccess(secondResponse?.response?.status)) {
138187
return secondResponse;
139188
}
@@ -146,11 +195,14 @@ const gaAudienceProxyRequest = async (request) => {
146195
}
147196

148197
// step3: running the job
149-
const thirdResponse = await runTheJob({ endpoint, headers, method, jobId, metadata });
198+
const thirdResponse = await runTheJob({ endpoint, headers, method, jobId: jobId!, metadata });
150199
return thirdResponse;
151200
};
152201

153-
const gaAudienceRespHandler = (destResponse, stageMsg) => {
202+
const gaAudienceRespHandler = (
203+
destResponse: { status: number; response: unknown },
204+
stageMsg: string,
205+
) => {
154206
let { status } = destResponse;
155207
const { response } = destResponse;
156208

@@ -172,7 +224,12 @@ const gaAudienceRespHandler = (destResponse, stageMsg) => {
172224
);
173225
};
174226

175-
const responseHandler = (responseParams) => {
227+
const responseHandler = (responseParams: {
228+
destinationResponse: {
229+
status: number;
230+
response: { partialFailureError?: { code: number } };
231+
};
232+
}) => {
176233
const { destinationResponse } = responseParams;
177234
const message = `Request Processed Successfully`;
178235
const { status, response } = destinationResponse;
@@ -206,10 +263,15 @@ const responseHandler = (responseParams) => {
206263
return undefined;
207264
};
208265

209-
function networkHandler() {
266+
function networkHandler(this: {
267+
proxy: typeof gaAudienceProxyRequest;
268+
processAxiosResponse: typeof processAxiosResponse;
269+
prepareProxy: typeof prepareProxyRequest;
270+
responseHandler: typeof responseHandler;
271+
}) {
210272
this.proxy = gaAudienceProxyRequest;
211273
this.processAxiosResponse = processAxiosResponse;
212274
this.prepareProxy = prepareProxyRequest;
213275
this.responseHandler = responseHandler;
214276
}
215-
module.exports = { networkHandler };
277+
export { networkHandler };

src/v0/destinations/google_adwords_remarketing_lists/recordTransform.js renamed to src/v0/destinations/google_adwords_remarketing_lists/recordTransform.ts

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
const {
1+
import {
22
InstrumentationError,
33
groupByInBatches,
44
mapInBatches,
55
reduceInBatches,
66
isDefinedAndNotNullAndNotEmpty,
7-
} = require('@rudderstack/integrations-lib');
8-
const {
7+
} from '@rudderstack/integrations-lib';
8+
import {
99
getAccessToken,
1010
constructPayload,
1111
returnArrayOfSubarrays,
@@ -14,17 +14,18 @@ const {
1414
isEventSentByVDMV2Flow,
1515
generateErrorObject,
1616
getErrorRespEvents,
17-
} = require('../../util');
18-
const { populateConsentFromConfig } = require('../../util/googleUtils');
19-
const {
20-
populateIdentifiersForRecordEvent,
21-
responseBuilder,
22-
getOperationAudienceId,
23-
} = require('./util');
24-
const { getErrorResponse, createFinalResponse } = require('../../util/recordUtils');
25-
const { offlineDataJobsMapping, consentConfigMap } = require('./config');
26-
27-
const processRecordEventArray = async (records, context, operationType) => {
17+
} from '../../util';
18+
import { populateConsentFromConfig } from '../../util/googleUtils';
19+
import { populateIdentifiersForRecordEvent, responseBuilder, getOperationAudienceId } from './util';
20+
import { getErrorResponse, createFinalResponse } from '../../util/recordUtils';
21+
import { offlineDataJobsMapping, consentConfigMap } from './config';
22+
import type { RecordEventContext, RecordInput } from './types';
23+
24+
const processRecordEventArray = async (
25+
records: RecordInput[],
26+
context: RecordEventContext,
27+
operationType: string,
28+
) => {
2829
const {
2930
message,
3031
destination,
@@ -47,7 +48,7 @@ const processRecordEventArray = async (records, context, operationType) => {
4748
isHashRequired,
4849
);
4950

50-
const outputPayload = constructPayload(message, offlineDataJobsMapping);
51+
const outputPayload = constructPayload(message, offlineDataJobsMapping)!;
5152

5253
const userIdentifierChunks = returnArrayOfSubarrays(userIdentifiersList, 20);
5354
outputPayload.operations = await mapInBatches(userIdentifierChunks, (chunk) => ({
@@ -66,7 +67,10 @@ const processRecordEventArray = async (records, context, operationType) => {
6667
return getSuccessRespEvents(toSendEvents, metadata, destination, true);
6768
};
6869

69-
async function preparePayload(events, config) {
70+
async function preparePayload(
71+
events: RecordInput[],
72+
config: Omit<RecordEventContext, 'message' | 'destination' | 'accessToken'>,
73+
) {
7074
/**
7175
* If we are getting invalid identifiers, we are preparing empty object response for that event and that is ending up
7276
* as an error from google ads api. So we are validating the identifiers and then processing the events.
@@ -94,7 +98,7 @@ async function preparePayload(events, config) {
9498
}
9599
return acc;
96100
},
97-
{ validEvents: [], invalidEvents: [] },
101+
{ validEvents: [] as RecordInput[], invalidEvents: [] as unknown[] },
98102
);
99103

100104
if (validEvents.length === 0) {
@@ -104,20 +108,21 @@ async function preparePayload(events, config) {
104108
const { destination, message, metadata } = validEvents[0];
105109
const accessToken = getAccessToken(metadata, 'access_token');
106110

107-
const context = {
111+
const context: RecordEventContext = {
108112
message,
109113
destination,
110114
accessToken,
111115
...config,
112116
};
113117

114-
const groupedRecordsByAction = await groupByInBatches(validEvents, (record) =>
115-
record.message.action?.toLowerCase(),
118+
const groupedRecordsByAction = await groupByInBatches(
119+
validEvents,
120+
(record) => record.message.action?.toLowerCase() || '',
116121
);
117122

118123
const actionResponses = await reduceInBatches(
119124
['delete', 'insert', 'update'],
120-
async (responses, action) => {
125+
async (responses: Record<string, unknown>, action: string) => {
121126
const operationType = action === 'delete' ? 'remove' : 'create';
122127
if (groupedRecordsByAction[action]) {
123128
return {
@@ -151,7 +156,7 @@ async function preparePayload(events, config) {
151156
return finalResponse;
152157
}
153158

154-
async function processEventStreamRecordV1Events(groupedRecordInputs) {
159+
async function processEventStreamRecordV1Events(groupedRecordInputs: RecordInput[]) {
155160
const { destination } = groupedRecordInputs[0];
156161
const {
157162
audienceId,
@@ -174,7 +179,7 @@ async function processEventStreamRecordV1Events(groupedRecordInputs) {
174179
return preparePayload(groupedRecordInputs, config);
175180
}
176181

177-
async function processVDMV1RecordEvents(groupedRecordInputs) {
182+
async function processVDMV1RecordEvents(groupedRecordInputs: RecordInput[]) {
178183
const { destination, message } = groupedRecordInputs[0];
179184
const {
180185
audienceId,
@@ -197,20 +202,20 @@ async function processVDMV1RecordEvents(groupedRecordInputs) {
197202
return preparePayload(groupedRecordInputs, config);
198203
}
199204

200-
async function processVDMV2RecordEvents(groupedRecordInputs) {
205+
async function processVDMV2RecordEvents(groupedRecordInputs: RecordInput[]) {
201206
const { connection, message } = groupedRecordInputs[0];
202207
const { audienceId, typeOfList, isHashRequired, userDataConsent, personalizationConsent } =
203208
connection.config.destination;
204209

205210
const userSchema = message?.identifiers ? Object.keys(message.identifiers) : undefined;
206211

207-
const events = await mapInBatches(groupedRecordInputs, (record) => ({
212+
const events = (await mapInBatches(groupedRecordInputs, (record) => ({
208213
...record,
209214
message: {
210215
...record.message,
211216
fields: record.message.identifiers,
212217
},
213-
}));
218+
}))) as RecordInput[];
214219

215220
const config = {
216221
audienceId,
@@ -224,7 +229,7 @@ async function processVDMV2RecordEvents(groupedRecordInputs) {
224229
return preparePayload(events, config);
225230
}
226231

227-
async function processRecordInputs(groupedRecordInputs) {
232+
async function processRecordInputs(groupedRecordInputs: RecordInput[]) {
228233
const event = groupedRecordInputs[0];
229234

230235
if (isEventSentByVDMV1Flow(event)) {
@@ -236,6 +241,4 @@ async function processRecordInputs(groupedRecordInputs) {
236241
return processEventStreamRecordV1Events(groupedRecordInputs);
237242
}
238243

239-
module.exports = {
240-
processRecordInputs,
241-
};
244+
export { processRecordInputs };

0 commit comments

Comments
 (0)