@prismatic-io/components-google-analytics-ga4 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1081 @@
1
+ let _prismatic_io_spectral = require("@prismatic-io/spectral");
2
+ let _prismatic_io_spectral_http = require("@prismatic-io/spectral/http");
3
+ //#region src/constants.ts
4
+ const LOOK_BACK_DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
5
+ const ANALYTICS_ENDPOINTS = {
6
+ adminv1beta: "https://analyticsadmin.googleapis.com/v1beta",
7
+ datav1beta: "https://analyticsdata.googleapis.com/v1beta"
8
+ };
9
+ const POLL_RESOURCE_CONFIG = {
10
+ accounts: { label: "Accounts" },
11
+ properties: { label: "Properties" }
12
+ };
13
+ const POLL_RESOURCE_KEYS = {
14
+ ACCOUNTS: "accounts",
15
+ PROPERTIES: "properties"
16
+ };
17
+ //#endregion
18
+ //#region src/client.ts
19
+ const createAnalyticsClient = ({ connection, endpointType, debug }) => {
20
+ const accessToken = connection.token?.access_token;
21
+ return (0, _prismatic_io_spectral_http.createClient)({
22
+ baseUrl: ANALYTICS_ENDPOINTS[endpointType],
23
+ headers: { Authorization: `Bearer ${accessToken}` },
24
+ debug
25
+ });
26
+ };
27
+ //#endregion
28
+ //#region src/examplePayloads.ts
29
+ const listAccountsExamplePayload = { data: { accounts: [{
30
+ name: "accounts/000000000",
31
+ createTime: "2012-11-20T15:12:07.864Z",
32
+ updateTime: "2015-08-11T21:08:55.416Z",
33
+ displayName: "Example Account",
34
+ regionCode: "US"
35
+ }] } };
36
+ const listPropertiesExamplePayload = { data: { properties: [{
37
+ name: "properties/111111111",
38
+ parent: "accounts/000000000",
39
+ createTime: "2022-02-02T04:40:59.611Z",
40
+ updateTime: "2022-02-02T04:40:59.611Z",
41
+ displayName: "www.example.com",
42
+ industryCategory: "OTHER",
43
+ timeZone: "America/Chicago",
44
+ currencyCode: "USD",
45
+ serviceLevel: "GOOGLE_ANALYTICS_STANDARD",
46
+ account: "accounts/000000000",
47
+ propertyType: "PROPERTY_TYPE_ORDINARY"
48
+ }] } };
49
+ const getPropertyExamplePayload = { data: {
50
+ name: "properties/111111111",
51
+ parent: "accounts/000000000",
52
+ createTime: "2022-02-02T04:40:59.611Z",
53
+ updateTime: "2022-02-02T04:40:59.611Z",
54
+ displayName: "www.example.com",
55
+ industryCategory: "OTHER",
56
+ timeZone: "America/Chicago",
57
+ currencyCode: "USD",
58
+ serviceLevel: "GOOGLE_ANALYTICS_STANDARD",
59
+ account: "accounts/000000000",
60
+ propertyType: "PROPERTY_TYPE_ORDINARY"
61
+ } };
62
+ const runReportExamplePayload = { data: {
63
+ dimensionHeaders: [{ name: "pageTitle" }],
64
+ metricHeaders: [{
65
+ name: "sessions",
66
+ type: "TYPE_INTEGER"
67
+ }],
68
+ rows: [{
69
+ dimensionValues: [{ value: "My Example Page | My Website" }],
70
+ metricValues: [{ value: "9" }]
71
+ }, {
72
+ dimensionValues: [{ value: "A blog post | My Blog" }],
73
+ metricValues: [{ value: "3" }]
74
+ }],
75
+ rowCount: 2,
76
+ metadata: {
77
+ currencyCode: "USD",
78
+ timeZone: "America/Chicago"
79
+ },
80
+ kind: "analyticsData#runReport"
81
+ } };
82
+ const sendMeasurementProtocolEventsExamplePayload = { data: { message: "Event Sent Successfully" } };
83
+ const listAccountsDataSourceExamplePayload = { result: [{
84
+ key: "accounts/000000000",
85
+ label: "My Account 1"
86
+ }, {
87
+ key: "accounts/000000001",
88
+ label: "My Account 2"
89
+ }] };
90
+ const pollChangesTriggerExamplePayload = { payload: {
91
+ headers: {},
92
+ queryParameters: {},
93
+ rawBody: { data: null },
94
+ body: { data: {
95
+ created: [{
96
+ name: "accounts/000000001",
97
+ createTime: "2026-08-13T09:15:22.101Z",
98
+ updateTime: "2026-08-13T09:15:22.101Z",
99
+ displayName: "Newly Created Account",
100
+ regionCode: "US"
101
+ }],
102
+ updated: [{
103
+ name: "accounts/000000000",
104
+ createTime: "2012-11-20T15:12:07.864Z",
105
+ updateTime: "2026-08-13T11:42:05.338Z",
106
+ displayName: "Example Account",
107
+ regionCode: "US"
108
+ }]
109
+ } },
110
+ pathFragment: "",
111
+ webhookUrls: {},
112
+ webhookApiKeys: {},
113
+ invokeUrl: "",
114
+ executionId: "RXhhbXBsZUV4ZWN1dGlvblJlc3VsdElk",
115
+ customer: {
116
+ id: "testCustomerId",
117
+ name: "Test Customer",
118
+ externalId: "testExternalId"
119
+ },
120
+ instance: {
121
+ id: "testInstanceId",
122
+ name: "Test Instance"
123
+ },
124
+ user: {
125
+ id: "testUserId",
126
+ email: "user@example.com",
127
+ name: "Test User",
128
+ externalId: "testUserExternalId"
129
+ },
130
+ integration: {
131
+ id: "testIntegrationId",
132
+ name: "Test Integration",
133
+ versionSequenceId: "1",
134
+ externalVersion: ""
135
+ },
136
+ flow: {
137
+ id: "testFlowId",
138
+ name: "Test Flow",
139
+ stableId: "testFlowStableId"
140
+ },
141
+ startedAt: "2026-08-13T00:00:00.000Z",
142
+ globalDebug: false
143
+ } };
144
+ //#endregion
145
+ //#region src/util.ts
146
+ const toOptionalString = (input) => {
147
+ return input ? _prismatic_io_spectral.util.types.toString(input) : void 0;
148
+ };
149
+ const pollResourceModel = Object.entries(POLL_RESOURCE_CONFIG).map(([value, { label }]) => ({
150
+ label,
151
+ value
152
+ }));
153
+ const paginateRecords = async (client, url, params, fetchAll, propertyKey) => {
154
+ const records = { [propertyKey]: [] };
155
+ const pageSize = params.pageSize ? _prismatic_io_spectral.util.types.toNumber(params.pageSize) : 200;
156
+ const { data } = await client.get(url, { params: {
157
+ ...params,
158
+ pageSize
159
+ } });
160
+ data[propertyKey] = data[propertyKey] ?? [];
161
+ records[propertyKey] = data[propertyKey];
162
+ let nextPageToken = data.nextPageToken || "";
163
+ if (fetchAll && nextPageToken) {
164
+ while (nextPageToken) {
165
+ const response = await client.get(url, { params: {
166
+ ...params,
167
+ pageSize,
168
+ pageToken: nextPageToken
169
+ } });
170
+ records[propertyKey] = records[propertyKey].concat(response.data[propertyKey] ?? []);
171
+ nextPageToken = response.data.nextPageToken || "";
172
+ }
173
+ return records;
174
+ }
175
+ return data;
176
+ };
177
+ const resolvePollingRecordChanges = (data) => {
178
+ const changesObject = data ?? {};
179
+ return [...(changesObject.created ?? []).map((record) => ({
180
+ changeType: "created",
181
+ record
182
+ })), ...(changesObject.updated ?? []).map((record) => ({
183
+ changeType: "updated",
184
+ record
185
+ }))];
186
+ };
187
+ const filterByTimestamp = (records, lastPolledAt, isBackfill, showNewRecords, showUpdatedRecords) => {
188
+ const lastPolledAtDate = new Date(lastPolledAt);
189
+ const created = [];
190
+ const updated = [];
191
+ for (const record of records) {
192
+ const { createTime, updateTime } = record;
193
+ const createdAtDate = typeof createTime === "string" ? new Date(createTime) : null;
194
+ const updatedAtDate = typeof updateTime === "string" ? new Date(updateTime) : null;
195
+ const isNew = createdAtDate !== null && createdAtDate > lastPolledAtDate;
196
+ const isUpdated = !isNew && updatedAtDate !== null && updatedAtDate > lastPolledAtDate;
197
+ if (isNew && (isBackfill || showNewRecords)) created.push(record);
198
+ else if (isUpdated && (isBackfill || showUpdatedRecords)) updated.push(record);
199
+ }
200
+ return {
201
+ created,
202
+ updated
203
+ };
204
+ };
205
+ const lookBackDateClean = (value) => {
206
+ const str = _prismatic_io_spectral.util.types.toString(value).trim();
207
+ if (!str) return "";
208
+ if (!LOOK_BACK_DATE_PATTERN.test(str)) throw new Error("Look-back Date must be in YYYY-MM-DD format.");
209
+ const parts = str.split("-");
210
+ const year = _prismatic_io_spectral.util.types.toNumber(parts[0]);
211
+ const month = _prismatic_io_spectral.util.types.toNumber(parts[1]);
212
+ const day = _prismatic_io_spectral.util.types.toNumber(parts[2]);
213
+ const parsed = new Date(year, month - 1, day);
214
+ if (parsed.getFullYear() !== year || parsed.getMonth() !== month - 1 || parsed.getDate() !== day) throw new Error(`Look-back Date "${str}" is not a valid calendar date.`);
215
+ if (parsed > /* @__PURE__ */ new Date()) throw new Error("Look-back Date cannot be a future date.");
216
+ return str;
217
+ };
218
+ //#endregion
219
+ //#region src/inputs.ts
220
+ const cleanId = (idType, id) => {
221
+ if (id.startsWith(idType)) return id;
222
+ else return `${idType}/${id}`;
223
+ };
224
+ const pageToken = (0, _prismatic_io_spectral.input)({
225
+ label: "Page Token",
226
+ type: "string",
227
+ required: false,
228
+ example: "CjwKCAjwpdqkBhB-EiwA4Kk0u3bXgYx5mXo3nZl6e7jYz1hA0r8D6Gq2b8g7fX9WlX1Jf4bJpQ8YhoCjvsQAvD_BwE",
229
+ placeholder: "Enter a page token",
230
+ comments: "If a previous response was truncated, the response includes a `nextPageToken`. To retrieve the next page of results, set this parameter to the value of `nextPageToken` from the previous response.",
231
+ clean: toOptionalString
232
+ });
233
+ const pageSize = (0, _prismatic_io_spectral.input)({
234
+ label: "Page Size",
235
+ type: "string",
236
+ required: false,
237
+ example: "100",
238
+ placeholder: "Enter a page size",
239
+ comments: `The maximum number of resources contained in the underlying API response. The API may return fewer values in a page, even if there are additional values to return. If unspecified, 200 is requested, which is also the maximum.`,
240
+ clean: toOptionalString
241
+ });
242
+ const connectionInput = (0, _prismatic_io_spectral.input)({
243
+ label: "Connection",
244
+ type: "connection",
245
+ required: true
246
+ });
247
+ const propertyIdInput = (0, _prismatic_io_spectral.input)({
248
+ label: "Property ID",
249
+ type: "string",
250
+ required: true,
251
+ example: "properties/111111111",
252
+ placeholder: "Enter a property ID",
253
+ comments: "The unique identifier for the GA4 property, in properties/NNNNNNNNN form. A bare numeric ID is accepted and normalized.",
254
+ dataSource: "listProperties",
255
+ clean: (value) => cleanId("properties", _prismatic_io_spectral.util.types.toString(value))
256
+ });
257
+ const appInstanceId = (0, _prismatic_io_spectral.input)({
258
+ label: "App Instance ID",
259
+ comments: "The unique identifier assigned to an app installation on a device. Sent as app_instance_id in the Measurement Protocol payload.",
260
+ type: "string",
261
+ required: true,
262
+ example: "12345678901234567890123456789012",
263
+ placeholder: "Enter an app instance ID",
264
+ clean: _prismatic_io_spectral.util.types.toString
265
+ });
266
+ const events = (0, _prismatic_io_spectral.input)({
267
+ label: "Events To Send",
268
+ comments: "The events to send to Google Analytics",
269
+ type: "code",
270
+ language: "json",
271
+ required: true,
272
+ example: JSON.stringify([{
273
+ name: "campaign_details",
274
+ params: {
275
+ campaign_id: "google_1234",
276
+ campaign: "Summer_fun",
277
+ source: "google",
278
+ medium: "cpc",
279
+ term: "summer+travel",
280
+ content: "logolink",
281
+ session_id: "123",
282
+ engagement_time_msec: "100"
283
+ }
284
+ }])
285
+ });
286
+ const apiSecret = (0, _prismatic_io_spectral.input)({
287
+ label: "API Secret",
288
+ type: "password",
289
+ required: true,
290
+ example: "Str5ahciR5SJtWClz1mkRA",
291
+ placeholder: "Enter an API secret",
292
+ comments: "The Measurement Protocol API secret. Generate one in the Google Analytics UI under Admin > Data Streams > Measurement Protocol API secrets.",
293
+ clean: _prismatic_io_spectral.util.types.toString
294
+ });
295
+ const firebaseAppId = (0, _prismatic_io_spectral.input)({
296
+ label: "Firebase App ID",
297
+ type: "string",
298
+ required: true,
299
+ example: "1:123456789012:web:1234567890123456789012",
300
+ placeholder: "1:123456789012:web:1234567890123456789012",
301
+ comments: "The Firebase App ID, found in the Firebase console under Project Settings > General > Your Apps > App ID",
302
+ clean: _prismatic_io_spectral.util.types.toString
303
+ });
304
+ const accountId = (0, _prismatic_io_spectral.input)({
305
+ label: "Account ID",
306
+ type: "string",
307
+ required: true,
308
+ example: "accounts/000000000",
309
+ placeholder: "Enter an account ID",
310
+ comments: "The unique identifier for the GA4 account that owns the properties, in accounts/NNNNNNNNN form.",
311
+ dataSource: "listAccounts",
312
+ clean: _prismatic_io_spectral.util.types.toString
313
+ });
314
+ const fetchAll = (0, _prismatic_io_spectral.input)({
315
+ label: "Fetch All",
316
+ type: "boolean",
317
+ required: false,
318
+ comments: "When true, retrieves all pages of results.",
319
+ clean: _prismatic_io_spectral.util.types.toBool
320
+ });
321
+ const { debugRequest: _, ...restInputs } = _prismatic_io_spectral_http.inputs;
322
+ const rawRequestInputs = {
323
+ connection: connectionInput,
324
+ baseUrl: (0, _prismatic_io_spectral.input)({
325
+ label: "Base URL",
326
+ type: "string",
327
+ required: true,
328
+ clean: _prismatic_io_spectral.util.types.toString,
329
+ model: Object.values(ANALYTICS_ENDPOINTS).map((endpoint) => ({
330
+ label: endpoint,
331
+ value: endpoint
332
+ }))
333
+ }),
334
+ ...restInputs,
335
+ url: {
336
+ ...restInputs.url,
337
+ comments: "Input the path only (/accounts), the base URL comes from the Base URL input. For example, to connect to the selected base URL followed by /accounts, only /accounts is entered in this field.",
338
+ example: "/accounts"
339
+ }
340
+ };
341
+ const pagination = (0, _prismatic_io_spectral.structuredObjectInput)({
342
+ label: "Pagination",
343
+ required: false,
344
+ comments: "Page and page-size controls.",
345
+ inputs: {
346
+ pageSize,
347
+ pageToken
348
+ }
349
+ });
350
+ const listAccountsInputs = {
351
+ fetchAll,
352
+ pagination,
353
+ connection: connectionInput
354
+ };
355
+ const listPropertiesInputs = {
356
+ accountId,
357
+ fetchAll,
358
+ pagination,
359
+ connection: connectionInput
360
+ };
361
+ const getPropertyInputs = {
362
+ accountId: {
363
+ ...accountId,
364
+ required: false
365
+ },
366
+ propertyId: propertyIdInput,
367
+ connection: connectionInput
368
+ };
369
+ const pollResourceType = (0, _prismatic_io_spectral.input)({
370
+ label: "Resource Type",
371
+ type: "string",
372
+ required: true,
373
+ model: pollResourceModel,
374
+ comments: "The Google Analytics GA4 resource type to poll for new or updated records.",
375
+ clean: _prismatic_io_spectral.util.types.toString
376
+ });
377
+ const showNewRecords = (0, _prismatic_io_spectral.input)({
378
+ label: "Show New Records",
379
+ type: "boolean",
380
+ required: false,
381
+ default: "true",
382
+ comments: "When true, newly created records are included in the trigger output.",
383
+ clean: _prismatic_io_spectral.util.types.toBool
384
+ });
385
+ const showUpdatedRecords = (0, _prismatic_io_spectral.input)({
386
+ label: "Show Updated Records",
387
+ type: "boolean",
388
+ required: false,
389
+ default: "true",
390
+ comments: "When true, records that were modified after the last poll are included in the trigger output.",
391
+ clean: _prismatic_io_spectral.util.types.toBool
392
+ });
393
+ const lookBackDate = (0, _prismatic_io_spectral.input)({
394
+ label: "Look-back Date",
395
+ placeholder: "Enter look-back date (YYYY-MM-DD)",
396
+ type: "string",
397
+ required: false,
398
+ comments: "The date the initial sync starts from, in YYYY-MM-DD format. Cannot be a future date. Leave empty to start from the first recurrence with no backfill. When set, the initial sync seeds each record modified on or after this date once, ignoring the visibility filters.",
399
+ example: "2026-01-01",
400
+ clean: lookBackDateClean
401
+ });
402
+ const pollChangesInputs = {
403
+ connection: {
404
+ ...connectionInput,
405
+ comments: "The Google Analytics GA4 connection to use."
406
+ },
407
+ pollResourceType,
408
+ accountId: {
409
+ ...accountId,
410
+ required: false,
411
+ placeholder: "Enter the account ID (e.g., accounts/000000000)",
412
+ comments: "The unique identifier for the Google Analytics account. Required when the Resource Type is 'Properties'."
413
+ },
414
+ showNewRecords,
415
+ showUpdatedRecords,
416
+ lookBackDate
417
+ };
418
+ const listAccountsOutputSchema = {
419
+ type: "object",
420
+ properties: {
421
+ accounts: {
422
+ type: "array",
423
+ items: {
424
+ type: "object",
425
+ properties: {
426
+ name: { type: "string" },
427
+ displayName: { type: "string" },
428
+ regionCode: { type: "string" },
429
+ createTime: {
430
+ type: "string",
431
+ format: "date-time"
432
+ },
433
+ updateTime: {
434
+ type: "string",
435
+ format: "date-time"
436
+ },
437
+ deleted: { type: "boolean" },
438
+ gmpOrganization: { type: "string" }
439
+ },
440
+ required: []
441
+ }
442
+ },
443
+ nextPageToken: { type: "string" }
444
+ },
445
+ required: ["accounts"]
446
+ };
447
+ const propertySchema = {
448
+ type: "object",
449
+ properties: {
450
+ name: { type: "string" },
451
+ parent: { type: "string" },
452
+ account: { type: "string" },
453
+ displayName: { type: "string" },
454
+ propertyType: {
455
+ type: "string",
456
+ enum: [
457
+ "PROPERTY_TYPE_UNSPECIFIED",
458
+ "PROPERTY_TYPE_ORDINARY",
459
+ "PROPERTY_TYPE_SUBPROPERTY",
460
+ "PROPERTY_TYPE_ROLLUP"
461
+ ]
462
+ },
463
+ industryCategory: {
464
+ type: "string",
465
+ enum: [
466
+ "INDUSTRY_CATEGORY_UNSPECIFIED",
467
+ "AUTOMOTIVE",
468
+ "BUSINESS_AND_INDUSTRIAL_MARKETS",
469
+ "FINANCE",
470
+ "HEALTHCARE",
471
+ "TECHNOLOGY",
472
+ "TRAVEL",
473
+ "OTHER",
474
+ "ARTS_AND_ENTERTAINMENT",
475
+ "BEAUTY_AND_FITNESS",
476
+ "BOOKS_AND_LITERATURE",
477
+ "FOOD_AND_DRINK",
478
+ "GAMES",
479
+ "HOBBIES_AND_LEISURE",
480
+ "HOME_AND_GARDEN",
481
+ "INTERNET_AND_TELECOM",
482
+ "LAW_AND_GOVERNMENT",
483
+ "NEWS",
484
+ "ONLINE_COMMUNITIES",
485
+ "PEOPLE_AND_SOCIETY",
486
+ "PETS_AND_ANIMALS",
487
+ "REAL_ESTATE",
488
+ "REFERENCE",
489
+ "SCIENCE",
490
+ "SPORTS",
491
+ "JOBS_AND_EDUCATION",
492
+ "SHOPPING"
493
+ ]
494
+ },
495
+ serviceLevel: {
496
+ type: "string",
497
+ enum: [
498
+ "SERVICE_LEVEL_UNSPECIFIED",
499
+ "GOOGLE_ANALYTICS_STANDARD",
500
+ "GOOGLE_ANALYTICS_360"
501
+ ]
502
+ },
503
+ timeZone: { type: "string" },
504
+ currencyCode: { type: "string" },
505
+ createTime: {
506
+ type: "string",
507
+ format: "date-time"
508
+ },
509
+ updateTime: {
510
+ type: "string",
511
+ format: "date-time"
512
+ },
513
+ deleteTime: {
514
+ type: "string",
515
+ format: "date-time"
516
+ },
517
+ expireTime: {
518
+ type: "string",
519
+ format: "date-time"
520
+ }
521
+ },
522
+ required: []
523
+ };
524
+ const listPropertiesOutputSchema = {
525
+ type: "object",
526
+ properties: {
527
+ properties: {
528
+ type: "array",
529
+ items: propertySchema
530
+ },
531
+ nextPageToken: { type: "string" }
532
+ },
533
+ required: ["properties"]
534
+ };
535
+ const getPropertyOutputSchema = propertySchema;
536
+ const rowSchema = {
537
+ type: "object",
538
+ properties: {
539
+ dimensionValues: {
540
+ type: "array",
541
+ items: {
542
+ type: "object",
543
+ properties: { value: { type: "string" } },
544
+ required: [],
545
+ additionalProperties: true
546
+ }
547
+ },
548
+ metricValues: {
549
+ type: "array",
550
+ items: {
551
+ type: "object",
552
+ properties: { value: { type: "string" } },
553
+ required: [],
554
+ additionalProperties: true
555
+ }
556
+ }
557
+ },
558
+ required: []
559
+ };
560
+ const quotaStatusSchema = {
561
+ type: "object",
562
+ properties: {
563
+ consumed: {
564
+ type: "integer",
565
+ format: "int32"
566
+ },
567
+ remaining: {
568
+ type: "integer",
569
+ format: "int32"
570
+ }
571
+ },
572
+ required: []
573
+ };
574
+ const runReportOutputSchema = {
575
+ type: "object",
576
+ properties: {
577
+ kind: { type: "string" },
578
+ dimensionHeaders: {
579
+ type: "array",
580
+ items: {
581
+ type: "object",
582
+ properties: { name: { type: "string" } },
583
+ required: []
584
+ }
585
+ },
586
+ metricHeaders: {
587
+ type: "array",
588
+ items: {
589
+ type: "object",
590
+ properties: {
591
+ name: { type: "string" },
592
+ type: {
593
+ type: "string",
594
+ enum: [
595
+ "METRIC_TYPE_UNSPECIFIED",
596
+ "TYPE_INTEGER",
597
+ "TYPE_FLOAT",
598
+ "TYPE_SECONDS",
599
+ "TYPE_MILLISECONDS",
600
+ "TYPE_MINUTES",
601
+ "TYPE_HOURS",
602
+ "TYPE_STANDARD",
603
+ "TYPE_CURRENCY",
604
+ "TYPE_FEET",
605
+ "TYPE_MILES",
606
+ "TYPE_METERS",
607
+ "TYPE_KILOMETERS"
608
+ ]
609
+ }
610
+ },
611
+ required: []
612
+ }
613
+ },
614
+ rows: {
615
+ type: "array",
616
+ items: rowSchema
617
+ },
618
+ totals: {
619
+ type: "array",
620
+ items: rowSchema
621
+ },
622
+ maximums: {
623
+ type: "array",
624
+ items: rowSchema
625
+ },
626
+ minimums: {
627
+ type: "array",
628
+ items: rowSchema
629
+ },
630
+ rowCount: {
631
+ type: "integer",
632
+ format: "int32"
633
+ },
634
+ metadata: {
635
+ type: "object",
636
+ properties: {
637
+ currencyCode: { type: "string" },
638
+ timeZone: { type: "string" },
639
+ emptyReason: { type: "string" },
640
+ dataLossFromOtherRow: { type: "boolean" },
641
+ subjectToThresholding: { type: "boolean" },
642
+ samplingMetadatas: {
643
+ type: "array",
644
+ items: {
645
+ type: "object",
646
+ properties: {
647
+ samplesReadCount: {
648
+ type: "string",
649
+ format: "int64"
650
+ },
651
+ samplingSpaceSize: {
652
+ type: "string",
653
+ format: "int64"
654
+ }
655
+ },
656
+ required: []
657
+ }
658
+ },
659
+ schemaRestrictionResponse: {
660
+ type: "object",
661
+ properties: { activeMetricRestrictions: {
662
+ type: "array",
663
+ items: {
664
+ type: "object",
665
+ properties: {
666
+ metricName: { type: "string" },
667
+ restrictedMetricTypes: {
668
+ type: "array",
669
+ items: {
670
+ type: "string",
671
+ enum: [
672
+ "RESTRICTED_METRIC_TYPE_UNSPECIFIED",
673
+ "COST_DATA",
674
+ "REVENUE_DATA"
675
+ ]
676
+ }
677
+ }
678
+ },
679
+ required: []
680
+ }
681
+ } },
682
+ required: []
683
+ }
684
+ },
685
+ required: []
686
+ },
687
+ propertyQuota: {
688
+ type: "object",
689
+ properties: {
690
+ tokensPerDay: quotaStatusSchema,
691
+ tokensPerHour: quotaStatusSchema,
692
+ concurrentRequests: quotaStatusSchema,
693
+ serverErrorsPerProjectPerHour: quotaStatusSchema,
694
+ potentiallyThresholdedRequestsPerHour: quotaStatusSchema,
695
+ tokensPerProjectPerHour: quotaStatusSchema
696
+ },
697
+ required: []
698
+ }
699
+ },
700
+ required: []
701
+ };
702
+ const sendMeasurementProtocolEventsOutputSchema = {
703
+ type: "object",
704
+ properties: { message: { type: "string" } },
705
+ required: ["message"]
706
+ };
707
+ var accounts_default$1 = { listAccounts: (0, _prismatic_io_spectral.action)({
708
+ display: {
709
+ label: "List Accounts",
710
+ description: "Return a list of accounts accessible by the caller"
711
+ },
712
+ inputs: listAccountsInputs,
713
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
714
+ type: "actionOutput",
715
+ schema: listAccountsOutputSchema
716
+ }),
717
+ performSafety: "notAllowed",
718
+ perform: async (context, { connection, fetchAll, pagination }) => {
719
+ return { data: await paginateRecords(createAnalyticsClient({
720
+ connection,
721
+ endpointType: "adminv1beta",
722
+ debug: context.debug.enabled
723
+ }), "/accounts", {
724
+ pageSize: pagination.pageSize,
725
+ pageToken: pagination.pageToken
726
+ }, fetchAll, "accounts") };
727
+ },
728
+ examplePerform: async () => ({ ...listAccountsExamplePayload }),
729
+ examplePayload: listAccountsExamplePayload
730
+ }) };
731
+ //#endregion
732
+ //#region src/consts.ts
733
+ const GOOGLE_ANALYTICS_COLLECT_EVENTS_URL = "https://www.google-analytics.com";
734
+ var measurementEvents_default = { sendMeasurementProtocolEvents: (0, _prismatic_io_spectral.action)({
735
+ display: {
736
+ label: "Send Measurement Protocol Events",
737
+ description: "Sends Measurement Protocol Events to the Google Analytics GA4 account."
738
+ },
739
+ performSafety: "notAllowed",
740
+ perform: async (_context, { firebaseAppId, events, appInstanceId, apiSecret }) => {
741
+ const params = {
742
+ firebase_app_id: firebaseAppId,
743
+ api_secret: apiSecret
744
+ };
745
+ await (0, _prismatic_io_spectral_http.createClient)({ baseUrl: GOOGLE_ANALYTICS_COLLECT_EVENTS_URL }).post("/mp/collect", {
746
+ app_instance_id: appInstanceId,
747
+ events
748
+ }, { params });
749
+ return { data: { message: "Event Sent Successfully" } };
750
+ },
751
+ examplePerform: async () => ({ ...sendMeasurementProtocolEventsExamplePayload }),
752
+ inputs: {
753
+ firebaseAppId,
754
+ appInstanceId,
755
+ apiSecret,
756
+ events
757
+ },
758
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
759
+ type: "actionOutput",
760
+ schema: sendMeasurementProtocolEventsOutputSchema
761
+ }),
762
+ examplePayload: sendMeasurementProtocolEventsExamplePayload
763
+ }) };
764
+ //#endregion
765
+ //#region src/actions/properties.ts
766
+ const listProperties$1 = (0, _prismatic_io_spectral.action)({
767
+ display: {
768
+ label: "List Properties",
769
+ description: "List Google Analytics GA4 properties for an account"
770
+ },
771
+ inputs: listPropertiesInputs,
772
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
773
+ type: "actionOutput",
774
+ schema: listPropertiesOutputSchema
775
+ }),
776
+ performSafety: "notAllowed",
777
+ perform: async (context, { connection, accountId, pagination, fetchAll }) => {
778
+ return { data: await paginateRecords(createAnalyticsClient({
779
+ connection,
780
+ endpointType: "adminv1beta",
781
+ debug: context.debug.enabled
782
+ }), "/properties", {
783
+ filter: `parent:${accountId}`,
784
+ pageSize: pagination.pageSize,
785
+ pageToken: pagination.pageToken
786
+ }, fetchAll, "properties") };
787
+ },
788
+ examplePerform: async (_context, { accountId }) => {
789
+ const properties = listPropertiesExamplePayload.data.properties;
790
+ return { data: {
791
+ ...listPropertiesExamplePayload.data,
792
+ properties: properties.map((property) => ({
793
+ ...property,
794
+ parent: accountId,
795
+ account: accountId
796
+ }))
797
+ } };
798
+ },
799
+ examplePayload: listPropertiesExamplePayload
800
+ });
801
+ var properties_default$1 = {
802
+ getProperty: (0, _prismatic_io_spectral.action)({
803
+ display: {
804
+ label: "Get Property",
805
+ description: "Get property by ID"
806
+ },
807
+ inputs: getPropertyInputs,
808
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
809
+ type: "actionOutput",
810
+ schema: getPropertyOutputSchema
811
+ }),
812
+ performSafety: "safe",
813
+ perform: async (context, params) => {
814
+ const { data } = await createAnalyticsClient({
815
+ connection: params.connection,
816
+ endpointType: "adminv1beta",
817
+ debug: context.debug.enabled
818
+ }).get(params.propertyId);
819
+ return { data };
820
+ },
821
+ examplePayload: getPropertyExamplePayload
822
+ }),
823
+ listProperties: listProperties$1,
824
+ runReport: (0, _prismatic_io_spectral.action)({
825
+ display: {
826
+ label: "Run Report",
827
+ description: "Run a customized report on Google Analytics event data."
828
+ },
829
+ inputs: {
830
+ connection: connectionInput,
831
+ accountId: {
832
+ ...accountId,
833
+ required: false
834
+ },
835
+ propertyId: propertyIdInput,
836
+ requestBody: (0, _prismatic_io_spectral.input)({
837
+ label: "Request Body",
838
+ type: "code",
839
+ language: "json",
840
+ required: true,
841
+ comments: "See [Google Analytics API documentation](https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/properties/runReport) for details on what dimensions, metrics, etc., can be specified.",
842
+ default: JSON.stringify({
843
+ dimensions: [{ name: "pageTitle" }],
844
+ metrics: [{ name: "sessions" }],
845
+ dateRanges: [{
846
+ startDate: "7daysAgo",
847
+ endDate: "yesterday"
848
+ }],
849
+ dimensionFilter: { notExpression: { filter: {
850
+ fieldName: "pageTitle",
851
+ stringFilter: { value: "My Homepage" }
852
+ } } }
853
+ }, null, 2),
854
+ clean: _prismatic_io_spectral.util.types.toObject
855
+ })
856
+ },
857
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
858
+ type: "actionOutput",
859
+ schema: runReportOutputSchema
860
+ }),
861
+ performSafety: "notAllowed",
862
+ perform: async (context, params) => {
863
+ const { data } = await createAnalyticsClient({
864
+ connection: params.connection,
865
+ endpointType: "datav1beta",
866
+ debug: context.debug.enabled
867
+ }).post(`${params.propertyId}:runReport`, params.requestBody);
868
+ return { data };
869
+ },
870
+ examplePerform: async (_context, { requestBody }) => {
871
+ const { dimensions, metrics } = requestBody;
872
+ const example = runReportExamplePayload.data;
873
+ const dimensionHeaders = dimensions?.length ? dimensions.map(({ name }) => ({ name })) : example.dimensionHeaders;
874
+ const metricHeaders = metrics?.length ? metrics.map(({ name }) => ({ name })) : example.metricHeaders;
875
+ return { data: {
876
+ ...example,
877
+ dimensionHeaders,
878
+ metricHeaders,
879
+ rows: example.rows.map((row) => ({
880
+ dimensionValues: dimensionHeaders.map((_header, index) => row.dimensionValues[index] ?? row.dimensionValues[0]),
881
+ metricValues: metricHeaders.map((_header, index) => row.metricValues[index] ?? row.metricValues[0])
882
+ }))
883
+ } };
884
+ },
885
+ examplePayload: runReportExamplePayload
886
+ })
887
+ };
888
+ var rawRequest_default = { rawRequest: (0, _prismatic_io_spectral.action)({
889
+ display: {
890
+ label: "Raw Request",
891
+ description: "Send raw HTTP request to Google Analytics GA4"
892
+ },
893
+ inputs: rawRequestInputs,
894
+ performSafety: "notAllowed",
895
+ perform: async (context, { connection, baseUrl, ...restInputs }) => {
896
+ const { data } = await (0, _prismatic_io_spectral_http.sendRawRequest)(baseUrl, {
897
+ ...restInputs,
898
+ debugRequest: context.debug.enabled
899
+ }, { Authorization: `Bearer ${connection.token?.access_token}` });
900
+ return { data };
901
+ }
902
+ }) };
903
+ //#endregion
904
+ //#region src/actions/index.ts
905
+ var actions_default = {
906
+ ...accounts_default$1,
907
+ ...properties_default$1,
908
+ ...rawRequest_default,
909
+ ...measurementEvents_default
910
+ };
911
+ var connections_default = [(0, _prismatic_io_spectral.oauth2Connection)({
912
+ oauth2Type: _prismatic_io_spectral.OAuth2Type.AuthorizationCode,
913
+ key: "oauth2",
914
+ display: {
915
+ label: "OAuth 2.0",
916
+ description: "OAuth 2.0 connection for Google Analytics GA4",
917
+ icons: { oauth2ConnectionIconPath: "oauth-button.png" }
918
+ },
919
+ inputs: {
920
+ authorizeUrl: {
921
+ label: "Authorize URL",
922
+ placeholder: "Authorize URL",
923
+ type: "string",
924
+ required: true,
925
+ shown: false,
926
+ comments: "The OAuth 2.0 Authorization URL for Google Calendar",
927
+ default: "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent"
928
+ },
929
+ tokenUrl: {
930
+ label: "Token URL",
931
+ placeholder: "Token URL",
932
+ type: "string",
933
+ required: true,
934
+ shown: false,
935
+ comments: "The OAuth 2.0 Token URL for Google Analytics",
936
+ default: "https://oauth2.googleapis.com/token"
937
+ },
938
+ scopes: {
939
+ label: "Scopes",
940
+ placeholder: "Scopes",
941
+ type: "string",
942
+ required: true,
943
+ shown: true,
944
+ comments: "A space-delimited set of one or more scopes to get the user's permission to access.",
945
+ default: "https://www.googleapis.com/auth/analytics https://www.googleapis.com/auth/analytics.manage.users https://www.googleapis.com/auth/analytics.edit https://www.googleapis.com/auth/analytics.readonly"
946
+ },
947
+ clientId: {
948
+ label: "Client ID",
949
+ placeholder: "Client ID",
950
+ type: "string",
951
+ required: true,
952
+ comments: "Provide a string value for the client Id of your OAuth 2.0 application.",
953
+ shown: true
954
+ },
955
+ clientSecret: {
956
+ label: "Client Secret",
957
+ placeholder: "Client Secret",
958
+ type: "password",
959
+ comments: "Provide a string value for the client secret of your OAuth 2.0 application.",
960
+ required: true,
961
+ shown: true
962
+ }
963
+ }
964
+ })];
965
+ var accounts_default = { listAccounts: (0, _prismatic_io_spectral.dataSource)({
966
+ display: {
967
+ label: "List Accounts",
968
+ description: "Return a list of accounts accessible by the caller."
969
+ },
970
+ dataSourceType: "picklist",
971
+ inputs: { connection: connectionInput },
972
+ perform: async (_context, params) => {
973
+ return { result: ((await paginateRecords(createAnalyticsClient({
974
+ connection: params.connection,
975
+ endpointType: "adminv1beta"
976
+ }), "/accounts", {}, true, "accounts")).accounts || []).map((account) => ({
977
+ key: account.name,
978
+ label: account.displayName
979
+ })) };
980
+ },
981
+ examplePayload: listAccountsDataSourceExamplePayload
982
+ }) };
983
+ var properties_default = { listProperties: (0, _prismatic_io_spectral.dataSource)({
984
+ display: {
985
+ label: "List Properties",
986
+ description: "List Google Analytics GA4 properties for an account."
987
+ },
988
+ inputs: {
989
+ connection: connectionInput,
990
+ accountId: {
991
+ ...accountId,
992
+ dataSource: void 0
993
+ }
994
+ },
995
+ dataSourceType: "picklist",
996
+ perform: async (_context, params) => {
997
+ return { result: (await paginateRecords(createAnalyticsClient({
998
+ connection: params.connection,
999
+ endpointType: "adminv1beta"
1000
+ }), "/properties", { filter: `parent:${params.accountId}` }, true, "properties")).properties.map((property) => ({
1001
+ key: property.name,
1002
+ label: `${property.displayName} - ${property.name}`
1003
+ })) };
1004
+ }
1005
+ }) };
1006
+ //#endregion
1007
+ //#region src/dataSources/index.ts
1008
+ var dataSources_default = {
1009
+ ...accounts_default,
1010
+ ...properties_default
1011
+ };
1012
+ //#endregion
1013
+ //#region src/triggers/index.ts
1014
+ var triggers_default = { pollChangesTrigger: (0, _prismatic_io_spectral.pollingTrigger)({
1015
+ display: {
1016
+ label: "New and Updated Records",
1017
+ description: "Retrieves existing and ongoing records in Google Analytics GA4. Load history once, check for changes on a schedule, or both."
1018
+ },
1019
+ inputs: pollChangesInputs,
1020
+ triggerResolverSupport: "valid",
1021
+ batchConfig: { batchSize: 50 },
1022
+ triggerResolver: { resolveItems: (_context, { payload }) => resolvePollingRecordChanges(payload.body.data) },
1023
+ perform: async (context, payload, params) => {
1024
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1025
+ const state = context.polling.getState();
1026
+ const isBackfill = !state?.lastPolledAt && Boolean(params.lookBackDate);
1027
+ const lastPolledAt = state?.lastPolledAt ?? (params.lookBackDate || now);
1028
+ const client = createAnalyticsClient({
1029
+ connection: params.connection,
1030
+ endpointType: "adminv1beta",
1031
+ debug: context.debug.enabled
1032
+ });
1033
+ let allRecords;
1034
+ if (params.pollResourceType === POLL_RESOURCE_KEYS.ACCOUNTS) allRecords = (await paginateRecords(client, "/accounts", { pageSize: 200 }, true, "accounts")).accounts;
1035
+ else if (params.pollResourceType === POLL_RESOURCE_KEYS.PROPERTIES) {
1036
+ if (!params.accountId) throw new Error("Account ID is required when polling Properties. Select an account in the trigger configuration.");
1037
+ allRecords = (await paginateRecords(client, "/properties", {
1038
+ pageSize: 200,
1039
+ filter: `parent:${params.accountId}`
1040
+ }, true, "properties")).properties;
1041
+ } else throw new Error(`Unsupported resource type: ${params.pollResourceType}`);
1042
+ const { created, updated } = filterByTimestamp(allRecords, lastPolledAt, isBackfill, params.showNewRecords, params.showUpdatedRecords);
1043
+ const totalMatched = created.length + updated.length;
1044
+ if (context.debug.enabled) context.logger.debug(`Polled ${allRecords.length} ${params.pollResourceType} records, ${created.length} created, ${updated.length} updated since last poll.`);
1045
+ context.polling.setState({ lastPolledAt: now });
1046
+ return {
1047
+ payload: {
1048
+ ...payload,
1049
+ body: { data: {
1050
+ created,
1051
+ updated
1052
+ } }
1053
+ },
1054
+ polledNoChanges: totalMatched === 0
1055
+ };
1056
+ },
1057
+ examplePayload: pollChangesTriggerExamplePayload
1058
+ }) };
1059
+ //#endregion
1060
+ //#region src/index.ts
1061
+ var src_default = (0, _prismatic_io_spectral.component)({
1062
+ key: "google-analytics-ga4",
1063
+ documentationUrl: "https://prismatic.io/docs/components/google-analytics-ga4/",
1064
+ public: true,
1065
+ display: {
1066
+ label: "Google Analytics - GA4",
1067
+ category: "Application Connectors",
1068
+ description: "Manage Google Analytics GA4 accounts and data",
1069
+ iconPath: "icon.png"
1070
+ },
1071
+ actions: actions_default,
1072
+ triggers: triggers_default,
1073
+ dataSources: dataSources_default,
1074
+ connections: connections_default,
1075
+ hooks: { error: _prismatic_io_spectral_http.handleErrors }
1076
+ });
1077
+ //#endregion
1078
+ //#region npm-dist/.entry.ts
1079
+ var _entry_default = (0, _prismatic_io_spectral.callableComponent)(src_default);
1080
+ //#endregion
1081
+ module.exports = _entry_default;