@quillsql/react 2.16.20 → 2.16.22

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.
package/dist/index.js CHANGED
@@ -277,6 +277,9 @@ var init_valueFormatter = __esm({
277
277
  maximumFractionDigits: 2
278
278
  });
279
279
  formatPercent = (value) => {
280
+ if (typeof value === "string" && /^\s*[+-]?(?:\d+(?:\.\d+)?|\.\d+)\s*%\s*$/.test(value)) {
281
+ return value;
282
+ }
280
283
  return formatterPercent.format(Number(value));
281
284
  };
282
285
  _getUTCDateHelper = (value, fmt) => {
@@ -1572,6 +1575,7 @@ var init_dateRangePickerUtils = __esm({
1572
1575
  }
1573
1576
  });
1574
1577
  const defaultCustomIntervals = defaultIntervals.flatMap((interval2) => {
1578
+ const normalizedLabel = interval2.label?.toLowerCase().trim();
1575
1579
  let createdIntervals = [];
1576
1580
  if (interval2.label === "This week") {
1577
1581
  createdIntervals = [
@@ -1721,13 +1725,13 @@ var init_dateRangePickerUtils = __esm({
1721
1725
  unit: "months"
1722
1726
  }
1723
1727
  ];
1724
- } else if (interval2.label === "Last month") {
1728
+ } else if (normalizedLabel === "last month") {
1725
1729
  createdIntervals = [
1726
1730
  {
1727
1731
  type: "previous_month"
1728
1732
  }
1729
1733
  ];
1730
- } else if (interval2.label === "Last quarter") {
1734
+ } else if (normalizedLabel === "previous quarter" || normalizedLabel === "last quarter") {
1731
1735
  createdIntervals = [
1732
1736
  {
1733
1737
  type: "previous_quarter"
@@ -2121,13 +2125,13 @@ var init_Filter = __esm({
2121
2125
  TimeUnit2[TimeUnit2["Hour"] = HOUR] = "Hour";
2122
2126
  return TimeUnit2;
2123
2127
  })(TimeUnit || {});
2124
- FieldType = /* @__PURE__ */ ((FieldType2) => {
2125
- FieldType2[FieldType2["String"] = STRING] = "String";
2126
- FieldType2[FieldType2["Number"] = NUMBER] = "Number";
2127
- FieldType2[FieldType2["Date"] = DATE] = "Date";
2128
- FieldType2[FieldType2["Null"] = NULL] = "Null";
2129
- FieldType2[FieldType2["Boolean"] = BOOLEAN] = "Boolean";
2130
- return FieldType2;
2128
+ FieldType = /* @__PURE__ */ ((FieldType3) => {
2129
+ FieldType3[FieldType3["String"] = STRING] = "String";
2130
+ FieldType3[FieldType3["Number"] = NUMBER] = "Number";
2131
+ FieldType3[FieldType3["Date"] = DATE] = "Date";
2132
+ FieldType3[FieldType3["Null"] = NULL] = "Null";
2133
+ FieldType3[FieldType3["Boolean"] = BOOLEAN] = "Boolean";
2134
+ return FieldType3;
2131
2135
  })(FieldType || {});
2132
2136
  InternalFilterType = /* @__PURE__ */ ((InternalFilterType2) => {
2133
2137
  InternalFilterType2["StringFilter"] = "string-filter";
@@ -2273,7 +2277,7 @@ var init_Filter = __esm({
2273
2277
  fieldType = FieldType.Boolean;
2274
2278
  operator = filter.operator;
2275
2279
  field = filter.field;
2276
- value = null;
2280
+ value = filter.value;
2277
2281
  table = filter.table;
2278
2282
  return {
2279
2283
  filterType,
@@ -2284,7 +2288,7 @@ var init_Filter = __esm({
2284
2288
  operator
2285
2289
  };
2286
2290
  case "date" /* Date */:
2287
- if (typeof filter.value === "object" && filter.value && "value" in filter.value && filter.value.unit) {
2291
+ if (typeof filter.value === "object" && filter.value && "unit" in filter.value) {
2288
2292
  if (filter.value.value && typeof filter.value.value !== "number" || typeof filter.value.unit !== "string") {
2289
2293
  throw new Error(
2290
2294
  `Invalid value for DateFilter, expected { value: number, unit: string }, got ${filter.value}`
@@ -2340,7 +2344,7 @@ var init_Filter = __esm({
2340
2344
  table = filter.table;
2341
2345
  } else {
2342
2346
  throw new Error(
2343
- `Invalid value for DateFilter, expected { value: number, unit: string }, { startDate: string, endDate: string}, or string, got ${filter.value}`
2347
+ `Invalid value for DateFilter, expected { value?: number, unit: string }, { startDate: string, endDate: string}, or string, got ${filter.value}`
2344
2348
  );
2345
2349
  }
2346
2350
  return {
@@ -15951,7 +15955,7 @@ var init_dataProcessing = __esm({
15951
15955
  } else {
15952
15956
  if (data.dateField && data.dateField.field) {
15953
15957
  const dateField = data.dateField.field.replaceAll('"', "");
15954
- const maxDate = new Date(
15958
+ const maxDate2 = new Date(
15955
15959
  data.rows.reduce((acc, row) => {
15956
15960
  const rowValue = row[dateField];
15957
15961
  if (rowValue) {
@@ -15960,7 +15964,7 @@ var init_dataProcessing = __esm({
15960
15964
  return Math.max(acc, 0);
15961
15965
  }, 0)
15962
15966
  );
15963
- const minDate = new Date(
15967
+ const minDate2 = new Date(
15964
15968
  data.rows.reduce((acc, row) => {
15965
15969
  const rowValue = row[dateField];
15966
15970
  if (rowValue) {
@@ -15970,16 +15974,16 @@ var init_dataProcessing = __esm({
15970
15974
  }, (/* @__PURE__ */ new Date("3022-01-01")).getTime())
15971
15975
  );
15972
15976
  const dateBucket = getDateBucketFromRange({
15973
- start: minDate,
15974
- end: maxDate
15977
+ start: minDate2,
15978
+ end: maxDate2
15975
15979
  });
15976
15980
  const minDateBucket = getDateString(
15977
- minDate.toISOString(),
15981
+ minDate2.toISOString(),
15978
15982
  void 0,
15979
15983
  dateBucket
15980
15984
  );
15981
15985
  const maxDateBucket = getDateString(
15982
- maxDate.toISOString(),
15986
+ maxDate2.toISOString(),
15983
15987
  void 0,
15984
15988
  dateBucket
15985
15989
  );
@@ -16096,8 +16100,9 @@ async function generatePivotWithSQL({
16096
16100
  getPivotRowCount = true,
16097
16101
  caller,
16098
16102
  getToken,
16099
- reportBuilderState
16103
+ reportBuilderState,
16100
16104
  // Add reportBuilderState parameter
16105
+ overwriteCache = false
16101
16106
  }) {
16102
16107
  const databaseType = client.databaseType || "postgresql";
16103
16108
  if (!pivot.aggregations?.length && pivot.aggregationType) {
@@ -16146,7 +16151,8 @@ async function generatePivotWithSQL({
16146
16151
  })),
16147
16152
  // Only pass dashboard filters in Dashboard context, not ReportBuilder
16148
16153
  tenants,
16149
- additionalProcessing
16154
+ additionalProcessing,
16155
+ overwriteCache
16150
16156
  },
16151
16157
  getToken
16152
16158
  });
@@ -16320,8 +16326,9 @@ async function generatePivotTable({
16320
16326
  additionalProcessing,
16321
16327
  caller,
16322
16328
  pivotQuery,
16323
- reportBuilderState
16329
+ reportBuilderState,
16324
16330
  // Add reportBuilderState parameter
16331
+ overwriteCache
16325
16332
  }) {
16326
16333
  try {
16327
16334
  if (report && client) {
@@ -16338,8 +16345,9 @@ async function generatePivotTable({
16338
16345
  additionalProcessing,
16339
16346
  caller,
16340
16347
  getToken,
16341
- reportBuilderState
16348
+ reportBuilderState,
16342
16349
  // Pass reportBuilderState
16350
+ overwriteCache
16343
16351
  });
16344
16352
  return pivotTable;
16345
16353
  }
@@ -18224,7 +18232,7 @@ var init_tableProcessing = __esm({
18224
18232
  try {
18225
18233
  let data;
18226
18234
  if (reportBuilderState) {
18227
- let { data: data2 } = await quillFetch({
18235
+ ({ data } = await quillFetch({
18228
18236
  client,
18229
18237
  task: "report-builder-counts",
18230
18238
  metadata: {
@@ -18238,9 +18246,9 @@ var init_tableProcessing = __esm({
18238
18246
  tenants
18239
18247
  },
18240
18248
  getToken
18241
- });
18249
+ }));
18242
18250
  } else {
18243
- let { data: data2 } = await quillFetch({
18251
+ ({ data } = await quillFetch({
18244
18252
  client,
18245
18253
  task: "query",
18246
18254
  metadata: {
@@ -18254,7 +18262,7 @@ var init_tableProcessing = __esm({
18254
18262
  },
18255
18263
  urlParameters: `caller=getCounts&task=query`,
18256
18264
  getToken
18257
- });
18265
+ }));
18258
18266
  }
18259
18267
  if (data.errorMessage) {
18260
18268
  return { filteredColumns: columns, exceededColumns: void 0 };
@@ -19244,9 +19252,9 @@ var init_dataFetcher = __esm({
19244
19252
  return { error: "Failed to fetch data" };
19245
19253
  }
19246
19254
  };
19247
- parseFetchResponse = async (client, task, response, getToken) => {
19255
+ parseFetchResponse = async (client, task, response, getToken, useInMemory = false) => {
19248
19256
  try {
19249
- if (response.status === "error" || response.data?.error) {
19257
+ if (response.status === "error" || response.data?.error && !useInMemory) {
19250
19258
  let errorPrefix = "Error: ";
19251
19259
  let errorMessage = "Failed to fetch report: " + (response.error || response.data?.error);
19252
19260
  if (task === "query" || task === "report-builder") {
@@ -19831,7 +19839,8 @@ async function cleanDashboardItem({
19831
19839
  additionalProcessing,
19832
19840
  customFields,
19833
19841
  skipPivotFetch,
19834
- tenants
19842
+ tenants,
19843
+ overwriteCache
19835
19844
  }) {
19836
19845
  if (!item) return defaultDashboardItem;
19837
19846
  if (!item.rows) {
@@ -19887,11 +19896,12 @@ async function cleanDashboardItem({
19887
19896
  page: DEFAULT_PAGINATION
19888
19897
  };
19889
19898
  if (item.pivot && skipPivotFetch && item.rows && item.fields) {
19899
+ const pivotSourceRows = item.rows.map((row) => ({ ...row }));
19890
19900
  const dateFilter = dashboardFilters?.find(
19891
19901
  (filter) => filter.filterType === "date_range"
19892
19902
  );
19893
19903
  pivotTable = processPivotData({
19894
- rows: item.rows,
19904
+ rows: pivotSourceRows,
19895
19905
  fields: item.fields,
19896
19906
  pivot: {
19897
19907
  ...item.pivot,
@@ -19935,7 +19945,8 @@ async function cleanDashboardItem({
19935
19945
  dateBucket,
19936
19946
  shouldPaginatePivotAsTable ? additionalProcessing : pivotChartProcessing,
19937
19947
  tenants,
19938
- customFields
19948
+ customFields,
19949
+ overwriteCache
19939
19950
  );
19940
19951
  }
19941
19952
  } catch (e) {
@@ -20040,7 +20051,7 @@ async function cleanDashboardItem({
20040
20051
  referenceLines: item.referenceLines
20041
20052
  };
20042
20053
  }
20043
- async function getPivotTable(report, dashboardFilters, dashboardName, getToken, client, eventTracking, dateBucketInitial, additionalProcessing, tenants, customFields) {
20054
+ async function getPivotTable(report, dashboardFilters, dashboardName, getToken, client, eventTracking, dateBucketInitial, additionalProcessing, tenants, customFields, overwriteCache) {
20044
20055
  if (!report) return void 0;
20045
20056
  const dateFilter = Object.values(dashboardFilters ?? {}).find(
20046
20057
  (filter) => filter.filterType === "date_range" || filter.operator === "BETWEEN"
@@ -20108,7 +20119,8 @@ async function getPivotTable(report, dashboardFilters, dashboardName, getToken,
20108
20119
  dashboardFilters,
20109
20120
  tenants,
20110
20121
  additionalProcessing,
20111
- getToken
20122
+ getToken,
20123
+ overwriteCache
20112
20124
  });
20113
20125
  return pivotTable;
20114
20126
  } catch (e) {
@@ -20356,7 +20368,8 @@ async function fetchReportRows({
20356
20368
  filters = [],
20357
20369
  getToken,
20358
20370
  abortSignal,
20359
- additionalProcessing
20371
+ additionalProcessing,
20372
+ overwriteCache = false
20360
20373
  }) {
20361
20374
  const fetchResp = await quillFetch({
20362
20375
  client,
@@ -20368,7 +20381,8 @@ async function fetchReportRows({
20368
20381
  filters: filters.map((filter) => ({ ...filter, options: void 0 })),
20369
20382
  useNewNodeSql: true,
20370
20383
  tenants,
20371
- additionalProcessing
20384
+ additionalProcessing,
20385
+ overwriteCache
20372
20386
  },
20373
20387
  abortSignal,
20374
20388
  getToken
@@ -20399,7 +20413,8 @@ async function fetchReport({
20399
20413
  abortSignal,
20400
20414
  getToken,
20401
20415
  eventTracking,
20402
- usePivotTask = false
20416
+ usePivotTask = false,
20417
+ overwriteCache = false
20403
20418
  }) {
20404
20419
  let reportInfo = void 0;
20405
20420
  let errorMessage = void 0;
@@ -20419,7 +20434,8 @@ async function fetchReport({
20419
20434
  rowsOnly,
20420
20435
  rowCountOnly,
20421
20436
  tenants,
20422
- flags
20437
+ flags,
20438
+ overwriteCache
20423
20439
  },
20424
20440
  abortSignal,
20425
20441
  getToken
@@ -20440,7 +20456,9 @@ async function fetchReport({
20440
20456
  getToken,
20441
20457
  eventTracking,
20442
20458
  tenants,
20443
- skipPivotFetch: usePivotTask
20459
+ // When not using pivot-template, avoid fallback pivot-template fetches.
20460
+ skipPivotFetch: !usePivotTask,
20461
+ overwriteCache
20444
20462
  });
20445
20463
  } catch (error) {
20446
20464
  if (error instanceof Error && error.name === "AbortError") {
@@ -20490,7 +20508,8 @@ async function processReportResponse({
20490
20508
  getToken,
20491
20509
  eventTracking,
20492
20510
  tenants,
20493
- skipPivotFetch = false
20511
+ skipPivotFetch = false,
20512
+ overwriteCache
20494
20513
  }) {
20495
20514
  const shouldSkipPivotFetch = skipPivotFetch || !!resp?.pivotRows && !!resp?.fields;
20496
20515
  const dashboardItem = {
@@ -20523,7 +20542,8 @@ async function processReportResponse({
20523
20542
  getToken,
20524
20543
  tenants,
20525
20544
  eventTracking,
20526
- skipPivotFetch: shouldSkipPivotFetch
20545
+ skipPivotFetch: shouldSkipPivotFetch,
20546
+ overwriteCache
20527
20547
  });
20528
20548
  if (additionalProcessing) {
20529
20549
  reportInfo.pagination = additionalProcessing.page;
@@ -21600,6 +21620,1952 @@ async function getClientTenantIds({
21600
21620
 
21601
21621
  // src/Context.tsx
21602
21622
  init_columnProcessing();
21623
+
21624
+ // src/utils/cacheCab.ts
21625
+ init_dataFetcher();
21626
+
21627
+ // src/utils/inMemoryFilterEngine.ts
21628
+ init_Filter();
21629
+ init_dateRangePickerUtils();
21630
+ import {
21631
+ addHours,
21632
+ addMonths,
21633
+ addWeeks,
21634
+ addYears,
21635
+ addDays as addDays2,
21636
+ isValid as isValid4,
21637
+ startOfDay as startOfDay3,
21638
+ startOfHour,
21639
+ startOfMonth as startOfMonth2,
21640
+ startOfQuarter as startOfQuarter2,
21641
+ startOfWeek as startOfWeek3,
21642
+ startOfYear,
21643
+ subDays as subDays2,
21644
+ subHours as subHours2,
21645
+ subMonths as subMonths3,
21646
+ subWeeks as subWeeks2,
21647
+ subYears as subYears2
21648
+ } from "date-fns";
21649
+ var UNSET = Symbol("unset");
21650
+ var isFilter = (f) => {
21651
+ return f && typeof f === "object" && "filterType" in f && !("id" in f);
21652
+ };
21653
+ var isDashboardFilterUpdate = (f) => {
21654
+ return f && typeof f === "object" && "label" in f;
21655
+ };
21656
+ var normalizeFilterString = (value) => value.replaceAll("%", "").toLowerCase();
21657
+ var normalizeRowString = (value) => value.toLowerCase();
21658
+ var resolveFieldKeys = (field, table, resolver) => {
21659
+ if (!field) return [];
21660
+ const resolved = resolver?.(field, table);
21661
+ if (Array.isArray(resolved)) return resolved;
21662
+ if (typeof resolved === "string") return [resolved];
21663
+ return [field];
21664
+ };
21665
+ var getRowValue = (row, fieldKeys, cache) => {
21666
+ if (fieldKeys.length === 0) return void 0;
21667
+ const cacheKey = fieldKeys.join("|");
21668
+ if (Object.prototype.hasOwnProperty.call(cache, cacheKey)) {
21669
+ return cache[cacheKey];
21670
+ }
21671
+ for (const key of fieldKeys) {
21672
+ if (Object.prototype.hasOwnProperty.call(row, key)) {
21673
+ cache[cacheKey] = row[key];
21674
+ return row[key];
21675
+ }
21676
+ }
21677
+ cache[cacheKey] = void 0;
21678
+ return void 0;
21679
+ };
21680
+ var coerceNumber = (value) => {
21681
+ if (typeof value === "number" && Number.isFinite(value)) return value;
21682
+ if (typeof value === "string" && value !== "") {
21683
+ const parsed = Number(value);
21684
+ return Number.isFinite(parsed) ? parsed : null;
21685
+ }
21686
+ return null;
21687
+ };
21688
+ var coerceBoolean = (value) => {
21689
+ if (typeof value === "boolean") return value;
21690
+ if (typeof value === "number") return value !== 0;
21691
+ if (typeof value === "string") {
21692
+ const normalized = value.toLowerCase();
21693
+ if (normalized === "true" || normalized === "1") return true;
21694
+ if (normalized === "false" || normalized === "0") return false;
21695
+ }
21696
+ return null;
21697
+ };
21698
+ var toDateMs = (value) => {
21699
+ if (value instanceof Date) {
21700
+ return isValid4(value) ? value.getTime() : null;
21701
+ }
21702
+ if (typeof value === "number") {
21703
+ const date = new Date(value);
21704
+ return isValid4(date) ? date.getTime() : null;
21705
+ }
21706
+ if (typeof value === "string") {
21707
+ const date = new Date(value);
21708
+ return isValid4(date) ? date.getTime() : null;
21709
+ }
21710
+ return null;
21711
+ };
21712
+ var buildColumnTypeMap = (columns) => {
21713
+ if (!columns?.length) return null;
21714
+ const map = /* @__PURE__ */ Object.create(null);
21715
+ for (const column of columns) {
21716
+ map[column.field] = column.jsType;
21717
+ }
21718
+ return map;
21719
+ };
21720
+ var resolveDashboardField = (filter, options) => {
21721
+ const mapped = options.filterMap?.[filter.label];
21722
+ if (mapped?.field) {
21723
+ return { field: mapped.field, table: mapped.table };
21724
+ }
21725
+ if (filter.filterType === "date_range" /* Date */ && options.dateField?.field) {
21726
+ return { field: options.dateField.field, table: options.dateField.table };
21727
+ }
21728
+ if (filter.field) {
21729
+ return { field: filter.field, table: filter.table };
21730
+ }
21731
+ return null;
21732
+ };
21733
+ var buildRelativeDateRange = (operator, value, now2, weekStartsOn) => {
21734
+ const amount = value.value || 1;
21735
+ const unit = value.unit;
21736
+ const currentStart = (() => {
21737
+ switch (unit) {
21738
+ case TimeUnit.Hour:
21739
+ return startOfHour(now2);
21740
+ case TimeUnit.Day:
21741
+ return startOfDay3(now2);
21742
+ case TimeUnit.Week:
21743
+ return startOfWeek3(now2, { weekStartsOn });
21744
+ case TimeUnit.Month:
21745
+ return startOfMonth2(now2);
21746
+ case TimeUnit.Quarter:
21747
+ return startOfQuarter2(now2);
21748
+ case TimeUnit.Year:
21749
+ return startOfYear(now2);
21750
+ default:
21751
+ return startOfDay3(now2);
21752
+ }
21753
+ })();
21754
+ const addUnit = (base, multiplier) => {
21755
+ switch (unit) {
21756
+ case TimeUnit.Hour:
21757
+ return addHours(base, multiplier);
21758
+ case TimeUnit.Day:
21759
+ return addDays2(base, multiplier);
21760
+ case TimeUnit.Week:
21761
+ return addWeeks(base, multiplier);
21762
+ case TimeUnit.Month:
21763
+ return addMonths(base, multiplier);
21764
+ case TimeUnit.Quarter:
21765
+ return addMonths(base, multiplier * 3);
21766
+ case TimeUnit.Year:
21767
+ return addYears(base, multiplier);
21768
+ default:
21769
+ return addDays2(base, multiplier);
21770
+ }
21771
+ };
21772
+ const subUnit = (base, multiplier) => {
21773
+ switch (unit) {
21774
+ case TimeUnit.Hour:
21775
+ return subHours2(base, multiplier);
21776
+ case TimeUnit.Day:
21777
+ return subDays2(base, multiplier);
21778
+ case TimeUnit.Week:
21779
+ return subWeeks2(base, multiplier);
21780
+ case TimeUnit.Month:
21781
+ return subMonths3(base, multiplier);
21782
+ case TimeUnit.Quarter:
21783
+ return subMonths3(base, multiplier * 3);
21784
+ case TimeUnit.Year:
21785
+ return subYears2(base, multiplier);
21786
+ default:
21787
+ return subDays2(base, multiplier);
21788
+ }
21789
+ };
21790
+ switch (operator) {
21791
+ case DateOperator.InTheLast: {
21792
+ const start2 = subUnit(now2, amount);
21793
+ return { start: start2.getTime() };
21794
+ }
21795
+ case DateOperator.InThePrevious: {
21796
+ const start2 = subUnit(currentStart, amount);
21797
+ return { start: start2.getTime(), end: currentStart.getTime(), endExclusive: true };
21798
+ }
21799
+ case DateOperator.InTheCurrent: {
21800
+ const end = addUnit(currentStart, 1);
21801
+ return {
21802
+ start: currentStart.getTime(),
21803
+ end: end.getTime(),
21804
+ endExclusive: true
21805
+ };
21806
+ }
21807
+ default:
21808
+ return null;
21809
+ }
21810
+ };
21811
+ var buildStringPredicate = (operator, value, fieldKeys) => {
21812
+ if (value === void 0 || value === null) return null;
21813
+ const normalizedValue = Array.isArray(value) ? value.map((v) => normalizeFilterString(String(v))) : normalizeFilterString(String(value));
21814
+ if (Array.isArray(normalizedValue)) {
21815
+ if (normalizedValue.length === 0) return null;
21816
+ } else if (normalizedValue === "") {
21817
+ return null;
21818
+ }
21819
+ if (operator === StringOperator.Is || operator === StringOperator.IsNot) {
21820
+ const values = Array.isArray(normalizedValue) ? normalizedValue : [normalizedValue];
21821
+ const valueSet = new Set(values);
21822
+ return (row, cache) => {
21823
+ const rowValue = getRowValue(row, fieldKeys, cache);
21824
+ if (rowValue === null || rowValue === void 0) return false;
21825
+ const normalized = normalizeRowString(String(rowValue));
21826
+ const hasValue = valueSet.has(normalized);
21827
+ return operator === StringOperator.Is ? hasValue : !hasValue;
21828
+ };
21829
+ }
21830
+ if (Array.isArray(normalizedValue)) {
21831
+ return null;
21832
+ }
21833
+ return (row, cache) => {
21834
+ const rowValue = getRowValue(row, fieldKeys, cache);
21835
+ if (rowValue === null || rowValue === void 0) return false;
21836
+ const normalizedRow = normalizeRowString(String(rowValue));
21837
+ switch (operator) {
21838
+ case StringOperator.IsExactly:
21839
+ return normalizedRow === normalizedValue;
21840
+ case StringOperator.IsNotExactly:
21841
+ return normalizedRow !== normalizedValue;
21842
+ case StringOperator.Contains:
21843
+ return normalizedRow.includes(normalizedValue);
21844
+ default:
21845
+ return false;
21846
+ }
21847
+ };
21848
+ };
21849
+ var buildNumericPredicate = (operator, value, fieldKeys) => {
21850
+ const filterValue = coerceNumber(value);
21851
+ if (filterValue === null) return null;
21852
+ return (row, cache) => {
21853
+ const rowValue = getRowValue(row, fieldKeys, cache);
21854
+ const parsed = coerceNumber(rowValue);
21855
+ if (parsed === null) return false;
21856
+ switch (operator) {
21857
+ case NumberOperator.EqualTo:
21858
+ return parsed === filterValue;
21859
+ case NumberOperator.NotEqualTo:
21860
+ return parsed !== filterValue;
21861
+ case NumberOperator.GreaterThan:
21862
+ return parsed > filterValue;
21863
+ case NumberOperator.LessThan:
21864
+ return parsed < filterValue;
21865
+ case NumberOperator.GreaterThanOrEqualTo:
21866
+ return parsed >= filterValue;
21867
+ case NumberOperator.LessThanOrEqualTo:
21868
+ return parsed <= filterValue;
21869
+ default:
21870
+ return false;
21871
+ }
21872
+ };
21873
+ };
21874
+ var buildNullPredicate = (operator, fieldKeys) => {
21875
+ return (row, cache) => {
21876
+ const rowValue = getRowValue(row, fieldKeys, cache);
21877
+ const isNullish = rowValue === null || rowValue === void 0;
21878
+ return operator === NullOperator.IsNull ? isNullish : !isNullish;
21879
+ };
21880
+ };
21881
+ var buildBooleanPredicate = (operator, value, fieldKeys) => {
21882
+ const filterValue = coerceBoolean(value);
21883
+ if (filterValue === null) return null;
21884
+ return (row, cache) => {
21885
+ const rowValue = getRowValue(row, fieldKeys, cache);
21886
+ const parsed = coerceBoolean(rowValue);
21887
+ if (parsed === null) return false;
21888
+ return operator === BoolOperator.EqualTo ? parsed === filterValue : parsed !== filterValue;
21889
+ };
21890
+ };
21891
+ var buildDateComparisonPredicate = (operator, value, fieldKeys) => {
21892
+ const filterDate = toDateMs(value);
21893
+ if (filterDate === null) return null;
21894
+ return (row, cache) => {
21895
+ const rowValue = getRowValue(row, fieldKeys, cache);
21896
+ const rowDate = toDateMs(rowValue);
21897
+ if (rowDate === null) return false;
21898
+ switch (operator) {
21899
+ case DateOperator.EqualTo:
21900
+ return rowDate === filterDate;
21901
+ case DateOperator.NotEqualTo:
21902
+ return rowDate !== filterDate;
21903
+ case DateOperator.GreaterThan:
21904
+ return rowDate > filterDate;
21905
+ case DateOperator.LessThan:
21906
+ return rowDate < filterDate;
21907
+ case DateOperator.GreaterThanOrEqualTo:
21908
+ return rowDate >= filterDate;
21909
+ case DateOperator.LessThanOrEqualTo:
21910
+ return rowDate <= filterDate;
21911
+ default:
21912
+ return false;
21913
+ }
21914
+ };
21915
+ };
21916
+ var buildDateRangePredicate = (range, fieldKeys) => {
21917
+ if (range.start === void 0 && range.end === void 0) return null;
21918
+ return (row, cache) => {
21919
+ const rowValue = getRowValue(row, fieldKeys, cache);
21920
+ const rowDate = toDateMs(rowValue);
21921
+ if (rowDate === null) return false;
21922
+ if (range.start !== void 0 && rowDate < range.start) return false;
21923
+ if (range.end !== void 0) {
21924
+ if (range.endExclusive) {
21925
+ return rowDate < range.end;
21926
+ }
21927
+ return rowDate <= range.end;
21928
+ }
21929
+ return true;
21930
+ };
21931
+ };
21932
+ var buildCustomDatePredicate = (value, fieldKeys) => {
21933
+ const start2 = value.startDate ? toDateMs(value.startDate) : null;
21934
+ const end = value.endDate ? toDateMs(value.endDate) : null;
21935
+ if (start2 === null && end === null) return null;
21936
+ return buildDateRangePredicate(
21937
+ {
21938
+ start: start2 ?? void 0,
21939
+ end: end ?? void 0,
21940
+ endExclusive: false
21941
+ },
21942
+ fieldKeys
21943
+ );
21944
+ };
21945
+ var compileCustomFilter = (filter, options) => {
21946
+ const fieldKeys = resolveFieldKeys(
21947
+ filter.field,
21948
+ filter.table,
21949
+ options.fieldKeyResolver
21950
+ );
21951
+ if (fieldKeys.length === 0) return null;
21952
+ switch (filter.filterType) {
21953
+ case "string-filter" /* StringFilter */:
21954
+ case "string-in-filter" /* StringInFilter */:
21955
+ return buildStringPredicate(
21956
+ filter.operator,
21957
+ filter.value,
21958
+ fieldKeys
21959
+ );
21960
+ case "numeric-filter" /* NumericFilter */:
21961
+ return buildNumericPredicate(
21962
+ filter.operator,
21963
+ filter.value,
21964
+ fieldKeys
21965
+ );
21966
+ case "null-filter" /* NullFilter */:
21967
+ return buildNullPredicate(
21968
+ filter.operator,
21969
+ fieldKeys
21970
+ );
21971
+ case "boolean-filter" /* BooleanFilter */:
21972
+ return buildBooleanPredicate(
21973
+ filter.operator,
21974
+ filter.value,
21975
+ fieldKeys
21976
+ );
21977
+ case "date-custom-filter" /* DateCustomFilter */:
21978
+ return buildCustomDatePredicate(
21979
+ filter.value,
21980
+ fieldKeys
21981
+ );
21982
+ case "date-comparison-filter" /* DateComparisonFilter */:
21983
+ return buildDateComparisonPredicate(
21984
+ filter.operator,
21985
+ filter.value,
21986
+ fieldKeys
21987
+ );
21988
+ case "date-filter" /* DateFilter */: {
21989
+ const value = filter.value;
21990
+ if (!value?.unit) return null;
21991
+ const now2 = options.now ?? /* @__PURE__ */ new Date();
21992
+ const range = buildRelativeDateRange(
21993
+ filter.operator,
21994
+ value,
21995
+ now2,
21996
+ options.weekStartsOn ?? 0
21997
+ );
21998
+ if (!range) return null;
21999
+ return buildDateRangePredicate(range, fieldKeys);
22000
+ }
22001
+ default:
22002
+ return null;
22003
+ }
22004
+ };
22005
+ var compileDashboardFilter = (filter, options, columnTypeMap) => {
22006
+ const resolved = resolveDashboardField(filter, options);
22007
+ if (!resolved?.field) return null;
22008
+ const fieldKeys = resolveFieldKeys(
22009
+ resolved.field,
22010
+ resolved.table,
22011
+ options.fieldKeyResolver
22012
+ );
22013
+ if (fieldKeys.length === 0) return null;
22014
+ switch (filter.filterType) {
22015
+ case "string" /* String */: {
22016
+ const stringFilter = filter;
22017
+ if (stringFilter.stringFilterType === "multiselect" /* Multiselect */) {
22018
+ if (!stringFilter.values || stringFilter.values.length === 0) {
22019
+ return null;
22020
+ }
22021
+ return buildStringPredicate(
22022
+ StringOperator.Is,
22023
+ stringFilter.values,
22024
+ fieldKeys
22025
+ );
22026
+ }
22027
+ if (!stringFilter.selectedValue) return null;
22028
+ return buildStringPredicate(
22029
+ StringOperator.IsExactly,
22030
+ stringFilter.selectedValue,
22031
+ fieldKeys
22032
+ );
22033
+ }
22034
+ case "date_range" /* Date */: {
22035
+ const dateFilter = filter;
22036
+ const start2 = dateFilter.startDate ? dateFilter.startDate.getTime() : void 0;
22037
+ const end = dateFilter.endDate ? dateFilter.endDate.getTime() : void 0;
22038
+ return buildDateRangePredicate(
22039
+ { start: start2, end, endExclusive: false },
22040
+ fieldKeys
22041
+ );
22042
+ }
22043
+ case "tenant" /* Tenant */: {
22044
+ const tenantFilter = filter;
22045
+ if (!tenantFilter.values || tenantFilter.values.length === 0) {
22046
+ return null;
22047
+ }
22048
+ const normalizedValues = tenantFilter.values.map(
22049
+ (v) => normalizeFilterString(String(v))
22050
+ );
22051
+ const valueSet = new Set(normalizedValues);
22052
+ const fieldType = columnTypeMap?.[resolved.field] ?? "string";
22053
+ return (row, cache) => {
22054
+ const rowValue = getRowValue(row, fieldKeys, cache);
22055
+ if (rowValue === null || rowValue === void 0) return false;
22056
+ if (fieldType === "number") {
22057
+ const numeric = coerceNumber(rowValue);
22058
+ return numeric !== null && valueSet.has(normalizeFilterString(String(numeric)));
22059
+ }
22060
+ return valueSet.has(normalizeRowString(String(rowValue)));
22061
+ };
22062
+ }
22063
+ default:
22064
+ return null;
22065
+ }
22066
+ };
22067
+ var updateDashboardFilters = (filtersToUpdate, dashboardFilters) => {
22068
+ return dashboardFilters.map((filter) => {
22069
+ const update = filtersToUpdate.find((u) => u.label === filter.label);
22070
+ if (!update) return filter;
22071
+ if (filter.filterType === "string" /* String */) {
22072
+ if (filter.stringFilterType === "multiselect" /* Multiselect */) {
22073
+ return {
22074
+ ...filter,
22075
+ values: update.value
22076
+ };
22077
+ }
22078
+ return {
22079
+ ...filter,
22080
+ selectedValue: update.value
22081
+ };
22082
+ }
22083
+ if (filter.filterType === "date_range" /* Date */) {
22084
+ const presetOptions = convertPresetOptionsToSelectableList(
22085
+ filter.presetOptions ?? [],
22086
+ filter.defaultPresetRanges ?? []
22087
+ );
22088
+ const value = update.value;
22089
+ const preset = presetOptions.find((p) => {
22090
+ const pStart = p.startDate ? new Date(p.startDate).toISOString() : void 0;
22091
+ const vStart = value.startDate ? new Date(value.startDate).toISOString() : void 0;
22092
+ const pEnd = p.endDate ? new Date(p.endDate).toISOString() : void 0;
22093
+ const vEnd = value.endDate ? new Date(value.endDate).toISOString() : void 0;
22094
+ return pStart === vStart && pEnd === vEnd;
22095
+ });
22096
+ if (!preset) {
22097
+ return { ...filter, startDate: value.startDate, endDate: value.endDate };
22098
+ }
22099
+ return {
22100
+ ...filter,
22101
+ preset,
22102
+ startDate: value.startDate,
22103
+ endDate: value.endDate
22104
+ };
22105
+ }
22106
+ if (filter.filterType === "tenant" /* Tenant */) {
22107
+ const value = update.value;
22108
+ let values;
22109
+ if (Array.isArray(value)) {
22110
+ values = value;
22111
+ } else if (typeof value === "string") {
22112
+ values = [value];
22113
+ } else {
22114
+ values = [];
22115
+ }
22116
+ return {
22117
+ ...filter,
22118
+ values
22119
+ };
22120
+ }
22121
+ return filter;
22122
+ });
22123
+ };
22124
+ var compileApplyFiltersPredicate = (filters, options) => {
22125
+ const dashboardUpdates = filters.filter(isDashboardFilterUpdate);
22126
+ const customFilters = filters.filter(isFilter);
22127
+ const internalCustomFilters = customFilters.map((filter) => {
22128
+ try {
22129
+ return convertCustomFilter(filter);
22130
+ } catch (error) {
22131
+ return null;
22132
+ }
22133
+ }).concat(options.customFilters ?? []).filter((filter) => filter !== null && filter !== void 0);
22134
+ const dashboardFilters = options.dashboardFilters ?? [];
22135
+ const updatedDashboardFilters = dashboardUpdates.length ? updateDashboardFilters(dashboardUpdates, dashboardFilters) : dashboardFilters;
22136
+ const columnTypeMap = buildColumnTypeMap(options.columns);
22137
+ const predicates = [];
22138
+ for (const filter of internalCustomFilters) {
22139
+ const predicate = compileCustomFilter(filter, options);
22140
+ if (predicate) predicates.push(predicate);
22141
+ }
22142
+ for (const filter of updatedDashboardFilters) {
22143
+ const predicate = compileDashboardFilter(
22144
+ filter,
22145
+ options,
22146
+ columnTypeMap
22147
+ );
22148
+ if (predicate) predicates.push(predicate);
22149
+ }
22150
+ if (predicates.length === 0) {
22151
+ return { predicate: () => true, hasPredicates: false };
22152
+ }
22153
+ return {
22154
+ predicate: (row) => {
22155
+ const cache = /* @__PURE__ */ Object.create(null);
22156
+ for (const predicate of predicates) {
22157
+ if (!predicate(row, cache)) return false;
22158
+ }
22159
+ return true;
22160
+ },
22161
+ hasPredicates: true
22162
+ };
22163
+ };
22164
+ var applyFiltersInMemory = (rows, filters, options) => {
22165
+ if (!rows.length) return rows;
22166
+ const { predicate, hasPredicates } = compileApplyFiltersPredicate(
22167
+ filters,
22168
+ options
22169
+ );
22170
+ if (!hasPredicates) return rows;
22171
+ const results = [];
22172
+ for (let i = 0; i < rows.length; i += 1) {
22173
+ const row = rows[i];
22174
+ if (predicate(row)) {
22175
+ results.push(row);
22176
+ }
22177
+ }
22178
+ return results;
22179
+ };
22180
+
22181
+ // src/utils/inMemoryPivotEngine.ts
22182
+ init_columnType();
22183
+ var MS_IN_DAY = 24 * 60 * 60 * 1e3;
22184
+ var MONTHS = [
22185
+ "Jan",
22186
+ "Feb",
22187
+ "Mar",
22188
+ "Apr",
22189
+ "May",
22190
+ "Jun",
22191
+ "Jul",
22192
+ "Aug",
22193
+ "Sep",
22194
+ "Oct",
22195
+ "Nov",
22196
+ "Dec"
22197
+ ];
22198
+ var INVALID_DATE_KEY = "-2026";
22199
+ function singleAggToMultiAgg(pivot) {
22200
+ if (pivot.aggregations) {
22201
+ return pivot;
22202
+ }
22203
+ const newPivot = { ...pivot };
22204
+ const newAgg = { aggregationType: pivot.aggregationType };
22205
+ const fieldsToCopy = [
22206
+ "valueField",
22207
+ "valueFieldType",
22208
+ "valueField2",
22209
+ "valueField2Type"
22210
+ ];
22211
+ fieldsToCopy.forEach((x) => {
22212
+ if (pivot[x]) {
22213
+ newAgg[x] = pivot[x];
22214
+ delete newPivot[x];
22215
+ }
22216
+ });
22217
+ newPivot.aggregations = [newAgg];
22218
+ return newPivot;
22219
+ }
22220
+ function isDateFormat2(xAxisFormat) {
22221
+ const DATE_FORMATS = [
22222
+ "yyyy",
22223
+ "mmm_dd",
22224
+ "mmm_yyyy",
22225
+ "mmm_dd_yyyy",
22226
+ "hh_ap_pm",
22227
+ "mmm_dd-mmm_dd",
22228
+ "mmm_dd_hh:mm_ap_pm",
22229
+ "wo, yyyy"
22230
+ ];
22231
+ const isDate = DATE_FORMATS.includes(xAxisFormat.toLowerCase());
22232
+ return isDate;
22233
+ }
22234
+ function isValidDate3(date) {
22235
+ try {
22236
+ return date instanceof Date && !isNaN(date.getTime());
22237
+ } catch {
22238
+ return false;
22239
+ }
22240
+ }
22241
+ function isDateLikeType(fieldType) {
22242
+ if (!fieldType) {
22243
+ return false;
22244
+ }
22245
+ return isDateType(fieldType) || isDateFormat2(fieldType);
22246
+ }
22247
+ function inferDateBucketFromFilters(pivot, filters) {
22248
+ if (!pivot?.rowFieldType) {
22249
+ return void 0;
22250
+ }
22251
+ const normalizedType = String(pivot.rowFieldType).toLowerCase();
22252
+ if (isDateFormat2(normalizedType)) {
22253
+ switch (normalizedType) {
22254
+ case "mmm_dd-mmm_dd":
22255
+ return "week";
22256
+ case "yyyy":
22257
+ case "wo, yyyy":
22258
+ return "year";
22259
+ case "mmm_dd_hh:mm_ap_pm":
22260
+ case "hh_ap_pm":
22261
+ return "day";
22262
+ default:
22263
+ return "month";
22264
+ }
22265
+ }
22266
+ const isDateLike = isDateType(normalizedType);
22267
+ if (!isDateLike) {
22268
+ return void 0;
22269
+ }
22270
+ let earliest;
22271
+ let latest;
22272
+ const extractTimestamp = (value) => {
22273
+ if (!value) {
22274
+ return void 0;
22275
+ }
22276
+ if (value instanceof Date) {
22277
+ return value.getTime();
22278
+ }
22279
+ const parsed = Date.parse(value);
22280
+ return Number.isNaN(parsed) ? void 0 : parsed;
22281
+ };
22282
+ (Array.isArray(filters) ? filters : []).forEach((filter) => {
22283
+ if (!filter || filter.filterType !== "date_range") {
22284
+ return;
22285
+ }
22286
+ if (filter.primaryRange?.value === "ALL_TIME") {
22287
+ earliest = -1;
22288
+ return;
22289
+ }
22290
+ const possibleStarts = [
22291
+ filter.startDate,
22292
+ filter.start,
22293
+ filter.primaryRange?.startDate,
22294
+ filter.primaryRange?.start,
22295
+ filter.range?.startDate,
22296
+ filter.range?.start
22297
+ ];
22298
+ const possibleEnds = [
22299
+ filter.endDate,
22300
+ filter.end,
22301
+ filter.primaryRange?.endDate,
22302
+ filter.primaryRange?.end,
22303
+ filter.range?.endDate,
22304
+ filter.range?.end
22305
+ ];
22306
+ possibleStarts.forEach((value) => {
22307
+ const timestamp = extractTimestamp(value);
22308
+ if (timestamp === void 0) {
22309
+ return;
22310
+ }
22311
+ earliest = earliest === void 0 ? timestamp : Math.min(earliest, timestamp);
22312
+ });
22313
+ possibleEnds.forEach((value) => {
22314
+ const timestamp = extractTimestamp(value);
22315
+ if (timestamp === void 0) {
22316
+ return;
22317
+ }
22318
+ latest = latest === void 0 ? timestamp : Math.max(latest, timestamp);
22319
+ });
22320
+ });
22321
+ if (earliest === -1) {
22322
+ return "month";
22323
+ }
22324
+ if (earliest === void 0 || latest === void 0) {
22325
+ return void 0;
22326
+ }
22327
+ if (latest < earliest) {
22328
+ [earliest, latest] = [latest, earliest];
22329
+ }
22330
+ const diffInDays = Math.max(1, Math.round((latest - earliest) / MS_IN_DAY));
22331
+ if (diffInDays < 20) {
22332
+ return "day";
22333
+ }
22334
+ if (diffInDays <= 59) {
22335
+ return "week";
22336
+ }
22337
+ if (diffInDays <= 400) {
22338
+ return "month";
22339
+ }
22340
+ return "year";
22341
+ }
22342
+ function getDateRangeFromFilters(filters) {
22343
+ let earliest;
22344
+ let latest;
22345
+ const extractTimestamp = (value) => {
22346
+ if (!value) return void 0;
22347
+ if (value instanceof Date) return value.getTime();
22348
+ const parsed = Date.parse(value);
22349
+ return Number.isNaN(parsed) ? void 0 : parsed;
22350
+ };
22351
+ (Array.isArray(filters) ? filters : []).forEach((filter) => {
22352
+ if (!filter || filter.filterType !== "date_range") return;
22353
+ if (filter.primaryRange?.value === "ALL_TIME") return;
22354
+ const possibleStarts = [
22355
+ filter.startDate,
22356
+ filter.start,
22357
+ filter.primaryRange?.startDate,
22358
+ filter.primaryRange?.start,
22359
+ filter.range?.startDate,
22360
+ filter.range?.start
22361
+ ];
22362
+ const possibleEnds = [
22363
+ filter.endDate,
22364
+ filter.end,
22365
+ filter.primaryRange?.endDate,
22366
+ filter.primaryRange?.end,
22367
+ filter.range?.endDate,
22368
+ filter.range?.end
22369
+ ];
22370
+ possibleStarts.forEach((v) => {
22371
+ const ts = extractTimestamp(v);
22372
+ if (ts === void 0) return;
22373
+ earliest = earliest === void 0 ? ts : Math.min(earliest, ts);
22374
+ });
22375
+ possibleEnds.forEach((v) => {
22376
+ const ts = extractTimestamp(v);
22377
+ if (ts === void 0) return;
22378
+ latest = latest === void 0 ? ts : Math.max(latest, ts);
22379
+ });
22380
+ });
22381
+ if (earliest === void 0 || latest === void 0) return void 0;
22382
+ if (latest < earliest) [earliest, latest] = [latest, earliest];
22383
+ return {
22384
+ start: new Date(earliest),
22385
+ end: new Date(latest)
22386
+ };
22387
+ }
22388
+ function generateBucketKeys(start2, end, bucket) {
22389
+ const keys = /* @__PURE__ */ new Set();
22390
+ const startDay = new Date(
22391
+ Date.UTC(start2.getUTCFullYear(), start2.getUTCMonth(), start2.getUTCDate())
22392
+ );
22393
+ const endDay = new Date(
22394
+ Date.UTC(end.getUTCFullYear(), end.getUTCMonth(), end.getUTCDate())
22395
+ );
22396
+ const cur = new Date(startDay);
22397
+ while (cur <= endDay) {
22398
+ keys.add(getDateKey(cur, bucket));
22399
+ cur.setUTCDate(cur.getUTCDate() + 1);
22400
+ }
22401
+ return Array.from(keys).sort((a, b) => parseInt(a) - parseInt(b));
22402
+ }
22403
+ function buildEmptyAggRow(aggregations) {
22404
+ const multiAgg = aggregations.length > 1;
22405
+ const empty = {};
22406
+ aggregations.forEach((agg) => {
22407
+ const { aggregationType: type, valueField } = agg;
22408
+ const key = !valueField ? `${type}` + (multiAgg ? `_${type}` : "") : `${valueField}` + (multiAgg ? `_${type}` : "");
22409
+ empty[key] = 0;
22410
+ });
22411
+ return empty;
22412
+ }
22413
+ function getBucketRange(dateKey, bucket) {
22414
+ const d = new Date(parseInt(dateKey, 10));
22415
+ let start2 = new Date(d);
22416
+ let end = new Date(d);
22417
+ switch (bucket) {
22418
+ case "day":
22419
+ start2 = new Date(
22420
+ Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())
22421
+ );
22422
+ end = new Date(start2);
22423
+ break;
22424
+ case "week":
22425
+ start2 = new Date(d);
22426
+ end = new Date(start2);
22427
+ end.setUTCDate(start2.getUTCDate() + 6);
22428
+ break;
22429
+ case "month":
22430
+ start2 = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1));
22431
+ end = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + 1, 0));
22432
+ break;
22433
+ case "year":
22434
+ start2 = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
22435
+ end = new Date(Date.UTC(d.getUTCFullYear(), 11, 31));
22436
+ break;
22437
+ }
22438
+ return { start: start2, end };
22439
+ }
22440
+ function formatBucketNameFromRange(start2, end, bucket) {
22441
+ const pad = (n) => String(n).padStart(2, "0");
22442
+ switch (bucket) {
22443
+ case "day":
22444
+ return `${pad(start2.getUTCDate())} ${MONTHS[start2.getUTCMonth()]} ${start2.getUTCFullYear()}`;
22445
+ case "week": {
22446
+ const sameMonth = start2.getUTCMonth() === end.getUTCMonth();
22447
+ return sameMonth ? `${MONTHS[start2.getUTCMonth()]} ${start2.getUTCDate()} - ${end.getUTCDate()}` : `${MONTHS[start2.getUTCMonth()]} ${start2.getUTCDate()} - ${MONTHS[end.getUTCMonth()]} ${end.getUTCDate()}`;
22448
+ }
22449
+ case "month":
22450
+ return `${MONTHS[start2.getUTCMonth()]} ${start2.getUTCFullYear()}`;
22451
+ case "year":
22452
+ return `${start2.getUTCFullYear()}`;
22453
+ default:
22454
+ return "";
22455
+ }
22456
+ }
22457
+ function getDateKey(date, bucket) {
22458
+ if (!isValidDate3(date)) {
22459
+ return INVALID_DATE_KEY;
22460
+ }
22461
+ switch (bucket) {
22462
+ case "day":
22463
+ return Date.UTC(
22464
+ date.getUTCFullYear(),
22465
+ date.getUTCMonth(),
22466
+ date.getUTCDate()
22467
+ ).toString();
22468
+ case "week": {
22469
+ const day = date.getUTCDay();
22470
+ const mondayUtc = Date.UTC(
22471
+ date.getUTCFullYear(),
22472
+ date.getUTCMonth(),
22473
+ date.getUTCDate() - (day + 6) % 7
22474
+ );
22475
+ return mondayUtc.toString();
22476
+ }
22477
+ case "month":
22478
+ return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1).toString();
22479
+ case "year":
22480
+ return Date.UTC(date.getUTCFullYear(), 0, 1).toString();
22481
+ default:
22482
+ return INVALID_DATE_KEY;
22483
+ }
22484
+ }
22485
+ function sortRows(rows, pivot) {
22486
+ try {
22487
+ const { sort, sortField, sortFieldType, sortDirection } = pivot;
22488
+ if (!sort || sortField == null || sortFieldType == null) return rows;
22489
+ const sortFunc = isBoolType(sortFieldType) ? (a, b) => a === b ? 0 : a ? 1 : -1 : isDateType(sortFieldType) ? (a, b) => {
22490
+ const ta = new Date(a), tb = new Date(b);
22491
+ const aValid = isValidDate3(ta), bValid = isValidDate3(tb);
22492
+ if (!aValid && !bValid) return 0;
22493
+ if (!aValid) return 1;
22494
+ if (!bValid) return -1;
22495
+ return ta.getTime() - tb.getTime();
22496
+ } : isStringType(sortFieldType) ? (a, b) => a.localeCompare(b) : (a, b) => a - b;
22497
+ const dir = sortDirection === "ASC" ? 1 : -1;
22498
+ rows.sort((a, b) => {
22499
+ const av = a[sortField], bv = b[sortField];
22500
+ if (av == null && bv == null) return 0;
22501
+ if (av == null) return 1;
22502
+ if (bv == null) return -1;
22503
+ return dir * sortFunc(av, bv);
22504
+ });
22505
+ } catch {
22506
+ console.warn(`Sorting failed.`);
22507
+ }
22508
+ return rows;
22509
+ }
22510
+ function assembleFinalRows(seen, rowKey, dateBucket, dateRange) {
22511
+ const allColumns = /* @__PURE__ */ new Set();
22512
+ Object.keys(seen).forEach((rowKeyVal) => {
22513
+ Object.keys(seen[rowKeyVal]).forEach((col) => {
22514
+ allColumns.add(col);
22515
+ });
22516
+ });
22517
+ const filled = {};
22518
+ Object.keys(seen).forEach((rowKeyVal) => {
22519
+ filled[rowKeyVal] = {};
22520
+ allColumns.forEach((col) => {
22521
+ const value = seen[rowKeyVal][col];
22522
+ filled[rowKeyVal][col] = value ?? 0;
22523
+ });
22524
+ });
22525
+ if (dateBucket) {
22526
+ return Object.keys(filled).sort((a, b) => parseInt(a) - parseInt(b)).map((k) => {
22527
+ if (k === INVALID_DATE_KEY) {
22528
+ return { [rowKey]: "Invalid Date", ...filled[k], __quillRawDate: 0 };
22529
+ }
22530
+ const { start: start2, end } = getBucketRange(k, dateBucket);
22531
+ let bucketStart = start2;
22532
+ let bucketEnd = end;
22533
+ if (dateRange) {
22534
+ if (bucketStart < dateRange.start) bucketStart = dateRange.start;
22535
+ if (bucketEnd > dateRange.end) bucketEnd = dateRange.end;
22536
+ }
22537
+ return {
22538
+ [rowKey]: formatBucketNameFromRange(
22539
+ bucketStart,
22540
+ bucketEnd,
22541
+ dateBucket
22542
+ ),
22543
+ ...filled[k],
22544
+ __quillRawDate: bucketStart.toISOString()
22545
+ };
22546
+ });
22547
+ }
22548
+ return Object.keys(filled).sort().map((k) => ({ [rowKey]: k, ...filled[k] }));
22549
+ }
22550
+ function anyToNum(valueFieldType, obj) {
22551
+ if (!valueFieldType) {
22552
+ if (typeof obj === "number") return obj;
22553
+ if (typeof obj === "string" && !isNaN(Number(obj))) return Number(obj);
22554
+ return obj ? 1 : 0;
22555
+ }
22556
+ if (isNumberType(valueFieldType)) {
22557
+ return Number(obj);
22558
+ }
22559
+ return obj ? 1 : 0;
22560
+ }
22561
+ function updateAccumulator(acc, row, agg, isOneRow) {
22562
+ const {
22563
+ aggregationType: type,
22564
+ valueField,
22565
+ valueFieldType,
22566
+ valueField2,
22567
+ valueField2Type
22568
+ } = agg;
22569
+ if (!valueField) return;
22570
+ const distinctDenom = !!valueField2 && !!valueField2Type && valueField !== valueField2;
22571
+ const rawVal = row[valueField];
22572
+ if (valueFieldType && (type === "min" || type === "max") && (isDateType(valueFieldType) || isDateFormat2(valueFieldType))) {
22573
+ const dateVal = rawVal instanceof Date ? rawVal : new Date(rawVal);
22574
+ if (!isNaN(dateVal.getTime())) {
22575
+ if (type === "min") {
22576
+ acc.minDate = acc.minDate && acc.minDate.getTime() < dateVal.getTime() ? acc.minDate : dateVal;
22577
+ } else {
22578
+ acc.maxDate = acc.maxDate && acc.maxDate.getTime() > dateVal.getTime() ? acc.maxDate : dateVal;
22579
+ }
22580
+ }
22581
+ return;
22582
+ }
22583
+ const numVal = anyToNum(valueFieldType, rawVal);
22584
+ switch (type) {
22585
+ case "sum":
22586
+ acc.sum += numVal;
22587
+ break;
22588
+ case "average":
22589
+ case "avg":
22590
+ acc.sum += numVal;
22591
+ acc.count++;
22592
+ break;
22593
+ case "min":
22594
+ acc.min = acc.hasVal ? Math.min(acc.min, numVal) : numVal;
22595
+ acc.hasVal = true;
22596
+ break;
22597
+ case "max":
22598
+ acc.max = acc.hasVal ? Math.max(acc.max, numVal) : numVal;
22599
+ acc.hasVal = true;
22600
+ break;
22601
+ case "count":
22602
+ acc.count += numVal;
22603
+ break;
22604
+ case "percentage":
22605
+ acc.sum += numVal;
22606
+ acc.count += distinctDenom ? anyToNum(valueField2Type, row[valueField2]) : isOneRow ? 1 : numVal;
22607
+ break;
22608
+ }
22609
+ }
22610
+ function getFinalAggregationValue({
22611
+ type,
22612
+ valueFieldType,
22613
+ valueField,
22614
+ acc,
22615
+ distinctDenom = false,
22616
+ sumOfDenom,
22617
+ rowsLength,
22618
+ totalRowsForCount
22619
+ }) {
22620
+ if (valueFieldType && (type === "min" || type === "max") && (isDateType(valueFieldType) || isDateFormat2(valueFieldType))) {
22621
+ const dateVal = type === "min" ? acc.minDate : acc.maxDate;
22622
+ return dateVal?.toISOString() ?? "";
22623
+ }
22624
+ if (type === "count") {
22625
+ return totalRowsForCount ?? acc.count;
22626
+ }
22627
+ let final;
22628
+ if (!valueField && type === "percentage" && rowsLength !== void 0) {
22629
+ final = rowsLength ? (totalRowsForCount ?? 0) / rowsLength : 0;
22630
+ } else {
22631
+ switch (type) {
22632
+ case "sum":
22633
+ final = acc.sum;
22634
+ break;
22635
+ case "average":
22636
+ case "avg":
22637
+ final = acc.count ? acc.sum / acc.count : 0;
22638
+ break;
22639
+ case "min":
22640
+ final = acc.hasVal ? acc.min : 0;
22641
+ break;
22642
+ case "max":
22643
+ final = acc.hasVal ? acc.max : 0;
22644
+ break;
22645
+ case "percentage": {
22646
+ const denom = distinctDenom ? acc.count : sumOfDenom ?? acc.count;
22647
+ final = denom ? acc.sum / denom : 0;
22648
+ break;
22649
+ }
22650
+ default:
22651
+ final = 0;
22652
+ }
22653
+ }
22654
+ return final;
22655
+ }
22656
+ function aggregateToOneRow(rows, aggregations) {
22657
+ const multiAgg = aggregations.length > 1;
22658
+ const resultArray = aggregations.map((agg) => {
22659
+ const { aggregationType: type, valueField, valueFieldType } = agg;
22660
+ if (!valueField) {
22661
+ if (type !== "count") {
22662
+ return {};
22663
+ }
22664
+ const finalKey2 = "count" + (multiAgg ? "_count" : "");
22665
+ return { [finalKey2]: rows.length };
22666
+ }
22667
+ const finalKey = `${valueField}` + (multiAgg ? `_${type}` : "");
22668
+ const acc = {
22669
+ sum: 0,
22670
+ count: 0,
22671
+ min: Infinity,
22672
+ max: -Infinity,
22673
+ hasVal: false,
22674
+ minDate: null,
22675
+ maxDate: null
22676
+ };
22677
+ for (const row of rows) {
22678
+ updateAccumulator(acc, row, agg, true);
22679
+ }
22680
+ return {
22681
+ [finalKey]: getFinalAggregationValue({
22682
+ type,
22683
+ valueFieldType,
22684
+ valueField,
22685
+ acc,
22686
+ totalRowsForCount: acc.count
22687
+ })
22688
+ };
22689
+ });
22690
+ return [
22691
+ resultArray.reduce((acc, current) => {
22692
+ return { ...acc, ...current };
22693
+ }, {})
22694
+ ];
22695
+ }
22696
+ function aggregateWithColumn(rows, colKey, aggregations, getGroupKey) {
22697
+ if (!colKey) return;
22698
+ const seen = {};
22699
+ const multiAgg = aggregations.length > 1;
22700
+ aggregations.forEach((agg) => {
22701
+ const {
22702
+ aggregationType: type,
22703
+ valueField,
22704
+ valueFieldType,
22705
+ valueField2,
22706
+ valueField2Type
22707
+ } = agg;
22708
+ if (!valueField) {
22709
+ if (type !== "count" && type !== "percentage") return;
22710
+ }
22711
+ const distinctDenom = !!valueField2 && !!valueField2Type && valueField !== valueField2;
22712
+ const accs = {};
22713
+ rows.forEach((row) => {
22714
+ const groupKey = getGroupKey(row);
22715
+ const colVal = row[colKey];
22716
+ if (!accs[groupKey]) accs[groupKey] = {};
22717
+ if (!accs[groupKey][colVal]) {
22718
+ accs[groupKey][colVal] = {
22719
+ sum: 0,
22720
+ count: 0,
22721
+ min: Infinity,
22722
+ max: -Infinity,
22723
+ hasVal: false,
22724
+ minDate: null,
22725
+ maxDate: null,
22726
+ totalRows: 0
22727
+ };
22728
+ }
22729
+ const acc = accs[groupKey][colVal];
22730
+ acc.totalRows += 1;
22731
+ if (!valueField) {
22732
+ if (type === "count") acc.count += 1;
22733
+ else if (type === "percentage") acc.sum += 1;
22734
+ return;
22735
+ }
22736
+ updateAccumulator(acc, row, agg, false);
22737
+ });
22738
+ Object.keys(accs).forEach((groupKey) => {
22739
+ if (!seen[groupKey]) seen[groupKey] = {};
22740
+ const sumOfDenom = Object.values(accs[groupKey]).reduce(
22741
+ (t, a) => t + a.count,
22742
+ 0
22743
+ );
22744
+ Object.keys(accs[groupKey]).forEach((colVal) => {
22745
+ const acc = accs[groupKey][colVal];
22746
+ const finalKey = `${colVal}` + (multiAgg ? `_${type}` : "");
22747
+ seen[groupKey][finalKey] = getFinalAggregationValue({
22748
+ type,
22749
+ valueFieldType,
22750
+ valueField,
22751
+ acc,
22752
+ distinctDenom,
22753
+ sumOfDenom,
22754
+ totalRowsForCount: acc.totalRows
22755
+ });
22756
+ });
22757
+ });
22758
+ });
22759
+ return seen;
22760
+ }
22761
+ function aggregateWithoutColumn(rows, aggregations, getGroupKey) {
22762
+ const seen = {};
22763
+ const multiAgg = aggregations.length > 1;
22764
+ aggregations.forEach((agg) => {
22765
+ const {
22766
+ aggregationType: type,
22767
+ valueField,
22768
+ valueFieldType,
22769
+ valueField2,
22770
+ valueField2Type
22771
+ } = agg;
22772
+ if (!valueField) {
22773
+ if (type !== "count" && type !== "percentage") return;
22774
+ }
22775
+ const finalKey = !valueField ? `${type}` + (multiAgg ? `_${type}` : "") : `${valueField}` + (multiAgg ? `_${type}` : "");
22776
+ const distinctDenom = !!valueField2 && !!valueField2Type && valueField !== valueField2;
22777
+ const accs = {};
22778
+ rows.forEach((row) => {
22779
+ const key = getGroupKey(row);
22780
+ if (!seen[key]) seen[key] = {};
22781
+ if (!accs[key]) {
22782
+ accs[key] = {
22783
+ sum: 0,
22784
+ count: 0,
22785
+ min: Infinity,
22786
+ max: -Infinity,
22787
+ hasVal: false,
22788
+ minDate: null,
22789
+ maxDate: null,
22790
+ totalRows: 0
22791
+ };
22792
+ }
22793
+ const acc = accs[key];
22794
+ acc.totalRows += 1;
22795
+ if (!valueField) {
22796
+ if (type === "count") acc.count += 1;
22797
+ else if (type === "percentage") acc.sum += 1;
22798
+ return;
22799
+ }
22800
+ updateAccumulator(acc, row, agg, false);
22801
+ });
22802
+ const sumOfDenom = Object.values(accs).reduce(
22803
+ (t, a) => t + a.count,
22804
+ 0
22805
+ );
22806
+ Object.keys(accs).forEach((k) => {
22807
+ const acc = accs[k];
22808
+ seen[k][finalKey] = getFinalAggregationValue({
22809
+ type,
22810
+ valueFieldType,
22811
+ valueField,
22812
+ acc,
22813
+ distinctDenom,
22814
+ sumOfDenom,
22815
+ rowsLength: rows.length,
22816
+ totalRowsForCount: acc.totalRows
22817
+ });
22818
+ });
22819
+ });
22820
+ return seen;
22821
+ }
22822
+ function applyPivotInMemory(rows, pivot, filters) {
22823
+ if (!pivot) return rows;
22824
+ const multiAggPivot = pivot.aggregations ? pivot : singleAggToMultiAgg(pivot);
22825
+ if (multiAggPivot.rowField === void 0) {
22826
+ return aggregateToOneRow(rows, multiAggPivot.aggregations);
22827
+ }
22828
+ const { rowField, columnField, aggregations } = multiAggPivot;
22829
+ const dateBucket = inferDateBucketFromFilters(pivot, filters);
22830
+ const dateRange = dateBucket ? getDateRangeFromFilters(filters) : void 0;
22831
+ const getRowKey = dateBucket ? (row) => {
22832
+ const rawValue = row[rowField];
22833
+ const rawDateFromQuill = row.__quillRawDate;
22834
+ const rawDateValue = rawDateFromQuill ?? (rawValue !== null && typeof rawValue === "object" && rawValue.value ? rawValue.value : rawValue);
22835
+ return getDateKey(new Date(rawDateValue), dateBucket);
22836
+ } : (row) => row[rowField];
22837
+ const aggregated = columnField ? aggregateWithColumn(rows, columnField, aggregations, getRowKey) : aggregateWithoutColumn(rows, aggregations, getRowKey);
22838
+ const aggregatedRows = aggregated || {};
22839
+ if (rows.length === 0 && dateBucket && dateRange && !columnField) {
22840
+ const bucketKeys = generateBucketKeys(
22841
+ dateRange.start,
22842
+ dateRange.end,
22843
+ dateBucket
22844
+ );
22845
+ const emptyAgg = buildEmptyAggRow(aggregations);
22846
+ bucketKeys.forEach((k) => {
22847
+ aggregatedRows[k] = { ...emptyAgg };
22848
+ });
22849
+ }
22850
+ const finalRows = assembleFinalRows(
22851
+ aggregatedRows,
22852
+ rowField,
22853
+ dateBucket,
22854
+ rows.length === 0 ? dateRange : void 0
22855
+ );
22856
+ const sorted = sortRows(finalRows, pivot);
22857
+ if (pivot.rowLimit) {
22858
+ return sorted.slice(0, pivot.rowLimit);
22859
+ }
22860
+ return sorted;
22861
+ }
22862
+ function generatePivotColumnsInMemory({
22863
+ pivot,
22864
+ pivotRows,
22865
+ sourceColumns
22866
+ }) {
22867
+ if (!pivot?.rowField || !Array.isArray(pivotRows) || pivotRows.length === 0) {
22868
+ return void 0;
22869
+ }
22870
+ const columns = Array.isArray(sourceColumns) ? sourceColumns : [];
22871
+ const rowSource = columns.find((col) => col.field === pivot.rowField);
22872
+ const isRowFieldDate = isDateLikeType(pivot.rowFieldType);
22873
+ const rowFieldType = rowSource?.fieldType ?? pivot.rowFieldType ?? "string";
22874
+ const rowColumn = {
22875
+ field: pivot.rowField,
22876
+ label: rowSource?.label ?? pivot.rowField,
22877
+ format: isRowFieldDate ? "string" : rowSource?.format ?? "string",
22878
+ fieldType: rowFieldType,
22879
+ dataTypeID: rowSource?.dataTypeID ?? 1043,
22880
+ jsType: rowSource?.jsType ?? (isRowFieldDate ? "date" : "string")
22881
+ };
22882
+ const valueFieldFormat = (valueField) => columns.find((col) => col.field === valueField)?.format ?? "whole_number";
22883
+ const multiAgg = (pivot.aggregations?.length ?? 0) > 1;
22884
+ const aggregationBySuffix = new Map(
22885
+ (pivot.aggregations ?? []).map((agg) => [
22886
+ multiAgg ? `_${agg.aggregationType}` : "",
22887
+ agg
22888
+ ])
22889
+ );
22890
+ const valueKeys = Array.from(
22891
+ new Set(
22892
+ pivotRows.flatMap(
22893
+ (row) => Object.keys(row).filter(
22894
+ (key) => key !== pivot.rowField && key !== "__quillRawDate"
22895
+ )
22896
+ )
22897
+ )
22898
+ );
22899
+ const valueColumns = valueKeys.map((key) => {
22900
+ const matchingSuffix = Array.from(aggregationBySuffix.keys()).find(
22901
+ (suffix) => suffix && key.endsWith(suffix)
22902
+ );
22903
+ const matchedAgg = matchingSuffix ? aggregationBySuffix.get(matchingSuffix) : pivot.aggregations?.[0];
22904
+ const format9 = matchedAgg?.aggregationType === "percentage" ? "percent" : valueFieldFormat(matchedAgg?.valueField);
22905
+ const valueFieldType = matchedAgg?.valueFieldType ?? "number";
22906
+ return {
22907
+ field: key,
22908
+ label: key,
22909
+ format: format9,
22910
+ fieldType: valueFieldType,
22911
+ jsType: valueFieldType,
22912
+ dataTypeID: columns.find((col) => col.field === matchedAgg?.valueField)?.dataTypeID ?? 20
22913
+ };
22914
+ });
22915
+ return [rowColumn, ...valueColumns];
22916
+ }
22917
+
22918
+ // src/utils/cacheCab.ts
22919
+ import { openDB } from "idb";
22920
+ import { endOfDay as endOfDay3, max as maxDate, subMonths as subMonths4, min as minDate, startOfDay as startOfDay4 } from "date-fns";
22921
+ import { utcToZonedTime as utcToZonedTime2, zonedTimeToUtc } from "date-fns-tz";
22922
+ var TZ = "America/Los_Angeles";
22923
+ var CacheCab = class {
22924
+ fetchedRange = {};
22925
+ cachedReportIds = [];
22926
+ uncacheableReportIDs = [];
22927
+ uncacheableInFlight = /* @__PURE__ */ new Map();
22928
+ storage;
22929
+ META_KEY = "cachecab:meta";
22930
+ DATA_PREFIX = "cachecab:data:";
22931
+ UNCACHEABLE_PREFIX = "cachecab:uncacheable:";
22932
+ constructor({
22933
+ storageType = "memory"
22934
+ } = {}) {
22935
+ this.storage = storageType !== "idb" ? new MemoryStorage() : new IdbStorage();
22936
+ void this.loadMetaFromStorage();
22937
+ }
22938
+ async get(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, pageSize, getToken, eventTracking, forceRefresh) {
22939
+ if (this.isCached(reportId, tenants) && !forceRefresh) {
22940
+ return this.getFromCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking);
22941
+ } else {
22942
+ return this.addToCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking, true, forceRefresh);
22943
+ }
22944
+ }
22945
+ isCached(reportId, tenants) {
22946
+ return this.cachedReportIds.includes(this.getCacheKey(reportId, tenants));
22947
+ }
22948
+ isCacheable(reportId) {
22949
+ return !this.uncacheableReportIDs.includes(reportId);
22950
+ }
22951
+ async getUncacheableResult(reportId, client, tenants, flags, filters, additionalProcessing, pivot) {
22952
+ const key = this.getUncacheableRequestKey(
22953
+ reportId,
22954
+ client,
22955
+ tenants,
22956
+ flags,
22957
+ filters,
22958
+ additionalProcessing,
22959
+ pivot
22960
+ );
22961
+ try {
22962
+ const raw = await this.storage.getItem(key);
22963
+ return raw ? JSON.parse(raw) : null;
22964
+ } catch {
22965
+ return null;
22966
+ }
22967
+ }
22968
+ async setUncacheableResult(reportId, client, tenants, flags, filters, additionalProcessing, pivot, report) {
22969
+ const key = this.getUncacheableRequestKey(
22970
+ reportId,
22971
+ client,
22972
+ tenants,
22973
+ flags,
22974
+ filters,
22975
+ additionalProcessing,
22976
+ pivot
22977
+ );
22978
+ try {
22979
+ await this.storage.setItem(key, JSON.stringify(report));
22980
+ } catch {
22981
+ }
22982
+ }
22983
+ async getOrFetchUncacheableResult(reportId, client, tenants, flags, filters, additionalProcessing, pivot, fetcher, forceRefresh = false) {
22984
+ const key = this.getUncacheableRequestKey(
22985
+ reportId,
22986
+ client,
22987
+ tenants,
22988
+ flags,
22989
+ filters,
22990
+ additionalProcessing,
22991
+ pivot
22992
+ );
22993
+ if (!forceRefresh) {
22994
+ const raw = await this.storage.getItem(key);
22995
+ if (raw) {
22996
+ return { report: JSON.parse(raw), fromCache: true };
22997
+ }
22998
+ const inFlight = this.uncacheableInFlight.get(key);
22999
+ if (inFlight) {
23000
+ const report = await inFlight;
23001
+ return { report, fromCache: false };
23002
+ }
23003
+ }
23004
+ const requestPromise = (async () => {
23005
+ const report = await fetcher();
23006
+ try {
23007
+ await this.storage.setItem(key, JSON.stringify(report));
23008
+ } catch {
23009
+ }
23010
+ return report;
23011
+ })();
23012
+ this.uncacheableInFlight.set(key, requestPromise);
23013
+ try {
23014
+ const report = await requestPromise;
23015
+ return { report, fromCache: false };
23016
+ } finally {
23017
+ this.uncacheableInFlight.delete(key);
23018
+ }
23019
+ }
23020
+ async addToCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking, firstTime = true, forceRefresh = false) {
23021
+ const tenantPart = tenants ? JSON.stringify(tenants) : "";
23022
+ const cacheKey = this.getCacheKey(reportId, tenants);
23023
+ if (firstTime) {
23024
+ const result = await this.fetchInitialReport({ reportId, client, tenants, flags, filters: dashboardFilters, getToken, eventTracking, forceRefresh });
23025
+ try {
23026
+ await this.storage.setItem(this.DATA_PREFIX + cacheKey, JSON.stringify(result));
23027
+ this.cachedReportIds.push(cacheKey);
23028
+ await this.persistMetaToStorage();
23029
+ } catch {
23030
+ console.warn(`Failed to cache report: ${reportId}. Cache full?`);
23031
+ }
23032
+ const newRows = await this.applyPivotsAndFilters(result, dashboardFilters, customFilters, pivot, tenantPart, false);
23033
+ if (pivot) {
23034
+ const pivotColumns = generatePivotColumnsInMemory({
23035
+ pivot,
23036
+ pivotRows: newRows,
23037
+ sourceColumns: result.columnInternal ?? result.columns
23038
+ });
23039
+ return { ...result, pivotRows: newRows, pivotColumns: pivotColumns ?? result.pivotColumns, pivotRowCount: newRows.length };
23040
+ }
23041
+ return { ...result, rows: newRows, rowCount: newRows.length };
23042
+ } else {
23043
+ const dbDateFilter = dashboardFilters.find((x) => x.filterType === "date_range");
23044
+ const { start: requestedStart, end: requestedEnd } = normalizePSTRanges(
23045
+ dbDateFilter?.startDate ? new Date(dbDateFilter.startDate) : void 0,
23046
+ dbDateFilter?.endDate ? new Date(dbDateFilter.endDate) : void 0
23047
+ );
23048
+ if (!requestedStart || !requestedEnd) {
23049
+ return this.addToCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking, true);
23050
+ }
23051
+ const currentRange = this.fetchedRange[cacheKey];
23052
+ const rangeStart = currentRange?.start ?? requestedStart;
23053
+ const rangeEnd = currentRange?.end ?? endOfDayPST(/* @__PURE__ */ new Date());
23054
+ const existingRaw = await this.storage.getItem(this.DATA_PREFIX + cacheKey);
23055
+ let existing;
23056
+ let fetchedRows = [];
23057
+ try {
23058
+ existing = JSON.parse(existingRaw);
23059
+ if (!existing.rows) {
23060
+ throw new Error("Invalid Cache!");
23061
+ }
23062
+ if (requestedStart < rangeStart) {
23063
+ const olderReport = await this.fetchReport({
23064
+ reportId,
23065
+ client,
23066
+ tenants,
23067
+ flags,
23068
+ filters: dashboardFilters,
23069
+ getToken,
23070
+ eventTracking,
23071
+ start: requestedStart,
23072
+ end: rangeStart
23073
+ });
23074
+ fetchedRows = olderReport.rows;
23075
+ }
23076
+ if (requestedEnd && requestedEnd > rangeEnd) {
23077
+ const newerReport = await this.fetchReport({
23078
+ reportId,
23079
+ client,
23080
+ tenants,
23081
+ flags,
23082
+ filters: dashboardFilters,
23083
+ getToken,
23084
+ eventTracking,
23085
+ start: rangeEnd,
23086
+ end: requestedEnd
23087
+ });
23088
+ fetchedRows = fetchedRows.concat(newerReport.rows);
23089
+ }
23090
+ } catch {
23091
+ existing = await this.fetchReport({
23092
+ reportId,
23093
+ client,
23094
+ tenants,
23095
+ flags,
23096
+ filters: dashboardFilters,
23097
+ getToken,
23098
+ eventTracking,
23099
+ start: requestedStart,
23100
+ end: requestedEnd
23101
+ });
23102
+ fetchedRows = existing.rows;
23103
+ }
23104
+ const dateKey = existing.dateField?.field ? removeQuotes(existing.dateField.field) : void 0;
23105
+ let mergedRows;
23106
+ if (dateKey) {
23107
+ const startMs = rangeStart.getTime();
23108
+ const endMs = rangeEnd.getTime();
23109
+ const boundaryRows = existing.rows.filter((row) => {
23110
+ const raw = row[dateKey];
23111
+ if (!raw) return false;
23112
+ const t = new Date(raw).getTime();
23113
+ return Math.abs(t - startMs) <= MS_IN_DAY || Math.abs(t - endMs) <= MS_IN_DAY;
23114
+ });
23115
+ const filteredNew = filterNewRows(boundaryRows, fetchedRows);
23116
+ mergedRows = filteredNew.concat(existing.rows);
23117
+ } else {
23118
+ mergedRows = filterNewRows(existing.rows, fetchedRows).concat(existing.rows);
23119
+ }
23120
+ const merged = { ...existing, rows: mergedRows };
23121
+ await this.storage.setItem(this.DATA_PREFIX + cacheKey, JSON.stringify(merged));
23122
+ await this.persistMetaToStorage();
23123
+ const newRows = await this.applyPivotsAndFilters(merged, dashboardFilters, customFilters, pivot, tenantPart, false);
23124
+ if (pivot) {
23125
+ const pivotColumns = generatePivotColumnsInMemory({
23126
+ pivot,
23127
+ pivotRows: newRows,
23128
+ sourceColumns: merged.columnInternal ?? merged.columns
23129
+ });
23130
+ return { ...merged, pivotRows: newRows, pivotColumns: pivotColumns ?? merged.pivotColumns, pivotRowCount: newRows.length };
23131
+ }
23132
+ return { ...merged, rows: newRows, rowCount: newRows.length };
23133
+ }
23134
+ }
23135
+ /**
23136
+ * Returns cache entry (or re-queries if stale)
23137
+ */
23138
+ async getFromCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking) {
23139
+ const dateRangeFilter = dashboardFilters.find((x) => x.filterType === "date_range");
23140
+ const cacheKey = this.getCacheKey(reportId, tenants);
23141
+ if (!dateRangeFilter || dateRangeFilter?.primaryRange.value === "ALL_TIME") {
23142
+ const fetchInfo = this.fetchedRange[cacheKey];
23143
+ if (!fetchInfo || fetchInfo?.allTimeDoneBefore === false || Date.now() - fetchInfo.fetchedAtUTCMS >= MS_IN_DAY) {
23144
+ return this.addToCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking, true);
23145
+ }
23146
+ } else if (this.weShouldReQuery(cacheKey, dateRangeFilter)) {
23147
+ return this.addToCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking, false);
23148
+ }
23149
+ try {
23150
+ const raw = await this.storage.getItem(this.DATA_PREFIX + cacheKey);
23151
+ if (!raw) {
23152
+ throw new Error("Invalid Cache!");
23153
+ }
23154
+ const parsed = JSON.parse(raw);
23155
+ const tenantPart = tenants ? JSON.stringify(tenants) : "";
23156
+ const newRows = await this.applyPivotsAndFilters(parsed, dashboardFilters, customFilters, pivot, tenantPart);
23157
+ if (pivot) {
23158
+ const pivotColumns = generatePivotColumnsInMemory({
23159
+ pivot,
23160
+ pivotRows: newRows,
23161
+ sourceColumns: parsed.columnInternal ?? parsed.columns
23162
+ });
23163
+ return { ...parsed, pivotRows: newRows, pivotColumns: pivotColumns ?? parsed.pivotColumns, pivotRowCount: newRows.length };
23164
+ }
23165
+ return { ...parsed, rows: newRows, rowCount: newRows.length };
23166
+ } catch (err) {
23167
+ console.error(err);
23168
+ return this.addToCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking, false);
23169
+ }
23170
+ }
23171
+ weShouldReQuery(cacheKey, dateRangeFilter) {
23172
+ const start2 = dateRangeFilter.startDate;
23173
+ const end = dateRangeFilter.endDate;
23174
+ const range = this.fetchedRange[cacheKey];
23175
+ const { start: s, end: e } = normalizePSTRanges(
23176
+ start2 ? new Date(start2) : void 0,
23177
+ end ? new Date(end) : void 0
23178
+ );
23179
+ return !range || !s || !e || range.start > s || range.end < e;
23180
+ }
23181
+ async fetchInitialReport({
23182
+ reportId,
23183
+ client,
23184
+ tenants,
23185
+ flags,
23186
+ filters = [],
23187
+ getToken,
23188
+ eventTracking,
23189
+ forceRefresh
23190
+ }) {
23191
+ let start2;
23192
+ let end;
23193
+ const dateRangeFilter = filters.find((x) => x.filterType === "date_range");
23194
+ const cacheRange = dateRangeFilter?.initialCacheDateRange;
23195
+ if (cacheRange && cacheRange.startDate) {
23196
+ const startDate = dateRangeFilter?.startDate;
23197
+ start2 = startDate ? minDate([new Date(cacheRange.startDate), new Date(startDate)]) : new Date(cacheRange.startDate);
23198
+ end = endOfDayPST(/* @__PURE__ */ new Date());
23199
+ } else {
23200
+ const endDate = dateRangeFilter?.endDate ? new Date(dateRangeFilter.endDate) : void 0;
23201
+ const currentDay = endOfDayPST(/* @__PURE__ */ new Date());
23202
+ end = endDate ? maxDate([currentDay, endDate]) : currentDay;
23203
+ start2 = subMonths4(end, 7);
23204
+ }
23205
+ const normalized = normalizePSTRanges(start2, end);
23206
+ return this.fetchReport({
23207
+ reportId,
23208
+ client,
23209
+ tenants,
23210
+ flags,
23211
+ filters,
23212
+ getToken,
23213
+ eventTracking,
23214
+ start: normalized.start,
23215
+ end: normalized.end,
23216
+ forceRefresh
23217
+ });
23218
+ }
23219
+ async fetchReport({
23220
+ reportId,
23221
+ client,
23222
+ tenants,
23223
+ flags,
23224
+ filters = [],
23225
+ getToken,
23226
+ eventTracking,
23227
+ start: start2,
23228
+ end,
23229
+ forceRefresh
23230
+ }) {
23231
+ let reportInfo = void 0;
23232
+ try {
23233
+ const { start: s, end: e } = normalizePSTRanges(start2, end);
23234
+ const adjusted = [...filters].map((x) => {
23235
+ return { ...x, options: void 0 };
23236
+ });
23237
+ const dateFilterIndex = adjusted.findIndex((x) => x.filterType === "date_range");
23238
+ const isAllTime = dateFilterIndex !== -1 && adjusted[dateFilterIndex].primaryRange?.value === "ALL_TIME";
23239
+ if (!isAllTime) {
23240
+ const dateFilter = adjusted[dateFilterIndex];
23241
+ if (dateFilter && dateFilter.startDate && dateFilter.endDate) {
23242
+ adjusted[dateFilterIndex] = { ...dateFilter, startDate: s, endDate: e };
23243
+ }
23244
+ }
23245
+ const fetchResp = await quillFetch({
23246
+ client,
23247
+ task: "report",
23248
+ metadata: {
23249
+ reportId,
23250
+ clientId: client.publicKey,
23251
+ databaseType: client.databaseType,
23252
+ filters: adjusted,
23253
+ additionalProcessing: { page: { rowsPerPage: 1e3, rowsPerRequest: 1e5 } },
23254
+ useNewNodeSql: true,
23255
+ tenants,
23256
+ flags,
23257
+ overwriteCache: forceRefresh ?? false
23258
+ },
23259
+ getToken
23260
+ });
23261
+ const resp = await parseFetchResponse(
23262
+ client,
23263
+ "report",
23264
+ fetchResp,
23265
+ getToken,
23266
+ true
23267
+ );
23268
+ reportInfo = await processReportResponse({
23269
+ resp,
23270
+ client,
23271
+ filters: adjusted,
23272
+ dateBucket: resp?.dateBucket,
23273
+ additionalProcessing: { page: { rowsPerPage: 1e3, rowsPerRequest: 1e5 } },
23274
+ getToken,
23275
+ eventTracking,
23276
+ tenants,
23277
+ // CacheCab fetches with task: 'report'; keep pivot processing local.
23278
+ skipPivotFetch: true,
23279
+ overwriteCache: forceRefresh ?? false
23280
+ });
23281
+ const dateField = reportInfo.dateField?.field;
23282
+ if (!isAllTime && dateField !== void 0 && reportInfo.rows.length > 0) {
23283
+ const cleanedDateField = removeQuotes(dateField);
23284
+ const missingDateFieldCount = reportInfo.rows.some((row) => row[cleanedDateField] === void 0);
23285
+ if (missingDateFieldCount) {
23286
+ this.uncacheableReportIDs.push(reportId);
23287
+ return EMPTY_INTERNAL_REPORT;
23288
+ }
23289
+ }
23290
+ const requiredFilterFields = adjusted.flatMap((x, idx) => {
23291
+ if (idx === dateFilterIndex || !x.values && !x.selectedValue) return [];
23292
+ return removeQuotes(x.field);
23293
+ });
23294
+ const missingFields = [...new Set(requiredFilterFields.filter(
23295
+ (field) => reportInfo?.rows.some((row) => row[field] === void 0)
23296
+ ))];
23297
+ if (missingFields.length > 0) {
23298
+ this.uncacheableReportIDs.push(reportId);
23299
+ return EMPTY_INTERNAL_REPORT;
23300
+ }
23301
+ const cacheKey = this.getCacheKey(reportId, tenants);
23302
+ if (isAllTime) {
23303
+ this.fetchedRange[cacheKey] = { start: /* @__PURE__ */ new Date(0), end: /* @__PURE__ */ new Date(), fetchedAtUTCMS: Date.now(), allTimeDoneBefore: true };
23304
+ } else {
23305
+ const existing = this.fetchedRange[cacheKey];
23306
+ if (existing && !forceRefresh) {
23307
+ const oldStart = existing.start;
23308
+ const oldEnd = existing.end;
23309
+ this.fetchedRange[cacheKey] = { start: getMinDate(oldStart, s), end: getMaxDate(oldEnd, e), fetchedAtUTCMS: Date.now(), allTimeDoneBefore: false };
23310
+ } else {
23311
+ this.fetchedRange[cacheKey] = { start: s, end: e, fetchedAtUTCMS: Date.now(), allTimeDoneBefore: false };
23312
+ }
23313
+ }
23314
+ } catch (error) {
23315
+ console.warn(error);
23316
+ if (error instanceof Error && error.name === "AbortError") {
23317
+ throw error;
23318
+ }
23319
+ }
23320
+ return reportInfo || EMPTY_INTERNAL_REPORT;
23321
+ }
23322
+ async applyPivotsAndFilters(report, dashboardFilters, customFilters, pivot, tenantPart, useCache = true) {
23323
+ const datasetVersion = this.fetchedRange[report.id + tenantPart]?.fetchedAtUTCMS ?? 0;
23324
+ const keyParts = [
23325
+ report.queryString,
23326
+ tenantPart,
23327
+ datasetVersion,
23328
+ // prevents fetching stale filters on hard refresh
23329
+ hashString(stableStringify(dashboardFilters)),
23330
+ hashString(stableStringify(customFilters))
23331
+ ];
23332
+ const queryKey = this.DATA_PREFIX + keyParts.join("|");
23333
+ let filtersApplied;
23334
+ if (useCache) {
23335
+ try {
23336
+ const cache = await this.storage.getItem(queryKey);
23337
+ if (cache) {
23338
+ filtersApplied = JSON.parse(cache);
23339
+ }
23340
+ } catch {
23341
+ }
23342
+ }
23343
+ if (!filtersApplied) {
23344
+ const dateIndex = dashboardFilters.findIndex((x) => x.filterType === "date_range");
23345
+ const requiredFilterFields = dashboardFilters.flatMap((x, idx) => {
23346
+ if (idx === dateIndex || !x.values?.length && !x.selectedValue) return [];
23347
+ return removeQuotes(x.field);
23348
+ });
23349
+ const missingFields = [...new Set(requiredFilterFields.filter(
23350
+ (field) => report.rows.some((row) => row[field] === void 0)
23351
+ ))];
23352
+ if (missingFields.length > 0) {
23353
+ this.uncacheableReportIDs.push(report.id);
23354
+ return [];
23355
+ }
23356
+ const dbDateFilter = dashboardFilters.find((x) => x.filterType === "date_range");
23357
+ if (report.dateField && dbDateFilter?.startDate) {
23358
+ const { start: startDate, end: endDate } = normalizePSTRanges(
23359
+ new Date(dbDateFilter.startDate),
23360
+ dbDateFilter.endDate ? new Date(dbDateFilter.endDate) : void 0
23361
+ );
23362
+ const fieldToUse = report.rows[0] && report.rows[0]["__quillRawDate"] ? "__quillRawDate" : removeQuotes(report.dateField.field);
23363
+ const dateFilteredRows = report.rows.filter((x) => {
23364
+ const rowDate = new Date(x[fieldToUse]);
23365
+ return startDate <= rowDate && (endDate ? rowDate <= endDate : true);
23366
+ });
23367
+ filtersApplied = applyFiltersInMemory(dateFilteredRows, [], { dashboardFilters, customFilters });
23368
+ } else {
23369
+ filtersApplied = applyFiltersInMemory(report.rows, [], { dashboardFilters, customFilters });
23370
+ }
23371
+ }
23372
+ try {
23373
+ await this.storage.setItem(queryKey, JSON.stringify(filtersApplied));
23374
+ } catch {
23375
+ }
23376
+ const withPivot = applyPivotInMemory(filtersApplied, pivot, dashboardFilters.concat(customFilters));
23377
+ return withPivot;
23378
+ }
23379
+ getCacheKey(reportId, tenants) {
23380
+ const tenantPart = tenants ? JSON.stringify(tenants) : "";
23381
+ return reportId + tenantPart;
23382
+ }
23383
+ getUncacheableRequestKey(reportId, client, tenants, flags, filters, additionalProcessing, pivot) {
23384
+ const canonicalFilters = filters.map(
23385
+ (f) => canonicalizeFilterForUncacheableKey(f)
23386
+ );
23387
+ const keyParts = [
23388
+ reportId,
23389
+ client.publicKey,
23390
+ client.databaseType,
23391
+ hashString(stableStringify(canonicalizeForKey(tenants ?? null))),
23392
+ hashString(stableStringify(canonicalizeForKey(flags ?? null))),
23393
+ hashString(stableStringify(canonicalFilters)),
23394
+ hashString(stableStringify(canonicalizeForKey(additionalProcessing ?? null))),
23395
+ hashString(stableStringify(canonicalizeForKey(pivot ?? null)))
23396
+ ];
23397
+ return this.UNCACHEABLE_PREFIX + keyParts.join("|");
23398
+ }
23399
+ async loadMetaFromStorage() {
23400
+ const meta = await this.storage.getItem(this.META_KEY);
23401
+ if (!meta) return;
23402
+ try {
23403
+ const parsed = JSON.parse(meta);
23404
+ this.cachedReportIds = parsed.cached || [];
23405
+ this.uncacheableReportIDs = parsed.cannotBeCached;
23406
+ this.fetchedRange = Object.fromEntries(
23407
+ Object.entries(parsed.fetchedRange || {}).map(([id2, range]) => [
23408
+ id2,
23409
+ { ...range, start: new Date(range.start), end: new Date(range.end) }
23410
+ ])
23411
+ );
23412
+ } catch (err) {
23413
+ console.warn("CacheCab: could not parse meta from storage", err);
23414
+ }
23415
+ }
23416
+ async persistMetaToStorage() {
23417
+ const serializableRange = Object.fromEntries(
23418
+ Object.entries(this.fetchedRange).map(([id2, range]) => [
23419
+ id2,
23420
+ { ...range }
23421
+ ])
23422
+ );
23423
+ await this.storage.setItem(
23424
+ this.META_KEY,
23425
+ JSON.stringify({
23426
+ fetchedRange: serializableRange,
23427
+ cached: this.cachedReportIds,
23428
+ cannotBeCached: this.uncacheableReportIDs
23429
+ })
23430
+ );
23431
+ }
23432
+ };
23433
+ var MemoryStorage = class {
23434
+ store = /* @__PURE__ */ new Map();
23435
+ async getItem(key) {
23436
+ return this.store.get(key) ?? null;
23437
+ }
23438
+ async setItem(key, value) {
23439
+ this.store.set(key, value);
23440
+ }
23441
+ };
23442
+ var DB_NAME = "quill.cachecab";
23443
+ var STORE_NAME = "kv";
23444
+ var IdbStorage = class {
23445
+ dbPromise;
23446
+ constructor() {
23447
+ this.dbPromise = openDB(DB_NAME, 1, {
23448
+ upgrade(db) {
23449
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
23450
+ db.createObjectStore(STORE_NAME);
23451
+ }
23452
+ }
23453
+ });
23454
+ }
23455
+ async getItem(key) {
23456
+ const db = await this.dbPromise;
23457
+ return await db.get(STORE_NAME, key) ?? null;
23458
+ }
23459
+ async setItem(key, value) {
23460
+ const db = await this.dbPromise;
23461
+ await db.put(STORE_NAME, value, key);
23462
+ }
23463
+ };
23464
+ function removeQuotes(str) {
23465
+ return str.replace(/^['"]|['"]$/g, "");
23466
+ }
23467
+ function getMaxDate(d1, d2) {
23468
+ return d1 > d2 ? d1 : d2;
23469
+ }
23470
+ function getMinDate(d1, d2) {
23471
+ return d2 > d1 ? d1 : d2;
23472
+ }
23473
+ function hashString(str) {
23474
+ let h1 = 3735928559 ^ 0, h2 = 1103547991 ^ 0;
23475
+ for (let i = 0, ch; i < str.length; i++) {
23476
+ ch = str.charCodeAt(i);
23477
+ h1 = Math.imul(h1 ^ ch, 2654435761);
23478
+ h2 = Math.imul(h2 ^ ch, 1597334677);
23479
+ }
23480
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
23481
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
23482
+ return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36);
23483
+ }
23484
+ function filterNewRows(existingRows, newRows) {
23485
+ const seen = /* @__PURE__ */ new Set();
23486
+ const getKey = (row) => stableStringify(row);
23487
+ existingRows.forEach((row) => seen.add(getKey(row)));
23488
+ return newRows.filter((row) => {
23489
+ const k = getKey(row);
23490
+ if (seen.has(k)) return false;
23491
+ seen.add(k);
23492
+ return true;
23493
+ });
23494
+ }
23495
+ function stableStringify(obj) {
23496
+ if (obj === null || typeof obj !== "object") return JSON.stringify(obj);
23497
+ if (Array.isArray(obj)) {
23498
+ return `[${obj.map(stableStringify).join(",")}]`;
23499
+ }
23500
+ const keys = Object.keys(obj).sort();
23501
+ return `{${keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",")}}`;
23502
+ }
23503
+ function canonicalizeForKey(value) {
23504
+ if (value instanceof Date) {
23505
+ return value.toISOString();
23506
+ }
23507
+ if (Array.isArray(value)) {
23508
+ return value.map(canonicalizeForKey);
23509
+ }
23510
+ if (value && typeof value === "object") {
23511
+ const out = {};
23512
+ Object.keys(value).sort().forEach((key) => {
23513
+ if (value[key] !== void 0) {
23514
+ out[key] = canonicalizeForKey(value[key]);
23515
+ }
23516
+ });
23517
+ return out;
23518
+ }
23519
+ return value;
23520
+ }
23521
+ function canonicalizeFilterForUncacheableKey(filter) {
23522
+ const raw = canonicalizeForKey(filter) ?? {};
23523
+ const base = {
23524
+ filterType: raw.filterType,
23525
+ field: raw.field,
23526
+ table: raw.table,
23527
+ label: raw.label,
23528
+ operator: raw.operator
23529
+ };
23530
+ if (raw.selectedValue !== void 0) {
23531
+ base.selectedValue = raw.selectedValue;
23532
+ }
23533
+ if (Array.isArray(raw.values)) {
23534
+ base.values = [...raw.values].map(canonicalizeForKey).sort();
23535
+ }
23536
+ if (raw.value !== void 0) {
23537
+ base.value = canonicalizeForKey(raw.value);
23538
+ }
23539
+ if (raw.filterType === "date_range") {
23540
+ const { start: start2, end } = normalizePSTRanges(
23541
+ raw.startDate ? new Date(raw.startDate) : void 0,
23542
+ raw.endDate ? new Date(raw.endDate) : void 0
23543
+ );
23544
+ base.startDate = start2 ? start2.toISOString() : void 0;
23545
+ base.endDate = end ? end.toISOString() : void 0;
23546
+ base.primaryRange = raw.primaryRange?.value;
23547
+ base.comparisonRange = raw.comparisonRange?.value;
23548
+ }
23549
+ return canonicalizeForKey(base);
23550
+ }
23551
+ function startOfDayPST(date) {
23552
+ const pstDate = utcToZonedTime2(date, TZ);
23553
+ const startPst = startOfDay4(pstDate);
23554
+ return zonedTimeToUtc(startPst, TZ);
23555
+ }
23556
+ function endOfDayPST(date) {
23557
+ const pstDate = utcToZonedTime2(date, TZ);
23558
+ const endPst = endOfDay3(pstDate);
23559
+ return zonedTimeToUtc(endPst, TZ);
23560
+ }
23561
+ function normalizePSTRanges(start2, end) {
23562
+ return {
23563
+ start: start2 ? startOfDayPST(start2) : void 0,
23564
+ end: end ? endOfDayPST(end) : void 0
23565
+ };
23566
+ }
23567
+
23568
+ // src/Context.tsx
21603
23569
  import { jsx } from "react/jsx-runtime";
21604
23570
  var dummySetter = () => {
21605
23571
  };
@@ -21931,7 +23897,9 @@ var ReportFiltersContext = createContext({
21931
23897
  loadFiltersForReport: async () => {
21932
23898
  },
21933
23899
  abortLoadingFilters: () => {
21934
- }
23900
+ },
23901
+ customFiltersLoaded: false,
23902
+ setCustomFiltersLoaded: dummySetter
21935
23903
  });
21936
23904
  var customDashboardFiltersReducer = (state, action) => {
21937
23905
  switch (action.type) {
@@ -22021,6 +23989,9 @@ var FetchContext = createContext({
22021
23989
  var EventTrackingContext = createContext({
22022
23990
  eventTracking: null
22023
23991
  });
23992
+ var CacheCabContext = createContext({
23993
+ getCacheCab: (storageType) => new CacheCab({ storageType })
23994
+ });
22024
23995
  var ContextProvider = ({
22025
23996
  children,
22026
23997
  initialTheme,
@@ -22034,6 +24005,14 @@ var ContextProvider = ({
22034
24005
  getAuthorizationToken = async () => "",
22035
24006
  eventTracking = null
22036
24007
  }) => {
24008
+ const cacheCabByTypeRef = useRef({});
24009
+ const getCacheCab = useCallback((storageType) => {
24010
+ const key = storageType === "idb" ? "idb" : "memory";
24011
+ if (!cacheCabByTypeRef.current[key]) {
24012
+ cacheCabByTypeRef.current[key] = new CacheCab({ storageType: key });
24013
+ }
24014
+ return cacheCabByTypeRef.current[key];
24015
+ }, []);
22037
24016
  const [client, setClient] = useState(
22038
24017
  typeof window !== "undefined" && sessionStorage ? JSON.parse(sessionStorage.getItem("quill-client") ?? "null") : null
22039
24018
  );
@@ -22096,6 +24075,7 @@ var ContextProvider = ({
22096
24075
  reportFiltersReducer,
22097
24076
  {}
22098
24077
  );
24078
+ const [customFiltersLoaded, setCustomFiltersLoaded] = useState(false);
22099
24079
  const reportFilterOptionsAbortControllers = useRef({});
22100
24080
  const [reports, reportsDispatch] = useReducer(reportsReducer, {});
22101
24081
  const [reportsLoadingState, reportsLoadingStateDispatch] = useReducer(
@@ -22119,6 +24099,11 @@ var ContextProvider = ({
22119
24099
  const viewerTenantsByOwnerRequests = useRef(/* @__PURE__ */ new Set());
22120
24100
  const mappedTenantsRequests = useRef(/* @__PURE__ */ new Set());
22121
24101
  const loadDashboardProcessId = useRef({});
24102
+ useEffect(() => {
24103
+ if (Object.keys(customReportFilters).length) {
24104
+ setCustomFiltersLoaded(true);
24105
+ }
24106
+ }, [customReportFilters]);
22122
24107
  useEffect(() => {
22123
24108
  if (!theme) {
22124
24109
  const populatedTheme = {
@@ -22887,6 +24872,7 @@ var ContextProvider = ({
22887
24872
  ...dashboard2,
22888
24873
  dateFilter: dashboard2.dateFilter ? {
22889
24874
  ...dashboard2.dateFilter,
24875
+ initialCacheDateRange: dashboard2.initialCacheDateRange ? { ...dashboard2.initialCacheDateRange } : void 0,
22890
24876
  presetOptions: dashboard2.dateFilter.presetOptions?.map(
22891
24877
  (preset) => ({
22892
24878
  ...preset,
@@ -23229,6 +25215,7 @@ var ContextProvider = ({
23229
25215
  ...dashboard2,
23230
25216
  dateFilter: dashboard2.dateFilter ? {
23231
25217
  ...dashboard2.dateFilter,
25218
+ initialCacheDateRange: dashboard2.initialCacheDateRange ? { ...dashboard2.initialCacheDateRange } : void 0,
23232
25219
  presetOptions: dashboard2.dateFilter.presetOptions?.map(
23233
25220
  (preset) => ({
23234
25221
  ...preset,
@@ -23273,6 +25260,7 @@ var ContextProvider = ({
23273
25260
  if (dashboard2.dateFilter) {
23274
25261
  dashboard2.dateFilter = {
23275
25262
  ...dashboard2.dateFilter,
25263
+ initialCacheDateRange: dashboard2.initialCacheDateRange ? { ...dashboard2.initialCacheDateRange } : void 0,
23276
25264
  startDate: presetOptions.find(
23277
25265
  (elem) => elem.value === dashboard2.dateFilter.primaryRange?.value
23278
25266
  )?.startDate,
@@ -23541,7 +25529,7 @@ var ContextProvider = ({
23541
25529
  if (!theme) {
23542
25530
  return null;
23543
25531
  }
23544
- return /* @__PURE__ */ jsx(
25532
+ return /* @__PURE__ */ jsx(CacheCabContext.Provider, { value: { getCacheCab }, children: /* @__PURE__ */ jsx(
23545
25533
  ClientContext.Provider,
23546
25534
  {
23547
25535
  value: [
@@ -23582,7 +25570,9 @@ var ContextProvider = ({
23582
25570
  reportFiltersDispatch,
23583
25571
  customReportFiltersDispatch,
23584
25572
  loadFiltersForReport,
23585
- abortLoadingFilters
25573
+ abortLoadingFilters,
25574
+ customFiltersLoaded,
25575
+ setCustomFiltersLoaded
23586
25576
  },
23587
25577
  children: /* @__PURE__ */ jsx(
23588
25578
  ReportsContext.Provider,
@@ -23625,7 +25615,7 @@ var ContextProvider = ({
23625
25615
  }
23626
25616
  ) })
23627
25617
  }
23628
- );
25618
+ ) });
23629
25619
  };
23630
25620
 
23631
25621
  // src/hooks/useExport.tsx
@@ -23644,12 +25634,12 @@ init_filterProcessing();
23644
25634
  init_columnType();
23645
25635
  import {
23646
25636
  add,
23647
- startOfDay as startOfDay3,
23648
- startOfMonth as startOfMonth2,
23649
- startOfWeek as startOfWeek3,
23650
- startOfYear
25637
+ startOfDay as startOfDay5,
25638
+ startOfMonth as startOfMonth3,
25639
+ startOfWeek as startOfWeek4,
25640
+ startOfYear as startOfYear2
23651
25641
  } from "date-fns";
23652
- import { utcToZonedTime as utcToZonedTime2 } from "date-fns-tz";
25642
+ import { utcToZonedTime as utcToZonedTime3 } from "date-fns-tz";
23653
25643
  function mergeComparisonRange(resp) {
23654
25644
  if (resp.chartType === "table") return resp;
23655
25645
  const compRows = resp.compareRows;
@@ -23692,6 +25682,18 @@ var useDashboardInternal = (dashboardName, customFilters) => {
23692
25682
  (f) => f.filter
23693
25683
  ) : null;
23694
25684
  }, [dashboardFilters, dashboardName]);
25685
+ const getExistingDateFilter = useMemo2(
25686
+ () => (name2) => {
25687
+ if (!name2) return void 0;
25688
+ const filtersForDashboard = dashboardFilters[name2];
25689
+ if (!filtersForDashboard) return void 0;
25690
+ const entry = Object.values(filtersForDashboard).find(
25691
+ (f) => f?.filter?.filterType === "date_range" /* Date */
25692
+ );
25693
+ return entry?.filter;
25694
+ },
25695
+ [dashboardFilters]
25696
+ );
23695
25697
  useEffect2(() => {
23696
25698
  if (!dashboardName) return;
23697
25699
  if (backfilledDashboards.current.has(dashboardName)) return;
@@ -23729,7 +25731,7 @@ var useDashboardInternal = (dashboardName, customFilters) => {
23729
25731
  customFilters
23730
25732
  );
23731
25733
  }, [dashboardName, loading, dashboardConfig, dashboardFilters]);
23732
- const handleReload = async (overrideDashboardName, fetchFromServer = false, reportAction, overrideFilters) => {
25734
+ const handleReload = async (overrideDashboardName, fetchFromServer = false, reportAction, overrideFilters, options) => {
23733
25735
  if (!dashboardName) return;
23734
25736
  if (overrideFilters) {
23735
25737
  const dateFilter2 = overrideFilters.filters.find(
@@ -23782,10 +25784,19 @@ var useDashboardInternal = (dashboardName, customFilters) => {
23782
25784
  Object.values(dashboard?.sections ?? {}).flat(),
23783
25785
  dashboardName
23784
25786
  ) : void 0;
25787
+ const existingDateFilter = options?.preserveExistingDateFilter ? getExistingDateFilter(dashboardName) : void 0;
25788
+ const mergedDateFilter = dateFilter && existingDateFilter ? {
25789
+ ...dateFilter,
25790
+ startDate: existingDateFilter.startDate ?? dateFilter.startDate,
25791
+ endDate: existingDateFilter.endDate ?? dateFilter.endDate,
25792
+ preset: existingDateFilter.preset ?? dateFilter.preset,
25793
+ primaryRange: existingDateFilter.primaryRange ?? dateFilter.primaryRange,
25794
+ comparisonRange: existingDateFilter.comparisonRange ?? dateFilter.comparisonRange
25795
+ } : dateFilter ?? existingDateFilter;
23785
25796
  loadFiltersForDashboard(
23786
25797
  dashboardName,
23787
25798
  [
23788
- ...dateFilter ? [dateFilter] : [],
25799
+ ...mergedDateFilter ? [mergedDateFilter] : [],
23789
25800
  ...updatedDashboard?.filters?.map((filter) => ({
23790
25801
  ...filter,
23791
25802
  query: void 0
@@ -24070,7 +26081,8 @@ var useDashboards = () => {
24070
26081
  tenantFilters,
24071
26082
  dateFilter,
24072
26083
  customFilters,
24073
- tenantKeys
26084
+ tenantKeys,
26085
+ initialCacheDateRange
24074
26086
  }) => {
24075
26087
  if (!client) return;
24076
26088
  if (tenantKeys?.some((key) => !key)) {
@@ -24091,6 +26103,7 @@ var useDashboards = () => {
24091
26103
  filters,
24092
26104
  tenantFilters,
24093
26105
  dateFilter: dateFilter ?? null,
26106
+ initialCacheDateRange,
24094
26107
  name: name2.trim(),
24095
26108
  task: "edit-dashboard",
24096
26109
  clientId: client.clientId,
@@ -24302,7 +26315,9 @@ var useDashboards = () => {
24302
26315
  };
24303
26316
  };
24304
26317
  var useDashboard = (dashboardName, config) => {
26318
+ const logCacheStatistics = config?.showCacheLogs || false;
24305
26319
  const { data, dashboardFilters, reload, isLoading } = useDashboardInternal(dashboardName);
26320
+ const [lastUpdated, setLastUpdated] = useState2(0);
24306
26321
  const { customFilterDispatch } = useContext(DashboardFiltersContext);
24307
26322
  const { reportsDispatch, reportsLoadingStateDispatch } = useContext(ReportsContext);
24308
26323
  const initialLoad = useRef2(true);
@@ -24311,15 +26326,19 @@ var useDashboard = (dashboardName, config) => {
24311
26326
  const [client] = useContext(ClientContext);
24312
26327
  const { tenants, flags } = useContext(TenantContext);
24313
26328
  const { getToken } = useContext(FetchContext);
24314
- const { customReportFilters } = useContext(ReportFiltersContext);
26329
+ const { customReportFilters, customFiltersLoaded, setCustomFiltersLoaded } = useContext(ReportFiltersContext);
24315
26330
  const { eventTracking } = useContext(EventTrackingContext);
24316
- const customFiltersRef = useRef2(customReportFilters);
26331
+ const { getCacheCab } = useContext(CacheCabContext);
24317
26332
  const reportRequestIds = useRef2({});
24318
26333
  const lastDashboardName = useRef2(null);
24319
26334
  const pendingNameChangeReload = useRef2(false);
24320
- useEffect2(() => {
24321
- customFiltersRef.current = customReportFilters;
24322
- }, [customReportFilters]);
26335
+ const [loadedDashes, setLoadedDashes] = useState2([]);
26336
+ const [lastUpdatedDict, setLastUpdatedDict] = useState2({});
26337
+ const cacheEnabled = config?.cacheEnabled ?? true;
26338
+ const cacheCab = useMemo2(
26339
+ () => getCacheCab(config?.cacheType),
26340
+ [getCacheCab, config?.cacheType]
26341
+ );
24323
26342
  useEffect2(() => {
24324
26343
  const nameChanged = dashboardName !== lastDashboardName.current;
24325
26344
  if (nameChanged) {
@@ -24329,19 +26348,29 @@ var useDashboard = (dashboardName, config) => {
24329
26348
  backfilledDashboards.current = false;
24330
26349
  reportRequestIds.current = {};
24331
26350
  pendingNameChangeReload.current = true;
26351
+ if (customFiltersLoaded && !loadedDashes.includes(dashboardName)) {
26352
+ setCustomFiltersLoaded(false);
26353
+ }
26354
+ setLastUpdated(lastUpdatedDict[dashboardName] || 0);
24332
26355
  }
24333
26356
  if (!dashboardName) return;
24334
26357
  if (pendingNameChangeReload.current && data) {
24335
26358
  pendingNameChangeReload.current = false;
24336
26359
  initialLoad.current = false;
26360
+ fetchedInitialReports.current = false;
24337
26361
  return;
24338
26362
  }
24339
26363
  if (pendingNameChangeReload.current && !isLoading && !data && initialLoad.current) {
24340
26364
  initialLoad.current = false;
24341
26365
  pendingNameChangeReload.current = false;
24342
- reload();
26366
+ reload(dashboardName, false, void 0, void 0, {
26367
+ preserveExistingDateFilter: true
26368
+ });
24343
26369
  }
24344
26370
  }, [isLoading, data, dashboardName]);
26371
+ useEffect2(() => {
26372
+ setLastUpdated(lastUpdatedDict[dashboardName] || 0);
26373
+ }, [lastUpdatedDict, dashboardName]);
24345
26374
  useEffect2(() => {
24346
26375
  if (backfilledDashboards.current) return;
24347
26376
  if (isLoading || !data) return;
@@ -24350,7 +26379,9 @@ var useDashboard = (dashboardName, config) => {
24350
26379
  );
24351
26380
  if (!needsBackfill) return;
24352
26381
  backfilledDashboards.current = true;
24353
- reload(dashboardName, false);
26382
+ reload(dashboardName, false, void 0, void 0, {
26383
+ preserveExistingDateFilter: true
26384
+ });
24354
26385
  }, [isLoading, data, dashboardFilters, dashboardName]);
24355
26386
  const { allReportsById } = useAllReports();
24356
26387
  const sections = useMemo2(() => {
@@ -24374,7 +26405,13 @@ var useDashboard = (dashboardName, config) => {
24374
26405
  fetchedInitialReports.current = true;
24375
26406
  fetchReports([], dashboardFilters ?? [], config?.pageSize);
24376
26407
  }
24377
- }, [fetchedInitialReports, data, dashboardFilters]);
26408
+ }, [fetchedInitialReports, data, dashboardFilters, customFiltersLoaded]);
26409
+ useEffect2(() => {
26410
+ if (customFiltersLoaded && data && dashboardFilters !== null) {
26411
+ fetchedInitialReports.current = true;
26412
+ fetchReports([], dashboardFilters ?? [], config?.pageSize);
26413
+ }
26414
+ }, [customFiltersLoaded]);
24378
26415
  const applyDashboardFilters = (filtersToUpdate) => {
24379
26416
  const newFilters = (dashboardFilters ?? []).map((f) => {
24380
26417
  const update = filtersToUpdate.find((u) => u.label === f.label);
@@ -24453,7 +26490,7 @@ var useDashboard = (dashboardName, config) => {
24453
26490
  });
24454
26491
  return internalFilters;
24455
26492
  };
24456
- const isFilter = (f) => {
26493
+ const isFilter2 = (f) => {
24457
26494
  return f && typeof f === "object" && "filterType" in f && !("id" in f);
24458
26495
  };
24459
26496
  const isDashboardFilter = (f) => {
@@ -24461,29 +26498,22 @@ var useDashboard = (dashboardName, config) => {
24461
26498
  };
24462
26499
  const applyFilters = (filters2) => {
24463
26500
  const dashboardFilters2 = filters2.filter(isDashboardFilter);
24464
- const customFilters = filters2.filter(isFilter);
26501
+ const customFilters = filters2.filter(isFilter2);
24465
26502
  const newCustomFilters = applyCustomFilters(customFilters);
24466
26503
  const newDashboardFilters = applyDashboardFilters(dashboardFilters2);
24467
26504
  fetchReports(newCustomFilters, newDashboardFilters);
24468
26505
  };
24469
- const waitForCustomFilters = async (reportId) => {
24470
- let attempts = 0;
24471
- const maxAttempts = 10;
24472
- while (attempts < maxAttempts) {
24473
- const filters2 = customFiltersRef.current[reportId];
24474
- if (filters2 && filters2.length > 0) {
24475
- return filters2;
24476
- }
24477
- await new Promise((resolve) => setTimeout(resolve, 100));
24478
- attempts++;
24479
- }
24480
- return [];
26506
+ const forceCacheRefresh = () => {
26507
+ if (!cacheEnabled) return;
26508
+ fetchReports([], dashboardFilters ?? [], config?.pageSize, true);
24481
26509
  };
24482
- const fetchReports = async (customFilters, dashboardFilters2, pageSize) => {
26510
+ const fetchReports = async (customFilters, dashboardFilters2, pageSize, forceCacheToRefresh = false) => {
24483
26511
  if (!client || !sections) return;
24484
26512
  const allReports = Object.values(sections).flat();
26513
+ const fetchStartTime = Date.now();
26514
+ let totalCached = 0;
24485
26515
  await Promise.all(
24486
- allReports.map(async (reportInfo) => {
26516
+ allReports.map(async (reportInfo, idx) => {
24487
26517
  const reportId = reportInfo.id;
24488
26518
  const requestId = (reportRequestIds.current[reportId] ?? 0) + 1;
24489
26519
  reportRequestIds.current[reportId] = requestId;
@@ -24492,7 +26522,7 @@ var useDashboard = (dashboardName, config) => {
24492
26522
  id: reportId,
24493
26523
  data: true
24494
26524
  });
24495
- const customReportFiltersArray = await waitForCustomFilters(reportId);
26525
+ const customReportFiltersArray = customReportFilters[reportId] ?? [];
24496
26526
  let rowsPerRequest = pageSize || 600;
24497
26527
  if (!pageSize && (reportInfo.chartType === "table" || reportInfo.chartType === "metric")) {
24498
26528
  rowsPerRequest = 10;
@@ -24504,17 +26534,162 @@ var useDashboard = (dashboardName, config) => {
24504
26534
  const additionalProcessing = {
24505
26535
  page: pagination
24506
26536
  };
24507
- const usePivotTask = !!reportInfo.pivot;
26537
+ const usePivotTask = !cacheEnabled && !!reportInfo.pivot;
26538
+ const allFilters = dashboardFilters2.concat(customFilters).concat(customReportFiltersArray);
26539
+ const applyInMemoryPivotIfNeeded = (report2) => {
26540
+ const pivotToApply = reportInfo.pivot ?? report2.pivot;
26541
+ if (!pivotToApply || usePivotTask) {
26542
+ return report2;
26543
+ }
26544
+ const pivotRows = applyPivotInMemory(
26545
+ report2.rows ?? [],
26546
+ pivotToApply,
26547
+ allFilters
26548
+ );
26549
+ const pivotColumns = generatePivotColumnsInMemory({
26550
+ pivot: pivotToApply,
26551
+ pivotRows,
26552
+ sourceColumns: report2.columnInternal ?? report2.columns
26553
+ });
26554
+ return {
26555
+ ...report2,
26556
+ pivot: pivotToApply,
26557
+ pivotRows,
26558
+ pivotColumns: pivotColumns ?? report2.pivotColumns,
26559
+ pivotRowCount: pivotRows.length
26560
+ };
26561
+ };
26562
+ if (cacheEnabled && cacheCab.isCacheable(reportId)) {
26563
+ const report2 = await cacheCab.get(reportId, dashboardFilters2, customFilters.concat(customReportFiltersArray), reportInfo.pivot, client, tenants, flags, pageSize, getToken, eventTracking, forceCacheToRefresh);
26564
+ if (reportRequestIds.current[reportId] !== requestId) {
26565
+ return null;
26566
+ }
26567
+ if (cacheCab.isCacheable(reportId)) {
26568
+ reportsDispatch({
26569
+ type: "UPDATE_REPORT",
26570
+ id: reportId,
26571
+ data: {
26572
+ ...report2,
26573
+ pagination,
26574
+ triggerReload: false
26575
+ }
26576
+ });
26577
+ reportsLoadingStateDispatch({
26578
+ type: "SET_REPORT_LOADING",
26579
+ id: reportId,
26580
+ data: false
26581
+ });
26582
+ totalCached += 1;
26583
+ return report2;
26584
+ }
26585
+ }
26586
+ if (cacheEnabled && !cacheCab.isCacheable(reportId)) {
26587
+ try {
26588
+ const { report: uncacheableReport, fromCache } = await cacheCab.getOrFetchUncacheableResult(
26589
+ reportId,
26590
+ client,
26591
+ tenants,
26592
+ flags,
26593
+ allFilters,
26594
+ additionalProcessing,
26595
+ reportInfo.pivot,
26596
+ async () => {
26597
+ const { report: report2, error: error2 } = await fetchReport({
26598
+ reportId,
26599
+ client,
26600
+ tenants,
26601
+ flags,
26602
+ additionalProcessing,
26603
+ filters: allFilters,
26604
+ getToken,
26605
+ eventTracking,
26606
+ usePivotTask,
26607
+ overwriteCache: forceCacheToRefresh
26608
+ });
26609
+ if (error2 || !report2) {
26610
+ throw error2 ?? new Error("Failed to fetch uncacheable report");
26611
+ }
26612
+ return report2;
26613
+ },
26614
+ forceCacheToRefresh
26615
+ );
26616
+ if (reportRequestIds.current[reportId] !== requestId) {
26617
+ return null;
26618
+ }
26619
+ const uncacheableReportWithPivot = applyInMemoryPivotIfNeeded(uncacheableReport);
26620
+ reportsDispatch({
26621
+ type: "UPDATE_REPORT",
26622
+ id: reportId,
26623
+ data: {
26624
+ ...uncacheableReportWithPivot,
26625
+ pagination,
26626
+ triggerReload: false
26627
+ }
26628
+ });
26629
+ reportsLoadingStateDispatch({
26630
+ type: "SET_REPORT_LOADING",
26631
+ id: reportId,
26632
+ data: false
26633
+ });
26634
+ if (fromCache) {
26635
+ totalCached += 1;
26636
+ }
26637
+ if (usePivotTask) {
26638
+ fetchReportRows({
26639
+ reportId,
26640
+ client,
26641
+ tenants,
26642
+ filters: allFilters,
26643
+ getToken,
26644
+ additionalProcessing,
26645
+ overwriteCache: forceCacheToRefresh
26646
+ }).then(({ rows, rowCount, columns, fields }) => {
26647
+ if (reportRequestIds.current[reportId] !== requestId) {
26648
+ return;
26649
+ }
26650
+ reportsDispatch({
26651
+ type: "UPDATE_REPORT",
26652
+ id: reportId,
26653
+ data: {
26654
+ rows,
26655
+ rowCount,
26656
+ columnInternal: columns,
26657
+ columns: columns.map((col) => ({
26658
+ field: col.field,
26659
+ format: col.format,
26660
+ label: col.label,
26661
+ inferFormat: col.inferFormat
26662
+ })),
26663
+ // @ts-ignore fields is not typed on QuillReportInternal
26664
+ fields,
26665
+ triggerReload: false
26666
+ }
26667
+ });
26668
+ }).catch((e) => {
26669
+ if (e instanceof Error && e.name === "AbortError") {
26670
+ return;
26671
+ }
26672
+ console.error("Failed to fetch background rows", e);
26673
+ });
26674
+ }
26675
+ return uncacheableReportWithPivot;
26676
+ } catch (error2) {
26677
+ console.error(error2);
26678
+ return null;
26679
+ }
26680
+ }
24508
26681
  const { report, error } = await fetchReport({
24509
26682
  reportId,
24510
26683
  client,
24511
26684
  tenants,
24512
26685
  flags,
24513
26686
  additionalProcessing,
24514
- filters: dashboardFilters2.concat(customFilters).concat(customReportFiltersArray),
26687
+ filters: allFilters,
24515
26688
  getToken,
24516
26689
  eventTracking,
24517
- usePivotTask
26690
+ usePivotTask,
26691
+ overwriteCache: forceCacheToRefresh
26692
+ // usePivotTask: false,
24518
26693
  });
24519
26694
  if (error) {
24520
26695
  console.error(error);
@@ -24542,9 +26717,10 @@ var useDashboard = (dashboardName, config) => {
24542
26717
  reportId,
24543
26718
  client,
24544
26719
  tenants,
24545
- filters: dashboardFilters2.concat(customFilters).concat(customReportFiltersArray),
26720
+ filters: allFilters,
24546
26721
  getToken,
24547
- additionalProcessing
26722
+ additionalProcessing,
26723
+ overwriteCache: forceCacheToRefresh
24548
26724
  }).then(({ rows, rowCount, columns, fields }) => {
24549
26725
  if (reportRequestIds.current[reportId] !== requestId) {
24550
26726
  return;
@@ -24568,19 +26744,30 @@ var useDashboard = (dashboardName, config) => {
24568
26744
  }
24569
26745
  });
24570
26746
  }).catch((e) => {
24571
- if (e instanceof Error && e.name === "AbortError") return;
26747
+ if (e instanceof Error && e.name === "AbortError") {
26748
+ return;
26749
+ }
24572
26750
  console.error("Failed to fetch background rows", e);
24573
26751
  });
24574
26752
  }
24575
26753
  return report;
24576
26754
  })
24577
26755
  );
26756
+ if (!loadedDashes.includes(dashboardName) || !cacheEnabled || forceCacheToRefresh) {
26757
+ setLoadedDashes((prev) => prev.includes(dashboardName) ? prev : [...prev, dashboardName]);
26758
+ setLastUpdatedDict((prev) => ({ ...prev, [dashboardName]: Date.now() }));
26759
+ }
26760
+ if (logCacheStatistics) {
26761
+ console.log(`Dashboard "${dashboardName}": Cache Rate: ${(100 * totalCached / allReports.length).toFixed(1)}%, load time: ${((Date.now() - fetchStartTime) / 1e3).toFixed(2)} secs`);
26762
+ }
24578
26763
  };
24579
26764
  return {
24580
26765
  isLoading: !!isLoading || !sections,
24581
26766
  sections: isLoading ? null : sections,
24582
26767
  filters,
24583
- applyFilters
26768
+ applyFilters,
26769
+ lastUpdated,
26770
+ forceCacheRefresh
24584
26771
  };
24585
26772
  };
24586
26773
  var useDashboardReportInternal = (reportId, config) => {
@@ -24604,15 +26791,6 @@ var useDashboardReportInternal = (reportId, config) => {
24604
26791
  } = useDashboardInternal(reports[reportId]?.dashboardName ?? null);
24605
26792
  const [pageLoading, setPageLoading] = useState2(false);
24606
26793
  const [maxPage, setMaxPage] = useState2(0);
24607
- useEffect2(() => {
24608
- if (config?.initialFilters) {
24609
- customReportFiltersDispatch({
24610
- type: "ADD_CUSTOM_REPORT_FILTERS",
24611
- reportId,
24612
- data: config.initialFilters.map(convertCustomFilter)
24613
- });
24614
- }
24615
- }, []);
24616
26794
  const processedReport = useMemo2(() => {
24617
26795
  if (!reports[reportId]) return null;
24618
26796
  const dashboardName = reports[reportId].dashboardName;
@@ -24817,6 +26995,7 @@ var useDashboardReportInternal = (reportId, config) => {
24817
26995
  };
24818
26996
  var useDashboardReport = (reportId, config) => {
24819
26997
  const { report, loading, applyFilters, deleteReport, nextPage } = useDashboardReportInternal(reportId, config);
26998
+ const { customReportFiltersDispatch } = useContext(ReportFiltersContext);
24820
26999
  const fetchingRef = useRef2(false);
24821
27000
  const fetchNextPage = async () => {
24822
27001
  if (fetchingRef.current) {
@@ -24831,6 +27010,15 @@ var useDashboardReport = (reportId, config) => {
24831
27010
  fetchingRef.current = false;
24832
27011
  }
24833
27012
  };
27013
+ useEffect2(() => {
27014
+ if (config?.initialFilters) {
27015
+ customReportFiltersDispatch({
27016
+ type: "ADD_CUSTOM_REPORT_FILTERS",
27017
+ reportId,
27018
+ data: config.initialFilters.map(convertCustomFilter)
27019
+ });
27020
+ }
27021
+ }, []);
24834
27022
  return {
24835
27023
  report,
24836
27024
  isLoading: loading || fetchingRef.current,
@@ -27995,19 +30183,23 @@ var RenderLegend = ({
27995
30183
  const seeAllMeasureRef = useRef5(null);
27996
30184
  const seeAllTriggerRef = useRef5(null);
27997
30185
  const itemRefs = useRef5([]);
27998
- if (!payload || payload.length === 0) return null;
27999
- const maxItems = limit ?? payload.length;
30186
+ const safePayload = payload ?? [];
30187
+ const maxItems = limit ?? safePayload.length;
28000
30188
  const measuredLimit = visibleCount ?? maxItems;
28001
- const visiblePayload = payload.slice(0, Math.min(maxItems, measuredLimit));
30189
+ const visiblePayload = safePayload.slice(0, Math.min(maxItems, measuredLimit));
28002
30190
  const handleOpen = () => setIsOpen(true);
28003
30191
  const handleClose = () => setIsOpen(false);
28004
30192
  useLayoutEffect2(() => {
28005
30193
  if (!containerRef.current) return;
28006
- if (!payload.length) return;
30194
+ if (!safePayload.length) {
30195
+ setShowSeeAll(false);
30196
+ setVisibleCount(null);
30197
+ return;
30198
+ }
28007
30199
  const updateVisibleCount = () => {
28008
30200
  const containerWidth = containerRef.current?.offsetWidth ?? 0;
28009
30201
  if (!containerWidth) return;
28010
- const maxCount = Math.min(maxItems, payload.length);
30202
+ const maxCount = Math.min(maxItems, safePayload.length);
28011
30203
  const seeAllWidth = getOuterWidth(seeAllMeasureRef.current);
28012
30204
  const computeCount = (reserveSeeAll) => {
28013
30205
  const availableWidth = containerWidth - (reserveSeeAll ? seeAllWidth : 0);
@@ -28023,7 +30215,7 @@ var RenderLegend = ({
28023
30215
  return Math.max(1, nextCount);
28024
30216
  };
28025
30217
  const countWithSeeAll = computeCount(true);
28026
- const shouldShowSeeAll = payload.length > countWithSeeAll;
30218
+ const shouldShowSeeAll = safePayload.length > countWithSeeAll;
28027
30219
  const finalCount = shouldShowSeeAll ? countWithSeeAll : computeCount(false);
28028
30220
  setShowSeeAll(shouldShowSeeAll);
28029
30221
  setVisibleCount(finalCount);
@@ -28032,7 +30224,8 @@ var RenderLegend = ({
28032
30224
  const observer = new ResizeObserver(updateVisibleCount);
28033
30225
  observer.observe(containerRef.current);
28034
30226
  return () => observer.disconnect();
28035
- }, [payload.length, maxItems]);
30227
+ }, [safePayload.length, maxItems]);
30228
+ if (!safePayload.length) return null;
28036
30229
  return /* @__PURE__ */ jsxs19(
28037
30230
  "div",
28038
30231
  {
@@ -28072,7 +30265,7 @@ var RenderLegend = ({
28072
30265
  justifyContent: "flex-start",
28073
30266
  flexWrap: "nowrap"
28074
30267
  },
28075
- children: payload.slice(0, maxItems).map((entry, index) => /* @__PURE__ */ jsx27(
30268
+ children: safePayload.slice(0, maxItems).map((entry, index) => /* @__PURE__ */ jsx27(
28076
30269
  "div",
28077
30270
  {
28078
30271
  ref: (element) => {
@@ -28161,7 +30354,7 @@ var RenderLegend = ({
28161
30354
  maxWidth: 520,
28162
30355
  gap: "0.5rem"
28163
30356
  },
28164
- children: payload.map((entry, index) => /* @__PURE__ */ jsx27(
30357
+ children: safePayload.map((entry, index) => /* @__PURE__ */ jsx27(
28165
30358
  LegendItem,
28166
30359
  {
28167
30360
  entry,
@@ -28511,8 +30704,8 @@ var PieChartWrapper = React4.forwardRef(
28511
30704
  cy: "50%",
28512
30705
  startAngle: 90,
28513
30706
  endAngle: -270,
28514
- innerRadius: isDonut ? showLegend ? "50%" : "70%" : "0%",
28515
- outerRadius: showLegend ? "70%" : "100%",
30707
+ innerRadius: isDonut ? "70%" : "0%",
30708
+ outerRadius: "100%",
28516
30709
  paddingAngle: 0,
28517
30710
  dataKey: category,
28518
30711
  nameKey: index,
@@ -29115,8 +31308,8 @@ import {
29115
31308
  } from "recharts";
29116
31309
 
29117
31310
  // src/utils/axisFormatter.ts
29118
- import { endOfWeek as endOfWeek2, format as format5, getWeek as getWeek2, isValid as isValid4, startOfWeek as startOfWeek4 } from "date-fns";
29119
- import { utcToZonedTime as utcToZonedTime3 } from "date-fns-tz";
31311
+ import { endOfWeek as endOfWeek2, format as format5, getWeek as getWeek2, isValid as isValid5, startOfWeek as startOfWeek5 } from "date-fns";
31312
+ import { utcToZonedTime as utcToZonedTime4 } from "date-fns-tz";
29120
31313
  var axisFormatter = ({ value, field, fields }) => {
29121
31314
  if (field === void 0 || field === null) return "";
29122
31315
  if (value === void 0 || value === null) return "";
@@ -29232,8 +31425,8 @@ var formatPercent2 = (value) => {
29232
31425
  return formatter.format(Number(value));
29233
31426
  };
29234
31427
  var _getUTCDateHelper2 = (value, fmt) => {
29235
- const utcDate = utcToZonedTime3(new Date(value), "UTC");
29236
- if (!isValid4(utcDate)) return "Invalid date";
31428
+ const utcDate = utcToZonedTime4(new Date(value), "UTC");
31429
+ if (!isValid5(utcDate)) return "Invalid date";
29237
31430
  return format5(utcDate, fmt);
29238
31431
  };
29239
31432
  var format_YYYY2 = (value) => _getUTCDateHelper2(value, "yyyy");
@@ -29242,9 +31435,9 @@ var format_MMM_yyyy2 = (value) => _getUTCDateHelper2(value, "MMM yyyy");
29242
31435
  var format_hh_ap_pm2 = (value) => _getUTCDateHelper2(value, "hh:mm aa");
29243
31436
  var format_MMM_dd_yyyy2 = (value) => _getUTCDateHelper2(value, "dd MMM yyyy");
29244
31437
  var format_MMM_dd_MMM_dd = (value) => {
29245
- const utcDate = utcToZonedTime3(new Date(value), "UTC");
29246
- if (!isValid4(utcDate)) return "Invalid date";
29247
- const monday = startOfWeek4(utcDate, { weekStartsOn: 1 });
31438
+ const utcDate = utcToZonedTime4(new Date(value), "UTC");
31439
+ if (!isValid5(utcDate)) return "Invalid date";
31440
+ const monday = startOfWeek5(utcDate, { weekStartsOn: 1 });
29248
31441
  const sunday = endOfWeek2(utcDate, { weekStartsOn: 1 });
29249
31442
  if (format5(monday, "MMM") === format5(sunday, "MMM")) {
29250
31443
  return `${format5(monday, "MMM dd")} - ${format5(sunday, "dd")}`;
@@ -29253,20 +31446,20 @@ var format_MMM_dd_MMM_dd = (value) => {
29253
31446
  }
29254
31447
  };
29255
31448
  var format_MMM_dd_hh_mm_ap_pm2 = (value) => {
29256
- const utcDate = utcToZonedTime3(new Date(value), "UTC");
29257
- if (!isValid4(utcDate)) return "Invalid date";
31449
+ const utcDate = utcToZonedTime4(new Date(value), "UTC");
31450
+ if (!isValid5(utcDate)) return "Invalid date";
29258
31451
  const formatStr = utcDate.getMinutes() === 0 ? "MMM do h a" : "MMM do h:mm a";
29259
31452
  const res = format5(utcDate, formatStr);
29260
31453
  return res.slice(0, -2) + res.slice(-2).toLowerCase();
29261
31454
  };
29262
31455
  var format_wo_yyyy2 = (value) => {
29263
- const utcDate = utcToZonedTime3(new Date(value), "UTC");
29264
- if (!isValid4(utcDate)) return "Invalid date";
31456
+ const utcDate = utcToZonedTime4(new Date(value), "UTC");
31457
+ if (!isValid5(utcDate)) return "Invalid date";
29265
31458
  return `${getWeek2(utcDate)},${utcDate.getFullYear()}`;
29266
31459
  };
29267
31460
 
29268
31461
  // src/components/Chart/ChartTooltip.tsx
29269
- import { format as format6, subDays as subDays2 } from "date-fns";
31462
+ import { format as format6, subDays as subDays3 } from "date-fns";
29270
31463
 
29271
31464
  // src/components/Chart/ChartTooltipFrame.tsx
29272
31465
  import { jsx as jsx30 } from "react/jsx-runtime";
@@ -29510,7 +31703,7 @@ function reformatComparisonPayload(props, primaryLabel, comparisonLabel) {
29510
31703
  const nameKey = isComparison ? `comparison_${props.xAxisField}` : props.xAxisField;
29511
31704
  const days = LABEL_TO_DAYS[primaryLabel] ?? 0;
29512
31705
  const primaryDate = item.payload[props.xAxisField] ?? 0;
29513
- const compDate = subDays2(new Date(primaryDate), days + 1);
31706
+ const compDate = subDays3(new Date(primaryDate), days + 1);
29514
31707
  const date = item.payload[nameKey] ?? format6(compDate, props.xAxisFormat);
29515
31708
  const name2 = props.dateFormatter(date);
29516
31709
  const color2 = item.color;
@@ -30576,16 +32769,16 @@ import {
30576
32769
  useState as useState12
30577
32770
  } from "react";
30578
32771
  import {
30579
- startOfMonth as startOfMonth3,
32772
+ startOfMonth as startOfMonth4,
30580
32773
  endOfMonth as endOfMonth2,
30581
32774
  format as format7,
30582
32775
  eachDayOfInterval,
30583
- subMonths as subMonths3,
30584
- startOfWeek as startOfWeek5,
32776
+ subMonths as subMonths5,
32777
+ startOfWeek as startOfWeek6,
30585
32778
  endOfWeek as endOfWeek3,
30586
32779
  differenceInDays as differenceInDays3,
30587
- startOfDay as startOfDay4,
30588
- addMonths,
32780
+ startOfDay as startOfDay6,
32781
+ addMonths as addMonths2,
30589
32782
  isBefore as isBefore2,
30590
32783
  isAfter as isAfter2
30591
32784
  } from "date-fns";
@@ -31110,20 +33303,20 @@ function CalendarRow({
31110
33303
  setLocalPreset
31111
33304
  }) {
31112
33305
  const firstMonthDisplayedDates = eachDayOfInterval({
31113
- start: startOfWeek5(anchorStartDate),
33306
+ start: startOfWeek6(anchorStartDate),
31114
33307
  end: endOfWeek3(endOfMonth2(anchorStartDate))
31115
33308
  });
31116
33309
  const secondMonthDisplayedDates = eachDayOfInterval({
31117
- start: startOfWeek5(startOfMonth3(anchorEndDate)),
33310
+ start: startOfWeek6(startOfMonth4(anchorEndDate)),
31118
33311
  end: endOfWeek3(anchorEndDate)
31119
33312
  });
31120
33313
  const incrementAnchorDates = () => {
31121
- setAnchorStartDate(startOfMonth3(addMonths(anchorStartDate, 1)));
31122
- setAnchorEndDate(endOfMonth2(addMonths(anchorEndDate, 1)));
33314
+ setAnchorStartDate(startOfMonth4(addMonths2(anchorStartDate, 1)));
33315
+ setAnchorEndDate(endOfMonth2(addMonths2(anchorEndDate, 1)));
31123
33316
  };
31124
33317
  const decrementAnchorDates = () => {
31125
- setAnchorStartDate(startOfMonth3(subMonths3(anchorStartDate, 1)));
31126
- setAnchorEndDate(endOfMonth2(subMonths3(anchorEndDate, 1)));
33318
+ setAnchorStartDate(startOfMonth4(subMonths5(anchorStartDate, 1)));
33319
+ setAnchorEndDate(endOfMonth2(subMonths5(anchorEndDate, 1)));
31127
33320
  };
31128
33321
  return /* @__PURE__ */ jsx40("div", { style: { position: "absolute", zIndex: 100, marginTop: 12 }, children: /* @__PURE__ */ jsxs30(
31129
33322
  "div",
@@ -31377,10 +33570,10 @@ function DayPicker({
31377
33570
  setLocalPreset,
31378
33571
  theme
31379
33572
  }) {
31380
- const isStartDate = differenceInDays3(startOfDay4(date), startOfDay4(localStartDate || 0)) === 0;
31381
- const isEndDate = differenceInDays3(startOfDay4(date), startOfDay4(localEndDate || 0)) === 0;
33573
+ const isStartDate = differenceInDays3(startOfDay6(date), startOfDay6(localStartDate || 0)) === 0;
33574
+ const isEndDate = differenceInDays3(startOfDay6(date), startOfDay6(localEndDate || 0)) === 0;
31382
33575
  const isBetweenStartAndEnd = isBefore2(date, localEndDate || 0) && isAfter2(date, localStartDate || 0);
31383
- const isBeginningOfWeek = differenceInDays3(startOfWeek5(date), date) === 0;
33576
+ const isBeginningOfWeek = differenceInDays3(startOfWeek6(date), date) === 0;
31384
33577
  const isEndOfWeek = differenceInDays3(endOfWeek3(date), date) === 0;
31385
33578
  return /* @__PURE__ */ jsx40(
31386
33579
  "button",
@@ -31429,7 +33622,7 @@ function DayPicker({
31429
33622
  });
31430
33623
  setLocalPreset("");
31431
33624
  }
31432
- if (localStartDate && localEndDate && differenceInDays3(startOfDay4(date), startOfDay4(localStartDate)) === 0) {
33625
+ if (localStartDate && localEndDate && differenceInDays3(startOfDay6(date), startOfDay6(localStartDate)) === 0) {
31433
33626
  setLocalStartDate(void 0);
31434
33627
  setLocalEndDate(void 0);
31435
33628
  }
@@ -31440,16 +33633,16 @@ function DayPicker({
31440
33633
  }
31441
33634
  function getAnchorStartDate(startDate, endDate) {
31442
33635
  if (!startDate && !endDate) {
31443
- return startOfMonth3(subMonths3(/* @__PURE__ */ new Date(), 1));
33636
+ return startOfMonth4(subMonths5(/* @__PURE__ */ new Date(), 1));
31444
33637
  }
31445
33638
  if (startDate && !endDate) {
31446
- return startOfMonth3(startDate);
33639
+ return startOfMonth4(startDate);
31447
33640
  }
31448
33641
  if (!startDate && endDate) {
31449
- return startOfMonth3(subMonths3(endDate, 1));
33642
+ return startOfMonth4(subMonths5(endDate, 1));
31450
33643
  }
31451
33644
  if (startDate && endDate) {
31452
- return startOfMonth3(startDate);
33645
+ return startOfMonth4(startDate);
31453
33646
  }
31454
33647
  return /* @__PURE__ */ new Date();
31455
33648
  }
@@ -31458,13 +33651,13 @@ function getAnchorEndDate(startDate, endDate) {
31458
33651
  return endOfMonth2(/* @__PURE__ */ new Date());
31459
33652
  }
31460
33653
  if (startDate && !endDate) {
31461
- return endOfMonth2(addMonths(startDate, 1));
33654
+ return endOfMonth2(addMonths2(startDate, 1));
31462
33655
  }
31463
33656
  if (!startDate && endDate) {
31464
33657
  return endOfMonth2(endDate);
31465
33658
  }
31466
33659
  if (startDate && endDate) {
31467
- return endOfMonth2(addMonths(startDate, 1));
33660
+ return endOfMonth2(addMonths2(startDate, 1));
31468
33661
  }
31469
33662
  return /* @__PURE__ */ new Date();
31470
33663
  }
@@ -32659,6 +34852,164 @@ function DashboardFilter2({
32659
34852
  // src/Chart.tsx
32660
34853
  init_paginationProcessing();
32661
34854
 
34855
+ // src/utils/cloudCacheValidation.ts
34856
+ var LIMIT_CLAUSE_REGEX = /^limit\b\s+(?:all|\d+|\$\d+|:[a-zA-Z_][a-zA-Z0-9_]*|\?)/i;
34857
+ var SQL_CONTENT_TO_IGNORE_REGEX = /'(?:''|[^'])*'|"(?:[^"]|"")*"|`[^`]*`|\[[^\]]*\]|--[^\n]*|\/\*[\s\S]*?\*\//g;
34858
+ function isWordChar(char) {
34859
+ if (!char) {
34860
+ return false;
34861
+ }
34862
+ return /[A-Za-z0-9_]/.test(char);
34863
+ }
34864
+ function getFinalSqlStatement(query) {
34865
+ const statements = query.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
34866
+ return statements[statements.length - 1] ?? "";
34867
+ }
34868
+ function hasTopLevelLimitClause(statement) {
34869
+ let depth = 0;
34870
+ for (let i = 0; i < statement.length; i += 1) {
34871
+ const char = statement[i];
34872
+ if (char === "(") {
34873
+ depth += 1;
34874
+ continue;
34875
+ }
34876
+ if (char === ")") {
34877
+ depth = Math.max(0, depth - 1);
34878
+ continue;
34879
+ }
34880
+ if (depth !== 0) {
34881
+ continue;
34882
+ }
34883
+ const previousChar = i > 0 ? statement[i - 1] : void 0;
34884
+ if (isWordChar(previousChar)) {
34885
+ continue;
34886
+ }
34887
+ if (!LIMIT_CLAUSE_REGEX.test(statement.slice(i))) {
34888
+ continue;
34889
+ }
34890
+ return true;
34891
+ }
34892
+ return false;
34893
+ }
34894
+ function unwrapIdentifier(identifier) {
34895
+ if (!identifier) {
34896
+ return "";
34897
+ }
34898
+ const trimmed = identifier.trim();
34899
+ if (trimmed.length < 2) {
34900
+ return trimmed;
34901
+ }
34902
+ const startsAndEndsWithDoubleQuote = trimmed.startsWith('"') && trimmed.endsWith('"');
34903
+ const startsAndEndsWithSingleQuote = trimmed.startsWith("'") && trimmed.endsWith("'");
34904
+ const startsAndEndsWithBackticks = trimmed.startsWith("`") && trimmed.endsWith("`");
34905
+ const startsAndEndsWithBrackets = trimmed.startsWith("[") && trimmed.endsWith("]");
34906
+ if (startsAndEndsWithDoubleQuote || startsAndEndsWithSingleQuote || startsAndEndsWithBackticks || startsAndEndsWithBrackets) {
34907
+ return trimmed.slice(1, -1);
34908
+ }
34909
+ return trimmed;
34910
+ }
34911
+ function normalizeIdentifier(identifier) {
34912
+ return unwrapIdentifier(identifier).toLowerCase();
34913
+ }
34914
+ function hasRowFieldWithIdentifier(row, normalizedField) {
34915
+ if (!row || !normalizedField) {
34916
+ return false;
34917
+ }
34918
+ return Object.keys(row).some(
34919
+ (fieldName) => normalizeIdentifier(fieldName) === normalizedField
34920
+ );
34921
+ }
34922
+ function reportReferencesField(report, field, table) {
34923
+ const normalizedField = normalizeIdentifier(field);
34924
+ if (!normalizedField) {
34925
+ return false;
34926
+ }
34927
+ const referencedColumns = report?.referencedColumns ?? {};
34928
+ let entries = Object.entries(referencedColumns);
34929
+ if (entries.length === 0) {
34930
+ return false;
34931
+ }
34932
+ const normalizedTable = normalizeIdentifier(table);
34933
+ if (normalizedTable) {
34934
+ entries = entries.filter(
34935
+ ([tableName]) => normalizeIdentifier(tableName) === normalizedTable
34936
+ );
34937
+ }
34938
+ const referencedFields = entries.flatMap(([, fields]) => fields ?? []);
34939
+ if (referencedFields.length === 0) {
34940
+ return false;
34941
+ }
34942
+ return referencedFields.some((referencedField) => {
34943
+ const normalizedReferencedField = normalizeIdentifier(referencedField);
34944
+ return normalizedReferencedField === normalizedField || normalizedReferencedField === "*";
34945
+ });
34946
+ }
34947
+ function isDateFieldMissingInReport(report) {
34948
+ if (!report?.dateField?.field || !report.dateField.table) {
34949
+ return false;
34950
+ }
34951
+ const dateField = report.dateField;
34952
+ if (!reportReferencesField(report, dateField.field, dateField.table)) {
34953
+ return true;
34954
+ }
34955
+ const firstRow = report.rows?.[0];
34956
+ if (!firstRow) {
34957
+ return false;
34958
+ }
34959
+ const normalizedDateField = normalizeIdentifier(dateField.field);
34960
+ const dateFieldExistsInFirstRow = hasRowFieldWithIdentifier(
34961
+ firstRow,
34962
+ normalizedDateField
34963
+ );
34964
+ return !dateFieldExistsInFirstRow;
34965
+ }
34966
+ function hasLimitClause(query) {
34967
+ if (!query) {
34968
+ return false;
34969
+ }
34970
+ const sanitizedQuery = query.replace(SQL_CONTENT_TO_IGNORE_REGEX, " ");
34971
+ const finalStatement = getFinalSqlStatement(sanitizedQuery);
34972
+ if (!finalStatement) {
34973
+ return false;
34974
+ }
34975
+ return hasTopLevelLimitClause(finalStatement);
34976
+ }
34977
+ function reportUsesLimitClause(report) {
34978
+ const queriesToInspect = report?.itemQuery && report.itemQuery.length > 0 ? report.itemQuery : report?.queryString ? [report.queryString] : [];
34979
+ return queriesToInspect.some(hasLimitClause);
34980
+ }
34981
+ function getMissingDashboardFilterFields({
34982
+ rows = [],
34983
+ dashboardFilters = []
34984
+ }) {
34985
+ const dateIndex = dashboardFilters.findIndex(
34986
+ (filter) => filter.filterType === "date_range"
34987
+ );
34988
+ const requiredFields = dashboardFilters.flatMap((filter, index) => {
34989
+ if (index === dateIndex || !filter.field) {
34990
+ return [];
34991
+ }
34992
+ const normalizedField = normalizeIdentifier(filter.field);
34993
+ if (!normalizedField) {
34994
+ return [];
34995
+ }
34996
+ return [
34997
+ {
34998
+ displayField: unwrapIdentifier(filter.field),
34999
+ normalizedField
35000
+ }
35001
+ ];
35002
+ });
35003
+ const dedupedRequiredFields = requiredFields.filter(
35004
+ (field, index, fields) => fields.findIndex(
35005
+ (candidate) => candidate.normalizedField === field.normalizedField
35006
+ ) === index
35007
+ );
35008
+ return dedupedRequiredFields.filter(
35009
+ ({ normalizedField }) => rows.some((row) => !hasRowFieldWithIdentifier(row, normalizedField))
35010
+ ).map(({ displayField }) => displayField);
35011
+ }
35012
+
32662
35013
  // src/components/Dashboard/MetricComponent.tsx
32663
35014
  init_dateRangePickerUtils();
32664
35015
  import { useContext as useContext12 } from "react";
@@ -32766,7 +35117,7 @@ function QuillMetricComponent({
32766
35117
  width: "100%"
32767
35118
  }
32768
35119
  }
32769
- ) : !report?.rows || report?.rows?.length === 0 || report.rows[0]?.[report.xAxisField] === null || report.rows[0]?.[report.xAxisField] === void 0 ? /* @__PURE__ */ jsx44(
35120
+ ) : !report?.rows || report?.rows?.length === 0 ? /* @__PURE__ */ jsx44(
32770
35121
  "div",
32771
35122
  {
32772
35123
  style: {
@@ -36285,6 +38636,7 @@ function QuillTableDashboardComponent({
36285
38636
  }
36286
38637
 
36287
38638
  // src/Chart.tsx
38639
+ init_valueFormatter();
36288
38640
  import { Fragment as Fragment5, jsx as jsx49, jsxs as jsxs37 } from "react/jsx-runtime";
36289
38641
  var MAX_ROWS_FOR_GENERIC_TABLE = 500;
36290
38642
  function sumByKey(arr, key) {
@@ -36321,7 +38673,8 @@ function Chart({
36321
38673
  filters,
36322
38674
  onClickChartElement,
36323
38675
  dateBucket,
36324
- propagateChanges
38676
+ propagateChanges,
38677
+ isAdmin = false
36325
38678
  }) {
36326
38679
  const [schemaData] = useContext16(SchemaDataContext);
36327
38680
  const { reload } = useReportInternal(reportId);
@@ -36443,6 +38796,25 @@ function Chart({
36443
38796
  }
36444
38797
  }, [report, theme]);
36445
38798
  const [client, clientLoading] = useContext16(ClientContext);
38799
+ const cloudCacheEnabled = useMemo13(() => {
38800
+ return !!client?.features?.cloudCache;
38801
+ }, [client]);
38802
+ const shouldShowCloudCacheValidationErrors = isAdmin && cloudCacheEnabled;
38803
+ const isMissingDateField = useMemo13(() => {
38804
+ if (!shouldShowCloudCacheValidationErrors) return false;
38805
+ return isDateFieldMissingInReport(report);
38806
+ }, [report, shouldShowCloudCacheValidationErrors]);
38807
+ const usesLimitInQuery = useMemo13(() => {
38808
+ if (!shouldShowCloudCacheValidationErrors) return false;
38809
+ return reportUsesLimitClause(report);
38810
+ }, [report, shouldShowCloudCacheValidationErrors]);
38811
+ const missingDashboardFilterFields = useMemo13(() => {
38812
+ if (!shouldShowCloudCacheValidationErrors || !report) return [];
38813
+ return getMissingDashboardFilterFields({
38814
+ rows: report.rows,
38815
+ dashboardFilters
38816
+ });
38817
+ }, [report, dashboardFilters, shouldShowCloudCacheValidationErrors]);
36446
38818
  const [error, setError] = useState19(void 0);
36447
38819
  const updateFilter = (filter, value, comparison) => {
36448
38820
  let filterValue = {};
@@ -36583,6 +38955,30 @@ function Chart({
36583
38955
  if (report?.error || error) {
36584
38956
  return /* @__PURE__ */ jsx49("div", { style: containerStyle, className, children: /* @__PURE__ */ jsx49(ChartError, { errorMessage: report?.error ?? error }) });
36585
38957
  }
38958
+ if (isMissingDateField) {
38959
+ return /* @__PURE__ */ jsx49("div", { style: containerStyle, className, children: /* @__PURE__ */ jsx49(
38960
+ ChartError,
38961
+ {
38962
+ errorMessage: `The query for this report is missing dashboard date filter field ${report?.dateField?.field}, which is necessary for date filtering on this dashboard when the cache is enabled.`
38963
+ }
38964
+ ) });
38965
+ }
38966
+ if (usesLimitInQuery) {
38967
+ return /* @__PURE__ */ jsx49("div", { style: containerStyle, className, children: /* @__PURE__ */ jsx49(
38968
+ ChartError,
38969
+ {
38970
+ errorMessage: `The query for this report uses the LIMIT keyword, which is not supported when the cache is enabled.`
38971
+ }
38972
+ ) });
38973
+ }
38974
+ if (missingDashboardFilterFields.length) {
38975
+ return /* @__PURE__ */ jsx49("div", { style: containerStyle, className, children: /* @__PURE__ */ jsx49(
38976
+ ChartError,
38977
+ {
38978
+ errorMessage: `The query for this report is missing dashboard filter fields: ${missingDashboardFilterFields.join(", ")}`
38979
+ }
38980
+ ) });
38981
+ }
36586
38982
  return /* @__PURE__ */ jsxs37(
36587
38983
  "div",
36588
38984
  {
@@ -36913,7 +39309,7 @@ var ChartDisplay = ({
36913
39309
  );
36914
39310
  }
36915
39311
  if (config?.chartType?.toLowerCase() === "metric") {
36916
- if (!config?.rows || config?.rows?.length === 0 || config?.rows[0]?.[config?.xAxisField] === null || config?.rows[0]?.[config?.xAxisField] === void 0) {
39312
+ if (!config?.rows || config?.rows?.length === 0) {
36917
39313
  return /* @__PURE__ */ jsx49(
36918
39314
  "div",
36919
39315
  {
@@ -36951,6 +39347,36 @@ var ChartDisplay = ({
36951
39347
  }
36952
39348
  );
36953
39349
  }
39350
+ if (config?.pivotRows && config?.pivotRows?.length > 0) {
39351
+ return /* @__PURE__ */ jsx49(
39352
+ "div",
39353
+ {
39354
+ style: {
39355
+ fontFamily: theme?.fontFamily,
39356
+ fontSize: 32,
39357
+ color: theme?.primaryTextColor,
39358
+ fontWeight: "600",
39359
+ textOverflow: "ellipsis",
39360
+ margin: 0,
39361
+ whiteSpace: "nowrap",
39362
+ boxSizing: "content-box",
39363
+ maxWidth: "100%",
39364
+ textAlign: "left",
39365
+ overflow: "hidden",
39366
+ height: containerStyle?.height || "100%",
39367
+ display: "flex",
39368
+ width: "100%",
39369
+ flexDirection: "row",
39370
+ ...containerStyle
39371
+ },
39372
+ className,
39373
+ children: /* @__PURE__ */ jsx49(QuillMetricComponent, { report: config ?? EMPTY_REPORT, children: quillFormat({
39374
+ value: config?.pivotRows?.[0]?.[config?.xAxisField],
39375
+ format: config?.xAxisFormat
39376
+ }) ?? "No results" })
39377
+ }
39378
+ );
39379
+ }
36954
39380
  return /* @__PURE__ */ jsx49(
36955
39381
  "div",
36956
39382
  {
@@ -37899,7 +40325,7 @@ init_Filter();
37899
40325
  import { useState as useState22, useEffect as useEffect18, useMemo as useMemo15 } from "react";
37900
40326
  init_textProcessing();
37901
40327
  init_filterProcessing();
37902
- import { format as format8, isValid as isValid5, parse as parse4, startOfToday as startOfToday2 } from "date-fns";
40328
+ import { format as format8, isValid as isValid6, parse as parse4, startOfToday as startOfToday2 } from "date-fns";
37903
40329
  import { Fragment as Fragment8, jsx as jsx55, jsxs as jsxs41 } from "react/jsx-runtime";
37904
40330
  function FilterModal({
37905
40331
  schema,
@@ -38100,7 +40526,7 @@ function FilterModal({
38100
40526
  DateOperator.LessThan,
38101
40527
  DateOperator.LessThanOrEqualTo,
38102
40528
  DateOperator.NotEqualTo
38103
- ].includes(operator2) && !isValid5(parse4(value, "yyyy-mm-dd", /* @__PURE__ */ new Date())) && (filterInitialized || !filter)) {
40529
+ ].includes(operator2) && !isValid6(parse4(value, "yyyy-mm-dd", /* @__PURE__ */ new Date())) && (filterInitialized || !filter)) {
38104
40530
  setValue(startOfToday2().toISOString().substring(0, 10));
38105
40531
  } else if (type === FieldType.Date && [
38106
40532
  DateOperator.InTheLast,
@@ -38290,7 +40716,7 @@ function FilterModal({
38290
40716
  case DateOperator.GreaterThanOrEqualTo:
38291
40717
  case DateOperator.LessThanOrEqualTo: {
38292
40718
  const parsedDate = parse4(value, "yyyy-mm-dd", /* @__PURE__ */ new Date());
38293
- if (!isValid5(parsedDate)) {
40719
+ if (!isValid6(parsedDate)) {
38294
40720
  alert("Please specify a valid date in yyyy-mm-dd");
38295
40721
  return;
38296
40722
  }
@@ -38818,6 +41244,9 @@ function validateTemplatesAgainstFilters(filters, templates) {
38818
41244
  }
38819
41245
  async function addTemplatesToDashboard(name2, newTemplates, client, dashboardData, getToken, eventTracking) {
38820
41246
  try {
41247
+ if (!newTemplates?.length) {
41248
+ return null;
41249
+ }
38821
41250
  const { publicKey, tenants } = client;
38822
41251
  if (tenants) {
38823
41252
  throw new Error("Adding from template not yet supported for tenants");
@@ -38870,6 +41299,7 @@ async function addTemplatesToDashboard(name2, newTemplates, client, dashboardDat
38870
41299
  function: "addTemplatesToDashboard"
38871
41300
  }
38872
41301
  });
41302
+ return null;
38873
41303
  }
38874
41304
  }
38875
41305
 
@@ -40366,7 +42796,7 @@ import {
40366
42796
  useEffect as useEffect25,
40367
42797
  useRef as useRef20,
40368
42798
  useMemo as useMemo22,
40369
- useCallback as useCallback4
42799
+ useCallback as useCallback5
40370
42800
  } from "react";
40371
42801
  import MonacoEditor from "@monaco-editor/react";
40372
42802
 
@@ -40377,7 +42807,7 @@ import {
40377
42807
  useState as useState30,
40378
42808
  useContext as useContext26,
40379
42809
  useMemo as useMemo21,
40380
- useCallback as useCallback3
42810
+ useCallback as useCallback4
40381
42811
  } from "react";
40382
42812
  import {
40383
42813
  closestCenter,
@@ -40398,7 +42828,7 @@ import { CSS as DND_CSS } from "@dnd-kit/utilities";
40398
42828
 
40399
42829
  // src/internals/ReportBuilder/PivotModal.tsx
40400
42830
  import {
40401
- useCallback as useCallback2,
42831
+ useCallback as useCallback3,
40402
42832
  useContext as useContext23,
40403
42833
  useMemo as useMemo18,
40404
42834
  useState as useState27,
@@ -40793,7 +43223,7 @@ import {
40793
43223
  eachMonthOfInterval,
40794
43224
  eachWeekOfInterval,
40795
43225
  eachYearOfInterval,
40796
- isValid as isValid6,
43226
+ isValid as isValid7,
40797
43227
  parseISO as parseISO3
40798
43228
  } from "date-fns";
40799
43229
  init_pivotProcessing();
@@ -41281,7 +43711,7 @@ var PivotModal = ({
41281
43711
  setIsOpen(false);
41282
43712
  setPopUpTitle("Add pivot");
41283
43713
  };
41284
- const onCommitPivot = useCallback2(() => {
43714
+ const onCommitPivot = useCallback3(() => {
41285
43715
  const errors2 = [];
41286
43716
  if ((pivotAggregations?.length ?? 0) === 0) {
41287
43717
  errors2.push("You must have at least one aggregation");
@@ -41423,7 +43853,7 @@ var PivotModal = ({
41423
43853
  const onEditRecommendedPivot = (pivot) => {
41424
43854
  onEditPivot(pivot, null);
41425
43855
  };
41426
- const refreshPivots = useCallback2(async () => {
43856
+ const refreshPivots = useCallback3(async () => {
41427
43857
  if (!client) {
41428
43858
  return;
41429
43859
  }
@@ -44474,7 +46904,7 @@ function ChartBuilder({
44474
46904
  template: true,
44475
46905
  referenceLines: []
44476
46906
  };
44477
- const updateDashboardFilters = async (dashboardName) => {
46907
+ const updateDashboardFilters2 = async (dashboardName) => {
44478
46908
  if (dashboardConfig && dashboardConfig[dashboardName]) {
44479
46909
  return dashboardConfig[dashboardName];
44480
46910
  }
@@ -44796,7 +47226,7 @@ function ChartBuilder({
44796
47226
  if (destinationDashboardName) {
44797
47227
  dashboardName = destinationDashboardName;
44798
47228
  }
44799
- const curDashboard = await updateDashboardFilters(dashboardName);
47229
+ const curDashboard = await updateDashboardFilters2(dashboardName);
44800
47230
  setDashboardOptions(dashboardOptions2);
44801
47231
  curFormData.dashboardName = dashboardName;
44802
47232
  const curSchemaData = schemaData.schemaWithCustomFields;
@@ -44940,7 +47370,7 @@ function ChartBuilder({
44940
47370
  ) ?? {};
44941
47371
  }, [client?.allTenantTypes]);
44942
47372
  const [selectedPivotTable, setSelectedPivotTable] = useState30(pivotData);
44943
- const getDefaultXAxisFormat = useCallback3(
47373
+ const getDefaultXAxisFormat = useCallback4(
44944
47374
  (form) => {
44945
47375
  if (form.pivot?.rowField) {
44946
47376
  if (isDateField(form.pivot.rowFieldType ?? "")) {
@@ -46083,7 +48513,7 @@ function ChartBuilder({
46083
48513
  value: formData.dashboardName || "",
46084
48514
  onChange: async (e) => {
46085
48515
  handleChange(e.target.value, "dashboardName");
46086
- await updateDashboardFilters(e.target.value);
48516
+ await updateDashboardFilters2(e.target.value);
46087
48517
  },
46088
48518
  options: dashboardOptions.map((elem) => ({
46089
48519
  label: elem.label,
@@ -48316,7 +50746,7 @@ function SQLEditor({
48316
50746
  onCloseChartBuilder && onCloseChartBuilder();
48317
50747
  }
48318
50748
  }, [isChartBuilderOpen]);
48319
- const handleRunSqlPrompt = useCallback4(async () => {
50749
+ const handleRunSqlPrompt = useCallback5(async () => {
48320
50750
  if (!client || sqlResponseLoading) {
48321
50751
  return;
48322
50752
  }
@@ -48335,7 +50765,7 @@ function SQLEditor({
48335
50765
  setQuery(resp.message);
48336
50766
  setSqlResponseLoading(false);
48337
50767
  }, [sqlPrompt, sqlResponseLoading]);
48338
- const debounceRunSqlPrompt = useCallback4(
50768
+ const debounceRunSqlPrompt = useCallback5(
48339
50769
  createDebounce(handleRunSqlPrompt, 500),
48340
50770
  [handleRunSqlPrompt]
48341
50771
  );
@@ -54450,7 +56880,7 @@ function StaticChart(props) {
54450
56880
  pageLoading,
54451
56881
  nextPage,
54452
56882
  prevPage,
54453
- sortRows
56883
+ sortRows: sortRows2
54454
56884
  } = useDashboardReportInternal(reportId);
54455
56885
  const baseStyle = report?.chartType && CHART_TYPE_STYLES[report.chartType] ? CHART_TYPE_STYLES[report.chartType] : DEFAULT_STYLE;
54456
56886
  const mergedStyle = className ? { ...containerStyle ?? {} } : { ...baseStyle, ...containerStyle ?? {} };
@@ -54470,7 +56900,7 @@ function StaticChart(props) {
54470
56900
  }
54471
56901
  };
54472
56902
  const onSortChange = (sort) => {
54473
- sortRows({
56903
+ sortRows2({
54474
56904
  field: sort.field,
54475
56905
  direction: sort.direction
54476
56906
  });