@quillsql/react 2.16.76 → 2.16.78

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
@@ -17280,7 +17280,8 @@ async function generatePivotWithSQL({
17280
17280
  getToken,
17281
17281
  reportBuilderState,
17282
17282
  // Add reportBuilderState parameter
17283
- overwriteCache = false
17283
+ overwriteCache = false,
17284
+ abortSignal
17284
17285
  }) {
17285
17286
  const databaseType = client.databaseType || "postgresql";
17286
17287
  pivot = normalizeLegacyPivotSortField(pivot);
@@ -17344,7 +17345,8 @@ async function generatePivotWithSQL({
17344
17345
  overwriteCache,
17345
17346
  dateBucket: resolvedDateBucket
17346
17347
  },
17347
- getToken
17348
+ getToken,
17349
+ abortSignal
17348
17350
  });
17349
17351
  if (resp.data?.success === false) {
17350
17352
  throw resp.data.errorMessage;
@@ -17354,9 +17356,7 @@ async function generatePivotWithSQL({
17354
17356
  const rowCount = resp.queries?.queryResults?.[1]?.rows?.[0]?.row_count || 0;
17355
17357
  parseValueFromBigQueryDates(queryResponseRows, queryResponseFields);
17356
17358
  const resultRowField = processColumnName(
17357
- String(
17358
- resp.data?.config?.rowField ?? pivot.rowField ?? ""
17359
- )
17359
+ String(resp.data?.config?.rowField ?? pivot.rowField ?? "")
17360
17360
  );
17361
17361
  const responseRows = queryResponseRows;
17362
17362
  const responseFields = queryResponseFields;
@@ -17522,7 +17522,8 @@ async function generatePivotTable({
17522
17522
  pivotQuery,
17523
17523
  reportBuilderState,
17524
17524
  // Add reportBuilderState parameter
17525
- overwriteCache
17525
+ overwriteCache,
17526
+ abortSignal
17526
17527
  }) {
17527
17528
  try {
17528
17529
  if (report && client) {
@@ -17541,11 +17542,15 @@ async function generatePivotTable({
17541
17542
  getToken,
17542
17543
  reportBuilderState,
17543
17544
  // Pass reportBuilderState
17544
- overwriteCache
17545
+ overwriteCache,
17546
+ abortSignal
17545
17547
  });
17546
17548
  return pivotTable;
17547
17549
  }
17548
17550
  } catch (e) {
17551
+ if (e instanceof Error && e.name === "AbortError") {
17552
+ throw e;
17553
+ }
17549
17554
  eventTracking?.logError?.({
17550
17555
  type: "bug",
17551
17556
  // TODO: determine type
@@ -19912,7 +19917,7 @@ var init_tableProcessing = __esm({
19912
19917
  }
19913
19918
  return { rows, columns, rowCount, error };
19914
19919
  };
19915
- fetchTableByState = async (reportBuilderState, client, getToken, tenants, eventTracking, dashboardName, processing, customFields, rowsOnly, rowCountOnly, reportId, draftSessionId) => {
19920
+ fetchTableByState = async (reportBuilderState, client, getToken, tenants, eventTracking, dashboardName, processing, customFields, rowsOnly, rowCountOnly, reportId, draftSessionId, abortSignal, filters) => {
19916
19921
  let rows = [];
19917
19922
  let columns = [];
19918
19923
  let rowCount;
@@ -19925,8 +19930,7 @@ var init_tableProcessing = __esm({
19925
19930
  processing,
19926
19931
  // report-builder-query reads `additionalProcessing` (same shape as /query); keep `processing` for compatibility
19927
19932
  additionalProcessing: processing,
19928
- filters: void 0,
19929
- // No dashboard filters for ReportBuilder table fetching
19933
+ filters,
19930
19934
  dateField: void 0,
19931
19935
  rowsOnly,
19932
19936
  rowCountOnly,
@@ -19945,7 +19949,8 @@ var init_tableProcessing = __esm({
19945
19949
  client,
19946
19950
  task: "report-builder-query",
19947
19951
  metadata: requestMetadata,
19948
- getToken
19952
+ getToken,
19953
+ abortSignal
19949
19954
  });
19950
19955
  const resp = await parseFetchResponse(
19951
19956
  client,
@@ -19979,6 +19984,9 @@ var init_tableProcessing = __esm({
19979
19984
  });
19980
19985
  }
19981
19986
  } catch (e) {
19987
+ if (e instanceof Error && e.name === "AbortError") {
19988
+ throw e;
19989
+ }
19982
19990
  eventTracking?.logError?.({
19983
19991
  type: "bug",
19984
19992
  // TODO: determine type
@@ -20008,8 +20016,8 @@ var init_tableProcessing = __esm({
20008
20016
  function setChangelogHandler(handler) {
20009
20017
  changelogHandler = handler;
20010
20018
  }
20011
- function notifyChangelogs(entries) {
20012
- changelogHandler?.(entries);
20019
+ function notifyChangelogs(entries2) {
20020
+ changelogHandler?.(entries2);
20013
20021
  }
20014
20022
  var changelogHandler;
20015
20023
  var init_changelogNotify = __esm({
@@ -20019,6 +20027,147 @@ var init_changelogNotify = __esm({
20019
20027
  }
20020
20028
  });
20021
20029
 
20030
+ // src/utils/reportRequestPool.ts
20031
+ function createAbortError(signal) {
20032
+ if (signal?.reason instanceof Error) return signal.reason;
20033
+ return Object.assign(new Error("The operation was aborted"), {
20034
+ name: "AbortError"
20035
+ });
20036
+ }
20037
+ function stableStringify(value) {
20038
+ if (value === void 0) return "undefined";
20039
+ if (value === null || typeof value !== "object") {
20040
+ return JSON.stringify(value);
20041
+ }
20042
+ if (Array.isArray(value)) {
20043
+ return `[${value.map(stableStringify).join(",")}]`;
20044
+ }
20045
+ const object = value;
20046
+ return `{${Object.keys(object).filter((key) => object[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
20047
+ }
20048
+ function normalizeAdditionalProcessing(value) {
20049
+ if (!value || typeof value !== "object") return value ?? null;
20050
+ const additionalProcessing = value;
20051
+ const page = additionalProcessing.page;
20052
+ if (!page || typeof page !== "object") return additionalProcessing;
20053
+ const pagination = page;
20054
+ return {
20055
+ ...additionalProcessing,
20056
+ page: {
20057
+ ...pagination,
20058
+ page: pagination.page ?? 0,
20059
+ rowsPerPage: pagination.rowsPerPage ?? pagination.rowsPerRequest ?? void 0
20060
+ }
20061
+ };
20062
+ }
20063
+ function createSharedRequestKey({
20064
+ client,
20065
+ task,
20066
+ metadata,
20067
+ token
20068
+ }) {
20069
+ return stableStringify({
20070
+ endpoint: client.queryEndpoint,
20071
+ clientId: client.id ?? client.clientId,
20072
+ headers: client.queryHeaders,
20073
+ withCredentials: client.withCredentials,
20074
+ token,
20075
+ task,
20076
+ metadata: {
20077
+ ...metadata,
20078
+ draftSessionId: void 0,
20079
+ overwriteCache: void 0,
20080
+ // Initial editor requests send the saved pivot/state explicitly while
20081
+ // dashboard requests ask the backend to resolve the same saved values.
20082
+ pivot: void 0,
20083
+ reportBuilderState: void 0,
20084
+ additionalProcessing: normalizeAdditionalProcessing(
20085
+ metadata?.additionalProcessing
20086
+ )
20087
+ }
20088
+ });
20089
+ }
20090
+ function cloneResult(result) {
20091
+ return JSON.parse(JSON.stringify(result));
20092
+ }
20093
+ async function getOrFetchSharedRequest({
20094
+ key,
20095
+ signal,
20096
+ fetcher
20097
+ }) {
20098
+ if (signal?.aborted) throw createAbortError(signal);
20099
+ let entry = entries.get(key);
20100
+ if (!entry) {
20101
+ const controller = new AbortController();
20102
+ entry = {
20103
+ controller,
20104
+ subscribers: 0,
20105
+ settled: false,
20106
+ promise: Promise.resolve({})
20107
+ };
20108
+ entry.promise = fetcher(controller.signal).then(
20109
+ (result) => {
20110
+ entry.settled = true;
20111
+ if (result.error || result.status === "error") {
20112
+ if (entries.get(key) === entry) entries.delete(key);
20113
+ return result;
20114
+ }
20115
+ entry.expiryTimer = setTimeout(() => {
20116
+ if (entries.get(key) === entry) entries.delete(key);
20117
+ }, COMPLETED_TTL_MS);
20118
+ return result;
20119
+ },
20120
+ (error) => {
20121
+ if (entries.get(key) === entry) entries.delete(key);
20122
+ throw error;
20123
+ }
20124
+ );
20125
+ entries.set(key, entry);
20126
+ }
20127
+ clearTimeout(entry.abortTimer);
20128
+ entry.subscribers += 1;
20129
+ return new Promise((resolve, reject) => {
20130
+ let released = false;
20131
+ const release = () => {
20132
+ if (released) return;
20133
+ released = true;
20134
+ signal?.removeEventListener("abort", onAbort);
20135
+ entry.subscribers -= 1;
20136
+ if (entry.settled || entry.subscribers > 0) return;
20137
+ entry.abortTimer = setTimeout(() => {
20138
+ if (!entry.settled && entry.subscribers === 0) {
20139
+ entry.controller.abort();
20140
+ entries.delete(key);
20141
+ }
20142
+ }, ORPHAN_GRACE_MS);
20143
+ };
20144
+ const onAbort = () => {
20145
+ release();
20146
+ reject(createAbortError(signal));
20147
+ };
20148
+ signal?.addEventListener("abort", onAbort, { once: true });
20149
+ entry.promise.then(
20150
+ (result) => {
20151
+ release();
20152
+ resolve(cloneResult(result));
20153
+ },
20154
+ (error) => {
20155
+ release();
20156
+ reject(error);
20157
+ }
20158
+ );
20159
+ });
20160
+ }
20161
+ var ORPHAN_GRACE_MS, COMPLETED_TTL_MS, entries;
20162
+ var init_reportRequestPool = __esm({
20163
+ "src/utils/reportRequestPool.ts"() {
20164
+ "use strict";
20165
+ ORPHAN_GRACE_MS = 100;
20166
+ COMPLETED_TTL_MS = 15e3;
20167
+ entries = /* @__PURE__ */ new Map();
20168
+ }
20169
+ });
20170
+
20022
20171
  // src/utils/dataFetcher.tsx
20023
20172
  var dataFetcher_exports = {};
20024
20173
  __export(dataFetcher_exports, {
@@ -20491,7 +20640,7 @@ async function fetchSqlQueryFromState(reportBuilderState, client, getToken, data
20491
20640
  return { query: "", error: `Failed to generate query: ${error.message}` };
20492
20641
  }
20493
20642
  }
20494
- async function fetchQueryDateRangesFromState(reportBuilderState, columns, client, getToken, databaseType, tenants, customFields, dashboardName, eventTracking) {
20643
+ async function fetchQueryDateRangesFromState(reportBuilderState, columns, client, getToken, databaseType, tenants, customFields, dashboardName, eventTracking, abortSignal) {
20495
20644
  try {
20496
20645
  const { data } = await quillFetch({
20497
20646
  client,
@@ -20505,7 +20654,8 @@ async function fetchQueryDateRangesFromState(reportBuilderState, columns, client
20505
20654
  customFields,
20506
20655
  dashboardName
20507
20656
  },
20508
- getToken
20657
+ getToken,
20658
+ abortSignal
20509
20659
  });
20510
20660
  if (!data || !data.rows) {
20511
20661
  return null;
@@ -20525,11 +20675,17 @@ async function fetchQueryDateRangesFromState(reportBuilderState, columns, client
20525
20675
  );
20526
20676
  return results;
20527
20677
  } catch (error) {
20678
+ if (error instanceof Error && error.name === "AbortError") {
20679
+ throw error;
20680
+ }
20528
20681
  return null;
20529
20682
  }
20530
20683
  }
20531
- async function fetchRelevantInfoFromState(reportBuilderState, tables, columns, aliasedColumns, reportBuilderColumns, client, getToken, databaseType, tenants, customFields, dashboardName, eventTracking) {
20684
+ async function fetchRelevantInfoFromState(reportBuilderState, tables, columns, aliasedColumns, reportBuilderColumns, client, getToken, databaseType, tenants, customFields, dashboardName, eventTracking, abortSignal) {
20532
20685
  try {
20686
+ if (abortSignal?.aborted) {
20687
+ throw new DOMException("Aborted", "AbortError");
20688
+ }
20533
20689
  const result = await getRelevantInfoFromQuery({
20534
20690
  tables,
20535
20691
  columns,
@@ -20552,6 +20708,9 @@ async function fetchRelevantInfoFromState(reportBuilderState, tables, columns, a
20552
20708
  error: result.error
20553
20709
  };
20554
20710
  } catch (error) {
20711
+ if (abortSignal?.aborted || error instanceof Error && error.name === "AbortError") {
20712
+ throw error;
20713
+ }
20555
20714
  return { error: error.message };
20556
20715
  }
20557
20716
  }
@@ -20564,6 +20723,7 @@ var init_dataFetcher = __esm({
20564
20723
  init_tableProcessing();
20565
20724
  init_dates();
20566
20725
  init_changelogNotify();
20726
+ init_reportRequestPool();
20567
20727
  quillFetch = async ({
20568
20728
  client,
20569
20729
  task,
@@ -20572,55 +20732,67 @@ var init_dataFetcher = __esm({
20572
20732
  abortSignal,
20573
20733
  credentials = "omit",
20574
20734
  urlParameters,
20735
+ shareRequest = false,
20575
20736
  getToken
20576
20737
  }) => {
20577
- const token = await getToken();
20578
20738
  const queryString = urlParameters ?? `task=${task}`;
20579
20739
  const endpoint = client.queryEndpoint ? `${client.queryEndpoint}?${queryString}` : `${QUILL_SERVER}${QUILL_QUERY_ENDPOINT}?${queryString}`;
20580
- try {
20581
- const response = await fetch(endpoint, {
20582
- method,
20583
- headers: {
20584
- ...token ? { Authorization: `Bearer ${token}` } : {},
20585
- ...client.queryHeaders,
20586
- "Content-Type": "application/json"
20587
- },
20588
- body: JSON.stringify({
20589
- metadata: {
20590
- task,
20591
- clientId: client.id ?? client.clientId,
20592
- ...metadata
20593
- }
20594
- }),
20595
- credentials: client.withCredentials ? "include" : credentials,
20596
- signal: abortSignal
20597
- });
20598
- if (!response.ok) {
20599
- throw new Error("API request failed");
20600
- }
20601
- let result = await response.json();
20602
- if (result.data?.data && (result.data.queries || result.data.status || result.data.error)) {
20603
- result = result.data;
20604
- }
20605
- const normalizedData = normalizeChangelogData(result.data);
20606
- if (task !== "fetch-changelog-list" && Array.isArray(normalizedData?.changelogs)) {
20607
- notifyChangelogs(normalizedData.changelogs);
20608
- }
20609
- return {
20610
- data: normalizedData,
20611
- queries: result.queries,
20612
- status: result.status,
20613
- error: result.error
20614
- };
20615
- } catch (e) {
20616
- if (e instanceof Error && e.name === "AbortError") {
20617
- throw e;
20618
- }
20619
- if (task !== "set-section-order") {
20620
- console.error("Failed to fetch:", e);
20740
+ const execute = async (signal, resolvedToken) => {
20741
+ const token2 = resolvedToken ?? await getToken();
20742
+ try {
20743
+ const response = await fetch(endpoint, {
20744
+ method,
20745
+ headers: {
20746
+ ...token2 ? { Authorization: `Bearer ${token2}` } : {},
20747
+ ...client.queryHeaders,
20748
+ "Content-Type": "application/json"
20749
+ },
20750
+ body: JSON.stringify({
20751
+ metadata: {
20752
+ task,
20753
+ clientId: client.id ?? client.clientId,
20754
+ ...metadata
20755
+ }
20756
+ }),
20757
+ credentials: client.withCredentials ? "include" : credentials,
20758
+ signal
20759
+ });
20760
+ if (!response.ok) {
20761
+ throw new Error("API request failed");
20762
+ }
20763
+ let result = await response.json();
20764
+ if (result.data?.data && (result.data.queries || result.data.status || result.data.error)) {
20765
+ result = result.data;
20766
+ }
20767
+ const normalizedData = normalizeChangelogData(result.data);
20768
+ if (task !== "fetch-changelog-list" && Array.isArray(normalizedData?.changelogs)) {
20769
+ notifyChangelogs(normalizedData.changelogs);
20770
+ }
20771
+ return {
20772
+ data: normalizedData,
20773
+ queries: result.queries,
20774
+ status: result.status,
20775
+ error: result.error
20776
+ };
20777
+ } catch (e) {
20778
+ if (e instanceof Error && e.name === "AbortError") {
20779
+ throw e;
20780
+ }
20781
+ if (task !== "set-section-order") {
20782
+ console.error("Failed to fetch:", e);
20783
+ }
20784
+ return { error: "Failed to fetch data" };
20621
20785
  }
20622
- return { error: "Failed to fetch data" };
20786
+ };
20787
+ if (!shareRequest) {
20788
+ return execute(abortSignal);
20623
20789
  }
20790
+ const token = await getToken();
20791
+ return getOrFetchSharedRequest({
20792
+ key: createSharedRequestKey({ client, task, metadata, token }),
20793
+ signal: abortSignal,
20794
+ fetcher: (signal) => execute(signal, token)
20795
+ });
20624
20796
  };
20625
20797
  parseFetchResponse = async (client, task, response, getToken, useInMemory = false) => {
20626
20798
  try {
@@ -21929,13 +22101,15 @@ async function requestPivotTemplateTask({
21929
22101
  client,
21930
22102
  metadata,
21931
22103
  getToken,
21932
- abortSignal
22104
+ abortSignal,
22105
+ shareRequest = false
21933
22106
  }) {
21934
22107
  return quillFetch({
21935
22108
  client,
21936
22109
  task: "pivot-template",
21937
22110
  metadata,
21938
22111
  abortSignal,
22112
+ shareRequest,
21939
22113
  getToken
21940
22114
  });
21941
22115
  }
@@ -22103,7 +22277,8 @@ async function fetchPivotTemplateReportForUseForm({
22103
22277
  abortSignal,
22104
22278
  baseReport,
22105
22279
  responseSelection,
22106
- draftSessionId
22280
+ draftSessionId,
22281
+ shareRequest = false
22107
22282
  }) {
22108
22283
  let fetchStep = "create-metadata";
22109
22284
  try {
@@ -22125,7 +22300,8 @@ async function fetchPivotTemplateReportForUseForm({
22125
22300
  client,
22126
22301
  metadata,
22127
22302
  getToken,
22128
- abortSignal
22303
+ abortSignal,
22304
+ shareRequest
22129
22305
  });
22130
22306
  fetchStep = "parse-task-response";
22131
22307
  const parsedResponse = await parsePivotTemplateTaskResponse({
@@ -22276,7 +22452,8 @@ async function fetchReport({
22276
22452
  pivot,
22277
22453
  reportBuilderState,
22278
22454
  skipPivotFetch,
22279
- draftSessionId
22455
+ draftSessionId,
22456
+ shareRequest = false
22280
22457
  }) {
22281
22458
  let reportInfo = void 0;
22282
22459
  let errorMessage = void 0;
@@ -22320,6 +22497,7 @@ async function fetchReport({
22320
22497
  ...resolvedDateBucket ? { dateBucket: resolvedDateBucket } : {}
22321
22498
  },
22322
22499
  abortSignal,
22500
+ shareRequest,
22323
22501
  getToken
22324
22502
  });
22325
22503
  const resp = await parseFetchResponse(client, task, fetchResp, getToken);
@@ -22987,7 +23165,8 @@ var fetchReportBuilderDataFromState = async ({
22987
23165
  dashboardName,
22988
23166
  getToken,
22989
23167
  eventTracking,
22990
- draftSessionId
23168
+ draftSessionId,
23169
+ abortSignal
22991
23170
  }) => {
22992
23171
  let newRows = [];
22993
23172
  let newColumns = [];
@@ -23061,7 +23240,8 @@ var fetchReportBuilderDataFromState = async ({
23061
23240
  skipRowCount,
23062
23241
  rowCountOnly,
23063
23242
  curReport?.id ? String(curReport.id).trim() : void 0,
23064
- draftSessionId ? String(draftSessionId).trim() : void 0
23243
+ draftSessionId ? String(draftSessionId).trim() : void 0,
23244
+ abortSignal
23065
23245
  );
23066
23246
  if (tableData.error) {
23067
23247
  throw new Error(tableData.error);
@@ -23116,7 +23296,9 @@ var fetchReportBuilderDataFromState = async ({
23116
23296
  client.databaseType?.toLowerCase() || "postgresql",
23117
23297
  tenants,
23118
23298
  customFields,
23119
- dashboardName
23299
+ dashboardName,
23300
+ void 0,
23301
+ abortSignal
23120
23302
  );
23121
23303
  const uniqueStrings = uniqueStringsByTable;
23122
23304
  const columnUniqueValues = uniqueStringsByColumn;
@@ -23136,7 +23318,9 @@ var fetchReportBuilderDataFromState = async ({
23136
23318
  client.databaseType?.toLowerCase() || "postgresql",
23137
23319
  tenants,
23138
23320
  customFields ?? [],
23139
- dashboardName
23321
+ dashboardName,
23322
+ void 0,
23323
+ abortSignal
23140
23324
  );
23141
23325
  if (dateRanges === null) {
23142
23326
  throw new Error("Couldn't fetch date ranges");
@@ -23186,8 +23370,9 @@ var fetchReportBuilderDataFromState = async ({
23186
23370
  getToken,
23187
23371
  pivotQuery: skipPivotColumnFetch ? curReport?.pivotQuery : void 0,
23188
23372
  eventTracking,
23189
- reportBuilderState
23373
+ reportBuilderState,
23190
23374
  // Pass reportBuilderState for ReportBuilder context
23375
+ abortSignal
23191
23376
  });
23192
23377
  newPivot = pivot;
23193
23378
  }
@@ -23213,6 +23398,9 @@ var fetchReportBuilderDataFromState = async ({
23213
23398
  newRows = tableData.rows;
23214
23399
  newColumns = tableData.columns;
23215
23400
  } catch (e) {
23401
+ if (e instanceof Error && e.name === "AbortError") {
23402
+ throw e;
23403
+ }
23216
23404
  eventTracking?.logError?.({
23217
23405
  type: "bug",
23218
23406
  // TODO: determine type
@@ -24166,15 +24354,226 @@ var applyFiltersInMemory = (rows, filters, options) => {
24166
24354
  // src/utils/cacheCab.ts
24167
24355
  init_inMemoryPivotEngine();
24168
24356
  import { openDB } from "idb";
24169
- import { endOfDay as endOfDay3, max as maxDate, subMonths as subMonths4, min as minDate, startOfDay as startOfDay4 } from "date-fns";
24357
+ import {
24358
+ endOfDay as endOfDay3,
24359
+ max as maxDate,
24360
+ subMonths as subMonths4,
24361
+ min as minDate,
24362
+ startOfDay as startOfDay4
24363
+ } from "date-fns";
24170
24364
  import { utcToZonedTime as utcToZonedTime2, zonedTimeToUtc } from "date-fns-tz";
24365
+
24366
+ // src/utils/cloudCacheValidation.ts
24367
+ var LIMIT_CLAUSE_REGEX = /^limit\b\s+(?:all|\d+|\$\d+|:[a-zA-Z_][a-zA-Z0-9_]*|\?)/i;
24368
+ var SQL_CONTENT_TO_IGNORE_REGEX = /'(?:''|[^'])*'|"(?:[^"]|"")*"|`[^`]*`|\[[^\]]*\]|--[^\n]*|\/\*[\s\S]*?\*\//g;
24369
+ function isWordChar(char) {
24370
+ if (!char) {
24371
+ return false;
24372
+ }
24373
+ return /[A-Za-z0-9_]/.test(char);
24374
+ }
24375
+ function getFinalSqlStatement(query) {
24376
+ const statements = query.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
24377
+ return statements[statements.length - 1] ?? "";
24378
+ }
24379
+ function hasTopLevelLimitClause(statement) {
24380
+ let depth = 0;
24381
+ for (let i = 0; i < statement.length; i += 1) {
24382
+ const char = statement[i];
24383
+ if (char === "(") {
24384
+ depth += 1;
24385
+ continue;
24386
+ }
24387
+ if (char === ")") {
24388
+ depth = Math.max(0, depth - 1);
24389
+ continue;
24390
+ }
24391
+ if (depth !== 0) {
24392
+ continue;
24393
+ }
24394
+ const previousChar = i > 0 ? statement[i - 1] : void 0;
24395
+ if (isWordChar(previousChar)) {
24396
+ continue;
24397
+ }
24398
+ if (!LIMIT_CLAUSE_REGEX.test(statement.slice(i))) {
24399
+ continue;
24400
+ }
24401
+ return true;
24402
+ }
24403
+ return false;
24404
+ }
24405
+ function unwrapIdentifier(identifier) {
24406
+ if (!identifier) {
24407
+ return "";
24408
+ }
24409
+ const trimmed = identifier.trim();
24410
+ if (trimmed.length < 2) {
24411
+ return trimmed;
24412
+ }
24413
+ const startsAndEndsWithDoubleQuote = trimmed.startsWith('"') && trimmed.endsWith('"');
24414
+ const startsAndEndsWithSingleQuote = trimmed.startsWith("'") && trimmed.endsWith("'");
24415
+ const startsAndEndsWithBackticks = trimmed.startsWith("`") && trimmed.endsWith("`");
24416
+ const startsAndEndsWithBrackets = trimmed.startsWith("[") && trimmed.endsWith("]");
24417
+ if (startsAndEndsWithDoubleQuote || startsAndEndsWithSingleQuote || startsAndEndsWithBackticks || startsAndEndsWithBrackets) {
24418
+ return trimmed.slice(1, -1);
24419
+ }
24420
+ return trimmed;
24421
+ }
24422
+ function normalizeIdentifier(identifier) {
24423
+ return unwrapIdentifier(identifier).toLowerCase();
24424
+ }
24425
+ function hasRowFieldWithIdentifier(row, normalizedField) {
24426
+ if (!row || !normalizedField) {
24427
+ return false;
24428
+ }
24429
+ return Object.keys(row).some(
24430
+ (fieldName) => normalizeIdentifier(fieldName) === normalizedField
24431
+ );
24432
+ }
24433
+ function reportReferencesField(report, field, table) {
24434
+ const normalizedField = normalizeIdentifier(field);
24435
+ if (!normalizedField) {
24436
+ return false;
24437
+ }
24438
+ const referencedColumns = report?.referencedColumns ?? {};
24439
+ let entries2 = Object.entries(referencedColumns);
24440
+ if (entries2.length === 0) {
24441
+ return false;
24442
+ }
24443
+ const normalizedTable = normalizeIdentifier(table);
24444
+ if (normalizedTable) {
24445
+ entries2 = entries2.filter(
24446
+ ([tableName]) => normalizeIdentifier(tableName) === normalizedTable
24447
+ );
24448
+ }
24449
+ const referencedFields = entries2.flatMap(([, fields]) => fields ?? []);
24450
+ if (referencedFields.length === 0) {
24451
+ return false;
24452
+ }
24453
+ return referencedFields.some((referencedField) => {
24454
+ const normalizedReferencedField = normalizeIdentifier(referencedField);
24455
+ return normalizedReferencedField === normalizedField || normalizedReferencedField === "*";
24456
+ });
24457
+ }
24458
+ function isDateFieldMissingInReport(report) {
24459
+ if (!report?.dateField?.field || !report.dateField.table) {
24460
+ return false;
24461
+ }
24462
+ const dateField = report.dateField;
24463
+ if (!reportReferencesField(report, dateField.field, dateField.table)) {
24464
+ return true;
24465
+ }
24466
+ const firstRow = report.rows?.[0];
24467
+ if (!firstRow) {
24468
+ return false;
24469
+ }
24470
+ const normalizedDateField = normalizeIdentifier(dateField.field);
24471
+ const dateFieldExistsInFirstRow = hasRowFieldWithIdentifier(
24472
+ firstRow,
24473
+ normalizedDateField
24474
+ );
24475
+ return !dateFieldExistsInFirstRow;
24476
+ }
24477
+ function hasLimitClause(query) {
24478
+ if (!query) {
24479
+ return false;
24480
+ }
24481
+ const sanitizedQuery = query.replace(SQL_CONTENT_TO_IGNORE_REGEX, " ");
24482
+ const finalStatement = getFinalSqlStatement(sanitizedQuery);
24483
+ if (!finalStatement) {
24484
+ return false;
24485
+ }
24486
+ return hasTopLevelLimitClause(finalStatement);
24487
+ }
24488
+ function reportUsesLimitClause(report) {
24489
+ const queriesToInspect = report?.itemQuery && report.itemQuery.length > 0 ? report.itemQuery : report?.queryString ? [report.queryString] : [];
24490
+ return queriesToInspect.some(hasLimitClause);
24491
+ }
24492
+ function getMissingDashboardFilterFields({
24493
+ rows = [],
24494
+ dashboardFilters = []
24495
+ }) {
24496
+ const dateIndex = dashboardFilters.findIndex(
24497
+ (filter) => filter.filterType === "date_range"
24498
+ );
24499
+ const requiredFields = dashboardFilters.flatMap((filter, index) => {
24500
+ if (index === dateIndex || !filter.field) {
24501
+ return [];
24502
+ }
24503
+ const normalizedField = normalizeIdentifier(filter.field);
24504
+ if (!normalizedField) {
24505
+ return [];
24506
+ }
24507
+ return [
24508
+ {
24509
+ displayField: unwrapIdentifier(filter.field),
24510
+ normalizedField
24511
+ }
24512
+ ];
24513
+ });
24514
+ const dedupedRequiredFields = requiredFields.filter(
24515
+ (field, index, fields) => fields.findIndex(
24516
+ (candidate) => candidate.normalizedField === field.normalizedField
24517
+ ) === index
24518
+ );
24519
+ return dedupedRequiredFields.filter(
24520
+ ({ normalizedField }) => rows.some((row) => !hasRowFieldWithIdentifier(row, normalizedField))
24521
+ ).map(({ displayField }) => displayField);
24522
+ }
24523
+
24524
+ // src/utils/cacheCab.ts
24525
+ init_tableProcessing();
24526
+
24527
+ // src/utils/cacheCabSource.ts
24528
+ var stableStringify2 = (value) => {
24529
+ if (value === null || typeof value !== "object") {
24530
+ return JSON.stringify(value);
24531
+ }
24532
+ if (Array.isArray(value)) {
24533
+ return `[${value.map(stableStringify2).join(",")}]`;
24534
+ }
24535
+ const object = value;
24536
+ return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify2(object[key])}`).join(",")}}`;
24537
+ };
24538
+ var createCacheCabSource = (reportBuilderState, config) => {
24539
+ const tableNames = [...new Set(config.referencedTables)].sort();
24540
+ const tables = reportBuilderState.tables;
24541
+ return {
24542
+ key: stableStringify2({
24543
+ version: 1,
24544
+ clientId: config.clientId,
24545
+ databaseType: config.databaseType,
24546
+ tenants: config.tenants ?? null,
24547
+ flags: config.flags ?? null,
24548
+ tables: tableNames,
24549
+ dateField: config.dateField
24550
+ }),
24551
+ dateField: config.dateField,
24552
+ state: {
24553
+ tables,
24554
+ columns: reportBuilderState.columns,
24555
+ filterStack: [],
24556
+ pivot: null,
24557
+ sort: [],
24558
+ limit: null
24559
+ }
24560
+ };
24561
+ };
24562
+
24563
+ // src/utils/cacheCab.ts
24171
24564
  var TZ = "America/Los_Angeles";
24565
+ var MAX_CACHECAB_ROWS = 1e5;
24172
24566
  var CacheCab = class {
24173
24567
  fetchedRange = {};
24174
24568
  cachedReportIds = [];
24175
24569
  uncacheableReportIDs = [];
24176
24570
  uncacheableInFlight = /* @__PURE__ */ new Map();
24571
+ reportMemoryCache = /* @__PURE__ */ new Map();
24572
+ reportLoadInFlight = /* @__PURE__ */ new Map();
24573
+ sourceAliases = /* @__PURE__ */ new Map();
24574
+ sourceFetchInFlight = /* @__PURE__ */ new Map();
24177
24575
  storage;
24576
+ metaReady;
24178
24577
  META_KEY = "cachecab:meta";
24179
24578
  DATA_PREFIX = "cachecab:data:";
24180
24579
  UNCACHEABLE_PREFIX = "cachecab:uncacheable:";
@@ -24182,13 +24581,38 @@ var CacheCab = class {
24182
24581
  storageType = "memory"
24183
24582
  } = {}) {
24184
24583
  this.storage = storageType !== "idb" ? new MemoryStorage() : new IdbStorage();
24185
- void this.loadMetaFromStorage();
24584
+ this.metaReady = this.loadMetaFromStorage();
24186
24585
  }
24187
- async get(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, pageSize, getToken, eventTracking, forceRefresh) {
24586
+ async get(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, pageSize, getToken, eventTracking, forceRefresh, sourceConfig) {
24587
+ await this.metaReady;
24188
24588
  if (this.isCached(reportId, tenants) && !forceRefresh) {
24189
- return this.getFromCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking);
24589
+ return this.getFromCache(
24590
+ reportId,
24591
+ dashboardFilters,
24592
+ customFilters,
24593
+ pivot,
24594
+ client,
24595
+ tenants,
24596
+ flags,
24597
+ getToken,
24598
+ eventTracking,
24599
+ sourceConfig
24600
+ );
24190
24601
  } else {
24191
- return this.addToCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking, true, forceRefresh);
24602
+ return this.addToCache(
24603
+ reportId,
24604
+ dashboardFilters,
24605
+ customFilters,
24606
+ pivot,
24607
+ client,
24608
+ tenants,
24609
+ flags,
24610
+ getToken,
24611
+ eventTracking,
24612
+ true,
24613
+ forceRefresh,
24614
+ sourceConfig
24615
+ );
24192
24616
  }
24193
24617
  }
24194
24618
  isCached(reportId, tenants) {
@@ -24197,6 +24621,47 @@ var CacheCab = class {
24197
24621
  isCacheable(reportId) {
24198
24622
  return !this.uncacheableReportIDs.includes(reportId);
24199
24623
  }
24624
+ /**
24625
+ * Returns CacheCab's raw report without fetching or applying dashboard state.
24626
+ * Consumers must only run report-builder operations locally when `complete`
24627
+ * is true; otherwise the cached rows are an unsafe sample of the report.
24628
+ */
24629
+ async getReportSnapshot(reportId, tenants) {
24630
+ await this.metaReady;
24631
+ const cacheKey = this.getCacheKey(reportId, tenants);
24632
+ if (!this.cachedReportIds.includes(cacheKey)) return null;
24633
+ const cachedReport = await this.readCachedReport(cacheKey);
24634
+ if (!cachedReport) return null;
24635
+ try {
24636
+ const report = { ...cachedReport, id: reportId };
24637
+ const rowCount = report.rows?.length ?? 0;
24638
+ const hasQueryLimit = Boolean(report.reportBuilderState?.limit) || reportUsesLimitClause(report);
24639
+ const hitRowLimit = rowCount >= MAX_CACHECAB_ROWS;
24640
+ const fetchedRange = this.fetchedRange[cacheKey];
24641
+ const coversAllTime = fetchedRange?.allTimeDoneBefore === true;
24642
+ const isSourceEntry = cacheKey.startsWith("source:");
24643
+ return {
24644
+ report,
24645
+ complete: (coversAllTime || isSourceEntry) && !hasQueryLimit && !hitRowLimit,
24646
+ reason: hasQueryLimit ? "query_limit" : hitRowLimit ? "row_limit" : !coversAllTime && !isSourceEntry ? "partial_date_range" : "complete",
24647
+ rowCount,
24648
+ fetchedRange
24649
+ };
24650
+ } catch {
24651
+ return null;
24652
+ }
24653
+ }
24654
+ async evictReport(reportId, tenants) {
24655
+ await this.metaReady;
24656
+ const cacheKey = this.getCacheKey(reportId, tenants);
24657
+ this.cachedReportIds = this.cachedReportIds.filter(
24658
+ (cachedKey) => cachedKey !== cacheKey
24659
+ );
24660
+ delete this.fetchedRange[cacheKey];
24661
+ this.reportMemoryCache.delete(cacheKey);
24662
+ await this.storage.removeItem(`${this.DATA_PREFIX}${cacheKey}`);
24663
+ await this.persistMetaToStorage();
24664
+ }
24200
24665
  async getUncacheableResult(reportId, client, tenants, flags, filters, additionalProcessing, pivot) {
24201
24666
  const key = this.getUncacheableRequestKey(
24202
24667
  reportId,
@@ -24242,7 +24707,10 @@ var CacheCab = class {
24242
24707
  if (!forceRefresh) {
24243
24708
  const raw = await this.storage.getItem(key);
24244
24709
  if (raw) {
24245
- return { report: JSON.parse(raw), fromCache: true };
24710
+ return {
24711
+ report: JSON.parse(raw),
24712
+ fromCache: true
24713
+ };
24246
24714
  }
24247
24715
  const inFlight = this.uncacheableInFlight.get(key);
24248
24716
  if (inFlight) {
@@ -24266,48 +24734,86 @@ var CacheCab = class {
24266
24734
  this.uncacheableInFlight.delete(key);
24267
24735
  }
24268
24736
  }
24269
- async addToCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking, firstTime = true, forceRefresh = false) {
24737
+ async addToCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking, firstTime = true, forceRefresh = false, sourceConfig) {
24270
24738
  const tenantPart = tenants ? JSON.stringify(tenants) : "";
24271
- const cacheKey = this.getCacheKey(reportId, tenants);
24739
+ let cacheKey = this.getCacheKey(reportId, tenants);
24272
24740
  if (firstTime) {
24273
- const result = await this.fetchInitialReport({ reportId, client, tenants, flags, filters: dashboardFilters, getToken, eventTracking, forceRefresh });
24741
+ const result = await this.fetchInitialReport({
24742
+ reportId,
24743
+ client,
24744
+ tenants,
24745
+ flags,
24746
+ filters: dashboardFilters,
24747
+ getToken,
24748
+ eventTracking,
24749
+ forceRefresh,
24750
+ sourceConfig
24751
+ });
24752
+ cacheKey = this.getCacheKey(reportId, tenants);
24274
24753
  try {
24275
- await this.storage.setItem(this.DATA_PREFIX + cacheKey, JSON.stringify(result));
24754
+ await this.writeCachedReport(cacheKey, result);
24276
24755
  this.cachedReportIds.push(cacheKey);
24277
24756
  await this.persistMetaToStorage();
24278
24757
  } catch {
24279
24758
  console.warn(`Failed to cache report: ${reportId}. Cache full?`);
24280
24759
  }
24281
- const newRows = await this.applyPivotsAndFilters(result, dashboardFilters, customFilters, pivot, tenantPart, false);
24760
+ const newRows = await this.applyPivotsAndFilters(
24761
+ result,
24762
+ dashboardFilters,
24763
+ customFilters,
24764
+ pivot,
24765
+ tenantPart,
24766
+ false
24767
+ );
24282
24768
  if (pivot) {
24283
24769
  const pivotColumns = generatePivotColumnsInMemory({
24284
24770
  pivot,
24285
24771
  pivotRows: newRows,
24286
24772
  sourceColumns: result.columnInternal ?? result.columns
24287
24773
  });
24288
- return { ...result, pivotRows: newRows, pivotColumns: pivotColumns ?? result.pivotColumns, pivotRowCount: newRows.length };
24774
+ return {
24775
+ ...result,
24776
+ pivotRows: newRows,
24777
+ pivotColumns: pivotColumns ?? result.pivotColumns,
24778
+ pivotRowCount: newRows.length
24779
+ };
24289
24780
  }
24290
24781
  return { ...result, rows: newRows, rowCount: newRows.length };
24291
24782
  } else {
24292
- const dbDateFilter = dashboardFilters.find((x) => x.filterType === "date_range");
24783
+ const dbDateFilter = dashboardFilters.find(
24784
+ (x) => x.filterType === "date_range"
24785
+ );
24293
24786
  const { start: requestedStart, end: requestedEnd } = normalizePSTRanges(
24294
24787
  dbDateFilter?.startDate ? new Date(dbDateFilter.startDate) : void 0,
24295
24788
  dbDateFilter?.endDate ? new Date(dbDateFilter.endDate) : void 0
24296
24789
  );
24297
24790
  if (!requestedStart || !requestedEnd) {
24298
- return this.addToCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking, true);
24791
+ return this.addToCache(
24792
+ reportId,
24793
+ dashboardFilters,
24794
+ customFilters,
24795
+ pivot,
24796
+ client,
24797
+ tenants,
24798
+ flags,
24799
+ getToken,
24800
+ eventTracking,
24801
+ true,
24802
+ false,
24803
+ sourceConfig
24804
+ );
24299
24805
  }
24300
24806
  const currentRange = this.fetchedRange[cacheKey];
24301
24807
  const rangeStart = currentRange?.start ?? requestedStart;
24302
24808
  const rangeEnd = currentRange?.end ?? endOfDayPST(/* @__PURE__ */ new Date());
24303
- const existingRaw = await this.storage.getItem(this.DATA_PREFIX + cacheKey);
24304
24809
  let existing;
24305
24810
  let fetchedRows = [];
24306
24811
  try {
24307
- existing = JSON.parse(existingRaw);
24308
- if (!existing.rows) {
24812
+ const cachedReport = await this.readCachedReport(cacheKey);
24813
+ if (!cachedReport?.rows) {
24309
24814
  throw new Error("Invalid Cache!");
24310
24815
  }
24816
+ existing = cachedReport;
24311
24817
  if (requestedStart < rangeStart) {
24312
24818
  const olderReport = await this.fetchReport({
24313
24819
  reportId,
@@ -24364,19 +24870,33 @@ var CacheCab = class {
24364
24870
  const filteredNew = filterNewRows(boundaryRows, fetchedRows);
24365
24871
  mergedRows = filteredNew.concat(existing.rows);
24366
24872
  } else {
24367
- mergedRows = filterNewRows(existing.rows, fetchedRows).concat(existing.rows);
24873
+ mergedRows = filterNewRows(existing.rows, fetchedRows).concat(
24874
+ existing.rows
24875
+ );
24368
24876
  }
24369
24877
  const merged = { ...existing, rows: mergedRows };
24370
- await this.storage.setItem(this.DATA_PREFIX + cacheKey, JSON.stringify(merged));
24878
+ await this.writeCachedReport(cacheKey, merged);
24371
24879
  await this.persistMetaToStorage();
24372
- const newRows = await this.applyPivotsAndFilters(merged, dashboardFilters, customFilters, pivot, tenantPart, false);
24880
+ const newRows = await this.applyPivotsAndFilters(
24881
+ merged,
24882
+ dashboardFilters,
24883
+ customFilters,
24884
+ pivot,
24885
+ tenantPart,
24886
+ false
24887
+ );
24373
24888
  if (pivot) {
24374
24889
  const pivotColumns = generatePivotColumnsInMemory({
24375
24890
  pivot,
24376
24891
  pivotRows: newRows,
24377
24892
  sourceColumns: merged.columnInternal ?? merged.columns
24378
24893
  });
24379
- return { ...merged, pivotRows: newRows, pivotColumns: pivotColumns ?? merged.pivotColumns, pivotRowCount: newRows.length };
24894
+ return {
24895
+ ...merged,
24896
+ pivotRows: newRows,
24897
+ pivotColumns: pivotColumns ?? merged.pivotColumns,
24898
+ pivotRowCount: newRows.length
24899
+ };
24380
24900
  }
24381
24901
  return { ...merged, rows: newRows, rowCount: newRows.length };
24382
24902
  }
@@ -24384,37 +24904,88 @@ var CacheCab = class {
24384
24904
  /**
24385
24905
  * Returns cache entry (or re-queries if stale)
24386
24906
  */
24387
- async getFromCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking) {
24388
- const dateRangeFilter = dashboardFilters.find((x) => x.filterType === "date_range");
24907
+ async getFromCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking, sourceConfig) {
24908
+ const dateRangeFilter = dashboardFilters.find(
24909
+ (x) => x.filterType === "date_range"
24910
+ );
24389
24911
  const cacheKey = this.getCacheKey(reportId, tenants);
24390
24912
  if (!dateRangeFilter || dateRangeFilter?.primaryRange.value === "ALL_TIME") {
24391
24913
  const fetchInfo = this.fetchedRange[cacheKey];
24392
24914
  if (!fetchInfo || fetchInfo?.allTimeDoneBefore === false || Date.now() - fetchInfo.fetchedAtUTCMS >= MS_IN_DAY) {
24393
- return this.addToCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking, true);
24915
+ return this.addToCache(
24916
+ reportId,
24917
+ dashboardFilters,
24918
+ customFilters,
24919
+ pivot,
24920
+ client,
24921
+ tenants,
24922
+ flags,
24923
+ getToken,
24924
+ eventTracking,
24925
+ true,
24926
+ false,
24927
+ sourceConfig
24928
+ );
24394
24929
  }
24395
24930
  } else if (this.weShouldReQuery(cacheKey, dateRangeFilter)) {
24396
- return this.addToCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking, false);
24931
+ return this.addToCache(
24932
+ reportId,
24933
+ dashboardFilters,
24934
+ customFilters,
24935
+ pivot,
24936
+ client,
24937
+ tenants,
24938
+ flags,
24939
+ getToken,
24940
+ eventTracking,
24941
+ false,
24942
+ false,
24943
+ sourceConfig
24944
+ );
24397
24945
  }
24398
24946
  try {
24399
- const raw = await this.storage.getItem(this.DATA_PREFIX + cacheKey);
24400
- if (!raw) {
24947
+ const parsed = await this.readCachedReport(cacheKey);
24948
+ if (!parsed) {
24401
24949
  throw new Error("Invalid Cache!");
24402
24950
  }
24403
- const parsed = JSON.parse(raw);
24404
24951
  const tenantPart = tenants ? JSON.stringify(tenants) : "";
24405
- const newRows = await this.applyPivotsAndFilters(parsed, dashboardFilters, customFilters, pivot, tenantPart);
24952
+ const newRows = await this.applyPivotsAndFilters(
24953
+ parsed,
24954
+ dashboardFilters,
24955
+ customFilters,
24956
+ pivot,
24957
+ tenantPart
24958
+ );
24406
24959
  if (pivot) {
24407
24960
  const pivotColumns = generatePivotColumnsInMemory({
24408
24961
  pivot,
24409
24962
  pivotRows: newRows,
24410
24963
  sourceColumns: parsed.columnInternal ?? parsed.columns
24411
24964
  });
24412
- return { ...parsed, pivotRows: newRows, pivotColumns: pivotColumns ?? parsed.pivotColumns, pivotRowCount: newRows.length };
24965
+ return {
24966
+ ...parsed,
24967
+ pivotRows: newRows,
24968
+ pivotColumns: pivotColumns ?? parsed.pivotColumns,
24969
+ pivotRowCount: newRows.length
24970
+ };
24413
24971
  }
24414
24972
  return { ...parsed, rows: newRows, rowCount: newRows.length };
24415
24973
  } catch (err) {
24416
24974
  console.error(err);
24417
- return this.addToCache(reportId, dashboardFilters, customFilters, pivot, client, tenants, flags, getToken, eventTracking, false);
24975
+ return this.addToCache(
24976
+ reportId,
24977
+ dashboardFilters,
24978
+ customFilters,
24979
+ pivot,
24980
+ client,
24981
+ tenants,
24982
+ flags,
24983
+ getToken,
24984
+ eventTracking,
24985
+ false,
24986
+ false,
24987
+ sourceConfig
24988
+ );
24418
24989
  }
24419
24990
  }
24420
24991
  weShouldReQuery(cacheKey, dateRangeFilter) {
@@ -24435,13 +25006,17 @@ var CacheCab = class {
24435
25006
  filters = [],
24436
25007
  getToken,
24437
25008
  eventTracking,
24438
- forceRefresh
25009
+ forceRefresh,
25010
+ sourceConfig
24439
25011
  }) {
24440
25012
  let start;
24441
25013
  let end;
24442
25014
  const dateRangeFilter = filters.find((x) => x.filterType === "date_range");
24443
25015
  const cacheRange = dateRangeFilter?.initialCacheDateRange;
24444
- if (cacheRange && cacheRange.startDate) {
25016
+ if (sourceConfig) {
25017
+ start = sourceConfig.dateRange.startDate;
25018
+ end = sourceConfig.dateRange.endDate;
25019
+ } else if (cacheRange && cacheRange.startDate) {
24445
25020
  const startDate = dateRangeFilter?.startDate;
24446
25021
  start = startDate ? minDate([new Date(cacheRange.startDate), new Date(startDate)]) : new Date(cacheRange.startDate);
24447
25022
  end = endOfDayPST(/* @__PURE__ */ new Date());
@@ -24462,7 +25037,8 @@ var CacheCab = class {
24462
25037
  eventTracking,
24463
25038
  start: normalized.start,
24464
25039
  end: normalized.end,
24465
- forceRefresh
25040
+ forceRefresh,
25041
+ sourceConfig
24466
25042
  });
24467
25043
  }
24468
25044
  async fetchReport({
@@ -24475,7 +25051,8 @@ var CacheCab = class {
24475
25051
  eventTracking,
24476
25052
  start,
24477
25053
  end,
24478
- forceRefresh
25054
+ forceRefresh,
25055
+ sourceConfig
24479
25056
  }) {
24480
25057
  let reportInfo = void 0;
24481
25058
  try {
@@ -24483,100 +25060,225 @@ var CacheCab = class {
24483
25060
  const adjusted = [...filters].map((x) => {
24484
25061
  return { ...x, options: void 0 };
24485
25062
  });
24486
- const dateFilterIndex = adjusted.findIndex((x) => x.filterType === "date_range");
24487
- const isAllTime = dateFilterIndex !== -1 && adjusted[dateFilterIndex].primaryRange?.value === "ALL_TIME";
25063
+ const dateFilterIndex = adjusted.findIndex(
25064
+ (x) => x.filterType === "date_range"
25065
+ );
25066
+ const isAllTime = sourceConfig ? adjusted[dateFilterIndex]?.primaryRange?.value === "ALL_TIME" : dateFilterIndex === -1 || adjusted[dateFilterIndex].primaryRange?.value === "ALL_TIME";
24488
25067
  if (!isAllTime) {
24489
- const dateFilter = adjusted[dateFilterIndex];
24490
- if (dateFilter && dateFilter.startDate && dateFilter.endDate) {
24491
- adjusted[dateFilterIndex] = { ...dateFilter, startDate: s, endDate: e };
25068
+ const dateFilter2 = adjusted[dateFilterIndex];
25069
+ if (dateFilter2 && dateFilter2.startDate && dateFilter2.endDate) {
25070
+ adjusted[dateFilterIndex] = {
25071
+ ...dateFilter2,
25072
+ startDate: s,
25073
+ endDate: e
25074
+ };
24492
25075
  }
24493
25076
  }
24494
- const fetchResp = await quillFetch({
25077
+ const stateResponse = await quillFetch({
24495
25078
  client,
24496
- task: "report",
25079
+ task: "report-builder-state",
24497
25080
  metadata: {
24498
25081
  reportId,
24499
- clientId: client.id,
24500
- databaseType: client.databaseType,
24501
- filters: adjusted,
24502
- additionalProcessing: { page: { rowsPerPage: 1e3, rowsPerRequest: 1e5 } },
24503
- useNewNodeSql: true,
24504
- tenants,
24505
- flags,
24506
- overwriteCache: forceRefresh ?? false
25082
+ tenants
24507
25083
  },
24508
25084
  getToken
24509
25085
  });
24510
- const resp = await parseFetchResponse(
24511
- client,
24512
- "report",
24513
- fetchResp,
24514
- getToken,
24515
- true
24516
- );
24517
- reportInfo = await processReportResponse({
24518
- resp,
24519
- client,
24520
- filters: adjusted,
24521
- dateBucket: resp?.dateBucket,
24522
- additionalProcessing: { page: { rowsPerPage: 1e3, rowsPerRequest: 1e5 } },
24523
- getToken,
24524
- eventTracking,
24525
- tenants,
24526
- // CacheCab fetches with task: 'report'; keep pivot processing local.
24527
- skipPivotFetch: true,
24528
- overwriteCache: forceRefresh ?? false
25086
+ if (stateResponse.error || stateResponse.status === "error") {
25087
+ throw new Error(
25088
+ stateResponse.error || "Could not load report builder state"
25089
+ );
25090
+ }
25091
+ const reportBuilderState = stateResponse.data?.reportBuilderState;
25092
+ if (!reportBuilderState) {
25093
+ throw new Error("Report builder state was not returned");
25094
+ }
25095
+ const source = sourceConfig ? createCacheCabSource(reportBuilderState, sourceConfig) : void 0;
25096
+ const sourceCacheKey = source ? this.bindReportToSource(reportId, tenants, source.key) : this.getCacheKey(reportId, tenants);
25097
+ const sourceDateFilter = source ? {
25098
+ filterType: "date_range",
25099
+ field: source.dateField.field,
25100
+ table: source.dateField.table,
25101
+ dateField: [source.dateField],
25102
+ label: "CacheCab date range",
25103
+ dashboardName: "",
25104
+ startDate: s,
25105
+ endDate: e,
25106
+ primaryRange: { label: "Custom", value: "CUSTOM" },
25107
+ preset: { label: "Custom", value: "CUSTOM" },
25108
+ presetOptions: [],
25109
+ defaultPresetRanges: [],
25110
+ options: void 0
25111
+ } : void 0;
25112
+ const filtersForSource = source ? isAllTime ? [] : [sourceDateFilter] : adjusted;
25113
+ const builderFilters = filtersForSource.map((filter) => {
25114
+ if ("table" in filter && filter.table) return filter;
25115
+ const matchingTables = [
25116
+ ...new Set(
25117
+ reportBuilderState.columns.filter((column) => column.field === filter.field && column.table).map((column) => column.table)
25118
+ )
25119
+ ];
25120
+ return matchingTables.length === 1 ? { ...filter, table: matchingTables[0] } : filter;
24529
25121
  });
25122
+ const sourceState = source?.state ?? {
25123
+ ...reportBuilderState,
25124
+ pivot: null
25125
+ };
25126
+ const fetchSource = async () => {
25127
+ const existingRange = this.fetchedRange[sourceCacheKey];
25128
+ const rangeCovered = existingRange && (existingRange.allTimeDoneBefore || existingRange.start <= s && existingRange.end >= e);
25129
+ if (source && rangeCovered && !forceRefresh) {
25130
+ const existing = await this.readCachedReport(sourceCacheKey);
25131
+ if (existing) return existing;
25132
+ }
25133
+ const tableData = await fetchTableByState(
25134
+ sourceState,
25135
+ client,
25136
+ getToken,
25137
+ tenants,
25138
+ eventTracking,
25139
+ "",
25140
+ {
25141
+ page: {
25142
+ page: 0,
25143
+ rowsPerPage: 1e3,
25144
+ rowsPerRequest: 1e5
25145
+ }
25146
+ },
25147
+ void 0,
25148
+ true,
25149
+ false,
25150
+ reportId,
25151
+ void 0,
25152
+ void 0,
25153
+ builderFilters
25154
+ );
25155
+ if (tableData.error) {
25156
+ throw new Error(tableData.error);
25157
+ }
25158
+ const referencedColumns = sourceState.columns.reduce((result, column) => {
25159
+ const table = column.table ?? "";
25160
+ if (!result[table]) result[table] = [];
25161
+ if (!result[table].includes(column.field)) {
25162
+ result[table].push(column.field);
25163
+ }
25164
+ return result;
25165
+ }, {});
25166
+ return {
25167
+ ...EMPTY_INTERNAL_REPORT,
25168
+ id: reportId,
25169
+ name: reportId,
25170
+ rows: tableData.rows,
25171
+ rowCount: tableData.rows.length,
25172
+ columns: tableData.columns.map((column) => ({
25173
+ field: column.field,
25174
+ format: column.format,
25175
+ label: column.label
25176
+ })),
25177
+ columnInternal: tableData.columns,
25178
+ referencedTables: sourceState.tables.map((table) => table.name),
25179
+ referencedColumns,
25180
+ reportBuilderState,
25181
+ pivot: reportBuilderState.pivot,
25182
+ dateField: source?.dateField
25183
+ };
25184
+ };
25185
+ const existingSourceRequest = source ? this.sourceFetchInFlight.get(sourceCacheKey) : void 0;
25186
+ if (existingSourceRequest && !forceRefresh) {
25187
+ return { ...await existingSourceRequest, id: reportId };
25188
+ }
25189
+ const sourceRequest = fetchSource();
25190
+ if (source) this.sourceFetchInFlight.set(sourceCacheKey, sourceRequest);
25191
+ try {
25192
+ reportInfo = await sourceRequest;
25193
+ } finally {
25194
+ if (source) this.sourceFetchInFlight.delete(sourceCacheKey);
25195
+ }
25196
+ const dateFilter = builderFilters[dateFilterIndex];
25197
+ const dateFilterTable = dateFilter && "table" in dateFilter ? dateFilter.table : void 0;
25198
+ reportInfo.dateField = source?.dateField ?? (dateFilter?.field && dateFilterTable ? { field: dateFilter.field, table: dateFilterTable } : void 0);
24530
25199
  const dateField = reportInfo.dateField?.field;
24531
25200
  if (!isAllTime && dateField !== void 0 && reportInfo.rows.length > 0) {
24532
25201
  const cleanedDateField = removeQuotes(dateField);
24533
- const missingDateFieldCount = reportInfo.rows.some((row) => row[cleanedDateField] === void 0);
25202
+ const missingDateFieldCount = reportInfo.rows.some(
25203
+ (row) => row[cleanedDateField] === void 0
25204
+ );
24534
25205
  if (missingDateFieldCount) {
24535
25206
  this.uncacheableReportIDs.push(reportId);
25207
+ if (source) {
25208
+ throw new Error(
25209
+ `CacheCab source is missing date field ${dateField}`
25210
+ );
25211
+ }
24536
25212
  return EMPTY_INTERNAL_REPORT;
24537
25213
  }
24538
25214
  }
24539
25215
  const requiredFilterFields = adjusted.flatMap((x, idx) => {
24540
- if (idx === dateFilterIndex || !x.values && !x.selectedValue) return [];
25216
+ if (idx === dateFilterIndex || !x.values && !x.selectedValue)
25217
+ return [];
24541
25218
  return removeQuotes(x.field);
24542
25219
  });
24543
- const missingFields = [...new Set(requiredFilterFields.filter(
24544
- (field) => reportInfo?.rows.some((row) => row[field] === void 0)
24545
- ))];
25220
+ const missingFields = [
25221
+ ...new Set(
25222
+ requiredFilterFields.filter(
25223
+ (field) => reportInfo?.rows.some((row) => row[field] === void 0)
25224
+ )
25225
+ )
25226
+ ];
24546
25227
  if (missingFields.length > 0) {
24547
25228
  this.uncacheableReportIDs.push(reportId);
25229
+ if (source) {
25230
+ throw new Error(
25231
+ `CacheCab source is missing required fields: ${missingFields.join(", ")}`
25232
+ );
25233
+ }
24548
25234
  return EMPTY_INTERNAL_REPORT;
24549
25235
  }
24550
25236
  const cacheKey = this.getCacheKey(reportId, tenants);
24551
25237
  if (isAllTime) {
24552
- this.fetchedRange[cacheKey] = { start: /* @__PURE__ */ new Date(0), end: /* @__PURE__ */ new Date(), fetchedAtUTCMS: Date.now(), allTimeDoneBefore: true };
25238
+ this.fetchedRange[cacheKey] = {
25239
+ start: /* @__PURE__ */ new Date(0),
25240
+ end: /* @__PURE__ */ new Date(),
25241
+ fetchedAtUTCMS: Date.now(),
25242
+ allTimeDoneBefore: true
25243
+ };
24553
25244
  } else {
24554
25245
  const existing = this.fetchedRange[cacheKey];
24555
25246
  if (existing && !forceRefresh) {
24556
25247
  const oldStart = existing.start;
24557
25248
  const oldEnd = existing.end;
24558
- this.fetchedRange[cacheKey] = { start: getMinDate(oldStart, s), end: getMaxDate(oldEnd, e), fetchedAtUTCMS: Date.now(), allTimeDoneBefore: false };
25249
+ this.fetchedRange[cacheKey] = {
25250
+ start: getMinDate(oldStart, s),
25251
+ end: getMaxDate(oldEnd, e),
25252
+ fetchedAtUTCMS: Date.now(),
25253
+ allTimeDoneBefore: false
25254
+ };
24559
25255
  } else {
24560
- this.fetchedRange[cacheKey] = { start: s, end: e, fetchedAtUTCMS: Date.now(), allTimeDoneBefore: false };
25256
+ this.fetchedRange[cacheKey] = {
25257
+ start: s,
25258
+ end: e,
25259
+ fetchedAtUTCMS: Date.now(),
25260
+ allTimeDoneBefore: false
25261
+ };
24561
25262
  }
24562
25263
  }
24563
25264
  } catch (error) {
24564
25265
  console.warn(error);
24565
- if (error instanceof Error && error.name === "AbortError") {
25266
+ if (sourceConfig || error instanceof Error && error.name === "AbortError") {
24566
25267
  throw error;
24567
25268
  }
24568
25269
  }
24569
25270
  return reportInfo || EMPTY_INTERNAL_REPORT;
24570
25271
  }
24571
25272
  async applyPivotsAndFilters(report, dashboardFilters, customFilters, pivot, tenantPart, useCache = true) {
24572
- const datasetVersion = this.fetchedRange[report.id + tenantPart]?.fetchedAtUTCMS ?? 0;
25273
+ const reportCacheKey = this.sourceAliases.get(report.id + tenantPart) ?? report.id + tenantPart;
25274
+ const datasetVersion = this.fetchedRange[reportCacheKey]?.fetchedAtUTCMS ?? 0;
24573
25275
  const keyParts = [
24574
25276
  report.queryString,
24575
25277
  tenantPart,
24576
25278
  datasetVersion,
24577
25279
  // prevents fetching stale filters on hard refresh
24578
- hashString(stableStringify(dashboardFilters)),
24579
- hashString(stableStringify(customFilters))
25280
+ hashString(stableStringify3(dashboardFilters)),
25281
+ hashString(stableStringify3(customFilters))
24580
25282
  ];
24581
25283
  const queryKey = this.DATA_PREFIX + keyParts.join("|");
24582
25284
  let filtersApplied;
@@ -24590,19 +25292,30 @@ var CacheCab = class {
24590
25292
  }
24591
25293
  }
24592
25294
  if (!filtersApplied) {
24593
- const dateIndex = dashboardFilters.findIndex((x) => x.filterType === "date_range");
24594
- const requiredFilterFields = dashboardFilters.flatMap((x, idx) => {
24595
- if (idx === dateIndex || !x.values?.length && !x.selectedValue) return [];
24596
- return removeQuotes(x.field);
24597
- });
24598
- const missingFields = [...new Set(requiredFilterFields.filter(
24599
- (field) => report.rows.some((row) => row[field] === void 0)
24600
- ))];
25295
+ const dateIndex = dashboardFilters.findIndex(
25296
+ (x) => x.filterType === "date_range"
25297
+ );
25298
+ const requiredFilterFields = dashboardFilters.flatMap(
25299
+ (x, idx) => {
25300
+ if (idx === dateIndex || !x.values?.length && !x.selectedValue)
25301
+ return [];
25302
+ return removeQuotes(x.field);
25303
+ }
25304
+ );
25305
+ const missingFields = [
25306
+ ...new Set(
25307
+ requiredFilterFields.filter(
25308
+ (field) => report.rows.some((row) => row[field] === void 0)
25309
+ )
25310
+ )
25311
+ ];
24601
25312
  if (missingFields.length > 0) {
24602
25313
  this.uncacheableReportIDs.push(report.id);
24603
25314
  return [];
24604
25315
  }
24605
- const dbDateFilter = dashboardFilters.find((x) => x.filterType === "date_range");
25316
+ const dbDateFilter = dashboardFilters.find(
25317
+ (x) => x.filterType === "date_range"
25318
+ );
24606
25319
  if (report.dateField && dbDateFilter?.startDate) {
24607
25320
  const { start: startDate, end: endDate } = normalizePSTRanges(
24608
25321
  new Date(dbDateFilter.startDate),
@@ -24613,22 +25326,82 @@ var CacheCab = class {
24613
25326
  const rowDate = new Date(x[fieldToUse]);
24614
25327
  return startDate <= rowDate && (endDate ? rowDate <= endDate : true);
24615
25328
  });
24616
- filtersApplied = applyFiltersInMemory(dateFilteredRows, [], { dashboardFilters, customFilters });
25329
+ filtersApplied = applyFiltersInMemory(dateFilteredRows, [], {
25330
+ dashboardFilters,
25331
+ customFilters
25332
+ });
24617
25333
  } else {
24618
- filtersApplied = applyFiltersInMemory(report.rows, [], { dashboardFilters, customFilters });
25334
+ filtersApplied = applyFiltersInMemory(report.rows, [], {
25335
+ dashboardFilters,
25336
+ customFilters
25337
+ });
24619
25338
  }
24620
25339
  }
24621
25340
  try {
24622
25341
  await this.storage.setItem(queryKey, JSON.stringify(filtersApplied));
24623
25342
  } catch {
24624
25343
  }
24625
- const withPivot = applyPivotInMemory(filtersApplied, pivot, dashboardFilters.concat(customFilters));
25344
+ const withPivot = applyPivotInMemory(
25345
+ filtersApplied,
25346
+ pivot,
25347
+ dashboardFilters.concat(customFilters)
25348
+ );
24626
25349
  return withPivot;
24627
25350
  }
25351
+ /**
25352
+ * Reads and parses a persisted report at most once per CacheCab instance.
25353
+ * Concurrent callers share the same storage read and JSON.parse operation.
25354
+ */
25355
+ async readCachedReport(cacheKey) {
25356
+ const cached = this.reportMemoryCache.get(cacheKey);
25357
+ if (cached) return cached;
25358
+ const existingLoad = this.reportLoadInFlight.get(cacheKey);
25359
+ if (existingLoad) return existingLoad;
25360
+ const load = (async () => {
25361
+ const raw = await this.storage.getItem(`${this.DATA_PREFIX}${cacheKey}`);
25362
+ if (!raw) return null;
25363
+ try {
25364
+ const report = JSON.parse(raw);
25365
+ this.reportMemoryCache.set(cacheKey, report);
25366
+ return report;
25367
+ } catch {
25368
+ return null;
25369
+ }
25370
+ })();
25371
+ this.reportLoadInFlight.set(cacheKey, load);
25372
+ try {
25373
+ return await load;
25374
+ } finally {
25375
+ this.reportLoadInFlight.delete(cacheKey);
25376
+ }
25377
+ }
25378
+ /**
25379
+ * Persists a report and updates the parsed in-memory copy atomically from
25380
+ * CacheCab's perspective.
25381
+ */
25382
+ async writeCachedReport(cacheKey, report) {
25383
+ await this.storage.setItem(
25384
+ `${this.DATA_PREFIX}${cacheKey}`,
25385
+ JSON.stringify(report)
25386
+ );
25387
+ this.reportMemoryCache.set(cacheKey, report);
25388
+ }
24628
25389
  getCacheKey(reportId, tenants) {
24629
- const tenantPart = tenants ? JSON.stringify(tenants) : "";
25390
+ const aliasKey = this.getReportAliasKey(reportId, tenants);
25391
+ return this.sourceAliases.get(aliasKey) ?? aliasKey;
25392
+ }
25393
+ getReportAliasKey(reportId, tenants) {
25394
+ const tenantPart = tenants ? stableStringify3(tenants) : "";
24630
25395
  return reportId + tenantPart;
24631
25396
  }
25397
+ bindReportToSource(reportId, tenants, sourceKey) {
25398
+ const cacheKey = `source:${hashString(sourceKey)}`;
25399
+ this.sourceAliases.set(
25400
+ this.getReportAliasKey(reportId, tenants),
25401
+ cacheKey
25402
+ );
25403
+ return cacheKey;
25404
+ }
24632
25405
  getUncacheableRequestKey(reportId, client, tenants, flags, filters, additionalProcessing, pivot) {
24633
25406
  const canonicalFilters = filters.map(
24634
25407
  (f) => canonicalizeFilterForUncacheableKey(f)
@@ -24637,11 +25410,13 @@ var CacheCab = class {
24637
25410
  reportId,
24638
25411
  client.id,
24639
25412
  client.databaseType,
24640
- hashString(stableStringify(canonicalizeForKey(tenants ?? null))),
24641
- hashString(stableStringify(canonicalizeForKey(flags ?? null))),
24642
- hashString(stableStringify(canonicalFilters)),
24643
- hashString(stableStringify(canonicalizeForKey(additionalProcessing ?? null))),
24644
- hashString(stableStringify(canonicalizeForKey(pivot ?? null)))
25413
+ hashString(stableStringify3(canonicalizeForKey(tenants ?? null))),
25414
+ hashString(stableStringify3(canonicalizeForKey(flags ?? null))),
25415
+ hashString(stableStringify3(canonicalFilters)),
25416
+ hashString(
25417
+ stableStringify3(canonicalizeForKey(additionalProcessing ?? null))
25418
+ ),
25419
+ hashString(stableStringify3(canonicalizeForKey(pivot ?? null)))
24645
25420
  ];
24646
25421
  return this.UNCACHEABLE_PREFIX + keyParts.join("|");
24647
25422
  }
@@ -24652,6 +25427,9 @@ var CacheCab = class {
24652
25427
  const parsed = JSON.parse(meta);
24653
25428
  this.cachedReportIds = parsed.cached || [];
24654
25429
  this.uncacheableReportIDs = parsed.cannotBeCached;
25430
+ this.sourceAliases = new Map(
25431
+ Object.entries(parsed.sourceAliases ?? {})
25432
+ );
24655
25433
  this.fetchedRange = Object.fromEntries(
24656
25434
  Object.entries(parsed.fetchedRange || {}).map(([id, range]) => [
24657
25435
  id,
@@ -24674,7 +25452,8 @@ var CacheCab = class {
24674
25452
  JSON.stringify({
24675
25453
  fetchedRange: serializableRange,
24676
25454
  cached: this.cachedReportIds,
24677
- cannotBeCached: this.uncacheableReportIDs
25455
+ cannotBeCached: this.uncacheableReportIDs,
25456
+ sourceAliases: Object.fromEntries(this.sourceAliases)
24678
25457
  })
24679
25458
  );
24680
25459
  }
@@ -24687,6 +25466,9 @@ var MemoryStorage = class {
24687
25466
  async setItem(key, value) {
24688
25467
  this.store.set(key, value);
24689
25468
  }
25469
+ async removeItem(key) {
25470
+ this.store.delete(key);
25471
+ }
24690
25472
  };
24691
25473
  var DB_NAME = "quill.cachecab";
24692
25474
  var STORE_NAME = "kv";
@@ -24709,6 +25491,10 @@ var IdbStorage = class {
24709
25491
  const db = await this.dbPromise;
24710
25492
  await db.put(STORE_NAME, value, key);
24711
25493
  }
25494
+ async removeItem(key) {
25495
+ const db = await this.dbPromise;
25496
+ await db.delete(STORE_NAME, key);
25497
+ }
24712
25498
  };
24713
25499
  function removeQuotes(str) {
24714
25500
  return str.replace(/^['"]|['"]$/g, "");
@@ -24732,7 +25518,7 @@ function hashString(str) {
24732
25518
  }
24733
25519
  function filterNewRows(existingRows, newRows) {
24734
25520
  const seen = /* @__PURE__ */ new Set();
24735
- const getKey = (row) => stableStringify(row);
25521
+ const getKey = (row) => stableStringify3(row);
24736
25522
  existingRows.forEach((row) => seen.add(getKey(row)));
24737
25523
  return newRows.filter((row) => {
24738
25524
  const k = getKey(row);
@@ -24741,13 +25527,13 @@ function filterNewRows(existingRows, newRows) {
24741
25527
  return true;
24742
25528
  });
24743
25529
  }
24744
- function stableStringify(obj) {
25530
+ function stableStringify3(obj) {
24745
25531
  if (obj === null || typeof obj !== "object") return JSON.stringify(obj);
24746
25532
  if (Array.isArray(obj)) {
24747
- return `[${obj.map(stableStringify).join(",")}]`;
25533
+ return `[${obj.map(stableStringify3).join(",")}]`;
24748
25534
  }
24749
25535
  const keys = Object.keys(obj).sort();
24750
- return `{${keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",")}}`;
25536
+ return `{${keys.map((k) => JSON.stringify(k) + ":" + stableStringify3(obj[k])).join(",")}}`;
24751
25537
  }
24752
25538
  function canonicalizeForKey(value) {
24753
25539
  if (value instanceof Date) {
@@ -26903,6 +27689,7 @@ import jsPDF from "jspdf";
26903
27689
 
26904
27690
  // src/hooks/useDashboard.ts
26905
27691
  import { useContext, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
27692
+ import { useQueryClient } from "@tanstack/react-query";
26906
27693
  init_dateRangePickerUtils();
26907
27694
  init_Filter();
26908
27695
  init_filterProcessing();
@@ -26946,10 +27733,11 @@ function isDashboardPivotTable(report) {
26946
27733
  return String(report?.chartType ?? "").toLowerCase() === "table" && Boolean(report?.pivot);
26947
27734
  }
26948
27735
  function getDashboardReportProcessing(report, serverPagination) {
26949
- return isDashboardPivotTable(report) ? {} : serverPagination;
27736
+ return report.pivot ? {} : serverPagination;
26950
27737
  }
26951
27738
 
26952
27739
  // src/hooks/useDashboard.ts
27740
+ var DEFAULT_DASHBOARD_REPORT_STALE_TIME_MS = 5 * 60 * 1e3;
26953
27741
  var useDashboardInternal = (dashboardName, customFilters) => {
26954
27742
  const [dashboard] = useContext(DashboardContext);
26955
27743
  const {
@@ -27644,12 +28432,15 @@ var useDashboard = (dashboardName, config) => {
27644
28432
  const fetchedInitialReports = useRef2(false);
27645
28433
  const backfilledDashboards = useRef2(false);
27646
28434
  const [client] = useContext(ClientContext);
28435
+ const queryClient = useQueryClient();
28436
+ const reportStaleTimeMs = config?.reportStaleTimeMs ?? DEFAULT_DASHBOARD_REPORT_STALE_TIME_MS;
27647
28437
  const { tenants, flags } = useContext(TenantContext);
27648
28438
  const { getToken } = useContext(FetchContext);
27649
28439
  const { customReportFilters, customFiltersLoaded, setCustomFiltersLoaded } = useContext(ReportFiltersContext);
27650
28440
  const { eventTracking } = useContext(EventTrackingContext);
27651
28441
  const { getCacheCab } = useContext(CacheCabContext);
27652
28442
  const reportRequestIds = useRef2({});
28443
+ const reportFetchAbortController = useRef2(null);
27653
28444
  const lastDashboardName = useRef2(null);
27654
28445
  const pendingNameChangeReload = useRef2(false);
27655
28446
  const [loadedDashes, setLoadedDashes] = useState2([]);
@@ -27659,6 +28450,12 @@ var useDashboard = (dashboardName, config) => {
27659
28450
  () => getCacheCab(config?.cacheType),
27660
28451
  [getCacheCab, config?.cacheType]
27661
28452
  );
28453
+ useEffect2(() => {
28454
+ return () => {
28455
+ reportFetchAbortController.current?.abort();
28456
+ reportFetchAbortController.current = null;
28457
+ };
28458
+ }, [dashboardName]);
27662
28459
  useEffect2(() => {
27663
28460
  const nameChanged = dashboardName !== lastDashboardName.current;
27664
28461
  if (nameChanged) {
@@ -27846,8 +28643,52 @@ var useDashboard = (dashboardName, config) => {
27846
28643
  if (!cacheEnabled) return;
27847
28644
  fetchReports([], dashboardFilters ?? [], config?.pageSize, true);
27848
28645
  };
28646
+ const primeReportSource = async (reportId) => {
28647
+ const reportInfo = allReportsById[reportId];
28648
+ if (!reportInfo || !client) {
28649
+ throw new Error(`Could not prime CacheCab for report ${reportId}`);
28650
+ }
28651
+ const dashboardDateFilter = dashboardFilters?.find(
28652
+ (filter) => filter.filterType === "date_range" /* Date */ && filter.startDate && filter.endDate
28653
+ );
28654
+ const dateRange = config?.cacheDateRange ?? (dashboardDateFilter?.startDate && dashboardDateFilter.endDate ? {
28655
+ startDate: new Date(dashboardDateFilter.startDate),
28656
+ endDate: new Date(dashboardDateFilter.endDate)
28657
+ } : void 0);
28658
+ if (!dateRange) {
28659
+ throw new Error(
28660
+ `CacheCab source for report ${reportId} requires a date range`
28661
+ );
28662
+ }
28663
+ await getCacheCab("memory").get(
28664
+ reportId,
28665
+ dashboardFilters ?? [],
28666
+ customReportFilters[reportId] ?? [],
28667
+ null,
28668
+ client,
28669
+ tenants,
28670
+ flags,
28671
+ void 0,
28672
+ getToken,
28673
+ eventTracking,
28674
+ false,
28675
+ {
28676
+ referencedTables: reportInfo.referencedTables ?? [],
28677
+ dateField: reportInfo.dateField,
28678
+ clientId: client.id ?? client.clientId ?? "",
28679
+ databaseType: client.databaseType,
28680
+ tenants,
28681
+ flags,
28682
+ dateRange
28683
+ }
28684
+ );
28685
+ };
27849
28686
  const fetchReports = async (customFilters, dashboardFilters2, pageSize, forceCacheToRefresh = false) => {
27850
28687
  if (!client || !sections) return;
28688
+ reportFetchAbortController.current?.abort();
28689
+ const abortController = new AbortController();
28690
+ reportFetchAbortController.current = abortController;
28691
+ const abortSignal = abortController.signal;
27851
28692
  const allReports = Object.values(sections).flat();
27852
28693
  const fetchStartTime = Date.now();
27853
28694
  let totalCached = 0;
@@ -27916,6 +28757,12 @@ var useDashboard = (dashboardName, config) => {
27916
28757
  eventTracking,
27917
28758
  forceCacheToRefresh
27918
28759
  );
28760
+ const cachedReport = {
28761
+ ...reportInfo,
28762
+ ...report2,
28763
+ id: reportId,
28764
+ pivot: reportInfo.pivot ?? report2.pivot
28765
+ };
27919
28766
  if (reportRequestIds.current[reportId] !== requestId) {
27920
28767
  return null;
27921
28768
  }
@@ -27924,7 +28771,7 @@ var useDashboard = (dashboardName, config) => {
27924
28771
  type: "UPDATE_REPORT",
27925
28772
  id: reportId,
27926
28773
  data: {
27927
- ...report2,
28774
+ ...cachedReport,
27928
28775
  pagination,
27929
28776
  triggerReload: false
27930
28777
  }
@@ -27935,7 +28782,7 @@ var useDashboard = (dashboardName, config) => {
27935
28782
  data: false
27936
28783
  });
27937
28784
  totalCached += 1;
27938
- return report2;
28785
+ return cachedReport;
27939
28786
  }
27940
28787
  }
27941
28788
  if (cacheEnabled && !cacheCab.isCacheable(reportId)) {
@@ -27958,6 +28805,7 @@ var useDashboard = (dashboardName, config) => {
27958
28805
  filters: allFilters,
27959
28806
  getToken,
27960
28807
  eventTracking,
28808
+ abortSignal,
27961
28809
  usePivotTask,
27962
28810
  overwriteCache: forceCacheToRefresh
27963
28811
  });
@@ -27997,6 +28845,7 @@ var useDashboard = (dashboardName, config) => {
27997
28845
  tenants,
27998
28846
  filters: allFilters,
27999
28847
  getToken,
28848
+ abortSignal,
28000
28849
  additionalProcessing,
28001
28850
  overwriteCache: forceCacheToRefresh
28002
28851
  }).then(({ rows, rowCount, columns, fields }) => {
@@ -28049,7 +28898,32 @@ var useDashboard = (dashboardName, config) => {
28049
28898
  return null;
28050
28899
  }
28051
28900
  }
28052
- const { report, error } = await fetchReport({
28901
+ const requestFilters = allFilters.map(
28902
+ (filter) => Object.fromEntries(
28903
+ Object.entries(filter).filter(([key]) => key !== "options")
28904
+ )
28905
+ );
28906
+ const queryKey = [
28907
+ "quill",
28908
+ "dashboard-report",
28909
+ reportId,
28910
+ {
28911
+ dashboardName,
28912
+ task: usePivotTask ? "pivot-template" : "report",
28913
+ client: {
28914
+ id: client.id ?? client.clientId,
28915
+ databaseType: client.databaseType,
28916
+ queryEndpoint: client.queryEndpoint,
28917
+ queryHeaders: client.queryHeaders,
28918
+ withCredentials: client.withCredentials
28919
+ },
28920
+ tenants,
28921
+ flags,
28922
+ filters: requestFilters,
28923
+ additionalProcessing: reportAdditionalProcessing
28924
+ }
28925
+ ];
28926
+ const fetchDashboardReport = () => fetchReport({
28053
28927
  reportId,
28054
28928
  client,
28055
28929
  tenants,
@@ -28058,10 +28932,23 @@ var useDashboard = (dashboardName, config) => {
28058
28932
  filters: allFilters,
28059
28933
  getToken,
28060
28934
  eventTracking,
28935
+ abortSignal,
28061
28936
  usePivotTask,
28062
- overwriteCache: forceCacheToRefresh
28063
- // usePivotTask: false,
28937
+ overwriteCache: forceCacheToRefresh,
28938
+ shareRequest: !forceCacheToRefresh
28064
28939
  });
28940
+ const result = forceCacheToRefresh ? await fetchDashboardReport() : await queryClient.fetchQuery({
28941
+ queryKey,
28942
+ queryFn: fetchDashboardReport,
28943
+ staleTime: reportStaleTimeMs,
28944
+ gcTime: Math.max(reportStaleTimeMs * 2, 10 * 60 * 1e3)
28945
+ });
28946
+ if (result.error) {
28947
+ queryClient.removeQueries({ queryKey, exact: true });
28948
+ } else if (forceCacheToRefresh) {
28949
+ queryClient.setQueryData(queryKey, result);
28950
+ }
28951
+ const { report, error } = result;
28065
28952
  if (reportRequestIds.current[reportId] !== requestId) {
28066
28953
  return null;
28067
28954
  }
@@ -28093,6 +28980,7 @@ var useDashboard = (dashboardName, config) => {
28093
28980
  tenants,
28094
28981
  filters: allFilters,
28095
28982
  getToken,
28983
+ abortSignal,
28096
28984
  additionalProcessing,
28097
28985
  overwriteCache: forceCacheToRefresh
28098
28986
  }).then(({ rows, rowCount, columns, fields }) => {
@@ -28126,7 +29014,11 @@ var useDashboard = (dashboardName, config) => {
28126
29014
  }
28127
29015
  return report;
28128
29016
  })
28129
- );
29017
+ ).catch((error) => {
29018
+ if (!(error instanceof Error) || error.name !== "AbortError") {
29019
+ throw error;
29020
+ }
29021
+ });
28130
29022
  if (!loadedDashes.includes(dashboardName) || !cacheEnabled || forceCacheToRefresh) {
28131
29023
  setLoadedDashes(
28132
29024
  (prev) => prev.includes(dashboardName) ? prev : [...prev, dashboardName]
@@ -28147,6 +29039,7 @@ var useDashboard = (dashboardName, config) => {
28147
29039
  applyFilters,
28148
29040
  lastUpdated,
28149
29041
  forceCacheRefresh,
29042
+ primeReportSource,
28150
29043
  reload
28151
29044
  };
28152
29045
  };
@@ -36840,164 +37733,6 @@ function DashboardFilter2({
36840
37733
  // src/Chart.tsx
36841
37734
  init_paginationProcessing();
36842
37735
 
36843
- // src/utils/cloudCacheValidation.ts
36844
- var LIMIT_CLAUSE_REGEX = /^limit\b\s+(?:all|\d+|\$\d+|:[a-zA-Z_][a-zA-Z0-9_]*|\?)/i;
36845
- var SQL_CONTENT_TO_IGNORE_REGEX = /'(?:''|[^'])*'|"(?:[^"]|"")*"|`[^`]*`|\[[^\]]*\]|--[^\n]*|\/\*[\s\S]*?\*\//g;
36846
- function isWordChar(char) {
36847
- if (!char) {
36848
- return false;
36849
- }
36850
- return /[A-Za-z0-9_]/.test(char);
36851
- }
36852
- function getFinalSqlStatement(query) {
36853
- const statements = query.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
36854
- return statements[statements.length - 1] ?? "";
36855
- }
36856
- function hasTopLevelLimitClause(statement) {
36857
- let depth = 0;
36858
- for (let i = 0; i < statement.length; i += 1) {
36859
- const char = statement[i];
36860
- if (char === "(") {
36861
- depth += 1;
36862
- continue;
36863
- }
36864
- if (char === ")") {
36865
- depth = Math.max(0, depth - 1);
36866
- continue;
36867
- }
36868
- if (depth !== 0) {
36869
- continue;
36870
- }
36871
- const previousChar = i > 0 ? statement[i - 1] : void 0;
36872
- if (isWordChar(previousChar)) {
36873
- continue;
36874
- }
36875
- if (!LIMIT_CLAUSE_REGEX.test(statement.slice(i))) {
36876
- continue;
36877
- }
36878
- return true;
36879
- }
36880
- return false;
36881
- }
36882
- function unwrapIdentifier(identifier) {
36883
- if (!identifier) {
36884
- return "";
36885
- }
36886
- const trimmed = identifier.trim();
36887
- if (trimmed.length < 2) {
36888
- return trimmed;
36889
- }
36890
- const startsAndEndsWithDoubleQuote = trimmed.startsWith('"') && trimmed.endsWith('"');
36891
- const startsAndEndsWithSingleQuote = trimmed.startsWith("'") && trimmed.endsWith("'");
36892
- const startsAndEndsWithBackticks = trimmed.startsWith("`") && trimmed.endsWith("`");
36893
- const startsAndEndsWithBrackets = trimmed.startsWith("[") && trimmed.endsWith("]");
36894
- if (startsAndEndsWithDoubleQuote || startsAndEndsWithSingleQuote || startsAndEndsWithBackticks || startsAndEndsWithBrackets) {
36895
- return trimmed.slice(1, -1);
36896
- }
36897
- return trimmed;
36898
- }
36899
- function normalizeIdentifier(identifier) {
36900
- return unwrapIdentifier(identifier).toLowerCase();
36901
- }
36902
- function hasRowFieldWithIdentifier(row, normalizedField) {
36903
- if (!row || !normalizedField) {
36904
- return false;
36905
- }
36906
- return Object.keys(row).some(
36907
- (fieldName) => normalizeIdentifier(fieldName) === normalizedField
36908
- );
36909
- }
36910
- function reportReferencesField(report, field, table) {
36911
- const normalizedField = normalizeIdentifier(field);
36912
- if (!normalizedField) {
36913
- return false;
36914
- }
36915
- const referencedColumns = report?.referencedColumns ?? {};
36916
- let entries = Object.entries(referencedColumns);
36917
- if (entries.length === 0) {
36918
- return false;
36919
- }
36920
- const normalizedTable = normalizeIdentifier(table);
36921
- if (normalizedTable) {
36922
- entries = entries.filter(
36923
- ([tableName]) => normalizeIdentifier(tableName) === normalizedTable
36924
- );
36925
- }
36926
- const referencedFields = entries.flatMap(([, fields]) => fields ?? []);
36927
- if (referencedFields.length === 0) {
36928
- return false;
36929
- }
36930
- return referencedFields.some((referencedField) => {
36931
- const normalizedReferencedField = normalizeIdentifier(referencedField);
36932
- return normalizedReferencedField === normalizedField || normalizedReferencedField === "*";
36933
- });
36934
- }
36935
- function isDateFieldMissingInReport(report) {
36936
- if (!report?.dateField?.field || !report.dateField.table) {
36937
- return false;
36938
- }
36939
- const dateField = report.dateField;
36940
- if (!reportReferencesField(report, dateField.field, dateField.table)) {
36941
- return true;
36942
- }
36943
- const firstRow = report.rows?.[0];
36944
- if (!firstRow) {
36945
- return false;
36946
- }
36947
- const normalizedDateField = normalizeIdentifier(dateField.field);
36948
- const dateFieldExistsInFirstRow = hasRowFieldWithIdentifier(
36949
- firstRow,
36950
- normalizedDateField
36951
- );
36952
- return !dateFieldExistsInFirstRow;
36953
- }
36954
- function hasLimitClause(query) {
36955
- if (!query) {
36956
- return false;
36957
- }
36958
- const sanitizedQuery = query.replace(SQL_CONTENT_TO_IGNORE_REGEX, " ");
36959
- const finalStatement = getFinalSqlStatement(sanitizedQuery);
36960
- if (!finalStatement) {
36961
- return false;
36962
- }
36963
- return hasTopLevelLimitClause(finalStatement);
36964
- }
36965
- function reportUsesLimitClause(report) {
36966
- const queriesToInspect = report?.itemQuery && report.itemQuery.length > 0 ? report.itemQuery : report?.queryString ? [report.queryString] : [];
36967
- return queriesToInspect.some(hasLimitClause);
36968
- }
36969
- function getMissingDashboardFilterFields({
36970
- rows = [],
36971
- dashboardFilters = []
36972
- }) {
36973
- const dateIndex = dashboardFilters.findIndex(
36974
- (filter) => filter.filterType === "date_range"
36975
- );
36976
- const requiredFields = dashboardFilters.flatMap((filter, index) => {
36977
- if (index === dateIndex || !filter.field) {
36978
- return [];
36979
- }
36980
- const normalizedField = normalizeIdentifier(filter.field);
36981
- if (!normalizedField) {
36982
- return [];
36983
- }
36984
- return [
36985
- {
36986
- displayField: unwrapIdentifier(filter.field),
36987
- normalizedField
36988
- }
36989
- ];
36990
- });
36991
- const dedupedRequiredFields = requiredFields.filter(
36992
- (field, index, fields) => fields.findIndex(
36993
- (candidate) => candidate.normalizedField === field.normalizedField
36994
- ) === index
36995
- );
36996
- return dedupedRequiredFields.filter(
36997
- ({ normalizedField }) => rows.some((row) => !hasRowFieldWithIdentifier(row, normalizedField))
36998
- ).map(({ displayField }) => displayField);
36999
- }
37000
-
37001
37736
  // src/components/Dashboard/MetricComponent.tsx
37002
37737
  init_dateRangePickerUtils();
37003
37738
  import { useContext as useContext12 } from "react";
@@ -43707,7 +44442,7 @@ function StaticChart(props) {
43707
44442
  pageLoading,
43708
44443
  nextPage,
43709
44444
  prevPage,
43710
- sortRows: sortRows2
44445
+ sortRows: sortRows3
43711
44446
  } = useDashboardReportInternal(reportId);
43712
44447
  const baseStyle = report?.chartType && CHART_TYPE_STYLES[report.chartType] ? CHART_TYPE_STYLES[report.chartType] : DEFAULT_STYLE;
43713
44448
  const mergedStyle = className ? { ...containerStyle ?? {} } : { ...baseStyle, ...containerStyle ?? {} };
@@ -43727,7 +44462,7 @@ function StaticChart(props) {
43727
44462
  }
43728
44463
  };
43729
44464
  const onSortChange = (sort) => {
43730
- sortRows2({
44465
+ sortRows3({
43731
44466
  field: sort.field,
43732
44467
  direction: sort.direction
43733
44468
  });
@@ -45154,7 +45889,7 @@ var PivotModal = ({
45154
45889
  setPivotRowField,
45155
45890
  pivotColumnField,
45156
45891
  setPivotColumnField,
45157
- pivotAggregations,
45892
+ pivotAggregations: pivotAggregations2,
45158
45893
  setPivotAggregations,
45159
45894
  pivotSort,
45160
45895
  setPivotSort,
@@ -45271,18 +46006,18 @@ var PivotModal = ({
45271
46006
  columnField: pivotColumnField,
45272
46007
  columnFieldType: columnTypes[pivotColumnField ?? ""],
45273
46008
  dateBucket: pivotDateBucket,
45274
- aggregations: pivotAggregations?.map((p) => ({
46009
+ aggregations: pivotAggregations2?.map((p) => ({
45275
46010
  valueField: p.valueField,
45276
46011
  valueFieldType: columnTypes[p.valueField ?? ""],
45277
46012
  valueField2: p.valueField2,
45278
46013
  valueField2Type: columnTypes[p.valueField2 ?? ""],
45279
46014
  aggregationType: p.aggregationType
45280
46015
  })),
45281
- valueField: pivotAggregations?.[0]?.valueField,
45282
- valueFieldType: columnTypes[pivotAggregations?.[0]?.valueField ?? ""],
45283
- valueField2: pivotAggregations?.[0]?.valueField2,
45284
- valueField2Type: columnTypes[pivotAggregations?.[0]?.valueField2 ?? ""],
45285
- aggregationType: pivotAggregations?.[0]?.aggregationType
46016
+ valueField: pivotAggregations2?.[0]?.valueField,
46017
+ valueFieldType: columnTypes[pivotAggregations2?.[0]?.valueField ?? ""],
46018
+ valueField2: pivotAggregations2?.[0]?.valueField2,
46019
+ valueField2Type: columnTypes[pivotAggregations2?.[0]?.valueField2 ?? ""],
46020
+ aggregationType: pivotAggregations2?.[0]?.aggregationType
45286
46021
  });
45287
46022
  const getResolvedDateBucket = (pivot) => resolvePivotDateBucket({
45288
46023
  pivot,
@@ -45398,7 +46133,7 @@ var PivotModal = ({
45398
46133
  }, [showUpdatePivot, isOpen]);
45399
46134
  useEffect21(() => {
45400
46135
  const fetchPivotData = async () => {
45401
- if (pivotRowField && data && columns && pivotAggregations?.every((p) => p?.valueField && p?.aggregationType)) {
46136
+ if (pivotRowField && data && columns && pivotAggregations2?.every((p) => p?.valueField && p?.aggregationType)) {
45402
46137
  const pivot = buildCurrentPivot();
45403
46138
  try {
45404
46139
  const { rows, columns: columns2 } = await generatePivotTable({
@@ -45621,17 +46356,17 @@ var PivotModal = ({
45621
46356
  };
45622
46357
  const onCommitPivot = () => {
45623
46358
  const errors2 = [];
45624
- if ((pivotAggregations?.length ?? 0) === 0) {
46359
+ if ((pivotAggregations2?.length ?? 0) === 0) {
45625
46360
  errors2.push("You must have at least one aggregation");
45626
46361
  }
45627
- if (pivotAggregations.some(
46362
+ if (pivotAggregations2.some(
45628
46363
  (p) => !p.valueField && p.aggregationType !== "count" && p.aggregationType !== "percentage"
45629
46364
  )) {
45630
46365
  errors2.push(
45631
46366
  "Value field cannot be empty when aggregation is not 'count' or 'percentage'"
45632
46367
  );
45633
46368
  }
45634
- if (pivotAggregations.some((p) => !p.aggregationType)) {
46369
+ if (pivotAggregations2.some((p) => !p.aggregationType)) {
45635
46370
  errors2.push("Aggregation cannot be empty");
45636
46371
  }
45637
46372
  if (pivotRowField && !columnsToShow[pivotRowField]) {
@@ -45643,7 +46378,7 @@ var PivotModal = ({
45643
46378
  if (showLimitInput && limitInput && !Number.isInteger(Number(limitInput))) {
45644
46379
  errors2.push("Limit must be an integer");
45645
46380
  }
45646
- if (errors2.length === 0 && pivotAggregations?.every(
46381
+ if (errors2.length === 0 && pivotAggregations2?.every(
45647
46382
  (p) => p.aggregationType && (p.valueField || p.aggregationType === "count" || p.aggregationType === "percentage")
45648
46383
  )) {
45649
46384
  const sort = showSortInput && !!sortFieldInput && !!sortDirectionInput;
@@ -45987,7 +46722,7 @@ var PivotModal = ({
45987
46722
  setPivotColumnField(void 0);
45988
46723
  setPivotDateBucket(void 0);
45989
46724
  setPivotAggregations(
45990
- pivotAggregations.map((agg) => {
46725
+ pivotAggregations2.map((agg) => {
45991
46726
  return {
45992
46727
  ...agg,
45993
46728
  aggregationType: agg.aggregationType === "percentage" ? void 0 : agg.aggregationType
@@ -46007,9 +46742,9 @@ var PivotModal = ({
46007
46742
  if (!value && agg.valueField === agg.valueField2) {
46008
46743
  agg.valueField2 = void 0;
46009
46744
  setPivotAggregations([
46010
- ...pivotAggregations.slice(0, index),
46011
- { ...pivotAggregations[index], valueField2: void 0 },
46012
- ...pivotAggregations.slice(index + 1)
46745
+ ...pivotAggregations2.slice(0, index),
46746
+ { ...pivotAggregations2[index], valueField2: void 0 },
46747
+ ...pivotAggregations2.slice(index + 1)
46013
46748
  ]);
46014
46749
  }
46015
46750
  });
@@ -46430,7 +47165,7 @@ var PivotModal = ({
46430
47165
  /* @__PURE__ */ jsx66("div", { style: { width: 200 }, children: /* @__PURE__ */ jsx66(SubheaderComponent, { label: "Aggregation Type" }) }),
46431
47166
  /* @__PURE__ */ jsx66("div", { style: { width: 200 }, children: /* @__PURE__ */ jsx66(SubheaderComponent, { label: "Value Field" }) })
46432
47167
  ] }),
46433
- pivotAggregations?.map((agg, index) => /* @__PURE__ */ jsxs47(PivotRowContainer, { children: [
47168
+ pivotAggregations2?.map((agg, index) => /* @__PURE__ */ jsxs47(PivotRowContainer, { children: [
46434
47169
  /* @__PURE__ */ jsx66(
46435
47170
  SelectComponent,
46436
47171
  {
@@ -46438,12 +47173,12 @@ var PivotModal = ({
46438
47173
  value: agg.aggregationType ?? "",
46439
47174
  onChange: (e) => {
46440
47175
  const newAgg = [
46441
- ...pivotAggregations.slice(0, index),
47176
+ ...pivotAggregations2.slice(0, index),
46442
47177
  {
46443
47178
  ...agg,
46444
47179
  aggregationType: e.target.value === "" ? void 0 : e.target.value
46445
47180
  },
46446
- ...pivotAggregations.slice(index + 1)
47181
+ ...pivotAggregations2.slice(index + 1)
46447
47182
  ];
46448
47183
  pivotFieldChange("aggregations", newAgg);
46449
47184
  setPivotAggregations(newAgg);
@@ -46470,12 +47205,12 @@ var PivotModal = ({
46470
47205
  value: agg.valueField ?? "",
46471
47206
  onChange: (e) => {
46472
47207
  const newAgg = [
46473
- ...pivotAggregations.slice(0, index),
47208
+ ...pivotAggregations2.slice(0, index),
46474
47209
  {
46475
47210
  ...agg,
46476
47211
  valueField: e.target.value === "" ? void 0 : e.target.value
46477
47212
  },
46478
- ...pivotAggregations.slice(index + 1)
47213
+ ...pivotAggregations2.slice(index + 1)
46479
47214
  ];
46480
47215
  pivotFieldChange("aggregations", newAgg);
46481
47216
  setPivotAggregations(newAgg);
@@ -46503,8 +47238,8 @@ var PivotModal = ({
46503
47238
  {
46504
47239
  onClick: () => {
46505
47240
  setPivotAggregations([
46506
- ...pivotAggregations.slice(0, index),
46507
- ...pivotAggregations.slice(index + 1)
47241
+ ...pivotAggregations2.slice(0, index),
47242
+ ...pivotAggregations2.slice(index + 1)
46508
47243
  ]);
46509
47244
  }
46510
47245
  }
@@ -46531,7 +47266,7 @@ var PivotModal = ({
46531
47266
  label: "Add Aggregation",
46532
47267
  onClick: () => {
46533
47268
  setPivotAggregations([
46534
- ...pivotAggregations,
47269
+ ...pivotAggregations2,
46535
47270
  {
46536
47271
  aggregationType: void 0,
46537
47272
  valueField: void 0,
@@ -46765,7 +47500,7 @@ var PivotModal = ({
46765
47500
  label: "Save",
46766
47501
  disabled: showSortInput && (!sortFieldInput || !(samplePivotTable?.columns ?? []).some(
46767
47502
  (c) => c.field === sortFieldInput
46768
- ) || !sortDirectionInput) || !(pivotAggregations?.length > 0) || pivotAggregations.some(
47503
+ ) || !sortDirectionInput) || !(pivotAggregations2?.length > 0) || pivotAggregations2.some(
46769
47504
  (agg) => !agg.aggregationType || !agg.valueField && agg.aggregationType !== "count" && (agg.aggregationType !== "percentage" || !pivotRowField || pivotColumnField)
46770
47505
  )
46771
47506
  }
@@ -48747,7 +49482,7 @@ function ChartBuilder({
48747
49482
  const [pivotColumnField, setPivotColumnField] = useState31(
48748
49483
  report?.pivot?.columnField
48749
49484
  );
48750
- const [pivotAggregations, setPivotAggregations] = useState31(
49485
+ const [pivotAggregations2, setPivotAggregations] = useState31(
48751
49486
  report?.pivot?.aggregations ?? [
48752
49487
  {
48753
49488
  valueField: report?.pivot?.valueField,
@@ -50558,7 +51293,7 @@ function ChartBuilder({
50558
51293
  setPivotRowField,
50559
51294
  pivotColumnField,
50560
51295
  setPivotColumnField,
50561
- pivotAggregations,
51296
+ pivotAggregations: pivotAggregations2,
50562
51297
  pivotSort,
50563
51298
  setPivotSort,
50564
51299
  pivotLimit,
@@ -54203,7 +54938,7 @@ var useReportBuilderInternal = ({
54203
54938
  const [pivotColumnField, setPivotColumnField] = useState34(
54204
54939
  void 0
54205
54940
  );
54206
- const [pivotAggregations, setPivotAggregations] = useState34([]);
54941
+ const [pivotAggregations2, setPivotAggregations] = useState34([]);
54207
54942
  const [pivotDateBucket, setPivotDateBucket] = useState34(void 0);
54208
54943
  const [pivotLimit, setPivotLimit] = useState34(void 0);
54209
54944
  const [pivotSort, setPivotSort] = useState34(void 0);
@@ -54531,7 +55266,7 @@ var useReportBuilderInternal = ({
54531
55266
  if (changeField === "" || changeField === void 0) {
54532
55267
  setPivotColumnField(void 0);
54533
55268
  setPivotAggregations(
54534
- pivotAggregations.map((agg) => ({
55269
+ pivotAggregations2.map((agg) => ({
54535
55270
  ...agg,
54536
55271
  aggregationType: agg.aggregationType === "percentage" ? void 0 : agg.aggregationType
54537
55272
  }))
@@ -55447,7 +56182,7 @@ var useReportBuilderInternal = ({
55447
56182
  pivotData,
55448
56183
  pivotRowField,
55449
56184
  pivotColumnField,
55450
- pivotAggregations,
56185
+ pivotAggregations: pivotAggregations2,
55451
56186
  pivotLimit,
55452
56187
  pivotSort,
55453
56188
  pivotDateBucket,
@@ -56588,7 +57323,7 @@ function PivotForm({
56588
57323
  setPivotRowField,
56589
57324
  pivotColumnField,
56590
57325
  setPivotColumnField,
56591
- pivotAggregations,
57326
+ pivotAggregations: pivotAggregations2,
56592
57327
  setPivotAggregations,
56593
57328
  pivotLimit,
56594
57329
  setPivotLimit,
@@ -56697,7 +57432,7 @@ function PivotForm({
56697
57432
  gap: 24
56698
57433
  },
56699
57434
  children: [
56700
- pivotAggregations.map((pivotAggregation, index) => /* @__PURE__ */ jsxs56(
57435
+ pivotAggregations2.map((pivotAggregation, index) => /* @__PURE__ */ jsxs56(
56701
57436
  "div",
56702
57437
  {
56703
57438
  style: {
@@ -56724,12 +57459,12 @@ function PivotForm({
56724
57459
  value: pivotAggregation.aggregationType,
56725
57460
  onChange: (e) => {
56726
57461
  setPivotAggregations([
56727
- ...pivotAggregations.slice(0, index),
57462
+ ...pivotAggregations2.slice(0, index),
56728
57463
  {
56729
- ...pivotAggregations[index],
57464
+ ...pivotAggregations2[index],
56730
57465
  aggregationType: e.target.value === "" ? void 0 : e.target.value
56731
57466
  },
56732
- ...pivotAggregations.slice(index + 1)
57467
+ ...pivotAggregations2.slice(index + 1)
56733
57468
  ]);
56734
57469
  },
56735
57470
  options: [
@@ -56755,12 +57490,12 @@ function PivotForm({
56755
57490
  value: pivotAggregation.valueField,
56756
57491
  onChange: (e) => {
56757
57492
  setPivotAggregations([
56758
- ...pivotAggregations.slice(0, index),
57493
+ ...pivotAggregations2.slice(0, index),
56759
57494
  {
56760
- ...pivotAggregations[index],
57495
+ ...pivotAggregations2[index],
56761
57496
  valueField: e.target.value === "" ? void 0 : e.target.value
56762
57497
  },
56763
- ...pivotAggregations.slice(index + 1)
57498
+ ...pivotAggregations2.slice(index + 1)
56764
57499
  ]);
56765
57500
  },
56766
57501
  isLoading: uniqueValuesIsLoading,
@@ -56783,8 +57518,8 @@ function PivotForm({
56783
57518
  {
56784
57519
  onClick: () => {
56785
57520
  setPivotAggregations([
56786
- ...pivotAggregations.slice(0, index),
56787
- ...pivotAggregations.slice(index + 1)
57521
+ ...pivotAggregations2.slice(0, index),
57522
+ ...pivotAggregations2.slice(index + 1)
56788
57523
  ]);
56789
57524
  }
56790
57525
  }
@@ -56798,7 +57533,7 @@ function PivotForm({
56798
57533
  {
56799
57534
  onClick: () => {
56800
57535
  setPivotAggregations([
56801
- ...pivotAggregations,
57536
+ ...pivotAggregations2,
56802
57537
  {
56803
57538
  valueField: void 0,
56804
57539
  aggregationType: void 0
@@ -58844,7 +59579,7 @@ function ChartEditor({
58844
59579
  }
58845
59580
 
58846
59581
  // src/Chat.tsx
58847
- import { useContext as useContext37, useEffect as useEffect32, useRef as useRef25, useState as useState42 } from "react";
59582
+ import { useContext as useContext37, useEffect as useEffect33, useRef as useRef25, useState as useState43 } from "react";
58848
59583
 
58849
59584
  // src/ChatChartCard.tsx
58850
59585
  import { useMemo as useMemo34 } from "react";
@@ -58855,14 +59590,19 @@ init_ReportBuilder();
58855
59590
  import {
58856
59591
  useCallback as useCallback5,
58857
59592
  useContext as useContext36,
58858
- useEffect as useEffect31,
59593
+ useEffect as useEffect32,
58859
59594
  useLayoutEffect as useLayoutEffect4,
58860
59595
  useMemo as useMemo33,
58861
59596
  useReducer as useReducer2,
58862
59597
  useRef as useRef24,
58863
- useState as useState41
59598
+ useState as useState42
58864
59599
  } from "react";
58865
- import { keepPreviousData, useMutation, useQuery } from "@tanstack/react-query";
59600
+ import {
59601
+ keepPreviousData,
59602
+ useMutation,
59603
+ useQuery,
59604
+ useQueryClient as useQueryClient2
59605
+ } from "@tanstack/react-query";
58866
59606
  init_util();
58867
59607
  init_columnType();
58868
59608
  init_columnProcessing();
@@ -60046,6 +60786,20 @@ function useFormReducer(state, action) {
60046
60786
  }
60047
60787
  }
60048
60788
 
60789
+ // src/hooks/useForm.refreshDebounce.ts
60790
+ import { useEffect as useEffect31, useState as useState41 } from "react";
60791
+ var REPORT_REFRESH_DEBOUNCE_MS = 200;
60792
+ function useDebouncedRefreshInput(value, delayMs = REPORT_REFRESH_DEBOUNCE_MS) {
60793
+ const [debouncedValue, setDebouncedValue] = useState41(value);
60794
+ useEffect31(() => {
60795
+ const timeout = setTimeout(() => {
60796
+ setDebouncedValue(value);
60797
+ }, delayMs);
60798
+ return () => clearTimeout(timeout);
60799
+ }, [delayMs, value]);
60800
+ return debouncedValue;
60801
+ }
60802
+
60049
60803
  // src/utils/pivotDateBuckets.ts
60050
60804
  import {
60051
60805
  eachDayOfInterval as eachDayOfInterval3,
@@ -60077,10 +60831,252 @@ function buildPivotDateBucketStarts(range, bucket) {
60077
60831
  );
60078
60832
  }
60079
60833
 
60834
+ // src/utils/cacheCabReportResolver.ts
60835
+ init_inMemoryPivotEngine();
60836
+ var availableRowKeys = (report) => {
60837
+ const keys = /* @__PURE__ */ new Set();
60838
+ for (const row of report.rows?.slice(0, 10) ?? []) {
60839
+ Object.keys(row).forEach((key) => keys.add(key));
60840
+ }
60841
+ for (const column of report.columnInternal ?? []) {
60842
+ if (column.field) keys.add(column.field);
60843
+ }
60844
+ for (const column of report.reportBuilderState?.columns ?? []) {
60845
+ if (column.field) keys.add(column.field);
60846
+ if (column.alias) keys.add(column.alias);
60847
+ }
60848
+ return keys;
60849
+ };
60850
+ var createFieldResolver = (report) => {
60851
+ const keys = availableRowKeys(report);
60852
+ return (field, table) => {
60853
+ const matchingColumns = (report.reportBuilderState?.columns ?? []).filter(
60854
+ (column) => column.field === field && (!table || !column.table || column.table === table)
60855
+ );
60856
+ const candidates = [
60857
+ ...matchingColumns.map((column) => column.alias).filter(Boolean),
60858
+ field,
60859
+ table ? `${table}.${field}` : void 0,
60860
+ table ? `${table}__${field}` : void 0,
60861
+ table ? `${table}_${field}` : void 0
60862
+ ].filter((candidate) => Boolean(candidate));
60863
+ return candidates.find((candidate) => keys.has(candidate));
60864
+ };
60865
+ };
60866
+ var pivotAggregations = (pivot) => pivot.aggregations ?? (pivot.aggregationType ? [
60867
+ {
60868
+ aggregationType: pivot.aggregationType,
60869
+ valueField: pivot.valueField,
60870
+ valueField2: pivot.valueField2,
60871
+ valueFieldType: pivot.valueFieldType,
60872
+ valueField2Type: pivot.valueField2Type
60873
+ }
60874
+ ] : []);
60875
+ var resolvePivot = (pivot, resolveField) => {
60876
+ const rowField = pivot.rowField ? resolveField(pivot.rowField, pivot.rowFieldTable) : void 0;
60877
+ const columnField = pivot.columnField ? resolveField(pivot.columnField, pivot.columnFieldTable) : void 0;
60878
+ if (pivot.rowField && !rowField || pivot.columnField && !columnField) {
60879
+ return null;
60880
+ }
60881
+ const aggregations = pivotAggregations(pivot).map((aggregation) => ({
60882
+ ...aggregation,
60883
+ valueField: aggregation.valueField ? resolveField(aggregation.valueField, aggregation.valueFieldTable) : void 0,
60884
+ valueField2: aggregation.valueField2 ? resolveField(aggregation.valueField2, aggregation.valueFieldTable) : void 0
60885
+ }));
60886
+ if (aggregations.some(
60887
+ (aggregation, index) => pivotAggregations(pivot)[index]?.valueField && !aggregation.valueField || pivotAggregations(pivot)[index]?.valueField2 && !aggregation.valueField2
60888
+ )) {
60889
+ return null;
60890
+ }
60891
+ return {
60892
+ ...pivot,
60893
+ rowField,
60894
+ columnField,
60895
+ aggregations,
60896
+ aggregationType: void 0,
60897
+ valueField: void 0,
60898
+ valueField2: void 0,
60899
+ valueFieldType: void 0,
60900
+ valueField2Type: void 0
60901
+ };
60902
+ };
60903
+ var sortRows2 = (rows, state, resolveField) => {
60904
+ if (!state.sort.length) return rows;
60905
+ return [...rows].sort((left, right) => {
60906
+ for (const sort of state.sort) {
60907
+ const key = resolveField(sort.field);
60908
+ if (!key) continue;
60909
+ const a = left[key];
60910
+ const b = right[key];
60911
+ if (a === b) continue;
60912
+ const comparison = a == null ? -1 : b == null ? 1 : a < b ? -1 : 1;
60913
+ return sort.direction.toLowerCase() === "desc" ? -comparison : comparison;
60914
+ }
60915
+ return 0;
60916
+ });
60917
+ };
60918
+ var buildUniqueValues = (report, resolveField) => {
60919
+ const result = {};
60920
+ const stringColumns = (report.columnInternal ?? []).filter(
60921
+ (column) => column.jsType === "string" || /char|text|string|uuid/i.test(column.fieldType ?? "")
60922
+ );
60923
+ for (const column of stringColumns) {
60924
+ const table = column.table ?? "";
60925
+ const key = resolveField(column.field, column.table);
60926
+ if (!key) continue;
60927
+ const values = /* @__PURE__ */ new Set();
60928
+ for (const row of report.rows ?? []) {
60929
+ const value = row[key];
60930
+ if (value !== null && value !== void 0) values.add(String(value));
60931
+ }
60932
+ result[table] ??= {};
60933
+ result[table][column.field] = [...values].sort();
60934
+ }
60935
+ return result;
60936
+ };
60937
+ function resolveReportFromCacheCab({
60938
+ snapshot,
60939
+ reportBuilderState,
60940
+ filters,
60941
+ pivot = reportBuilderState?.pivot ?? null,
60942
+ operation = "table",
60943
+ rowLimit
60944
+ }) {
60945
+ if (!snapshot) return { resolved: false, reason: "cache_miss" };
60946
+ if (!snapshot.complete) {
60947
+ return {
60948
+ resolved: false,
60949
+ reason: "incomplete_cache",
60950
+ details: { snapshotReason: snapshot.reason }
60951
+ };
60952
+ }
60953
+ const state = reportBuilderState ?? snapshot.report.reportBuilderState;
60954
+ if (!state) return { resolved: false, reason: "missing_builder_state" };
60955
+ const resolveField = createFieldResolver(snapshot.report);
60956
+ const missingFields = [];
60957
+ state.columns.forEach((column) => {
60958
+ if (!resolveField(column.field, column.table)) {
60959
+ missingFields.push({
60960
+ role: "column",
60961
+ field: column.field,
60962
+ table: column.table
60963
+ });
60964
+ }
60965
+ });
60966
+ filters.forEach((filter) => {
60967
+ if (filter.field && !resolveField(filter.field, filter.table)) {
60968
+ missingFields.push({
60969
+ role: "filter",
60970
+ field: filter.field,
60971
+ table: filter.table
60972
+ });
60973
+ }
60974
+ });
60975
+ if (pivot?.rowField && !resolveField(pivot.rowField, pivot.rowFieldTable)) {
60976
+ missingFields.push({
60977
+ role: "pivot-row",
60978
+ field: pivot.rowField,
60979
+ table: pivot.rowFieldTable
60980
+ });
60981
+ }
60982
+ if (pivot?.columnField && !resolveField(pivot.columnField, pivot.columnFieldTable)) {
60983
+ missingFields.push({
60984
+ role: "pivot-column",
60985
+ field: pivot.columnField,
60986
+ table: pivot.columnFieldTable
60987
+ });
60988
+ }
60989
+ if (pivot) {
60990
+ pivotAggregations(pivot).forEach((aggregation) => {
60991
+ [
60992
+ [aggregation.valueField, aggregation.valueFieldTable],
60993
+ [aggregation.valueField2, aggregation.valueFieldTable]
60994
+ ].forEach(([field, table]) => {
60995
+ if (field && !resolveField(field, table)) {
60996
+ missingFields.push({
60997
+ role: "aggregation",
60998
+ field,
60999
+ table
61000
+ });
61001
+ }
61002
+ });
61003
+ });
61004
+ }
61005
+ const resolvedPivot = pivot ? resolvePivot(pivot, resolveField) : null;
61006
+ if (missingFields.length > 0 || pivot && !resolvedPivot) {
61007
+ return {
61008
+ resolved: false,
61009
+ reason: "missing_field",
61010
+ details: { missingFields }
61011
+ };
61012
+ }
61013
+ const filteredRows = applyFiltersInMemory(
61014
+ snapshot.report.rows ?? [],
61015
+ filters,
61016
+ {
61017
+ columns: snapshot.report.columnInternal,
61018
+ fieldKeyResolver: resolveField
61019
+ }
61020
+ );
61021
+ const sortedRows = operation === "unique-values" ? [] : sortRows2(filteredRows, state, resolveField);
61022
+ const effectiveRowLimit = Math.min(
61023
+ state.limit?.value ?? Number.MAX_SAFE_INTEGER,
61024
+ rowLimit ?? Number.MAX_SAFE_INTEGER
61025
+ );
61026
+ const limitedRows = sortedRows.length > effectiveRowLimit ? sortedRows.slice(0, effectiveRowLimit) : sortedRows;
61027
+ const pivotRows = resolvedPivot ? applyPivotInMemory(filteredRows, resolvedPivot, filters) : void 0;
61028
+ const pivotColumns = resolvedPivot ? generatePivotColumnsInMemory({
61029
+ pivot: resolvedPivot,
61030
+ pivotRows: pivotRows ?? [],
61031
+ sourceColumns: snapshot.report.columnInternal
61032
+ }) : void 0;
61033
+ const requestedRowField = pivot?.rowField;
61034
+ const resolvedRowField = resolvedPivot?.rowField;
61035
+ const normalizedPivotRows = requestedRowField && resolvedRowField && requestedRowField !== resolvedRowField ? pivotRows?.map((row) => ({
61036
+ ...row,
61037
+ [requestedRowField]: row[resolvedRowField]
61038
+ })) : pivotRows;
61039
+ const normalizedPivotColumns = pivotColumns?.map(
61040
+ (column) => requestedRowField && resolvedRowField && column.field === resolvedRowField ? { ...column, field: requestedRowField } : column
61041
+ );
61042
+ return {
61043
+ resolved: true,
61044
+ report: {
61045
+ ...snapshot.report,
61046
+ rows: limitedRows,
61047
+ rowCount: filteredRows.length,
61048
+ reportBuilderState: { ...state, pivot },
61049
+ pivot,
61050
+ pivotRows: normalizedPivotRows,
61051
+ pivotColumns: normalizedPivotColumns,
61052
+ pivotRowCount: normalizedPivotRows?.length,
61053
+ pivotResultRowField: requestedRowField,
61054
+ pivotResultSourceRowField: requestedRowField,
61055
+ ...operation === "unique-values" ? {
61056
+ uniqueStringsByTable: buildUniqueValues(
61057
+ snapshot.report,
61058
+ resolveField
61059
+ )
61060
+ } : {},
61061
+ filtersApplied: filters
61062
+ }
61063
+ };
61064
+ }
61065
+
60080
61066
  // src/hooks/useForm.queries.ts
60081
61067
  var QUERY_KEY_UNDEFINED = "__undefined__";
60082
61068
  var QUERY_KEY_FUNCTION = "__function__";
60083
61069
  var QUERY_KEY_BIGINT = "__bigint__";
61070
+ function createFilterUniqueValuesReportBuilderState(source, columns) {
61071
+ return {
61072
+ tables: source.tables,
61073
+ columns,
61074
+ filterStack: [],
61075
+ pivot: null,
61076
+ sort: [],
61077
+ limit: null
61078
+ };
61079
+ }
60084
61080
  function stableSerializeForQueryKey(value) {
60085
61081
  try {
60086
61082
  return JSON.stringify(value, (_key, nestedValue) => {
@@ -60141,6 +61137,7 @@ function createUseFormPivotTableDataRefreshQueryKey(input) {
60141
61137
  "pivot-table-data-refresh",
60142
61138
  input.reportId,
60143
61139
  input.reportBuilderStateHash,
61140
+ input.pageSize,
60144
61141
  input.tenantHash,
60145
61142
  input.clientHash
60146
61143
  ];
@@ -60167,13 +61164,36 @@ function createUseFormReportNameQueryKey(input) {
60167
61164
  ];
60168
61165
  }
60169
61166
  function createUseFormQueryFn(loader) {
60170
- return async () => loader();
61167
+ return ({ signal }) => loader(signal);
60171
61168
  }
60172
61169
 
60173
61170
  // src/hooks/useForm.pagination.ts
60174
61171
  var DEFAULT_PAGE_INDEX = 0;
60175
61172
  var DEFAULT_PAGE_SIZE = 10;
60176
- var DEFAULT_USE_REPORT_ROWS_PER_REQUEST = DEFAULT_PAGE_SIZE * 10;
61173
+ var DEFAULT_USE_REPORT_ROWS_PER_REQUEST = DEFAULT_PAGE_SIZE;
61174
+ var isBareIdentifier = (value) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(value);
61175
+ function getStablePaginationSort({
61176
+ columns,
61177
+ sort
61178
+ }) {
61179
+ const outputColumns = (columns ?? []).map((column) => {
61180
+ const field2 = String(column.field ?? "").trim();
61181
+ const alias = String(column.alias ?? "").trim();
61182
+ const outputField = isBareIdentifier(alias) ? alias : isBareIdentifier(field2) ? field2 : "";
61183
+ return { field: field2, alias, outputField };
61184
+ }).filter((column) => column.outputField);
61185
+ const requestedSort = sort?.[0];
61186
+ const requestedField = String(requestedSort?.field ?? "").trim();
61187
+ const matchedColumn = requestedField ? outputColumns.find(
61188
+ (column) => column.field === requestedField || column.alias === requestedField
61189
+ ) : void 0;
61190
+ const field = matchedColumn?.outputField ?? outputColumns[0]?.outputField;
61191
+ if (!field) return void 0;
61192
+ return {
61193
+ field,
61194
+ direction: String(requestedSort?.direction ?? "ASC").toUpperCase() === "DESC" ? "DESC" : "ASC"
61195
+ };
61196
+ }
60177
61197
  function getDefaultPaginationState() {
60178
61198
  return { pageIndex: DEFAULT_PAGE_INDEX, pageSize: DEFAULT_PAGE_SIZE };
60179
61199
  }
@@ -60323,11 +61343,11 @@ var isMultiValueOperator = (operator) => {
60323
61343
  };
60324
61344
  var collectSelectedStringMultiselectValuesByField = (group) => {
60325
61345
  const out = /* @__PURE__ */ new Map();
60326
- const visit = (entries) => {
60327
- if (!entries) {
61346
+ const visit = (entries2) => {
61347
+ if (!entries2) {
60328
61348
  return;
60329
61349
  }
60330
- for (const entry of entries) {
61350
+ for (const entry of entries2) {
60331
61351
  if (!entry || typeof entry === "string") {
60332
61352
  continue;
60333
61353
  }
@@ -60525,11 +61545,11 @@ var cloneQueryBuilderRuleGroup = (group) => JSON.parse(JSON.stringify(group));
60525
61545
  var canonicalizeMultiselectStringRulesToOptionValues = (group, optionsByField, configByName) => {
60526
61546
  const next = cloneQueryBuilderRuleGroup(group);
60527
61547
  let changed = false;
60528
- const visit = (entries) => {
60529
- if (!entries) {
61548
+ const visit = (entries2) => {
61549
+ if (!entries2) {
60530
61550
  return;
60531
61551
  }
60532
- for (const entry of entries) {
61552
+ for (const entry of entries2) {
60533
61553
  if (!entry || typeof entry === "string") {
60534
61554
  continue;
60535
61555
  }
@@ -62894,15 +63914,17 @@ async function loadViaInMemoryEngines({
62894
63914
  pivot,
62895
63915
  reportBuilderState,
62896
63916
  allowReportTaskBootstrap = false,
62897
- draftSessionId
63917
+ draftSessionId,
63918
+ shareInitialRequest = false,
63919
+ abortSignal
62898
63920
  }) {
62899
63921
  const requestedTask = allowReportTaskBootstrap ? "report" : "item";
62900
- const additionalProcessing = allowReportTaskBootstrap ? {
63922
+ const additionalProcessing = {
62901
63923
  page: {
62902
63924
  ...DEFAULT_PAGINATION,
62903
63925
  rowsPerRequest: DEFAULT_USE_REPORT_ROWS_PER_REQUEST
62904
63926
  }
62905
- } : {};
63927
+ };
62906
63928
  const { report, error } = await fetchReport({
62907
63929
  reportId,
62908
63930
  client,
@@ -62916,7 +63938,9 @@ async function loadViaInMemoryEngines({
62916
63938
  usePivotTask: false,
62917
63939
  pivot,
62918
63940
  reportBuilderState,
62919
- draftSessionId
63941
+ draftSessionId,
63942
+ shareRequest: shareInitialRequest,
63943
+ abortSignal
62920
63944
  });
62921
63945
  if (error || !report) {
62922
63946
  return { report: null, error };
@@ -62982,7 +64006,9 @@ async function loadViaPivotTemplate({
62982
64006
  reportBuilderState,
62983
64007
  baseReport,
62984
64008
  schema,
62985
- draftSessionId
64009
+ draftSessionId,
64010
+ shareInitialRequest = false,
64011
+ abortSignal
62986
64012
  }) {
62987
64013
  const pivotForTemplate = enrichPivotRowFieldTypeForTemplate(
62988
64014
  pivot,
@@ -63002,6 +64028,8 @@ async function loadViaPivotTemplate({
63002
64028
  reportBuilderState,
63003
64029
  baseReport,
63004
64030
  draftSessionId,
64031
+ shareRequest: shareInitialRequest,
64032
+ abortSignal,
63005
64033
  responseSelection: baseReport ? {
63006
64034
  includeRows: false,
63007
64035
  includeColumns: false,
@@ -63020,7 +64048,8 @@ async function fetchReportBuilderStateByReportId({
63020
64048
  client,
63021
64049
  getToken,
63022
64050
  draftSessionId,
63023
- tenants
64051
+ tenants,
64052
+ abortSignal
63024
64053
  }) {
63025
64054
  try {
63026
64055
  const { data, error, status } = await quillFetch({
@@ -63031,7 +64060,8 @@ async function fetchReportBuilderStateByReportId({
63031
64060
  tenants,
63032
64061
  ...draftSessionId ? { draftSessionId: String(draftSessionId).trim() } : {}
63033
64062
  },
63034
- getToken
64063
+ getToken,
64064
+ abortSignal
63035
64065
  });
63036
64066
  if (error || status === "error") {
63037
64067
  return null;
@@ -63043,6 +64073,9 @@ async function fetchReportBuilderStateByReportId({
63043
64073
  const parsedState = state;
63044
64074
  return parsedState;
63045
64075
  } catch (error) {
64076
+ if (error instanceof Error && error.name === "AbortError") {
64077
+ throw error;
64078
+ }
63046
64079
  return null;
63047
64080
  }
63048
64081
  }
@@ -63104,7 +64137,8 @@ async function fetchUniqueValuesFromTables({
63104
64137
  customFields,
63105
64138
  tenants,
63106
64139
  dashboardName,
63107
- getToken
64140
+ getToken,
64141
+ abortSignal
63108
64142
  }) {
63109
64143
  const normalizedTables = Array.from(
63110
64144
  new Set(
@@ -63121,6 +64155,9 @@ async function fetchUniqueValuesFromTables({
63121
64155
  }
63122
64156
  const fetchResults = await Promise.all(
63123
64157
  normalizedTables.map(async (tableName) => {
64158
+ if (abortSignal?.aborted) {
64159
+ throw new DOMException("Aborted", "AbortError");
64160
+ }
63124
64161
  const tableColumns = stringColumnsByTable.filter((column) => {
63125
64162
  return String(column.table ?? "").trim() === tableName;
63126
64163
  });
@@ -63153,7 +64190,8 @@ async function fetchUniqueValuesFromTables({
63153
64190
  client,
63154
64191
  task: "report-builder-unique-values",
63155
64192
  metadata: requestMetadata,
63156
- getToken
64193
+ getToken,
64194
+ abortSignal
63157
64195
  });
63158
64196
  const responseData = fetchResult?.data;
63159
64197
  const responseRows = Array.isArray(responseData?.rows) && responseData.rows || Array.isArray(fetchResult?.queries?.queryResults?.[0]?.rows) && fetchResult.queries.queryResults[0].rows || [];
@@ -63216,7 +64254,9 @@ async function loadPivotTemplateInParallelWithReportTask({
63216
64254
  customFields,
63217
64255
  dashboardName,
63218
64256
  pivotRefreshOnly = false,
63219
- draftSessionId
64257
+ draftSessionId,
64258
+ shareInitialRequest = false,
64259
+ abortSignal
63220
64260
  }) {
63221
64261
  if (pivotRefreshOnly) {
63222
64262
  const pivotOnlyResult = await loadViaPivotTemplate({
@@ -63230,7 +64270,9 @@ async function loadPivotTemplateInParallelWithReportTask({
63230
64270
  reportBuilderState,
63231
64271
  baseReport,
63232
64272
  schema,
63233
- draftSessionId
64273
+ draftSessionId,
64274
+ shareInitialRequest,
64275
+ abortSignal
63234
64276
  });
63235
64277
  return pivotOnlyResult;
63236
64278
  }
@@ -63245,7 +64287,9 @@ async function loadPivotTemplateInParallelWithReportTask({
63245
64287
  pivot,
63246
64288
  reportBuilderState,
63247
64289
  allowReportTaskBootstrap: true,
63248
- draftSessionId
64290
+ draftSessionId,
64291
+ shareInitialRequest,
64292
+ abortSignal
63249
64293
  }),
63250
64294
  loadViaPivotTemplate({
63251
64295
  reportId,
@@ -63258,14 +64302,17 @@ async function loadPivotTemplateInParallelWithReportTask({
63258
64302
  reportBuilderState,
63259
64303
  baseReport,
63260
64304
  schema,
63261
- draftSessionId
64305
+ draftSessionId,
64306
+ shareInitialRequest,
64307
+ abortSignal
63262
64308
  }),
63263
64309
  fetchReportBuilderStateByReportId({
63264
64310
  reportId,
63265
64311
  client,
63266
64312
  getToken,
63267
64313
  draftSessionId,
63268
- tenants
64314
+ tenants,
64315
+ abortSignal
63269
64316
  })
63270
64317
  ]);
63271
64318
  const withFetchedReportBuilderState = (result) => applyFetchedReportBuilderState(result, fetchedReportBuilderState);
@@ -63300,7 +64347,8 @@ async function loadPivotTemplateInParallelWithReportTask({
63300
64347
  customFields,
63301
64348
  dashboardName: String(dashboardName ?? "").trim(),
63302
64349
  report: baseReport,
63303
- draftSessionId
64350
+ draftSessionId,
64351
+ abortSignal
63304
64352
  });
63305
64353
  if (reportBuilderStateFallbackResult.report && !reportBuilderStateFallbackResult.error) {
63306
64354
  return withFetchedReportBuilderState(reportBuilderStateFallbackResult);
@@ -63330,7 +64378,8 @@ async function loadViaReportBuilderState({
63330
64378
  pagination,
63331
64379
  paginationSort,
63332
64380
  rowsOnly,
63333
- rowCountOnly
64381
+ rowCountOnly,
64382
+ abortSignal
63334
64383
  }) {
63335
64384
  const baseReport = {
63336
64385
  ...EMPTY_INTERNAL_REPORT,
@@ -63369,7 +64418,8 @@ async function loadViaReportBuilderState({
63369
64418
  dashboardName,
63370
64419
  getToken,
63371
64420
  eventTracking: null,
63372
- draftSessionId
64421
+ draftSessionId,
64422
+ abortSignal
63373
64423
  });
63374
64424
  if (!reportBuilderInfo) {
63375
64425
  return {
@@ -63450,7 +64500,9 @@ async function loadReportForUseForm({
63450
64500
  dashboardName,
63451
64501
  useInMemoryEngines,
63452
64502
  draftSessionId,
63453
- rowsOnly = false
64503
+ shareInitialRequest = false,
64504
+ rowsOnly = false,
64505
+ abortSignal
63454
64506
  }) {
63455
64507
  const effectivePivot = pivot ?? initialReportBuilderState?.pivot ?? void 0;
63456
64508
  const hasPivot = Boolean(effectivePivot);
@@ -63473,7 +64525,9 @@ async function loadReportForUseForm({
63473
64525
  customFields,
63474
64526
  dashboardName,
63475
64527
  pivotRefreshOnly: includeReportBuilderStateInPivotTask,
63476
- draftSessionId
64528
+ draftSessionId,
64529
+ shareInitialRequest,
64530
+ abortSignal
63477
64531
  });
63478
64532
  const pivotRows = pivotResult.report?.pivotRows;
63479
64533
  const rowField = pivotResult.report?.pivotResultRowField ?? effectivePivot?.rowField;
@@ -63496,7 +64550,8 @@ async function loadReportForUseForm({
63496
64550
  customFields,
63497
64551
  dashboardName,
63498
64552
  report: baseReport ?? void 0,
63499
- draftSessionId
64553
+ draftSessionId,
64554
+ abortSignal
63500
64555
  });
63501
64556
  const rbPivotRows = rbResult.report?.pivotRows;
63502
64557
  const rbEmpty = !Array.isArray(rbPivotRows) || rbPivotRows.length === 0;
@@ -63521,7 +64576,9 @@ async function loadReportForUseForm({
63521
64576
  schema,
63522
64577
  customFields,
63523
64578
  dashboardName,
63524
- draftSessionId
64579
+ draftSessionId,
64580
+ shareInitialRequest,
64581
+ abortSignal
63525
64582
  });
63526
64583
  const normalizedBootstrapResult = bootstrapResult.report?.pivot == null ? stripPivotFromResult(bootstrapResult) : bootstrapResult;
63527
64584
  return normalizedBootstrapResult;
@@ -63535,7 +64592,9 @@ async function loadReportForUseForm({
63535
64592
  tenants,
63536
64593
  flags,
63537
64594
  allowReportTaskBootstrap: shouldUseReportTaskForReload,
63538
- draftSessionId
64595
+ draftSessionId,
64596
+ shareInitialRequest,
64597
+ abortSignal
63539
64598
  });
63540
64599
  }
63541
64600
  return loadViaReportBuilderState({
@@ -63549,7 +64608,8 @@ async function loadReportForUseForm({
63549
64608
  dashboardName,
63550
64609
  report: baseReport,
63551
64610
  draftSessionId,
63552
- rowsOnly
64611
+ rowsOnly,
64612
+ abortSignal
63553
64613
  });
63554
64614
  }
63555
64615
  function generateDraftSessionId() {
@@ -63557,10 +64617,10 @@ function generateDraftSessionId() {
63557
64617
  }
63558
64618
  function useReport(reportIdArg, options = {}) {
63559
64619
  const propReportId = String(reportIdArg ?? "").trim();
63560
- const [createdReportBootstrap, setCreatedReportBootstrap] = useState41(null);
64620
+ const [createdReportBootstrap, setCreatedReportBootstrap] = useState42(null);
63561
64621
  const effectiveReportId = propReportId || createdReportBootstrap?.reportId || "";
63562
64622
  const { eventTracking } = useContext36(EventTrackingContext);
63563
- const [draftSessionId, setDraftSessionId] = useState41(generateDraftSessionId);
64623
+ const [draftSessionId, setDraftSessionId] = useState42(generateDraftSessionId);
63564
64624
  const useInMemoryEngines = options.useInMemoryEngines ?? false;
63565
64625
  const dateBucketMode = options.dateBucketMode ?? "dynamic";
63566
64626
  const restrictFieldOptionsToSelectedDatasources = options.restrictFieldOptionsToSelectedDatasources ?? true;
@@ -63574,16 +64634,10 @@ function useReport(reportIdArg, options = {}) {
63574
64634
  ...getDefaultPaginationState(),
63575
64635
  ...options.initialState?.pagination ?? {}
63576
64636
  });
63577
- const [internalPagination, setInternalPagination] = useState41(
64637
+ const [internalPagination, setInternalPagination] = useState42(
63578
64638
  initialPaginationRef.current
63579
64639
  );
63580
- const [paginationInteracted, setPaginationInteracted] = useState41(
63581
- () => Boolean(
63582
- options.state?.pagination || options.initialState?.pagination || options.onPaginationChange
63583
- )
63584
- );
63585
64640
  const pagination = controlledPagination ?? internalPagination;
63586
- const paginationActive = paginationInteracted || Boolean(controlledPagination);
63587
64641
  const paginationRef = useRef24(pagination);
63588
64642
  paginationRef.current = pagination;
63589
64643
  const onPaginationChangeRef = useRef24(options.onPaginationChange);
@@ -63594,10 +64648,7 @@ function useReport(reportIdArg, options = {}) {
63594
64648
  autoResetPageIndexRef.current = options.autoResetPageIndex ?? true;
63595
64649
  const paginationPageCountRef = useRef24(-1);
63596
64650
  const applyPaginationUpdate = useCallback5(
63597
- (updater, { activate = true } = {}) => {
63598
- if (activate) {
63599
- setPaginationInteracted(true);
63600
- }
64651
+ (updater) => {
63601
64652
  onPaginationChangeRef.current?.(updater);
63602
64653
  if (!controlledPaginationRef.current) {
63603
64654
  setInternalPagination((old) => functionalUpdate(updater, old));
@@ -63676,13 +64727,13 @@ function useReport(reportIdArg, options = {}) {
63676
64727
  dateBucketSetViaSetReportRef.current = false;
63677
64728
  aggregationStateSetViaSetReportRef.current = false;
63678
64729
  }
63679
- const [chartPivotHydrationEpoch, setChartPivotHydrationEpoch] = useState41(0);
64730
+ const [chartPivotHydrationEpoch, setChartPivotHydrationEpoch] = useState42(0);
63680
64731
  const [
63681
64732
  expandReportBuilderColumnsForFlatTable,
63682
64733
  setExpandReportBuilderColumnsForFlatTable
63683
- ] = useState41(false);
64734
+ ] = useState42(false);
63684
64735
  const prevPivotStateForColumnExpansionRef = useRef24(null);
63685
- const [sourceReport, setSourceReport] = useState41(
64736
+ const [sourceReport, setSourceReport] = useState42(
63686
64737
  null
63687
64738
  );
63688
64739
  const prevSourceReportIdForPivotHydrationRef = useRef24(null);
@@ -63697,18 +64748,21 @@ function useReport(reportIdArg, options = {}) {
63697
64748
  lastPivotHydrateCompletedSourceIdRef.current = null;
63698
64749
  }
63699
64750
  }
63700
- const [preserveSchemaWideOptions, setPreserveSchemaWideOptions] = useState41(false);
63701
- const [initialSchemaScopedTableNames, setInitialSchemaScopedTableNames] = useState41(null);
63702
- const [tableColumnsEditedSignature, setTableColumnsEditedSignature] = useState41(null);
63703
- useEffect31(() => {
64751
+ const [preserveSchemaWideOptions, setPreserveSchemaWideOptions] = useState42(false);
64752
+ const [initialSchemaScopedTableNames, setInitialSchemaScopedTableNames] = useState42(null);
64753
+ const [tableColumnsEditedSignature, setTableColumnsEditedSignature] = useState42(null);
64754
+ useEffect32(() => {
63704
64755
  setTableColumnsEditedSignature(null);
63705
64756
  }, [effectiveReportId]);
63706
- const [chartAxisEdits, setChartAxisEdits] = useState41({});
63707
- const [chartVisibilityOverrides, setChartVisibilityOverrides] = useState41({});
64757
+ const [chartAxisEdits, setChartAxisEdits] = useState42({});
64758
+ const [chartVisibilityOverrides, setChartVisibilityOverrides] = useState42({});
63708
64759
  const [client] = useContext36(ClientContext);
64760
+ const queryClient = useQueryClient2();
63709
64761
  const [schemaData] = useContext36(SchemaDataContext);
63710
64762
  const { tenants, flags } = useContext36(TenantContext);
63711
64763
  const { getToken } = useContext36(FetchContext);
64764
+ const { getCacheCab } = useContext36(CacheCabContext);
64765
+ const cacheCab = useMemo33(() => getCacheCab(), [getCacheCab]);
63712
64766
  const clientDefaultDashboardName = String(
63713
64767
  client?.defaultDashboard?.name ?? ""
63714
64768
  ).trim();
@@ -63824,16 +64878,17 @@ function useReport(reportIdArg, options = {}) {
63824
64878
  filterStackRef.current = filterStack;
63825
64879
  const resolvedGroupRowsBy = decodePivotGroupOptionValue(groupRowsBy);
63826
64880
  const resolvedGroupColumnsBy = decodePivotGroupOptionValue(groupColumnsBy);
63827
- useEffect31(() => {
64881
+ useEffect32(() => {
63828
64882
  if (propReportId) {
63829
64883
  setCreatedReportBootstrap(null);
63830
64884
  }
63831
64885
  }, [propReportId]);
63832
- useEffect31(() => {
64886
+ useEffect32(() => {
63833
64887
  setChartAxisEdits({});
63834
64888
  setChartVisibilityOverrides({});
63835
64889
  }, [effectiveReportId]);
63836
64890
  const bootstrapReportTaskUsedForReportIdRef = useRef24(null);
64891
+ const sharedInitialRequestUsedForReportIdRef = useRef24(null);
63837
64892
  const schemaScopeInitializedForReportIdRef = useRef24(null);
63838
64893
  const initializeSchemaScopeForReport = (report) => {
63839
64894
  if (!report) return;
@@ -63855,6 +64910,55 @@ function useReport(reportIdArg, options = {}) {
63855
64910
  () => customFiltersFromFilterStack(filterStack),
63856
64911
  [filterStack]
63857
64912
  );
64913
+ const forceIncompleteCacheCabForParity = process.env.NODE_ENV === "test" && process.env.QUILL_FORCE_INCOMPLETE_CACHECAB_FOR_PARITY === "true";
64914
+ const resolveCachedReport = useCallback5(
64915
+ async ({
64916
+ reportBuilderState,
64917
+ filters,
64918
+ pivot,
64919
+ operation
64920
+ }) => {
64921
+ const startedAt = performance.now();
64922
+ const snapshot = await cacheCab.getReportSnapshot(
64923
+ effectiveReportId,
64924
+ tenants
64925
+ );
64926
+ const state = reportBuilderState ?? snapshot?.report.reportBuilderState;
64927
+ const resolution = resolveReportFromCacheCab({
64928
+ snapshot: snapshot && forceIncompleteCacheCabForParity ? { ...snapshot, complete: true, reason: "complete" } : snapshot,
64929
+ reportBuilderState: state,
64930
+ filters: filters ?? customFiltersFromFilterStack(state?.filterStack ?? []),
64931
+ pivot,
64932
+ operation,
64933
+ rowLimit: operation === "unique-values" ? void 0 : Math.max(
64934
+ DEFAULT_USE_REPORT_ROWS_PER_REQUEST,
64935
+ pagination.pageSize
64936
+ )
64937
+ });
64938
+ const diagnostics = globalThis.__QUILL_CACHECAB_DIAGNOSTICS__;
64939
+ if (Array.isArray(diagnostics)) {
64940
+ diagnostics.push({
64941
+ reportId: effectiveReportId,
64942
+ operation,
64943
+ outcome: resolution.resolved ? "hit" : "fallback",
64944
+ reason: resolution.resolved ? "complete" : resolution.reason,
64945
+ details: resolution.resolved ? void 0 : resolution.details,
64946
+ snapshotReason: snapshot?.reason ?? "cache_miss",
64947
+ forcedIncompleteSnapshot: Boolean(snapshot) && !snapshot?.complete && forceIncompleteCacheCabForParity,
64948
+ rowCount: snapshot?.rowCount ?? 0,
64949
+ durationMs: performance.now() - startedAt
64950
+ });
64951
+ }
64952
+ return resolution.resolved ? resolution.report : null;
64953
+ },
64954
+ [
64955
+ cacheCab,
64956
+ effectiveReportId,
64957
+ forceIncompleteCacheCabForParity,
64958
+ pagination.pageSize,
64959
+ tenants
64960
+ ]
64961
+ );
63858
64962
  const reloadKey = useMemo33(() => 0, []);
63859
64963
  const schemaForReportBuilderState = useMemo33(() => {
63860
64964
  if (schemaData.schemaWithCustomFields?.length) {
@@ -63928,7 +65032,7 @@ function useReport(reportIdArg, options = {}) {
63928
65032
  restrictFieldOptionsToSelectedDatasources,
63929
65033
  selectedBaseTableNamesForFieldOptions
63930
65034
  ]);
63931
- useEffect31(() => {
65035
+ useEffect32(() => {
63932
65036
  if (reportOverride) return;
63933
65037
  if (propReportId) return;
63934
65038
  if (createdReportBootstrap) return;
@@ -64278,15 +65382,11 @@ function useReport(reportIdArg, options = {}) {
64278
65382
  return null;
64279
65383
  }
64280
65384
  return {
64281
- reportBuilderState: {
64282
- ...sourceReportBuilderState,
64283
- columns: columnsForUniqueValues,
64284
- // Distinct string options for the filter query builder must not use the
64285
- // live filterStack: on surfaces that call setFilters on every query-builder
64286
- // change, in-progress rules refetch unique values and the engine often
64287
- // returns empty columns (no rows / invalid stack), clearing all multiselects.
64288
- filterStack: []
64289
- },
65385
+ // Distinct string options must not depend on presentation or live filters.
65386
+ reportBuilderState: createFilterUniqueValuesReportBuilderState(
65387
+ sourceReportBuilderState,
65388
+ columnsForUniqueValues
65389
+ ),
64290
65390
  stringColumns: stringColumns2,
64291
65391
  stringColumnsByTable: columnsForUniqueValues,
64292
65392
  dashboardName
@@ -64390,11 +65490,36 @@ function useReport(reportIdArg, options = {}) {
64390
65490
  databaseType: client?.databaseType
64391
65491
  })
64392
65492
  ],
64393
- queryFn: createUseFormQueryFn(async () => {
65493
+ queryFn: createUseFormQueryFn(async (signal) => {
64394
65494
  try {
64395
65495
  if (!client || !filterUniqueValuesRequest) {
64396
65496
  return { uniqueValuesByColumn: {} };
64397
65497
  }
65498
+ const cachedReport = await resolveCachedReport({
65499
+ reportBuilderState: filterUniqueValuesRequest.reportBuilderState,
65500
+ filters: [],
65501
+ pivot: null,
65502
+ operation: "unique-values"
65503
+ });
65504
+ if (cachedReport?.uniqueStringsByTable) {
65505
+ const localValues = {};
65506
+ let hasEveryColumn = true;
65507
+ for (const column of filterUniqueValuesRequest.stringColumnsByTable) {
65508
+ const table2 = String(column.table ?? "").trim();
65509
+ const field = String(column.field ?? "").trim();
65510
+ const values = cachedReport.uniqueStringsByTable[table2]?.[field] ?? cachedReport.uniqueStringsByTable[""]?.[field];
65511
+ if (!values) {
65512
+ hasEveryColumn = false;
65513
+ break;
65514
+ }
65515
+ localValues[field] = values;
65516
+ const alias = String(column.alias ?? "").trim();
65517
+ if (alias) localValues[alias] = values;
65518
+ }
65519
+ if (hasEveryColumn) {
65520
+ return { uniqueValuesByColumn: localValues };
65521
+ }
65522
+ }
64398
65523
  const tablesForUniqueValues = filterUniqueValuesRequest.reportBuilderState?.tables?.map(
64399
65524
  (table2) => String(table2?.name ?? "").trim()
64400
65525
  ) ?? filterUniqueValuesRequest.stringColumnsByTable.map((column) => String(column.table ?? "").trim()).filter(Boolean);
@@ -64407,7 +65532,8 @@ function useReport(reportIdArg, options = {}) {
64407
65532
  customFields: schemaData.customFields ?? void 0,
64408
65533
  tenants,
64409
65534
  dashboardName: filterUniqueValuesRequest.dashboardName,
64410
- getToken
65535
+ getToken,
65536
+ abortSignal: signal
64411
65537
  });
64412
65538
  const aliasToField = {};
64413
65539
  for (const column of filterUniqueValuesRequest.stringColumnsByTable) {
@@ -64439,12 +65565,18 @@ function useReport(reportIdArg, options = {}) {
64439
65565
  }
64440
65566
  });
64441
65567
  return { uniqueValuesByColumn: normalizedUniqueValuesByColumn };
64442
- } catch {
65568
+ } catch (error) {
65569
+ if (signal.aborted || error instanceof Error && error.name === "AbortError") {
65570
+ throw error;
65571
+ }
64443
65572
  return { uniqueValuesByColumn: {} };
64444
65573
  }
64445
65574
  }),
64446
65575
  enabled: filterUniqueValuesEnabled,
64447
- retry: false
65576
+ retry: false,
65577
+ staleTime: 5 * 6e4,
65578
+ gcTime: 15 * 6e4,
65579
+ placeholderData: keepPreviousData
64448
65580
  });
64449
65581
  const backendUniqueValuesByFieldName = useMemo33(() => {
64450
65582
  const valuesByField = /* @__PURE__ */ new Map();
@@ -64824,14 +65956,15 @@ function useReport(reportIdArg, options = {}) {
64824
65956
  () => stableSerializeForQueryKey(reloadKey),
64825
65957
  [reloadKey]
64826
65958
  );
64827
- useEffect31(() => {
65959
+ useEffect32(() => {
64828
65960
  if (!reportOverride) return;
64829
65961
  initializeSchemaScopeForReport(reportOverride);
64830
65962
  setSourceReport(reportOverride);
64831
65963
  }, [reportOverride, propReportId]);
64832
- useEffect31(() => {
65964
+ useEffect32(() => {
64833
65965
  schemaScopeInitializedForReportIdRef.current = null;
64834
65966
  bootstrapReportTaskUsedForReportIdRef.current = null;
65967
+ sharedInitialRequestUsedForReportIdRef.current = null;
64835
65968
  setPreserveSchemaWideOptions(false);
64836
65969
  setInitialSchemaScopedTableNames(null);
64837
65970
  if (!reportOverride) {
@@ -64885,7 +66018,33 @@ function useReport(reportIdArg, options = {}) {
64885
66018
  flagHash,
64886
66019
  clientHash
64887
66020
  }),
64888
- queryFn: createUseFormQueryFn(async () => {
66021
+ queryFn: createUseFormQueryFn(async (signal) => {
66022
+ let cachedReportBuilderState = initialReportBuilderStateForLoad ?? void 0;
66023
+ if (!cachedReportBuilderState) {
66024
+ const snapshot = await cacheCab.getReportSnapshot(
66025
+ effectiveReportId,
66026
+ tenants
66027
+ );
66028
+ if (snapshot?.complete || snapshot && forceIncompleteCacheCabForParity) {
66029
+ cachedReportBuilderState = await fetchReportBuilderStateByReportId({
66030
+ reportId: effectiveReportId,
66031
+ client,
66032
+ getToken,
66033
+ draftSessionId: draftSessionId || void 0,
66034
+ tenants,
66035
+ abortSignal: signal
66036
+ }) ?? void 0;
66037
+ }
66038
+ }
66039
+ const cachedReport = await resolveCachedReport({
66040
+ reportBuilderState: cachedReportBuilderState,
66041
+ filters: cachedReportBuilderState ? customFiltersFromFilterStack(cachedReportBuilderState.filterStack) : void 0,
66042
+ pivot: cachedReportBuilderState?.pivot,
66043
+ operation: "initial"
66044
+ });
66045
+ if (cachedReport) {
66046
+ return { report: cachedReport };
66047
+ }
64889
66048
  const allowReportTaskBootstrap = !initialReportBuilderStateForLoad && bootstrapReportTaskUsedForReportIdRef.current !== effectiveReportId;
64890
66049
  const loadTargetId = String(effectiveReportId ?? "").trim();
64891
66050
  const sourceReportIdentity = String(
@@ -64893,9 +66052,7 @@ function useReport(reportIdArg, options = {}) {
64893
66052
  ).trim();
64894
66053
  const formFiltersBelongToLoadTarget = Boolean(loadTargetId) && sourceReportIdentity === loadTargetId;
64895
66054
  const internalFiltersForLoad = useInMemoryEngines || !formFiltersBelongToLoadTarget ? [] : internalFilters;
64896
- if (allowReportTaskBootstrap) {
64897
- bootstrapReportTaskUsedForReportIdRef.current = effectiveReportId;
64898
- }
66055
+ const shareInitialRequest = sharedInitialRequestUsedForReportIdRef.current !== effectiveReportId;
64899
66056
  const loadResult = await loadReportForUseForm({
64900
66057
  reportId: effectiveReportId,
64901
66058
  initialReportBuilderState: initialReportBuilderStateForLoad,
@@ -64911,8 +66068,18 @@ function useReport(reportIdArg, options = {}) {
64911
66068
  dashboardName: resolvedSourceDashboardName,
64912
66069
  useInMemoryEngines,
64913
66070
  draftSessionId: draftSessionId || void 0,
64914
- rowsOnly: isCreatedReportBootstrapLoad
66071
+ shareInitialRequest,
66072
+ rowsOnly: isCreatedReportBootstrapLoad,
66073
+ abortSignal: signal
64915
66074
  });
66075
+ if (loadResult.report && !loadResult.error) {
66076
+ if (allowReportTaskBootstrap) {
66077
+ bootstrapReportTaskUsedForReportIdRef.current = effectiveReportId;
66078
+ }
66079
+ if (shareInitialRequest) {
66080
+ sharedInitialRequestUsedForReportIdRef.current = effectiveReportId;
66081
+ }
66082
+ }
64916
66083
  return loadResult;
64917
66084
  }),
64918
66085
  enabled: Boolean(effectiveReportId) && Boolean(client) && !reportOverride,
@@ -64924,7 +66091,7 @@ function useReport(reportIdArg, options = {}) {
64924
66091
  reportId: reportNameQueryReportId,
64925
66092
  clientHash
64926
66093
  }),
64927
- queryFn: createUseFormQueryFn(async () => {
66094
+ queryFn: createUseFormQueryFn(async (signal) => {
64928
66095
  if (!client) {
64929
66096
  return { name: null };
64930
66097
  }
@@ -64932,7 +66099,8 @@ function useReport(reportIdArg, options = {}) {
64932
66099
  reportId: reportNameQueryReportId,
64933
66100
  client,
64934
66101
  getToken,
64935
- tenants
66102
+ tenants,
66103
+ abortSignal: signal
64936
66104
  });
64937
66105
  }),
64938
66106
  enabled: Boolean(reportNameQueryReportId) && Boolean(client),
@@ -64945,13 +66113,13 @@ function useReport(reportIdArg, options = {}) {
64945
66113
  const reportTitleFromTask = String(reportNameQuery.data?.name ?? "").trim();
64946
66114
  const resolvedReportName = reportNameQueryReportId && sourceReportIdForTitle === reportNameQueryReportId && sourceReportNameForTitle ? sourceReportNameForTitle : reportTitleFromTask || sourceReportNameForTitle || "";
64947
66115
  const displayReportName = formReportName !== void 0 ? formReportName : resolvedReportName;
64948
- useEffect31(() => {
66116
+ useEffect32(() => {
64949
66117
  if (reportOverride) return;
64950
66118
  const report = initialLoadQuery.data?.report ?? null;
64951
66119
  initializeSchemaScopeForReport(report);
64952
66120
  setSourceReport(report);
64953
66121
  }, [initialLoadQuery.data, reportOverride, effectiveReportId]);
64954
- useEffect31(() => {
66122
+ useEffect32(() => {
64955
66123
  if (!sourceReport) {
64956
66124
  return;
64957
66125
  }
@@ -65019,14 +66187,14 @@ function useReport(reportIdArg, options = {}) {
65019
66187
  }) || void 0
65020
66188
  ) : shouldApplyPivotAggregationsFromSource ? [] : prev.aggregationTablesByIndex;
65021
66189
  const sourceRowGroupOption = encodePivotGroupOptionValue(
65022
- resolveColumnTableFromReportMetadata(
66190
+ String(sourceReport.pivot?.rowFieldTable ?? "").trim() || resolveColumnTableFromReportMetadata(
65023
66191
  sourceReport,
65024
66192
  sourceReport.pivot?.rowField ?? ""
65025
66193
  ),
65026
66194
  sourceReport.pivot?.rowField ?? ""
65027
66195
  ) || sourceReport.pivot?.rowField;
65028
66196
  const sourceColumnGroupOption = encodePivotGroupOptionValue(
65029
- resolveColumnTableFromReportMetadata(
66197
+ String(sourceReport.pivot?.columnFieldTable ?? "").trim() || resolveColumnTableFromReportMetadata(
65030
66198
  sourceReport,
65031
66199
  sourceReport.pivot?.columnField ?? ""
65032
66200
  ),
@@ -65177,7 +66345,7 @@ function useReport(reportIdArg, options = {}) {
65177
66345
  chartPivotHydrationEpoch
65178
66346
  ]);
65179
66347
  const hasDatePivotRow = Boolean(String(pivotState?.rowField ?? "").trim()) && isDateType(String(pivotState?.rowFieldType ?? ""));
65180
- useEffect31(() => {
66348
+ useEffect32(() => {
65181
66349
  const previous = prevPivotStateForColumnExpansionRef.current;
65182
66350
  if (previous && !pivotState) {
65183
66351
  setExpandReportBuilderColumnsForFlatTable(true);
@@ -65442,7 +66610,7 @@ function useReport(reportIdArg, options = {}) {
65442
66610
  queryColumns,
65443
66611
  sourceReport
65444
66612
  ]);
65445
- useEffect31(() => {
66613
+ useEffect32(() => {
65446
66614
  if (!Array.isArray(filterStack) || filterStack.length === 0) {
65447
66615
  return;
65448
66616
  }
@@ -65546,7 +66714,7 @@ function useReport(reportIdArg, options = {}) {
65546
66714
  [effectiveReportBuilderState]
65547
66715
  );
65548
66716
  const prevPaginationResetStateHashRef = useRef24(null);
65549
- useEffect31(() => {
66717
+ useEffect32(() => {
65550
66718
  const prevHash = prevPaginationResetStateHashRef.current;
65551
66719
  prevPaginationResetStateHashRef.current = effectiveReportBuilderStateHash;
65552
66720
  if (prevHash === null || prevHash === effectiveReportBuilderStateHash) {
@@ -65558,9 +66726,7 @@ function useReport(reportIdArg, options = {}) {
65558
66726
  if (paginationRef.current.pageIndex === 0) {
65559
66727
  return;
65560
66728
  }
65561
- applyPaginationUpdate((old) => ({ ...old, pageIndex: 0 }), {
65562
- activate: false
65563
- });
66729
+ applyPaginationUpdate((old) => ({ ...old, pageIndex: 0 }));
65564
66730
  }, [applyPaginationUpdate, effectiveReportBuilderStateHash]);
65565
66731
  const pivotTableDataReportBuilderState = useMemo33(() => {
65566
66732
  if (!effectiveReportBuilderState?.pivot) {
@@ -65622,22 +66788,60 @@ function useReport(reportIdArg, options = {}) {
65622
66788
  filterStack
65623
66789
  ]);
65624
66790
  const shouldForceTableRefreshFromFilters = !useInMemoryEngines && !pivotState;
65625
- const tableRefreshQueryEnabled = !reportOverride && tableRefreshVersion > 0 && Boolean(sourceReport) && Boolean(client) && Boolean(effectiveReportBuilderState) && !pendingPivotRefresh && sourceReportMatchesEffectiveReportId && (refreshDecision.shouldRefresh || shouldForceTableRefreshFromFilters && !shouldSkipRedundantFlatTableReportBuilderQuery);
66791
+ const immediateTableRefreshInput = useMemo33(
66792
+ () => ({
66793
+ version: tableRefreshVersion,
66794
+ reportBuilderStateHash: effectiveReportBuilderStateHash,
66795
+ reportBuilderState: effectiveReportBuilderState,
66796
+ shouldRefresh: refreshDecision.shouldRefresh || shouldForceTableRefreshFromFilters && !shouldSkipRedundantFlatTableReportBuilderQuery
66797
+ }),
66798
+ [
66799
+ effectiveReportBuilderState,
66800
+ effectiveReportBuilderStateHash,
66801
+ refreshDecision.shouldRefresh,
66802
+ shouldForceTableRefreshFromFilters,
66803
+ shouldSkipRedundantFlatTableReportBuilderQuery,
66804
+ tableRefreshVersion
66805
+ ]
66806
+ );
66807
+ const debouncedTableRefreshInput = useDebouncedRefreshInput(
66808
+ immediateTableRefreshInput
66809
+ );
66810
+ const disableTableRefreshDebounceForDiagnostics = process.env.NODE_ENV === "test" && process.env.QUILL_DISABLE_REPORT_REFRESH_DEBOUNCE === "true";
66811
+ const tableRefreshInput = disableTableRefreshDebounceForDiagnostics ? immediateTableRefreshInput : debouncedTableRefreshInput;
66812
+ const tableRefreshInputIsCurrent = tableRefreshInput.version === tableRefreshVersion && tableRefreshInput.reportBuilderStateHash === effectiveReportBuilderStateHash;
66813
+ const completedTableRefreshRef = useRef24(null);
66814
+ if (completedTableRefreshRef.current?.reportId !== effectiveReportId) {
66815
+ completedTableRefreshRef.current = null;
66816
+ }
66817
+ const tableRefreshInputAlreadyCompleted = !disableTableRefreshDebounceForDiagnostics && completedTableRefreshRef.current?.reportId === effectiveReportId && completedTableRefreshRef.current.version >= tableRefreshInput.version;
66818
+ const tableRefreshQueryEnabled = !reportOverride && tableRefreshInputIsCurrent && !tableRefreshInputAlreadyCompleted && tableRefreshInput.version > 0 && Boolean(sourceReport) && Boolean(client) && Boolean(tableRefreshInput.reportBuilderState) && !pendingPivotRefresh && sourceReportMatchesEffectiveReportId && tableRefreshInput.shouldRefresh;
65626
66819
  const tableRefreshQuery = useQuery({
65627
66820
  queryKey: createUseFormTableRefreshQueryKey({
65628
66821
  reportId: effectiveReportId,
65629
- tableRefreshVersion,
65630
- reportBuilderStateHash: effectiveReportBuilderStateHash,
66822
+ tableRefreshVersion: tableRefreshInput.version,
66823
+ reportBuilderStateHash: tableRefreshInput.reportBuilderStateHash,
65631
66824
  tenantHash,
65632
66825
  clientHash
65633
66826
  }),
65634
- queryFn: createUseFormQueryFn(async () => {
65635
- if (!effectiveReportBuilderState || !sourceReport || !client) {
66827
+ queryFn: createUseFormQueryFn(async (signal) => {
66828
+ if (!tableRefreshInput.reportBuilderState || !sourceReport || !client) {
65636
66829
  return { report: null, error: "Missing table refresh prerequisites" };
65637
66830
  }
66831
+ const cachedReport = await resolveCachedReport({
66832
+ reportBuilderState: tableRefreshInput.reportBuilderState,
66833
+ filters: customFiltersFromFilterStack(
66834
+ tableRefreshInput.reportBuilderState.filterStack
66835
+ ),
66836
+ pivot: null,
66837
+ operation: "table"
66838
+ });
66839
+ if (cachedReport) {
66840
+ return { report: cachedReport };
66841
+ }
65638
66842
  return loadViaReportBuilderState({
65639
66843
  reportId: effectiveReportId,
65640
- reportBuilderState: effectiveReportBuilderState,
66844
+ reportBuilderState: tableRefreshInput.reportBuilderState,
65641
66845
  client,
65642
66846
  getToken,
65643
66847
  tenants,
@@ -65645,7 +66849,8 @@ function useReport(reportIdArg, options = {}) {
65645
66849
  customFields: schemaData.customFields,
65646
66850
  dashboardName: resolvedSourceDashboardName,
65647
66851
  report: sourceReport,
65648
- draftSessionId: draftSessionId || void 0
66852
+ draftSessionId: draftSessionId || void 0,
66853
+ abortSignal: signal
65649
66854
  });
65650
66855
  }),
65651
66856
  enabled: tableRefreshQueryEnabled,
@@ -65660,7 +66865,7 @@ function useReport(reportIdArg, options = {}) {
65660
66865
  tenantHash,
65661
66866
  clientHash
65662
66867
  ],
65663
- queryFn: createUseFormQueryFn(async () => {
66868
+ queryFn: createUseFormQueryFn(async (signal) => {
65664
66869
  if (!createdBootstrapForInitialLoad || !initialReportBuilderStateForLoad || !client) {
65665
66870
  return { report: null, error: "Missing bootstrap count prerequisites" };
65666
66871
  }
@@ -65675,19 +66880,29 @@ function useReport(reportIdArg, options = {}) {
65675
66880
  dashboardName: resolvedSourceDashboardName,
65676
66881
  report: createdBootstrapForInitialLoad.report,
65677
66882
  draftSessionId: draftSessionId || void 0,
65678
- rowCountOnly: true
66883
+ rowCountOnly: true,
66884
+ abortSignal: signal
65679
66885
  });
65680
66886
  }),
65681
66887
  enabled: isCreatedReportBootstrapLoad && Boolean(client) && !reportOverride,
65682
66888
  retry: false
65683
66889
  });
65684
- useEffect31(() => {
66890
+ useEffect32(() => {
65685
66891
  if (!tableRefreshQueryEnabled) return;
65686
66892
  const report = tableRefreshQuery.data?.report;
65687
66893
  if (tableRefreshQuery.data?.error || !report) return;
66894
+ completedTableRefreshRef.current = {
66895
+ reportId: effectiveReportId,
66896
+ version: tableRefreshInput.version
66897
+ };
65688
66898
  setSourceReport(report);
65689
- }, [effectiveReportId, tableRefreshQuery.data, tableRefreshQueryEnabled]);
65690
- useEffect31(() => {
66899
+ }, [
66900
+ effectiveReportId,
66901
+ tableRefreshInput.version,
66902
+ tableRefreshQuery.data,
66903
+ tableRefreshQueryEnabled
66904
+ ]);
66905
+ useEffect32(() => {
65691
66906
  const countReport = createdBootstrapRowCountQuery.data?.report;
65692
66907
  if (createdBootstrapRowCountQuery.data?.error || !countReport) return;
65693
66908
  setSourceReport((currentReport) => {
@@ -65707,7 +66922,7 @@ function useReport(reportIdArg, options = {}) {
65707
66922
  return getChartTypeOptions2({ pivot: pivotState });
65708
66923
  }, [pivotState]);
65709
66924
  const resolvedChartType = chartTypes.find((option) => option.value === chartType)?.value ?? chartTypes.find((option) => option.value === sourceReport?.chartType)?.value ?? chartTypes.find((option) => option.value === DEFAULT_CHART_TYPE)?.value ?? chartTypes[0]?.value ?? DEFAULT_CHART_TYPE;
65710
- useEffect31(() => {
66925
+ useEffect32(() => {
65711
66926
  if (!sourceReport) {
65712
66927
  return;
65713
66928
  }
@@ -66202,7 +67417,7 @@ function useReport(reportIdArg, options = {}) {
66202
67417
  );
66203
67418
  return hasSamePivot && sourceFilterStackHash === filterStackHash;
66204
67419
  }, [filterStackHash, nextPivot, sourceReport]);
66205
- useEffect31(() => {
67420
+ useEffect32(() => {
66206
67421
  if (!pendingPivotRefresh) return;
66207
67422
  if (!nextPivot) {
66208
67423
  if (!sourceReport) {
@@ -66261,10 +67476,19 @@ function useReport(reportIdArg, options = {}) {
66261
67476
  flagHash,
66262
67477
  clientHash
66263
67478
  }),
66264
- queryFn: createUseFormQueryFn(async () => {
67479
+ queryFn: createUseFormQueryFn(async (signal) => {
66265
67480
  if (!effectiveReportBuilderState || !nextPivot || !sourceReport || !client) {
66266
67481
  return { report: null, error: "Missing pivot refresh prerequisites" };
66267
67482
  }
67483
+ const cachedReport = await resolveCachedReport({
67484
+ reportBuilderState: effectiveReportBuilderState,
67485
+ filters: customFilters,
67486
+ pivot: nextPivot,
67487
+ operation: "pivot"
67488
+ });
67489
+ if (cachedReport) {
67490
+ return { report: cachedReport };
67491
+ }
66268
67492
  const pivotRefreshBaseReport = {
66269
67493
  ...sourceReport,
66270
67494
  reportBuilderState: effectiveReportBuilderState
@@ -66284,7 +67508,8 @@ function useReport(reportIdArg, options = {}) {
66284
67508
  customFields: schemaData.customFields,
66285
67509
  dashboardName: resolvedSourceDashboardName,
66286
67510
  useInMemoryEngines,
66287
- draftSessionId: draftSessionId || void 0
67511
+ draftSessionId: draftSessionId || void 0,
67512
+ abortSignal: signal
66288
67513
  });
66289
67514
  return pivotRefreshResult;
66290
67515
  }),
@@ -66295,16 +67520,28 @@ function useReport(reportIdArg, options = {}) {
66295
67520
  queryKey: createUseFormPivotTableDataRefreshQueryKey({
66296
67521
  reportId: effectiveReportId,
66297
67522
  reportBuilderStateHash: pivotTableDataReportBuilderStateHash,
67523
+ pageSize: pagination.pageSize,
66298
67524
  tenantHash,
66299
67525
  clientHash
66300
67526
  }),
66301
- queryFn: createUseFormQueryFn(async () => {
67527
+ queryFn: createUseFormQueryFn(async (signal) => {
66302
67528
  if (!pivotTableDataReportBuilderState || !sourceReport || !client) {
66303
67529
  return {
66304
67530
  report: null,
66305
67531
  error: "Missing pivot table-data refresh prerequisites"
66306
67532
  };
66307
67533
  }
67534
+ const cachedReport = await resolveCachedReport({
67535
+ reportBuilderState: pivotTableDataReportBuilderState,
67536
+ filters: customFiltersFromFilterStack(
67537
+ pivotTableDataReportBuilderState.filterStack
67538
+ ),
67539
+ pivot: null,
67540
+ operation: "pivot-table"
67541
+ });
67542
+ if (cachedReport) {
67543
+ return { report: cachedReport };
67544
+ }
66308
67545
  const tableDataResult = await loadViaReportBuilderState({
66309
67546
  reportId: effectiveReportId,
66310
67547
  reportBuilderState: pivotTableDataReportBuilderState,
@@ -66315,7 +67552,12 @@ function useReport(reportIdArg, options = {}) {
66315
67552
  customFields: schemaData.customFields,
66316
67553
  dashboardName: resolvedSourceDashboardName,
66317
67554
  report: sourceReport,
66318
- draftSessionId: draftSessionId || void 0
67555
+ draftSessionId: draftSessionId || void 0,
67556
+ pagination: {
67557
+ pageIndex: 0,
67558
+ pageSize: pagination.pageSize
67559
+ },
67560
+ abortSignal: signal
66319
67561
  });
66320
67562
  return tableDataResult;
66321
67563
  }),
@@ -66324,28 +67566,20 @@ function useReport(reportIdArg, options = {}) {
66324
67566
  });
66325
67567
  const tablePageReportBuilderState = pivotTableDataReportBuilderState ?? effectiveReportBuilderState;
66326
67568
  const tablePageReportBuilderStateHash = pivotTableDataReportBuilderState ? pivotTableDataReportBuilderStateHash : effectiveReportBuilderStateHash;
66327
- const tablePaginationSort = useMemo33(() => {
66328
- const sortEntry = tablePageReportBuilderState?.sort?.[0];
66329
- const field = String(sortEntry?.field ?? "").trim();
66330
- if (!field || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(field)) {
66331
- return void 0;
66332
- }
66333
- const isOutputColumn = (tablePageReportBuilderState?.columns ?? []).some(
66334
- (column) => String(column?.field ?? "").trim() === field
66335
- );
66336
- if (!isOutputColumn) {
66337
- return void 0;
66338
- }
66339
- const direction = String(sortEntry?.direction ?? "ASC").toUpperCase() === "DESC" ? "DESC" : "ASC";
66340
- return { field, direction };
66341
- }, [tablePageReportBuilderState]);
67569
+ const tablePaginationSort = useMemo33(
67570
+ () => getStablePaginationSort({
67571
+ columns: tablePageReportBuilderState?.columns,
67572
+ sort: tablePageReportBuilderState?.sort
67573
+ }),
67574
+ [tablePageReportBuilderState]
67575
+ );
66342
67576
  const baseWindowRowsLength = Array.isArray(sourceReport?.rows) ? sourceReport.rows.length : 0;
66343
67577
  const sourceReportRowCount = typeof sourceReport?.rowCount === "number" ? sourceReport.rowCount : void 0;
66344
67578
  const trustedSourceRowCount = sourceReportRowCount !== void 0 && (sourceReportRowCount > 0 || baseWindowRowsLength === 0) && sourceReportRowCount >= baseWindowRowsLength ? sourceReportRowCount : void 0;
66345
67579
  const paginationRangeStart = pagination.pageIndex * pagination.pageSize;
66346
67580
  const paginationRangeEnd = paginationRangeStart + pagination.pageSize;
66347
67581
  const pageWithinBaseWindow = paginationRangeEnd <= baseWindowRowsLength || trustedSourceRowCount !== void 0 && trustedSourceRowCount <= baseWindowRowsLength;
66348
- const tablePageQueryEnabled = paginationActive && !pageWithinBaseWindow && !reportOverride && !useInMemoryEngines && Boolean(client) && Boolean(sourceReport) && Boolean(tablePageReportBuilderState) && sourceReportMatchesEffectiveReportId && !pendingPivotRefresh;
67582
+ const tablePageQueryEnabled = !pageWithinBaseWindow && !reportOverride && !useInMemoryEngines && Boolean(client) && Boolean(sourceReport) && Boolean(tablePageReportBuilderState) && sourceReportMatchesEffectiveReportId && !pendingPivotRefresh;
66349
67583
  const tablePageQuery = useQuery({
66350
67584
  queryKey: createUseFormTablePageQueryKey({
66351
67585
  reportId: effectiveReportId,
@@ -66356,7 +67590,7 @@ function useReport(reportIdArg, options = {}) {
66356
67590
  tenantHash,
66357
67591
  clientHash
66358
67592
  }),
66359
- queryFn: createUseFormQueryFn(async () => {
67593
+ queryFn: createUseFormQueryFn(async (signal) => {
66360
67594
  if (!tablePageReportBuilderState || !sourceReport || !client) {
66361
67595
  return { report: null, error: "Missing table page prerequisites" };
66362
67596
  }
@@ -66376,7 +67610,8 @@ function useReport(reportIdArg, options = {}) {
66376
67610
  pageSize: pagination.pageSize
66377
67611
  },
66378
67612
  paginationSort: tablePaginationSort,
66379
- rowsOnly: true
67613
+ rowsOnly: true,
67614
+ abortSignal: signal
66380
67615
  });
66381
67616
  }),
66382
67617
  enabled: tablePageQueryEnabled,
@@ -66410,7 +67645,7 @@ function useReport(reportIdArg, options = {}) {
66410
67645
  pagination.pageSize
66411
67646
  );
66412
67647
  paginationPageCountRef.current = paginationPageCount;
66413
- useEffect31(() => {
67648
+ useEffect32(() => {
66414
67649
  if (!pivotTableDataRefreshQueryEnabled) return;
66415
67650
  if (pivotTableDataRefreshQuery.status !== "success" && pivotTableDataRefreshQuery.status !== "error") {
66416
67651
  return;
@@ -66451,7 +67686,7 @@ function useReport(reportIdArg, options = {}) {
66451
67686
  pivotTableDataRefreshQueryEnabled,
66452
67687
  effectiveReportId
66453
67688
  ]);
66454
- useEffect31(() => {
67689
+ useEffect32(() => {
66455
67690
  if (!pivotRefreshQueryEnabled) return;
66456
67691
  if (pivotRefreshQuery.status !== "success" && pivotRefreshQuery.status !== "error") {
66457
67692
  return;
@@ -66841,18 +68076,18 @@ function useReport(reportIdArg, options = {}) {
66841
68076
  resolvedYAxisFieldsForDisplay
66842
68077
  );
66843
68078
  if (String(baseChart.chartType ?? "").toLowerCase() === "table" && baseChart.pivot) {
66844
- const pivotAggregations = getPivotAggregations(baseChart);
68079
+ const pivotAggregations2 = getPivotAggregations(baseChart);
66845
68080
  const hasCustomMeasureLabel = resolvedYAxisFields.some((axis) => {
66846
68081
  const aggregation = findAggregationForFieldStrict(
66847
68082
  axis.field,
66848
- pivotAggregations
68083
+ pivotAggregations2
66849
68084
  );
66850
68085
  if (!aggregation) return false;
66851
68086
  const label = String(axis.label ?? "").trim();
66852
68087
  const defaultLabel = buildAggregationLabel(aggregation, baseChart);
66853
68088
  return Boolean(label) && label !== defaultLabel;
66854
68089
  });
66855
- if (pivotAggregations.length > 1 || hasCustomMeasureLabel) {
68090
+ if (pivotAggregations2.length > 1 || hasCustomMeasureLabel) {
66856
68091
  columns2 = mergePivotTableDisplayColumnLabelsFromYAxis({
66857
68092
  columns: columns2 ?? baseChart.columns,
66858
68093
  yAxisFields: resolvedYAxisFieldsForDisplay,
@@ -67204,7 +68439,7 @@ function useReport(reportIdArg, options = {}) {
67204
68439
  );
67205
68440
  const table = useMemo33(() => {
67206
68441
  const baseWindowRows = Array.isArray(sourceReport?.rows) ? sourceReport.rows : [];
67207
- const rawRows = !paginationActive ? baseWindowRows : tablePageRows ?? baseWindowRows.slice(paginationRangeStart, paginationRangeEnd);
68442
+ const rawRows = tablePageRows ?? baseWindowRows.slice(paginationRangeStart, paginationRangeEnd);
67208
68443
  const { columns: mergedFromReport } = mergeDisplayAndSourceForTableFormats({
67209
68444
  effectiveReportBuilderTableNames,
67210
68445
  scopedSchemaColumns,
@@ -67313,7 +68548,6 @@ function useReport(reportIdArg, options = {}) {
67313
68548
  sourceReport,
67314
68549
  tableColumnsEditedSignature,
67315
68550
  pagination,
67316
- paginationActive,
67317
68551
  paginationPageCount,
67318
68552
  paginationRangeEnd,
67319
68553
  paginationRangeStart,
@@ -68399,15 +69633,54 @@ function useReport(reportIdArg, options = {}) {
68399
69633
  });
68400
69634
  }
68401
69635
  } catch (error) {
69636
+ const requestedFieldNames = (resolved?.rules ?? []).map((rule) => String(rule?.field ?? "").trim()).filter(Boolean);
69637
+ const relevantFieldConfigs = Object.fromEntries(
69638
+ Object.entries(queryBuilderFieldConfigByName ?? {}).filter(
69639
+ ([key, config]) => {
69640
+ const configuredField = String(config?.field ?? "").trim();
69641
+ return requestedFieldNames.some((requestedField) => {
69642
+ const bareRequestedField = requestedField.split(".").pop() ?? "";
69643
+ return key === requestedField || configuredField === requestedField || configuredField === bareRequestedField;
69644
+ });
69645
+ }
69646
+ )
69647
+ );
68402
69648
  console.error("[useForm] setFilters swallowed error", {
68403
69649
  error: error instanceof Error ? error.message : String(error),
68404
69650
  stack: error instanceof Error ? error.stack : void 0,
69651
+ reportId: effectiveReportId,
68405
69652
  requestedRules: (resolved?.rules ?? []).map((rule) => ({
68406
69653
  table: rule?.table,
68407
69654
  field: rule?.field,
68408
69655
  operator: rule?.operator,
68409
69656
  value: rule?.value
68410
69657
  })),
69658
+ normalizedRules: (nextFiltersNormalizedForConfig?.rules ?? []).map((rule) => ({
69659
+ table: rule?.table,
69660
+ field: rule?.field,
69661
+ operator: rule?.operator,
69662
+ value: rule?.value
69663
+ })),
69664
+ relevantFieldConfigs,
69665
+ pivot: nextPivot,
69666
+ reportBuilderTables: effectiveReportBuilderState?.tables,
69667
+ reportBuilderColumns: effectiveReportBuilderColumns.filter(
69668
+ (column) => requestedFieldNames.some((requestedField) => {
69669
+ const bareRequestedField = requestedField.split(".").pop() ?? "";
69670
+ return String(column.field ?? "").trim() === bareRequestedField;
69671
+ })
69672
+ ).map((column) => ({
69673
+ table: column.table,
69674
+ field: column.field,
69675
+ alias: column.alias
69676
+ })),
69677
+ schemaTablesWithRequestedFields: schemaForReportBuilderState.filter(
69678
+ (table2) => table2.columns?.some(
69679
+ (column) => requestedFieldNames.some(
69680
+ (requestedField) => String(column.field ?? "").trim() === (requestedField.split(".").pop() ?? "")
69681
+ )
69682
+ )
69683
+ ).map((table2) => table2.name),
68411
69684
  fieldConfigKeys: Object.keys(queryBuilderFieldConfigByName ?? {})
68412
69685
  });
68413
69686
  }
@@ -68457,8 +69730,19 @@ function useReport(reportIdArg, options = {}) {
68457
69730
  tenants,
68458
69731
  draftSessionId: draftSessionId || void 0
68459
69732
  });
69733
+ const savedReportId = String(
69734
+ resp?.id ?? resp?._id ?? effectiveReportId ?? ""
69735
+ ).trim();
69736
+ if (savedReportId && resp && resp.name !== "error" && !resp.error) {
69737
+ await cacheCab.evictReport(savedReportId, tenants);
69738
+ await queryClient.invalidateQueries({
69739
+ queryKey: ["quill", "dashboard-report", savedReportId],
69740
+ refetchType: "none"
69741
+ });
69742
+ }
68460
69743
  return resp;
68461
69744
  }, [
69745
+ cacheCab,
68462
69746
  client,
68463
69747
  dashboardNameForNewReport,
68464
69748
  draftSessionId,
@@ -68467,6 +69751,7 @@ function useReport(reportIdArg, options = {}) {
68467
69751
  eventTracking,
68468
69752
  formReportName,
68469
69753
  getToken,
69754
+ queryClient,
68470
69755
  resolvedXAxisField,
68471
69756
  resolvedXAxisFormat,
68472
69757
  resolvedXAxisLabel,
@@ -68850,7 +70135,7 @@ function ToolCallBlock({ toolCall }) {
68850
70135
  );
68851
70136
  }
68852
70137
  function ToolCallResult({ content }) {
68853
- const [expanded, setExpanded] = useState42(false);
70138
+ const [expanded, setExpanded] = useState43(false);
68854
70139
  if (!content) return null;
68855
70140
  const isLong = content.length > 300;
68856
70141
  const displayed = isLong && !expanded ? content.slice(0, 300) + "..." : content;
@@ -68905,15 +70190,15 @@ function Chat({
68905
70190
  const [client] = useContext37(ClientContext);
68906
70191
  const { getToken } = useContext37(FetchContext);
68907
70192
  const { tenants } = useContext37(TenantContext);
68908
- const [messages, setMessages] = useState42([]);
68909
- const [input, setInput] = useState42("");
68910
- const [inputError, setInputError] = useState42("");
68911
- const [isLoading, setIsLoading] = useState42(false);
68912
- const [model, setModel] = useState42("gemini-3-flash-preview");
70193
+ const [messages, setMessages] = useState43([]);
70194
+ const [input, setInput] = useState43("");
70195
+ const [inputError, setInputError] = useState43("");
70196
+ const [isLoading, setIsLoading] = useState43(false);
70197
+ const [model, setModel] = useState43("gemini-3-flash-preview");
68913
70198
  const containerRef = useRef25(null);
68914
70199
  const textareaRef = useRef25(null);
68915
70200
  const abortControllerRef = useRef25(null);
68916
- useEffect32(() => {
70201
+ useEffect33(() => {
68917
70202
  if (!containerRef.current) {
68918
70203
  return;
68919
70204
  }
@@ -69347,7 +70632,7 @@ function Chat({
69347
70632
  }
69348
70633
 
69349
70634
  // src/hooks/useReportFilterDraft.ts
69350
- import { useCallback as useCallback6, useEffect as useEffect33, useMemo as useMemo35, useRef as useRef26, useState as useState43 } from "react";
70635
+ import { useCallback as useCallback6, useEffect as useEffect34, useMemo as useMemo35, useRef as useRef26, useState as useState44 } from "react";
69351
70636
  var normalizeOperatorKey = (operator) => String(operator ?? "").trim().toLowerCase().replace(/[_\s]+/g, "");
69352
70637
  var defaultFilterRuleValueForOperator = (operator) => {
69353
70638
  const key = normalizeOperatorKey(operator);
@@ -69409,11 +70694,11 @@ function useReportFilterDraft(args) {
69409
70694
  () => fieldCatalogSignature(effectiveFields),
69410
70695
  [effectiveFields]
69411
70696
  );
69412
- const [resetEpoch, setResetEpoch] = useState43(0);
69413
- const [draftQuery, setDraftQuery] = useState43(committed);
69414
- const [hasUnappliedFilterChanges, setHasUnappliedFilterChanges] = useState43(false);
70697
+ const [resetEpoch, setResetEpoch] = useState44(0);
70698
+ const [draftQuery, setDraftQuery] = useState44(committed);
70699
+ const [hasUnappliedFilterChanges, setHasUnappliedFilterChanges] = useState44(false);
69415
70700
  const draftResetKey = `${reportId}|${committedSignature}|${resetEpoch}`;
69416
- useEffect33(() => {
70701
+ useEffect34(() => {
69417
70702
  setDraftQuery(committedRef.current);
69418
70703
  setHasUnappliedFilterChanges(false);
69419
70704
  }, [draftResetKey]);
@@ -69505,7 +70790,7 @@ import {
69505
70790
  useContext as useContext38,
69506
70791
  useLayoutEffect as useLayoutEffect5,
69507
70792
  useRef as useRef27,
69508
- useState as useState44
70793
+ useState as useState45
69509
70794
  } from "react";
69510
70795
  import { jsx as jsx89, jsxs as jsxs66 } from "react/jsx-runtime";
69511
70796
  var CHART_MIN_PX = 220;
@@ -69607,14 +70892,14 @@ function ReportDetail({
69607
70892
  const useDetailTableFromUseForm = isTableChart && !isPivotTableChartConfig(chart);
69608
70893
  const showBottomRawTable = !isTableChart;
69609
70894
  const chartSlotRef = useRef27(null);
69610
- const [chartHeightPx, setChartHeightPx] = useState44(360);
70895
+ const [chartHeightPx, setChartHeightPx] = useState45(360);
69611
70896
  useLayoutEffect5(() => {
69612
70897
  const el = chartSlotRef.current;
69613
70898
  if (!el) {
69614
70899
  return;
69615
70900
  }
69616
- const ro = new ResizeObserver((entries) => {
69617
- const entry = entries[0];
70901
+ const ro = new ResizeObserver((entries2) => {
70902
+ const entry = entries2[0];
69618
70903
  if (!entry) return;
69619
70904
  const h = Math.floor(entry.contentRect.height);
69620
70905
  if (h >= CHART_MIN_PX) {
@@ -69733,7 +71018,7 @@ function ReportDetail({
69733
71018
  init_valueFormatter();
69734
71019
 
69735
71020
  // src/hooks/useTenants.ts
69736
- import { useContext as useContext39, useEffect as useEffect34 } from "react";
71021
+ import { useContext as useContext39, useEffect as useEffect35 } from "react";
69737
71022
  var useTenants = (dashboardName) => {
69738
71023
  const {
69739
71024
  tenants,
@@ -69747,12 +71032,12 @@ var useTenants = (dashboardName) => {
69747
71032
  getMappedTenantsForDashboard,
69748
71033
  getViewerTenantsByOwner
69749
71034
  } = useContext39(TenantContext);
69750
- useEffect34(() => {
71035
+ useEffect35(() => {
69751
71036
  if (dashboardName) {
69752
71037
  fetchViewerTenantsForDashboard(dashboardName);
69753
71038
  }
69754
71039
  }, [dashboardName, fetchViewerTenantsForDashboard]);
69755
- useEffect34(() => {
71040
+ useEffect35(() => {
69756
71041
  if (dashboardName) {
69757
71042
  fetchMappedTenantsForDashboard(dashboardName);
69758
71043
  }
@@ -69771,7 +71056,7 @@ var useTenants = (dashboardName) => {
69771
71056
  };
69772
71057
 
69773
71058
  // src/hooks/useQuill.ts
69774
- import { useContext as useContext40, useEffect as useEffect35, useMemo as useMemo36, useState as useState45 } from "react";
71059
+ import { useContext as useContext40, useEffect as useEffect36, useMemo as useMemo36, useState as useState46 } from "react";
69775
71060
  init_paginationProcessing();
69776
71061
  init_tableProcessing();
69777
71062
  init_dataProcessing();
@@ -69794,9 +71079,9 @@ var useQuill = (reportId, pagination) => {
69794
71079
  const [client, isClientLoading] = useContext40(ClientContext);
69795
71080
  const { tenants } = useContext40(TenantContext);
69796
71081
  const { eventTracking } = useContext40(EventTrackingContext);
69797
- const [loading, setLoading] = useState45(true);
69798
- const [error, setError] = useState45(void 0);
69799
- const [previousPage, setPreviousPage] = useState45(0);
71082
+ const [loading, setLoading] = useState46(true);
71083
+ const [error, setError] = useState46(void 0);
71084
+ const [previousPage, setPreviousPage] = useState46(0);
69800
71085
  const processedReport = useMemo36(() => {
69801
71086
  return reportId && allReportsById[reportId] ? convertInternalReportToReport(
69802
71087
  mergeComparisonRange(allReportsById[reportId]),
@@ -69805,7 +71090,7 @@ var useQuill = (reportId, pagination) => {
69805
71090
  "useQuill"
69806
71091
  ) : void 0;
69807
71092
  }, [reportId, reportId && allReportsById[reportId], specificReportFilters]);
69808
- const [additionalProcessing, setAdditionProcessing] = useState45(
71093
+ const [additionalProcessing, setAdditionProcessing] = useState46(
69809
71094
  pagination ? {
69810
71095
  page: pagination
69811
71096
  } : void 0
@@ -69954,7 +71239,7 @@ var useQuill = (reportId, pagination) => {
69954
71239
  setLoading(false);
69955
71240
  }
69956
71241
  };
69957
- useEffect35(() => {
71242
+ useEffect36(() => {
69958
71243
  if (isClientLoading) return;
69959
71244
  if (reportId && specificReportFilters) {
69960
71245
  fetchReportHelper(reportId, {
@@ -70021,7 +71306,7 @@ var useMemoizedRows = (reportId) => {
70021
71306
  };
70022
71307
 
70023
71308
  // src/hooks/useAskQuill.tsx
70024
- import { useContext as useContext41, useEffect as useEffect36, useState as useState46 } from "react";
71309
+ import { useContext as useContext41, useEffect as useEffect37, useState as useState47 } from "react";
70025
71310
  init_astProcessing();
70026
71311
  init_astFilterProcessing();
70027
71312
  init_pivotProcessing();
@@ -70054,8 +71339,8 @@ var useAskQuill = (dashboardName) => {
70054
71339
  const { tenants } = useContext41(TenantContext);
70055
71340
  const { getToken } = useContext41(FetchContext);
70056
71341
  const { eventTracking } = useContext41(EventTrackingContext);
70057
- const [astInfo, setAstInfo] = useState46(void 0);
70058
- const [data, setData] = useState46({
71342
+ const [astInfo, setAstInfo] = useState47(void 0);
71343
+ const [data, setData] = useState47({
70059
71344
  rows: [],
70060
71345
  columns: [],
70061
71346
  pivot: null,
@@ -70065,9 +71350,9 @@ var useAskQuill = (dashboardName) => {
70065
71350
  pivotColumnFields: [],
70066
71351
  pivotValueFields: []
70067
71352
  });
70068
- const [loading, setLoading] = useState46(false);
70069
- const [error, setError] = useState46(void 0);
70070
- const [ask, setAsk] = useState46(
71353
+ const [loading, setLoading] = useState47(false);
71354
+ const [error, setError] = useState47(void 0);
71355
+ const [ask, setAsk] = useState47(
70071
71356
  async () => void 0
70072
71357
  );
70073
71358
  const askHelper = async (query) => {
@@ -70267,7 +71552,7 @@ var useAskQuill = (dashboardName) => {
70267
71552
  });
70268
71553
  setLoading(false);
70269
71554
  };
70270
- useEffect36(() => {
71555
+ useEffect37(() => {
70271
71556
  setAsk(() => askHelper);
70272
71557
  }, [schemaData.schema]);
70273
71558
  return {
@@ -70281,13 +71566,13 @@ var useAskQuill = (dashboardName) => {
70281
71566
  };
70282
71567
 
70283
71568
  // src/hooks/useVirtualTables.tsx
70284
- import { useContext as useContext42, useState as useState47 } from "react";
71569
+ import { useContext as useContext42, useState as useState48 } from "react";
70285
71570
  var useVirtualTables = () => {
70286
71571
  const [schemaData, setSchemaData] = useContext42(SchemaDataContext);
70287
71572
  const { tenants } = useContext42(TenantContext);
70288
71573
  const { getToken, quillFetchWithToken } = useContext42(FetchContext);
70289
71574
  const { eventTracking } = useContext42(EventTrackingContext);
70290
- const [loadingTables, setLoadingTables] = useState47({});
71575
+ const [loadingTables, setLoadingTables] = useState48({});
70291
71576
  const handleReload = async (client, caller) => {
70292
71577
  setSchemaData({ ...schemaData, isSchemaLoading: true });
70293
71578
  setLoadingTables(
@@ -70457,14 +71742,14 @@ var useChangelogRefresh = () => {
70457
71742
  const [, setSchemaData] = useContext43(SchemaDataContext);
70458
71743
  const { allReportsById } = useAllReports();
70459
71744
  const { reloadSome } = useVirtualTables();
70460
- const refreshChangelogEntries = async (entries, client) => {
71745
+ const refreshChangelogEntries = async (entries2, client) => {
70461
71746
  const dashboardsToReload = /* @__PURE__ */ new Set();
70462
71747
  let reloadAllDashboards = false;
70463
71748
  const schemaIds = [];
70464
71749
  const schemaIdsToRemove = /* @__PURE__ */ new Set();
70465
71750
  const dashboardsToRemove = /* @__PURE__ */ new Set();
70466
71751
  const reportsToRemove = /* @__PURE__ */ new Map();
70467
- for (const entry of entries) {
71752
+ for (const entry of entries2) {
70468
71753
  if (!entry.entityId || !entry.entityType) continue;
70469
71754
  const changeType = getChangeType(entry);
70470
71755
  switch (entry.entityType) {