@levi-gemcommerce/analytics 0.0.1-dev.6 → 0.0.1-dev.7

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,1046 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import dayjs from 'dayjs';
3
+ import quarterOfYear from 'dayjs/plugin/quarterOfYear';
4
+ import timezone from 'dayjs/plugin/timezone';
5
+ import utc from 'dayjs/plugin/utc';
6
+ import { useState, useRef, useCallback, useEffect } from 'react';
7
+
8
+ const ColumnSelectFragmentDoc = `
9
+ fragment ColumnSelect on Column {
10
+ dataType
11
+ displayName
12
+ name
13
+ }
14
+ `;
15
+ const TableDataSelectFragmentDoc = `
16
+ fragment TableDataSelect on TableData {
17
+ columns {
18
+ ...ColumnSelect
19
+ }
20
+ rows
21
+ }
22
+ `;
23
+ const GemXqlResultSelectFragmentDoc = `
24
+ fragment GemXQLResultSelect on GemxQLResult {
25
+ tableData {
26
+ ...TableDataSelect
27
+ }
28
+ }
29
+ `;
30
+
31
+ const GemxQlDocument = `
32
+ query GemxQL($input: String!) {
33
+ gemxQLQuery(query: $input) {
34
+ ...GemXQLResultSelect
35
+ }
36
+ }
37
+ ${GemXqlResultSelectFragmentDoc}
38
+ ${TableDataSelectFragmentDoc}
39
+ ${ColumnSelectFragmentDoc}`;
40
+
41
+ const useCustomGemXQlQuery = (variables, fetcher, options) => {
42
+ return useQuery({
43
+ queryKey: ['GemXQlCustom', variables],
44
+ queryFn: fetcher(GemxQlDocument, variables),
45
+ ...options,
46
+ });
47
+ };
48
+ useCustomGemXQlQuery.getKey = (variables) => ['GemXQlCustom', variables];
49
+
50
+ const useGemxQlNamedQuery = ({ name, variables, fetcher }, options) => {
51
+ const document = GemxQlDocument.replace(/query\s+GemxQL\b/, `query ${name}`);
52
+ return useQuery({
53
+ queryKey: [name, variables],
54
+ queryFn: fetcher(document, variables),
55
+ retry: 0,
56
+ ...options,
57
+ });
58
+ };
59
+ useGemxQlNamedQuery.getKey = (name, variables) => [name, variables];
60
+
61
+ const NONE_VALUE = 'None';
62
+ const PLACEHOLDER_VALUE = '-';
63
+
64
+ const TOTALS_SUFFIX = '___totals';
65
+ const COMPARE_PREFIX = 'comparison___';
66
+ const COMPARE_SUFFIX = '___previous_period';
67
+ const COMPARE_TOTALS_SUFFIX = `${COMPARE_SUFFIX}${TOTALS_SUFFIX}`;
68
+
69
+ const OPERATOR_IS = 'is';
70
+ const OPERATOR_IS_ONE_OF = 'is_one_of';
71
+
72
+ var EAnalyticDataType;
73
+ (function (EAnalyticDataType) {
74
+ EAnalyticDataType["DATE"] = "DATE";
75
+ EAnalyticDataType["ARRAY"] = "ARRAY";
76
+ EAnalyticDataType["OBJECT"] = "OBJECT";
77
+ EAnalyticDataType["STRING"] = "STRING";
78
+ EAnalyticDataType["INTEGER"] = "INTEGER";
79
+ EAnalyticDataType["CURRENCY"] = "CURRENCY";
80
+ EAnalyticDataType["PERCENT"] = "PERCENT";
81
+ EAnalyticDataType["DURATION"] = "DURATION";
82
+ EAnalyticDataType["MONTH"] = "MONTH_TIMESTAMP";
83
+ EAnalyticDataType["QUARTER"] = "QUARTER_TIMESTAMP";
84
+ EAnalyticDataType["DAY"] = "DAY_TIMESTAMP";
85
+ EAnalyticDataType["WEEK"] = "WEEK_TIMESTAMP";
86
+ EAnalyticDataType["YEAR"] = "YEAR_TIMESTAMP";
87
+ EAnalyticDataType["HOUR"] = "HOUR_TIMESTAMP";
88
+ })(EAnalyticDataType || (EAnalyticDataType = {}));
89
+ var EAnalyticColumnKey;
90
+ (function (EAnalyticColumnKey) {
91
+ EAnalyticColumnKey["CAMPAIGNS"] = "experiments";
92
+ EAnalyticColumnKey["VISITOR_ITEMS"] = "visitor_items";
93
+ EAnalyticColumnKey["DEVICE_ITEMS"] = "device_items";
94
+ EAnalyticColumnKey["TRAFFIC_SOURCE_ITEMS"] = "traffic_source_items";
95
+ })(EAnalyticColumnKey || (EAnalyticColumnKey = {}));
96
+
97
+ var EAnalyticMode;
98
+ (function (EAnalyticMode) {
99
+ EAnalyticMode["ALL_SESSION"] = "ALL_SESSION";
100
+ EAnalyticMode["FIRST_SESSION"] = "FIRST_SESSION";
101
+ EAnalyticMode["PAGE_ONLY"] = "PAGE_ONLY";
102
+ })(EAnalyticMode || (EAnalyticMode = {}));
103
+
104
+ var EAnalyticSource;
105
+ (function (EAnalyticSource) {
106
+ EAnalyticSource["SESSIONS"] = "sessions";
107
+ EAnalyticSource["SALES"] = "sales";
108
+ })(EAnalyticSource || (EAnalyticSource = {}));
109
+
110
+ var EVisitorType;
111
+ (function (EVisitorType) {
112
+ EVisitorType["NEW"] = "new";
113
+ EVisitorType["RETURNING"] = "returning";
114
+ })(EVisitorType || (EVisitorType = {}));
115
+ var EDeviceType;
116
+ (function (EDeviceType) {
117
+ EDeviceType["DESKTOP"] = "desktop";
118
+ EDeviceType["MOBILE"] = "mobile";
119
+ EDeviceType["TABLET"] = "tablet";
120
+ })(EDeviceType || (EDeviceType = {}));
121
+ var ETrafficSourceType;
122
+ (function (ETrafficSourceType) {
123
+ ETrafficSourceType["DIRECT"] = "direct";
124
+ ETrafficSourceType["EMAIL"] = "email";
125
+ ETrafficSourceType["REFERRAL"] = "referral";
126
+ ETrafficSourceType["ORGANIC_SOCIAL"] = "organic-social";
127
+ ETrafficSourceType["ORGANIC_SEARCH"] = "organic-search";
128
+ ETrafficSourceType["PAID_SOCIAL"] = "paid-social";
129
+ ETrafficSourceType["PAID_SEARCH"] = "paid-search";
130
+ ETrafficSourceType["SMS"] = "sms";
131
+ })(ETrafficSourceType || (ETrafficSourceType = {}));
132
+
133
+ var EComparisonOperator;
134
+ (function (EComparisonOperator) {
135
+ EComparisonOperator["EQ"] = "=";
136
+ EComparisonOperator["IN"] = "IN";
137
+ EComparisonOperator["LIKE"] = "LIKE";
138
+ })(EComparisonOperator || (EComparisonOperator = {}));
139
+ var EGroupOperator;
140
+ (function (EGroupOperator) {
141
+ EGroupOperator["OR"] = "OR";
142
+ EGroupOperator["AND"] = "AND";
143
+ })(EGroupOperator || (EGroupOperator = {}));
144
+
145
+ var EFilterField;
146
+ (function (EFilterField) {
147
+ EFilterField["DEVICE"] = "device";
148
+ EFilterField["DEVICES"] = "devices";
149
+ EFilterField["VISITOR"] = "visitor_type";
150
+ EFilterField["VISITORS"] = "visitor_types";
151
+ EFilterField["TRAFFIC_SOURCE"] = "traffic_source";
152
+ EFilterField["TRAFFIC_SOURCES"] = "traffic_sources";
153
+ EFilterField["VERSION"] = "version";
154
+ EFilterField["VERSIONS"] = "versions";
155
+ EFilterField["SINGLE_PAGE"] = "page_path";
156
+ EFilterField["LIST_PAGE"] = "page_paths";
157
+ EFilterField["GROUP_CAMPAIGN_ITEM"] = "group_campaign_item";
158
+ EFilterField["GROUP_CAMPAIGN_ITEMS"] = "group_campaign_items";
159
+ EFilterField["SINGLE_CAMPAIGN"] = "experiment_id";
160
+ EFilterField["GROUP_CAMPAIGN"] = "experiment_group_id";
161
+ EFilterField["CAMPAIGN_VERSION_ID"] = "version_id";
162
+ EFilterField["GROUP_CAMPAIGN_VERSION_ID"] = "group_version_id";
163
+ })(EFilterField || (EFilterField = {}));
164
+
165
+ var EOperatorField;
166
+ (function (EOperatorField) {
167
+ EOperatorField["DEVICE_OPERATOR"] = "deviceOperator";
168
+ EOperatorField["VISITOR_OPERATOR"] = "visitorOperator";
169
+ EOperatorField["TRAFFIC_SOURCE_OPERATOR"] = "trafficSourceOperator";
170
+ EOperatorField["VERSION_OPERATOR"] = "versionOperator";
171
+ EOperatorField["PAGE_OPERATOR"] = "pageOperator";
172
+ EOperatorField["CAMPAIGN_ITEM_OPERATOR"] = "campaignItemOperator";
173
+ })(EOperatorField || (EOperatorField = {}));
174
+
175
+ /**
176
+ * Controls which totals columns are appended to the query result.
177
+ *
178
+ * - NONE → no totals, GROUP BY only.
179
+ * - TOTALS → adds `<metric>___totals` (grand total only). Use with single-dimension GROUP BY.
180
+ * - ALL → adds subtotals per dimension group + grand total. Use with multi-dimension GROUP BY.
181
+ */
182
+ var EGroupWithClause;
183
+ (function (EGroupWithClause) {
184
+ EGroupWithClause["NONE"] = "";
185
+ EGroupWithClause["TOTALS"] = "TOTALS";
186
+ EGroupWithClause["ALL"] = "WITH GROUP_TOTALS, TOTALS";
187
+ })(EGroupWithClause || (EGroupWithClause = {}));
188
+
189
+ var EOrderDirectionType;
190
+ (function (EOrderDirectionType) {
191
+ EOrderDirectionType["ASC"] = "ASC";
192
+ EOrderDirectionType["DESC"] = "DESC";
193
+ })(EOrderDirectionType || (EOrderDirectionType = {}));
194
+
195
+ var EPageMetric;
196
+ (function (EPageMetric) {
197
+ EPageMetric["PAGE_ITEMS"] = "page_items";
198
+ EPageMetric["PAGE_PATHS"] = "page_paths";
199
+ })(EPageMetric || (EPageMetric = {}));
200
+ var EPageDimension;
201
+ (function (EPageDimension) {
202
+ EPageDimension["PAGE_PATH"] = "page_path";
203
+ })(EPageDimension || (EPageDimension = {}));
204
+ var EPageField;
205
+ (function (EPageField) {
206
+ EPageField["SHOPIFY_PAGE_ID"] = "shopify_page_id";
207
+ EPageField["LOCATION_PATH"] = "location_path";
208
+ EPageField["PAGE_TYPE"] = "page_type";
209
+ EPageField["PAGE_TITLE"] = "page_title";
210
+ EPageField["PAGE_PATH"] = "page_path";
211
+ EPageField["TEMPLATE_SUFFIX"] = "template_suffix";
212
+ })(EPageField || (EPageField = {}));
213
+
214
+ var ERowReaderMode;
215
+ (function (ERowReaderMode) {
216
+ ERowReaderMode["DEFAULT"] = "DEFAULT";
217
+ ERowReaderMode["COMPARISON"] = "COMPARISON";
218
+ ERowReaderMode["TOTALS"] = "TOTALS";
219
+ ERowReaderMode["COMPARISON_TOTALS"] = "COMPARISON_TOTALS";
220
+ })(ERowReaderMode || (ERowReaderMode = {}));
221
+
222
+ var ETimeDimension;
223
+ (function (ETimeDimension) {
224
+ ETimeDimension["HOUR"] = "hour";
225
+ ETimeDimension["DAY"] = "day";
226
+ ETimeDimension["WEEK"] = "week";
227
+ ETimeDimension["MONTH"] = "month";
228
+ ETimeDimension["QUARTER"] = "quarter";
229
+ ETimeDimension["YEAR"] = "year";
230
+ })(ETimeDimension || (ETimeDimension = {}));
231
+
232
+ const ROW_READER_MODE_CONFIG = {
233
+ [ERowReaderMode.DEFAULT]: { prefix: '', suffix: '' },
234
+ [ERowReaderMode.COMPARISON]: { prefix: COMPARE_PREFIX, suffix: COMPARE_SUFFIX },
235
+ [ERowReaderMode.TOTALS]: { prefix: '', suffix: TOTALS_SUFFIX },
236
+ [ERowReaderMode.COMPARISON_TOTALS]: { prefix: COMPARE_PREFIX, suffix: COMPARE_TOTALS_SUFFIX },
237
+ };
238
+
239
+ const DATE_QUERY_FORMAT = 'YYYY-MM-DDTHH:mm:ss';
240
+
241
+ const CLAUSE_KEYWORDS = [
242
+ 'MODE',
243
+ 'FROM',
244
+ 'SHOW',
245
+ 'GROUP BY',
246
+ 'TIMESERIES',
247
+ 'SINCE',
248
+ 'DURING',
249
+ 'TIMEZONE',
250
+ 'WHERE',
251
+ 'ORDER BY',
252
+ 'LIMIT',
253
+ 'OFFSET',
254
+ ];
255
+ const CLAUSE_KEYWORD_BOUNDARY = CLAUSE_KEYWORDS.join('|');
256
+
257
+ const DEFAULT_QUERY_LIMIT = 1000;
258
+
259
+ const DEFAULT_GROUP_OPERATOR = EGroupOperator.OR;
260
+ const joinFieldClauses = (parts, groupOperator = DEFAULT_GROUP_OPERATOR) => {
261
+ if (!parts.length)
262
+ return '';
263
+ if (parts.length === 1)
264
+ return parts[0] ?? '';
265
+ return `(${parts.join(` ${groupOperator} `)})`;
266
+ };
267
+ const buildEq = ({ fields, value, groupOperator }) => {
268
+ if (!fields.length)
269
+ return '';
270
+ return joinFieldClauses(fields.map((field) => `${field} = ${value}`), groupOperator);
271
+ };
272
+ const buildIn = ({ fields, values, groupOperator }) => {
273
+ if (!fields.length || !values.length)
274
+ return '';
275
+ return joinFieldClauses(fields.map((field) => `${field} IN (${values.join(', ')})`), groupOperator);
276
+ };
277
+ const buildLike = ({ fields, value, groupOperator }) => {
278
+ const trimmed = value.trim();
279
+ if (!trimmed || !fields.length)
280
+ return '';
281
+ return joinFieldClauses(fields.map((field) => `${field} LIKE '%${trimmed}%'`), groupOperator);
282
+ };
283
+ const buildExtraCondition = (condition) => {
284
+ switch (condition.operator) {
285
+ case EComparisonOperator.EQ:
286
+ return buildEq(condition);
287
+ case EComparisonOperator.IN:
288
+ return buildIn(condition);
289
+ case EComparisonOperator.LIKE:
290
+ return buildLike(condition);
291
+ }
292
+ };
293
+ const buildExtraConditions = (conditions) => {
294
+ if (!conditions?.length)
295
+ return [];
296
+ return conditions.map(buildExtraCondition).filter(Boolean);
297
+ };
298
+
299
+ const filterConfigs = [
300
+ {
301
+ operator: EOperatorField.DEVICE_OPERATOR,
302
+ singleField: EFilterField.DEVICE,
303
+ multiField: EFilterField.DEVICES,
304
+ fieldName: EFilterField.DEVICE,
305
+ },
306
+ {
307
+ operator: EOperatorField.VISITOR_OPERATOR,
308
+ singleField: EFilterField.VISITOR,
309
+ multiField: EFilterField.VISITORS,
310
+ fieldName: EFilterField.VISITOR,
311
+ },
312
+ {
313
+ operator: EOperatorField.TRAFFIC_SOURCE_OPERATOR,
314
+ singleField: EFilterField.TRAFFIC_SOURCE,
315
+ multiField: EFilterField.TRAFFIC_SOURCES,
316
+ fieldName: EFilterField.TRAFFIC_SOURCE,
317
+ },
318
+ {
319
+ operator: EOperatorField.VERSION_OPERATOR,
320
+ singleField: EFilterField.VERSION,
321
+ multiField: EFilterField.VERSIONS,
322
+ fieldName: EFilterField.VERSION,
323
+ },
324
+ {
325
+ operator: EOperatorField.PAGE_OPERATOR,
326
+ singleField: EFilterField.SINGLE_PAGE,
327
+ multiField: EFilterField.LIST_PAGE,
328
+ fieldName: EFilterField.SINGLE_PAGE,
329
+ },
330
+ {
331
+ operator: EOperatorField.CAMPAIGN_ITEM_OPERATOR,
332
+ singleField: EFilterField.GROUP_CAMPAIGN_ITEM,
333
+ multiField: EFilterField.GROUP_CAMPAIGN_ITEMS,
334
+ condition: EFilterField.GROUP_CAMPAIGN,
335
+ fieldName: EFilterField.SINGLE_CAMPAIGN,
336
+ },
337
+ {
338
+ operatorVal: OPERATOR_IS,
339
+ singleField: EFilterField.SINGLE_CAMPAIGN,
340
+ multiField: EFilterField.GROUP_CAMPAIGN,
341
+ },
342
+ ];
343
+ const buildCondition = (data) => {
344
+ const { operatorValue, singleValue, multiValue, fieldName } = data;
345
+ if (operatorValue === OPERATOR_IS && singleValue) {
346
+ return `${fieldName} = ${singleValue}`;
347
+ }
348
+ else if (operatorValue === OPERATOR_IS_ONE_OF && Array.isArray(multiValue) && multiValue.length > 0) {
349
+ return `${fieldName} IN (${multiValue.join(', ')})`;
350
+ }
351
+ return '';
352
+ };
353
+ const buildOverrideFilterConditions = (overrideFilters) => {
354
+ if (!overrideFilters)
355
+ return {};
356
+ return Object.fromEntries(Object.entries(overrideFilters)
357
+ .filter(([, value]) => value !== undefined)
358
+ .map(([key, value]) => {
359
+ const condition = Array.isArray(value) ? `${key} IN (${value.join(', ')})` : `${key} = ${value}`;
360
+ return [key, condition];
361
+ }));
362
+ };
363
+ const generateFilterConditions = (filters, overrideFilters) => {
364
+ const conditions = [];
365
+ const hasFilters = Object.keys(filters).length > 0;
366
+ const hasOverride = !!overrideFilters && Object.keys(overrideFilters).length > 0;
367
+ if (!hasFilters && !hasOverride)
368
+ return conditions;
369
+ const overrideConditionsMap = buildOverrideFilterConditions(overrideFilters);
370
+ filterConfigs.forEach(({ operator, operatorVal, singleField, multiField, condition, fieldName }) => {
371
+ if (overrideConditionsMap[singleField]) {
372
+ conditions.push(overrideConditionsMap[singleField]);
373
+ return;
374
+ }
375
+ const operatorValue = operator ? filters[operator] : operatorVal;
376
+ let singleValue = filters[singleField];
377
+ const multiValue = filters[multiField];
378
+ const conditionValue = condition && filters[condition];
379
+ if (condition && !conditionValue) {
380
+ return;
381
+ }
382
+ const resolvedFieldName = fieldName ?? (singleValue ? singleField : multiField);
383
+ if (operatorVal && !singleValue) {
384
+ singleValue = filters[multiField];
385
+ }
386
+ const result = buildCondition({
387
+ operatorValue,
388
+ singleValue: Array.isArray(singleValue) ? undefined : singleValue,
389
+ multiValue: Array.isArray(multiValue) ? multiValue : undefined,
390
+ fieldName: resolvedFieldName,
391
+ });
392
+ if (result)
393
+ conditions.push(result);
394
+ });
395
+ return conditions;
396
+ };
397
+ const buildVersionIdCondition = (versionIds) => {
398
+ const conditions = [];
399
+ if (!versionIds)
400
+ return conditions;
401
+ Object.entries(versionIds).forEach(([key, value]) => {
402
+ if (!value)
403
+ return;
404
+ conditions.push(`${key} = ${value}`);
405
+ });
406
+ return conditions;
407
+ };
408
+ const getFirstMetricInOrderBy = (orderBy, metrics) => {
409
+ if (!orderBy || metrics.length === 0)
410
+ return [];
411
+ const metric = metrics.find((metric) => orderBy[metric]);
412
+ return metric ? [metric] : [];
413
+ };
414
+
415
+ const build$a = ({ filters, overrideFilters, versionIds, extraConditions }) => {
416
+ const conditions = generateFilterConditions(filters, overrideFilters);
417
+ const versionIdConditions = buildVersionIdCondition(versionIds);
418
+ const builtExtraConditions = buildExtraConditions(extraConditions);
419
+ const allConditions = [...conditions, ...versionIdConditions, ...builtExtraConditions];
420
+ return allConditions.length > 0 ? `WHERE ${allConditions.join(' AND ')}` : '';
421
+ };
422
+ const parse$b = (query) => {
423
+ const match = query.match(new RegExp(`\\bWHERE\\s+([\\s\\S]+?)(?=\\s+(?:${CLAUSE_KEYWORD_BOUNDARY})\\b|$)`, 'i'));
424
+ if (!match)
425
+ return { filters: {} };
426
+ const conditions = (match[1] ?? '').trim().split(/\s+AND\s+/i);
427
+ const filters = {};
428
+ const versionIds = {};
429
+ conditions.forEach((condition) => {
430
+ const trimmed = condition.trim();
431
+ const inMatch = trimmed.match(/^(\S+)\s+IN\s+\((.+)\)$/i);
432
+ if (inMatch) {
433
+ const field = inMatch[1] ?? '';
434
+ const valuesStr = inMatch[2] ?? '';
435
+ const values = valuesStr.split(',').map((v) => v.trim());
436
+ const config = filterConfigs.find((c) => c.fieldName === field || c.multiField === field);
437
+ if (config?.operator) {
438
+ filters[config.multiField] = values;
439
+ filters[config.operator] = OPERATOR_IS_ONE_OF;
440
+ }
441
+ return;
442
+ }
443
+ const eqMatch = trimmed.match(/^(\S+)\s+=\s+(.+)$/);
444
+ if (eqMatch) {
445
+ const field = eqMatch[1] ?? '';
446
+ const value = eqMatch[2] ?? '';
447
+ const config = filterConfigs.find((c) => c.fieldName === field || c.singleField === field);
448
+ if (config) {
449
+ filters[config.singleField] = value;
450
+ if (config.operator)
451
+ filters[config.operator] = OPERATOR_IS;
452
+ }
453
+ else if (field) {
454
+ versionIds[field] = value;
455
+ }
456
+ }
457
+ });
458
+ return {
459
+ filters: filters,
460
+ versionIds: Object.keys(versionIds).length > 0 ? versionIds : undefined,
461
+ };
462
+ };
463
+
464
+ const build$9 = (sources) => `FROM ${sources.join(', ')}`;
465
+ const parse$a = (query) => {
466
+ const match = query.match(new RegExp(`\\bFROM\\s+([\\s\\S]+?)(?=\\s+(?:${CLAUSE_KEYWORD_BOUNDARY})\\b)`, 'i'));
467
+ if (!match)
468
+ return [];
469
+ return (match[1] ?? '')
470
+ .split(',')
471
+ .map((s) => s.trim())
472
+ .filter(Boolean);
473
+ };
474
+
475
+ const build$8 = (dimensions, withClause = EGroupWithClause.ALL) => dimensions.length === 0 ? '' : `GROUP BY ${dimensions.join(', ')} ${withClause}`;
476
+ const parse$9 = (query) => {
477
+ const match = query.match(new RegExp(`\\bGROUP BY\\s+([\\s\\S]+?)(?=\\s+(?:${CLAUSE_KEYWORD_BOUNDARY})\\b)`, 'i'));
478
+ if (!match)
479
+ return { dimensions: [] };
480
+ const content = (match[1] ?? '').trim();
481
+ const withIdx = content.toUpperCase().indexOf(' WITH ');
482
+ if (withIdx === -1) {
483
+ return {
484
+ dimensions: content
485
+ .split(',')
486
+ .map((s) => s.trim())
487
+ .filter(Boolean),
488
+ groupWithClause: EGroupWithClause.NONE,
489
+ };
490
+ }
491
+ return {
492
+ dimensions: content
493
+ .slice(0, withIdx)
494
+ .split(',')
495
+ .map((s) => s.trim())
496
+ .filter(Boolean),
497
+ groupWithClause: content.slice(withIdx + 1).trim(),
498
+ };
499
+ };
500
+
501
+ const build$7 = (limit) => {
502
+ if (!limit)
503
+ return '';
504
+ return `LIMIT ${limit}`;
505
+ };
506
+ const parse$8 = (query) => {
507
+ const match = query.match(/\bLIMIT\s+(\d+)/i);
508
+ return match ? parseInt(match[1] ?? '0', 10) : undefined;
509
+ };
510
+
511
+ const build$6 = (mode) => `MODE ${mode}`;
512
+ const parse$7 = (query) => {
513
+ const match = query.match(/\bMODE\s+(\S+)/i);
514
+ return match?.[1];
515
+ };
516
+
517
+ const build$5 = (offset) => {
518
+ if (!offset || offset < 0)
519
+ return '';
520
+ return `OFFSET ${offset}`;
521
+ };
522
+ const parse$6 = (query) => {
523
+ const match = query.match(/\bOFFSET\s+(\d+)/i);
524
+ return match ? parseInt(match[1] ?? '0', 10) : undefined;
525
+ };
526
+
527
+ const build$4 = (orderBy, columns) => {
528
+ if (!orderBy)
529
+ return '';
530
+ const orderByQuery = Object.values(orderBy)
531
+ .filter((item) => columns?.includes(item.field))
532
+ .map((item) => `${item.field} ${item.direction}`)
533
+ .join(', ');
534
+ return orderByQuery ? `ORDER BY ${orderByQuery}` : '';
535
+ };
536
+ const parse$5 = (query) => {
537
+ const match = query.match(new RegExp(`\\bORDER BY\\s+([\\s\\S]+?)(?=\\s+(?:${CLAUSE_KEYWORD_BOUNDARY})\\b|$)`, 'i'));
538
+ if (!match)
539
+ return undefined;
540
+ const orderBy = {};
541
+ (match[1] ?? '').split(',').forEach((item) => {
542
+ const parts = item.trim().split(/\s+/);
543
+ if (parts.length === 2) {
544
+ const field = parts[0] ?? '';
545
+ const direction = parts[1] ?? '';
546
+ if (field)
547
+ orderBy[field] = { field, direction: direction };
548
+ }
549
+ });
550
+ return Object.keys(orderBy).length > 0 ? orderBy : undefined;
551
+ };
552
+
553
+ const build$3 = (metrics) => `SHOW ${metrics.join(', ')}`;
554
+ const parse$4 = (query) => {
555
+ const match = query.match(new RegExp(`\\bSHOW\\s+([\\s\\S]+?)(?=\\s+(?:${CLAUSE_KEYWORD_BOUNDARY})\\b)`, 'i'));
556
+ if (!match)
557
+ return [];
558
+ return (match[1] ?? '')
559
+ .split(',')
560
+ .map((s) => s.trim())
561
+ .filter(Boolean);
562
+ };
563
+
564
+ dayjs.extend(utc);
565
+ dayjs.extend(timezone);
566
+ dayjs.extend(quarterOfYear);
567
+ let tz = 'UTC';
568
+ const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
569
+ function getInitialTimezone() {
570
+ return tz;
571
+ }
572
+ function setTz(value) {
573
+ tz = value;
574
+ dayjs.tz.setDefault(value);
575
+ }
576
+ const dayjsTz = (date) => {
577
+ if (!date)
578
+ return dayjs().tz(tz);
579
+ return dayjs(date).tz(tz);
580
+ };
581
+ const convertDateToTz = (date) => {
582
+ if (!date)
583
+ return dayjs.tz(dayjs().format(DEFAULT_DATE_FORMAT), tz);
584
+ return dayjs.tz(dayjs(date).format(DEFAULT_DATE_FORMAT), tz);
585
+ };
586
+
587
+ const toDate = (value) => (typeof value === 'string' ? dayjsTz(value) : value);
588
+ const isMidnight = (date) => date.hour() === 0 && date.minute() === 0;
589
+ const formatDateForQuery = (date, isEndDay = false) => {
590
+ if (isEndDay && isMidnight(date)) {
591
+ return date.endOf('day').format(DATE_QUERY_FORMAT);
592
+ }
593
+ return date.format(DATE_QUERY_FORMAT);
594
+ };
595
+ const getEndOfDayForQuery = (value) => {
596
+ if (!value) {
597
+ return formatDateForQuery(dayjsTz().endOf('day'));
598
+ }
599
+ return formatDateForQuery(dayjsTz(value).endOf('day'));
600
+ };
601
+ const getDateRangeForQuery = (dateRange) => {
602
+ const startDate = toDate(dateRange.startDate);
603
+ const endDate = toDate(dateRange.endDate);
604
+ const isInTheDay = startDate.isSame(endDate);
605
+ if (isInTheDay) {
606
+ return {
607
+ dateGTE: formatDateForQuery(startDate),
608
+ dateLTE: getEndOfDayForQuery(endDate),
609
+ };
610
+ }
611
+ return {
612
+ dateGTE: formatDateForQuery(startDate),
613
+ dateLTE: formatDateForQuery(endDate, true),
614
+ };
615
+ };
616
+
617
+ const buildOverrideTimeQuery = (key, value) => `DURING (${value}, ${key})`;
618
+ const build$2 = (data) => {
619
+ const { dateRange, compareDateRange, overrideFilters, timeDimension } = data;
620
+ const overrideTimeValue = timeDimension
621
+ ? overrideFilters?.[timeDimension]
622
+ : undefined;
623
+ if (timeDimension && overrideTimeValue) {
624
+ return buildOverrideTimeQuery(timeDimension, overrideTimeValue);
625
+ }
626
+ if (!dateRange)
627
+ return '';
628
+ const { dateGTE, dateLTE } = getDateRangeForQuery(dateRange);
629
+ const { dateGTE: compareDateGTE, dateLTE: compareDateLTE } = compareDateRange
630
+ ? getDateRangeForQuery(compareDateRange)
631
+ : {};
632
+ const compareToClause = compareDateRange ? `COMPARE TO ${compareDateGTE} UNTIL ${compareDateLTE}` : '';
633
+ return `SINCE ${dateGTE} UNTIL ${dateLTE} ${compareToClause}`;
634
+ };
635
+ const parse$3 = (query) => {
636
+ const duringMatch = query.match(/\bDURING\s+\(([^,]+),\s*([^)]+)\)/i);
637
+ if (duringMatch) {
638
+ const value = duringMatch[1] ?? '';
639
+ const key = duringMatch[2] ?? '';
640
+ return {
641
+ timeDimension: key.trim(),
642
+ overrideFilters: { [key.trim()]: value.trim() },
643
+ };
644
+ }
645
+ const sinceMatch = query.match(/\bSINCE\s+(\S+)\s+UNTIL\s+(\S+)/i);
646
+ if (!sinceMatch)
647
+ return {};
648
+ const dateRange = { startDate: dayjsTz(sinceMatch[1] ?? ''), endDate: dayjsTz(sinceMatch[2] ?? '') };
649
+ const compareMatch = query.match(/\bCOMPARE TO\s+(\S+)\s+UNTIL\s+(\S+)/i);
650
+ const compareDateRange = compareMatch
651
+ ? { startDate: dayjsTz(compareMatch[1] ?? ''), endDate: dayjsTz(compareMatch[2] ?? '') }
652
+ : undefined;
653
+ return { dateRange, compareDateRange };
654
+ };
655
+
656
+ const build$1 = (time) => {
657
+ if (!time)
658
+ return '';
659
+ return `TIMESERIES ${time}`;
660
+ };
661
+ const parse$2 = (query) => {
662
+ const match = query.match(/\bTIMESERIES\s+(\S+)/i);
663
+ return match?.[1];
664
+ };
665
+
666
+ const build = (timezone) => {
667
+ const tz = timezone ?? getInitialTimezone();
668
+ return tz ? `TIMEZONE ${tz}` : '';
669
+ };
670
+ const parse$1 = (query) => {
671
+ const match = query.match(/\bTIMEZONE\s+(\S+)/i);
672
+ return match?.[1];
673
+ };
674
+
675
+ function isValidQueryInput(query) {
676
+ if (typeof query !== 'string')
677
+ return false;
678
+ const q = query.trim();
679
+ const pattern = /^MODE\b[\s\S]*\bFROM\b[\s\S]*\bSHOW\b[\s\S]*$/;
680
+ return pattern.test(q);
681
+ }
682
+
683
+ const buildGemXQlQuery = (params) => {
684
+ const { mode, metrics, dimensions, dateRange, compareDateRange, filters, overrideFilters, versionIds, extraConditions, orderBy, sources, timeDimension, groupWithClause, timezone, limit = DEFAULT_QUERY_LIMIT, offset, extraOrderByFields, } = params;
685
+ const columns = [...dimensions, ...getFirstMetricInOrderBy(orderBy, metrics), ...(extraOrderByFields ?? [])];
686
+ const builtQuery = [
687
+ build$6(mode),
688
+ build$9(sources),
689
+ build$3(metrics),
690
+ build$8(dimensions, groupWithClause),
691
+ build$1(timeDimension),
692
+ build$2({ dateRange, compareDateRange, timeDimension, overrideFilters }),
693
+ build(timezone),
694
+ build$a({ filters, overrideFilters, versionIds, extraConditions }),
695
+ build$4(orderBy, columns),
696
+ build$7(limit),
697
+ build$5(offset),
698
+ ]
699
+ .filter(Boolean)
700
+ .join(' ');
701
+ return isValidQueryInput(builtQuery) ? builtQuery : '';
702
+ };
703
+
704
+ const parse = (query) => {
705
+ const { dateRange, compareDateRange, timeDimension: overrideTimeDimension, overrideFilters } = parse$3(query);
706
+ const timeDimension = overrideTimeDimension ?? parse$2(query);
707
+ const { dimensions, groupWithClause } = parse$9(query);
708
+ const { filters, versionIds } = parse$b(query);
709
+ return {
710
+ mode: parse$7(query),
711
+ sources: parse$a(query),
712
+ metrics: parse$4(query),
713
+ dimensions,
714
+ groupWithClause,
715
+ timeDimension,
716
+ dateRange: dateRange,
717
+ compareDateRange: compareDateRange,
718
+ overrideFilters,
719
+ filters,
720
+ versionIds,
721
+ timezone: parse$1(query),
722
+ orderBy: parse$5(query),
723
+ limit: parse$8(query),
724
+ offset: parse$6(query),
725
+ };
726
+ };
727
+
728
+ const getTotalsKey = (metric) => `${metric}${TOTALS_SUFFIX}`;
729
+ const getComparisonKey = (metric) => `${COMPARE_PREFIX}${metric}${COMPARE_SUFFIX}`;
730
+ const getComparisonTotalsKey = (metric) => `${COMPARE_PREFIX}${metric}${COMPARE_SUFFIX}${TOTALS_SUFFIX}`;
731
+
732
+ const extractQueryRows = (response) => response?.gemxQLQuery?.tableData?.rows ?? [];
733
+ const extractQueryColumns = (response) => response?.gemxQLQuery?.tableData?.columns ?? [];
734
+ const extractColumnTypes = (response) => {
735
+ const columns = extractQueryColumns(response);
736
+ const columnTypes = {};
737
+ for (const column of columns) {
738
+ if (column?.name)
739
+ columnTypes[column.name] = column.dataType;
740
+ }
741
+ return columnTypes;
742
+ };
743
+
744
+ const TRIM_DECIMAL_ZEROS_REGEX = /\.0+$/;
745
+ const DEFAULT_DECIMALS = 2;
746
+ const trimDecimalZeros = (number) => {
747
+ return `${number}`.replace(TRIM_DECIMAL_ZEROS_REGEX, '');
748
+ };
749
+ const cleanDecimal = (number, decimals = DEFAULT_DECIMALS) => {
750
+ return trimDecimalZeros(number.toFixed(decimals));
751
+ };
752
+
753
+ /**
754
+ * Utility function to calculate percentage and format it.
755
+ * @param part - The part value.
756
+ * @param total - The total value.
757
+ * @param decimals - The number of decimal places to format the percentage to.
758
+ * @returns The formatted percentage as a string.
759
+ */
760
+ const PERCENTAGE_THRESHOLD = 0.005;
761
+ const PERCENTAGE_THRESHOLD_STRING = '~0%';
762
+ const calcPercentage = (part, total, decimals = DEFAULT_DECIMALS) => {
763
+ if (typeof part !== 'number' || !total)
764
+ return undefined;
765
+ if (part === 0)
766
+ return 0;
767
+ const percentage = (part / total) * 100;
768
+ return parseFloat(cleanDecimal(percentage, decimals));
769
+ };
770
+ const isLessThanThreshold = (percentage) => percentage > 0 && percentage < PERCENTAGE_THRESHOLD;
771
+ const calcPercentageString = (part, total, decimals = 2) => {
772
+ const percentage = calcPercentage(part, total, decimals);
773
+ if (typeof percentage !== 'number')
774
+ return undefined;
775
+ if (isLessThanThreshold(percentage)) {
776
+ return PERCENTAGE_THRESHOLD_STRING;
777
+ }
778
+ return `${percentage}%`;
779
+ };
780
+
781
+ var AnalyticInterval;
782
+ (function (AnalyticInterval) {
783
+ AnalyticInterval["DAY"] = "DAY";
784
+ AnalyticInterval["HOUR"] = "HOUR";
785
+ AnalyticInterval["MONTH"] = "MONTH";
786
+ AnalyticInterval["QUARTER"] = "QUARTER";
787
+ AnalyticInterval["WEEK"] = "WEEK";
788
+ AnalyticInterval["YEAR"] = "YEAR";
789
+ })(AnalyticInterval || (AnalyticInterval = {}));
790
+ function numberWithCommas(x) {
791
+ return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
792
+ }
793
+ const SECONDS_IN_MINUTE = 60;
794
+ const SECONDS_IN_HOUR = SECONDS_IN_MINUTE * SECONDS_IN_MINUTE;
795
+ const getTimeDurationLabel = (valueInSeconds) => {
796
+ let data = valueInSeconds;
797
+ if (!Number.isFinite(valueInSeconds) || valueInSeconds == null) {
798
+ data = 0;
799
+ }
800
+ const fixedValue = cleanDecimal(data);
801
+ if (data >= SECONDS_IN_HOUR) {
802
+ const hours = Math.floor(data / SECONDS_IN_HOUR);
803
+ const minutes = Math.floor((data % SECONDS_IN_HOUR) / SECONDS_IN_MINUTE);
804
+ const seconds = Math.floor(data % SECONDS_IN_MINUTE);
805
+ return `${hours}h ${minutes}m ${seconds}s`;
806
+ }
807
+ else if (data >= SECONDS_IN_MINUTE) {
808
+ const minutes = Math.floor(data / SECONDS_IN_MINUTE);
809
+ const seconds = Math.floor(data % SECONDS_IN_MINUTE);
810
+ return `${minutes}m ${seconds}s`;
811
+ }
812
+ else {
813
+ return `${fixedValue}s`;
814
+ }
815
+ };
816
+ const getFormattedByInterval = (value, interval, options) => {
817
+ if (!value)
818
+ return '';
819
+ const optionFormat = options?.isExpandDetail
820
+ ? {
821
+ formatHouse: 'MMM D, h:mm A',
822
+ formatDay: 'MMM D, YYYY',
823
+ formatMonth: 'MMM YYYY',
824
+ formatYear: 'MMM YYYY',
825
+ }
826
+ : {
827
+ formatHouse: 'h A',
828
+ formatDay: 'MMM D',
829
+ formatMonth: 'MMM YYYY',
830
+ formatYear: 'YYYY',
831
+ };
832
+ switch (interval) {
833
+ case AnalyticInterval.HOUR:
834
+ return dayjsTz(value).format(optionFormat.formatHouse);
835
+ case AnalyticInterval.DAY:
836
+ case AnalyticInterval.WEEK:
837
+ return dayjsTz(value).format(optionFormat.formatDay);
838
+ case AnalyticInterval.MONTH:
839
+ return dayjsTz(value).format(optionFormat.formatMonth);
840
+ case AnalyticInterval.QUARTER: {
841
+ const d = dayjsTz(value);
842
+ return `Q${d.quarter()} ${d.format('YYYY')}`;
843
+ }
844
+ case AnalyticInterval.YEAR:
845
+ return dayjsTz(value).format(optionFormat.formatYear);
846
+ }
847
+ return dayjsTz(value).format(optionFormat.formatMonth);
848
+ };
849
+
850
+ const formatAnalyticDate = (dateString) => {
851
+ if (!dateString)
852
+ return 'None';
853
+ const date = dayjsTz(dateString);
854
+ const now = dayjsTz();
855
+ const yesterday = dayjsTz().subtract(1, 'day');
856
+ const isToday = date.format('YYYY-MM-DD') === now.format('YYYY-MM-DD');
857
+ const isYesterday = date.format('YYYY-MM-DD') === yesterday.format('YYYY-MM-DD');
858
+ if (isToday) {
859
+ return `Today at ${date.format('HH:mm')}`;
860
+ }
861
+ if (isYesterday) {
862
+ return `Yesterday at ${date.format('HH:mm')}`;
863
+ }
864
+ const daysDiff = now.diff(date, 'day');
865
+ const isWithinWeek = daysDiff >= 0 && daysDiff < 7;
866
+ if (isWithinWeek) {
867
+ return `${date.format('dddd')} at ${date.format('HH:mm')}`;
868
+ }
869
+ return `${date.format('MMM D')} at ${date.format('hh:mm a')}`;
870
+ };
871
+
872
+ const formatAnalyticData = ({ value, formatter, getTextPrice, name, }) => {
873
+ const dataTypeIsObjectOrArray = value !== null && (typeof value === 'object' || Array.isArray(value));
874
+ if (dataTypeIsObjectOrArray)
875
+ return value;
876
+ switch (formatter) {
877
+ case EAnalyticDataType.INTEGER: {
878
+ return numberWithCommas((value ?? 0).toString());
879
+ }
880
+ case EAnalyticDataType.CURRENCY: {
881
+ if (!getTextPrice)
882
+ return `${value ?? 0}`;
883
+ return getTextPrice(value, false);
884
+ }
885
+ case EAnalyticDataType.DATE: {
886
+ return formatAnalyticDate(value);
887
+ }
888
+ case EAnalyticDataType.PERCENT: {
889
+ if (typeof value !== 'number')
890
+ return calcPercentageString(0, 1, 2) ?? '';
891
+ return calcPercentageString(value / 100, 1, 2) ?? '';
892
+ }
893
+ case EAnalyticDataType.DURATION: {
894
+ return getTimeDurationLabel(Number(value));
895
+ }
896
+ case EAnalyticDataType.STRING: {
897
+ const fallbackValue = name === EAnalyticColumnKey.CAMPAIGNS ? '' : NONE_VALUE;
898
+ return value ?? fallbackValue;
899
+ }
900
+ case EAnalyticDataType.DAY: {
901
+ return getFormattedByInterval(value, AnalyticInterval.DAY, { isExpandDetail: true });
902
+ }
903
+ case EAnalyticDataType.HOUR: {
904
+ return getFormattedByInterval(value, AnalyticInterval.HOUR, { isExpandDetail: true });
905
+ }
906
+ case EAnalyticDataType.MONTH: {
907
+ return getFormattedByInterval(value, AnalyticInterval.MONTH, { isExpandDetail: true });
908
+ }
909
+ case EAnalyticDataType.YEAR: {
910
+ return getFormattedByInterval(value, AnalyticInterval.YEAR);
911
+ }
912
+ case EAnalyticDataType.WEEK: {
913
+ return getFormattedByInterval(value, AnalyticInterval.WEEK, { isExpandDetail: true });
914
+ }
915
+ case EAnalyticDataType.QUARTER: {
916
+ return getFormattedByInterval(value, AnalyticInterval.QUARTER, {
917
+ isExpandDetail: true,
918
+ });
919
+ }
920
+ case EAnalyticDataType.OBJECT:
921
+ case EAnalyticDataType.ARRAY: {
922
+ return value;
923
+ }
924
+ default:
925
+ return `${value}`;
926
+ }
927
+ };
928
+
929
+ const getTimeDimensionByDate = (startDate, endDate) => {
930
+ const durationInDays = endDate.diff(startDate, 'day');
931
+ if (durationInDays <= 3)
932
+ return ETimeDimension.HOUR;
933
+ if (durationInDays <= 93)
934
+ return ETimeDimension.DAY;
935
+ return ETimeDimension.MONTH;
936
+ };
937
+
938
+ const SESSION_KEY = 'sessions';
939
+ const hasMetricData = (metric) => {
940
+ const sessions = metric?.[SESSION_KEY];
941
+ return typeof sessions === 'number' && sessions > 0;
942
+ };
943
+
944
+ const parseRawJson = (raw) => {
945
+ if (!raw)
946
+ return undefined;
947
+ return typeof raw === 'string' ? JSON.parse(raw) : raw;
948
+ };
949
+ const parseJsonObject = (value) => {
950
+ try {
951
+ const parsed = parseRawJson(value);
952
+ if (!parsed || typeof parsed !== 'object')
953
+ return null;
954
+ return parsed;
955
+ }
956
+ catch {
957
+ return null;
958
+ }
959
+ };
960
+ const parseJsonArray = (raw) => {
961
+ try {
962
+ const parsed = parseRawJson(raw);
963
+ if (!Array.isArray(parsed))
964
+ return undefined;
965
+ return parsed;
966
+ }
967
+ catch {
968
+ return undefined;
969
+ }
970
+ };
971
+ const parseBreakdownItems = (raw) => parseJsonArray(raw)?.map((item) => ({ ...item, total: Number(item.total) }));
972
+ const parsePageItems = (value) => parseJsonObject(value);
973
+
974
+ const readNumeric = (metric, key) => {
975
+ const raw = metric?.[key];
976
+ return typeof raw === 'number' ? raw : 0;
977
+ };
978
+ const createNumericRowReader = (row, mode = ERowReaderMode.DEFAULT) => (key) => {
979
+ const { prefix, suffix } = ROW_READER_MODE_CONFIG[mode];
980
+ const v = row[`${prefix}${key}${suffix}`];
981
+ if (typeof v === 'number')
982
+ return v;
983
+ if (typeof v === 'string') {
984
+ const n = parseFloat(v);
985
+ return isNaN(n) ? 0 : n;
986
+ }
987
+ return 0;
988
+ };
989
+
990
+ const useAnalyticData = (getTextPrice) => {
991
+ const formatData = ({ value, formatter, name }) => {
992
+ return formatAnalyticData({ value, formatter, getTextPrice, name });
993
+ };
994
+ const computeMetric = ({ metric, previousMetric, metricKey, formatter, }) => {
995
+ if (!hasMetricData(metric))
996
+ return { value: 0, change: PLACEHOLDER_VALUE };
997
+ const currentValue = readNumeric(metric, metricKey);
998
+ const previousValue = readNumeric(previousMetric, metricKey);
999
+ const value = formatData({ value: currentValue, formatter, name: metricKey });
1000
+ if (currentValue === 0 && previousValue !== 0)
1001
+ return { value, change: -100 };
1002
+ if (previousValue === 0)
1003
+ return { value, change: PLACEHOLDER_VALUE };
1004
+ const change = ((currentValue - previousValue) / previousValue) * 100;
1005
+ return { value, change };
1006
+ };
1007
+ return { formatData, computeMetric };
1008
+ };
1009
+
1010
+ var GPaginationDirection;
1011
+ (function (GPaginationDirection) {
1012
+ GPaginationDirection["NEXT"] = "NEXT";
1013
+ GPaginationDirection["PREV"] = "PREV";
1014
+ })(GPaginationDirection || (GPaginationDirection = {}));
1015
+ const useGemXQlPagination = ({ resetKey, itemsPerPage, }) => {
1016
+ const [currentPage, setCurrentPage] = useState(1);
1017
+ const previousResetKeyRef = useRef();
1018
+ const shouldResetPagination = currentPage > 1 && previousResetKeyRef.current !== undefined && previousResetKeyRef.current !== resetKey;
1019
+ const effectivePage = shouldResetPagination ? 1 : currentPage;
1020
+ const offset = (effectivePage - 1) * itemsPerPage;
1021
+ const handlePageChange = useCallback((totalPages) => (direction) => {
1022
+ setCurrentPage((prevPage) => {
1023
+ const isNextPage = direction === GPaginationDirection.NEXT;
1024
+ const nextPage = isNextPage ? prevPage + 1 : prevPage - 1;
1025
+ return Math.min(Math.max(nextPage, 1), totalPages);
1026
+ });
1027
+ }, []);
1028
+ useEffect(() => {
1029
+ if (previousResetKeyRef.current !== resetKey) {
1030
+ previousResetKeyRef.current = resetKey;
1031
+ setCurrentPage(1);
1032
+ }
1033
+ }, [resetKey]);
1034
+ const buildPagination = useCallback(({ totalRecords, isLoading }) => {
1035
+ const totalPages = Math.max(1, Math.ceil(totalRecords / itemsPerPage));
1036
+ return {
1037
+ currentPage: effectivePage,
1038
+ totalPages,
1039
+ loading: isLoading,
1040
+ handlePageChange: handlePageChange(totalPages),
1041
+ };
1042
+ }, [effectivePage, itemsPerPage, handlePageChange]);
1043
+ return { currentPage: effectivePage, offset, buildPagination };
1044
+ };
1045
+
1046
+ export { AnalyticInterval, CLAUSE_KEYWORDS, CLAUSE_KEYWORD_BOUNDARY, COMPARE_PREFIX, COMPARE_SUFFIX, COMPARE_TOTALS_SUFFIX, ColumnSelectFragmentDoc, DATE_QUERY_FORMAT, DEFAULT_QUERY_LIMIT, EAnalyticColumnKey, EAnalyticDataType, EAnalyticMode, EAnalyticSource, EComparisonOperator, EDeviceType, EFilterField, EGroupOperator, EGroupWithClause, EOperatorField, EOrderDirectionType, EPageDimension, EPageField, EPageMetric, ERowReaderMode, ETimeDimension, ETrafficSourceType, EVisitorType, GPaginationDirection, GemXqlResultSelectFragmentDoc, GemxQlDocument, NONE_VALUE, OPERATOR_IS, OPERATOR_IS_ONE_OF, PLACEHOLDER_VALUE, ROW_READER_MODE_CONFIG, TOTALS_SUFFIX, TableDataSelectFragmentDoc, build$a as buildConditionQuery, buildExtraCondition, buildExtraConditions, build$9 as buildFromQuery, buildGemXQlQuery, build$8 as buildGroupQuery, build$7 as buildLimitQuery, build$6 as buildModeQuery, build$5 as buildOffsetQuery, build$4 as buildOrderByQuery, build$3 as buildShowQuery, build$2 as buildTimeQuery, build$1 as buildTimeSeriesQuery, build as buildTimezoneQuery, buildVersionIdCondition, convertDateToTz, createNumericRowReader, dayjsTz, extractColumnTypes, extractQueryColumns, extractQueryRows, filterConfigs, formatAnalyticData, formatAnalyticDate, generateFilterConditions, getComparisonKey, getComparisonTotalsKey, getFirstMetricInOrderBy, getFormattedByInterval, getInitialTimezone, getTimeDimensionByDate, getTimeDurationLabel, getTotalsKey, hasMetricData, isValidQueryInput, numberWithCommas, parseBreakdownItems, parse$b as parseConditionQuery, parse$a as parseFromQuery, parse as parseGemXQlQuery, parse$9 as parseGroupQuery, parseJsonArray, parseJsonObject, parse$8 as parseLimitQuery, parse$7 as parseModeQuery, parse$6 as parseOffsetQuery, parse$5 as parseOrderByQuery, parsePageItems, parse$4 as parseShowQuery, parse$3 as parseTimeQuery, parse$2 as parseTimeSeriesQuery, parse$1 as parseTimezoneQuery, readNumeric, setTz, useAnalyticData, useCustomGemXQlQuery, useGemXQlPagination, useGemxQlNamedQuery };