@quillsql/react 2.16.90 → 2.16.91
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.cjs +46 -6
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +46 -6
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -18641,6 +18641,19 @@ function replaceBigQuerySpecialCharacters(column) {
|
|
|
18641
18641
|
function processColumnReference(column, databaseType, fallbackOnNull, isColumnFieldAlias, isValueFieldAlias) {
|
|
18642
18642
|
switch (databaseType.toLowerCase()) {
|
|
18643
18643
|
case "postgresql":
|
|
18644
|
+
case "redshift": {
|
|
18645
|
+
if (column === "") {
|
|
18646
|
+
return fallbackOnNull ? `"${fallbackOnNull}"` : `"_"`;
|
|
18647
|
+
}
|
|
18648
|
+
if (isColumnFieldAlias) {
|
|
18649
|
+
return `"${column.replaceAll('"', "")}"`;
|
|
18650
|
+
}
|
|
18651
|
+
const columnParts = column.split(".");
|
|
18652
|
+
if (columnParts.length > 1) {
|
|
18653
|
+
return `"` + columnParts.map((part) => part.replaceAll('"', "")).join('"."') + `"`;
|
|
18654
|
+
}
|
|
18655
|
+
return `"${column.replaceAll('"', "")}"`;
|
|
18656
|
+
}
|
|
18644
18657
|
case "clickhouse": {
|
|
18645
18658
|
if (column === "") {
|
|
18646
18659
|
return fallbackOnNull ? `"${fallbackOnNull}"` : `"_"`;
|
|
@@ -18755,6 +18768,8 @@ function generateDistinctQuery(stringFields, query, databaseType) {
|
|
|
18755
18768
|
return generateDistinctQueryBigQuery(stringFields, query);
|
|
18756
18769
|
case "postgresql":
|
|
18757
18770
|
return generateDistinctQueryPostgres(stringFields, query);
|
|
18771
|
+
case "redshift":
|
|
18772
|
+
return generateDistinctQueryRedshift(stringFields, query);
|
|
18758
18773
|
case "snowflake":
|
|
18759
18774
|
return generateDistinctQuerySnowflake(stringFields, query);
|
|
18760
18775
|
case "mssql":
|
|
@@ -18801,6 +18816,13 @@ function generateDistinctQueryPostgres(stringFields, query) {
|
|
|
18801
18816
|
const distinctQuery = distinctQueries.join(" UNION ALL ");
|
|
18802
18817
|
return `WITH querytable AS (${query.replace(";", "")}) ` + distinctQuery;
|
|
18803
18818
|
}
|
|
18819
|
+
function generateDistinctQueryRedshift(stringFields, query) {
|
|
18820
|
+
const distinctQueries = stringFields.map((field) => {
|
|
18821
|
+
return `SELECT '${field}' AS "field", LISTAGG(DISTINCT "${field}") AS "string_values" FROM querytable`;
|
|
18822
|
+
});
|
|
18823
|
+
const distinctQuery = distinctQueries.join(" UNION ALL ");
|
|
18824
|
+
return `WITH querytable AS (${query.replace(";", "")}) ` + distinctQuery;
|
|
18825
|
+
}
|
|
18804
18826
|
function generateDistinctQuerySnowflake(stringFields, query) {
|
|
18805
18827
|
const distinctQueries = stringFields.map((field) => {
|
|
18806
18828
|
return `SELECT '${field}' AS "field", ARRAY_AGG(DISTINCT "${field}") AS "string_values" FROM querytable`;
|
|
@@ -18842,7 +18864,7 @@ function generateMinMaxDateRangeQueries(columnFields, query, databaseType) {
|
|
|
18842
18864
|
const min2 = "MIN";
|
|
18843
18865
|
const cast = databaseType.toLowerCase() === "snowflake" ? "::TIMESTAMP_TZ" : "";
|
|
18844
18866
|
const distinctQueries = columnFields.map((field) => {
|
|
18845
|
-
const wrappedField = ["postgresql", "clickhouse"].includes(
|
|
18867
|
+
const wrappedField = ["postgresql", "redshift", "clickhouse"].includes(
|
|
18846
18868
|
databaseType.toLowerCase()
|
|
18847
18869
|
) ? processColumnReference(field, databaseType) : field;
|
|
18848
18870
|
return `SELECT '${field}' AS ${processColumnReference("field", databaseType)}, ${min2}(${wrappedField})${cast} AS ${processColumnReference("min_range", databaseType)}, ${max2}(${wrappedField})${cast} AS ${processColumnReference("max_range", databaseType)} FROM querytable`;
|
|
@@ -53707,6 +53729,7 @@ function generateSuggestionsByDatasource(monaco, range, databaseType) {
|
|
|
53707
53729
|
switch (databaseType.toLowerCase()) {
|
|
53708
53730
|
case "snowflake":
|
|
53709
53731
|
case "postgresql":
|
|
53732
|
+
case "redshift":
|
|
53710
53733
|
return [
|
|
53711
53734
|
...POSTGRES_DATASOURCE_SUGGESTIONS(monaco, range),
|
|
53712
53735
|
...GENERIC_DATASOURCE_SUGGESTIONS(monaco, range)
|
|
@@ -66534,9 +66557,8 @@ function useReport(reportIdArg, options = {}) {
|
|
|
66534
66557
|
}, [schemaForReportBuilderState, schemaForeignKeyMap]);
|
|
66535
66558
|
const reportBuilderBaseTableNamesKey = (sourceReport?.reportBuilderState?.tables ?? []).map((t) => String(t?.name ?? "").trim()).filter(Boolean).join("\0") ?? "";
|
|
66536
66559
|
const selectedBaseTableNamesForFieldOptions = (0, import_react63.useMemo)(() => {
|
|
66537
|
-
|
|
66538
|
-
|
|
66539
|
-
return fromPicker;
|
|
66560
|
+
if (Array.isArray(schemaDatasourceIds)) {
|
|
66561
|
+
return schemaDatasourceIds.map((id) => String(id ?? "").trim()).filter(Boolean);
|
|
66540
66562
|
}
|
|
66541
66563
|
return (sourceReport?.reportBuilderState?.tables ?? []).map((t) => String(t?.name ?? "").trim()).filter(Boolean);
|
|
66542
66564
|
}, [schemaDatasourceIdsKey, reportBuilderBaseTableNamesKey]);
|
|
@@ -67886,7 +67908,7 @@ function useReport(reportIdArg, options = {}) {
|
|
|
67886
67908
|
prevPivotStateForColumnExpansionRef.current = pivotState;
|
|
67887
67909
|
}, [pivotState]);
|
|
67888
67910
|
const effectiveReportBuilderTables = (0, import_react63.useMemo)(() => {
|
|
67889
|
-
const explicitBaseFromSchemaPicker = Array.isArray(schemaDatasourceIds)
|
|
67911
|
+
const explicitBaseFromSchemaPicker = Array.isArray(schemaDatasourceIds) ? schemaDatasourceIds.map((tableName) => String(tableName ?? "").trim()).filter(Boolean).map((name2) => ({ name: name2 })) : null;
|
|
67890
67912
|
const baseTablesForMerge = explicitBaseFromSchemaPicker ?? baseReportBuilderTables;
|
|
67891
67913
|
return mergeReportBuilderTables(
|
|
67892
67914
|
baseTablesForMerge,
|
|
@@ -67920,6 +67942,12 @@ function useReport(reportIdArg, options = {}) {
|
|
|
67920
67942
|
() => baseReportBuilderTables.map((table2) => String(table2.name ?? "").trim()).filter((name2) => Boolean(name2)),
|
|
67921
67943
|
[baseReportBuilderTables]
|
|
67922
67944
|
);
|
|
67945
|
+
const datasourcePickerValues = (0, import_react63.useMemo)(() => {
|
|
67946
|
+
if (Array.isArray(schemaDatasourceIds)) {
|
|
67947
|
+
return schemaDatasourceIds.map((tableName) => String(tableName ?? "").trim()).filter((name2) => Boolean(name2));
|
|
67948
|
+
}
|
|
67949
|
+
return baseReportBuilderTableNames;
|
|
67950
|
+
}, [baseReportBuilderTableNames, schemaDatasourceIdsKey]);
|
|
67923
67951
|
const normalizeQueryBuilderFieldNameForConfig = (0, import_react63.useMemo)(() => {
|
|
67924
67952
|
const configEntries = Object.entries(queryBuilderFieldConfigByName);
|
|
67925
67953
|
const preferredTableNames = /* @__PURE__ */ new Set([
|
|
@@ -70716,7 +70744,7 @@ function useReport(reportIdArg, options = {}) {
|
|
|
70716
70744
|
}
|
|
70717
70745
|
if (effectiveNextState.datasources !== void 0) {
|
|
70718
70746
|
const nextIds = effectiveNextState.datasources.map((tableName) => String(tableName ?? "").trim()).filter(Boolean);
|
|
70719
|
-
next.schemaDatasourceIds = nextIds
|
|
70747
|
+
next.schemaDatasourceIds = nextIds;
|
|
70720
70748
|
if (nextIds.length > 0) {
|
|
70721
70749
|
pruneFormStateToExplicitDatasourceTables(next, nextIds, {
|
|
70722
70750
|
sourceReport,
|
|
@@ -70769,6 +70797,15 @@ function useReport(reportIdArg, options = {}) {
|
|
|
70769
70797
|
}
|
|
70770
70798
|
}
|
|
70771
70799
|
}
|
|
70800
|
+
} else {
|
|
70801
|
+
next.queryColumns = [];
|
|
70802
|
+
next.groupRowsBy = void 0;
|
|
70803
|
+
next.groupColumnsBy = void 0;
|
|
70804
|
+
next.aggregationState = [];
|
|
70805
|
+
next.aggregationTablesByIndex = [];
|
|
70806
|
+
next.pivotSort = void 0;
|
|
70807
|
+
next.regularSort = void 0;
|
|
70808
|
+
next.queryFilters = { combinator: "and", rules: [] };
|
|
70772
70809
|
}
|
|
70773
70810
|
}
|
|
70774
70811
|
if (effectiveNextState.columns !== void 0) {
|
|
@@ -70789,6 +70826,8 @@ function useReport(reportIdArg, options = {}) {
|
|
|
70789
70826
|
if (replacesDatasourceTableScope) {
|
|
70790
70827
|
next.queryColumns = normalizedDisplayColumns;
|
|
70791
70828
|
next.schemaDatasourceIds = [...requestedDisplayTableNames];
|
|
70829
|
+
} else if (effectiveNextState.datasources !== void 0 && normalizedDisplayColumns.length === 0) {
|
|
70830
|
+
next.queryColumns = [];
|
|
70792
70831
|
} else {
|
|
70793
70832
|
next.queryColumns = mergeReportBuilderColumns(
|
|
70794
70833
|
next.queryColumns,
|
|
@@ -71296,6 +71335,7 @@ function useReport(reportIdArg, options = {}) {
|
|
|
71296
71335
|
reportId: effectiveReportId || void 0,
|
|
71297
71336
|
/* ── Selected value + pick list (same naming pattern throughout) ── */
|
|
71298
71337
|
datasources,
|
|
71338
|
+
datasourcePickerValues,
|
|
71299
71339
|
datasourceOptions,
|
|
71300
71340
|
groupRowsBy,
|
|
71301
71341
|
groupRowsByOptions,
|
package/dist/index.d.cts
CHANGED
|
@@ -3275,7 +3275,8 @@ interface SetReportBuilderInput {
|
|
|
3275
3275
|
chartAxes?: SetReportChartAxesInput;
|
|
3276
3276
|
/**
|
|
3277
3277
|
* Schema table names for datasource / multi-table picker UIs.
|
|
3278
|
-
*
|
|
3278
|
+
* An empty array clears the picker selection; omit or pass `undefined` via
|
|
3279
|
+
* report switches to fall back to saved report base tables.
|
|
3279
3280
|
*/
|
|
3280
3281
|
datasources?: string[];
|
|
3281
3282
|
}
|
|
@@ -3451,6 +3452,7 @@ declare function useReport(reportIdArg?: string, options?: UseReportOptions): {
|
|
|
3451
3452
|
*/
|
|
3452
3453
|
reportId: string | undefined;
|
|
3453
3454
|
datasources: string[];
|
|
3455
|
+
datasourcePickerValues: string[];
|
|
3454
3456
|
datasourceOptions: DatasourceOption[];
|
|
3455
3457
|
groupRowsBy: string | undefined;
|
|
3456
3458
|
groupRowsByOptions: SelectOption[];
|
package/dist/index.d.ts
CHANGED
|
@@ -3275,7 +3275,8 @@ interface SetReportBuilderInput {
|
|
|
3275
3275
|
chartAxes?: SetReportChartAxesInput;
|
|
3276
3276
|
/**
|
|
3277
3277
|
* Schema table names for datasource / multi-table picker UIs.
|
|
3278
|
-
*
|
|
3278
|
+
* An empty array clears the picker selection; omit or pass `undefined` via
|
|
3279
|
+
* report switches to fall back to saved report base tables.
|
|
3279
3280
|
*/
|
|
3280
3281
|
datasources?: string[];
|
|
3281
3282
|
}
|
|
@@ -3451,6 +3452,7 @@ declare function useReport(reportIdArg?: string, options?: UseReportOptions): {
|
|
|
3451
3452
|
*/
|
|
3452
3453
|
reportId: string | undefined;
|
|
3453
3454
|
datasources: string[];
|
|
3455
|
+
datasourcePickerValues: string[];
|
|
3454
3456
|
datasourceOptions: DatasourceOption[];
|
|
3455
3457
|
groupRowsBy: string | undefined;
|
|
3456
3458
|
groupRowsByOptions: SelectOption[];
|
package/dist/index.js
CHANGED
|
@@ -18687,6 +18687,19 @@ function replaceBigQuerySpecialCharacters(column) {
|
|
|
18687
18687
|
function processColumnReference(column, databaseType, fallbackOnNull, isColumnFieldAlias, isValueFieldAlias) {
|
|
18688
18688
|
switch (databaseType.toLowerCase()) {
|
|
18689
18689
|
case "postgresql":
|
|
18690
|
+
case "redshift": {
|
|
18691
|
+
if (column === "") {
|
|
18692
|
+
return fallbackOnNull ? `"${fallbackOnNull}"` : `"_"`;
|
|
18693
|
+
}
|
|
18694
|
+
if (isColumnFieldAlias) {
|
|
18695
|
+
return `"${column.replaceAll('"', "")}"`;
|
|
18696
|
+
}
|
|
18697
|
+
const columnParts = column.split(".");
|
|
18698
|
+
if (columnParts.length > 1) {
|
|
18699
|
+
return `"` + columnParts.map((part) => part.replaceAll('"', "")).join('"."') + `"`;
|
|
18700
|
+
}
|
|
18701
|
+
return `"${column.replaceAll('"', "")}"`;
|
|
18702
|
+
}
|
|
18690
18703
|
case "clickhouse": {
|
|
18691
18704
|
if (column === "") {
|
|
18692
18705
|
return fallbackOnNull ? `"${fallbackOnNull}"` : `"_"`;
|
|
@@ -18801,6 +18814,8 @@ function generateDistinctQuery(stringFields, query, databaseType) {
|
|
|
18801
18814
|
return generateDistinctQueryBigQuery(stringFields, query);
|
|
18802
18815
|
case "postgresql":
|
|
18803
18816
|
return generateDistinctQueryPostgres(stringFields, query);
|
|
18817
|
+
case "redshift":
|
|
18818
|
+
return generateDistinctQueryRedshift(stringFields, query);
|
|
18804
18819
|
case "snowflake":
|
|
18805
18820
|
return generateDistinctQuerySnowflake(stringFields, query);
|
|
18806
18821
|
case "mssql":
|
|
@@ -18847,6 +18862,13 @@ function generateDistinctQueryPostgres(stringFields, query) {
|
|
|
18847
18862
|
const distinctQuery = distinctQueries.join(" UNION ALL ");
|
|
18848
18863
|
return `WITH querytable AS (${query.replace(";", "")}) ` + distinctQuery;
|
|
18849
18864
|
}
|
|
18865
|
+
function generateDistinctQueryRedshift(stringFields, query) {
|
|
18866
|
+
const distinctQueries = stringFields.map((field) => {
|
|
18867
|
+
return `SELECT '${field}' AS "field", LISTAGG(DISTINCT "${field}") AS "string_values" FROM querytable`;
|
|
18868
|
+
});
|
|
18869
|
+
const distinctQuery = distinctQueries.join(" UNION ALL ");
|
|
18870
|
+
return `WITH querytable AS (${query.replace(";", "")}) ` + distinctQuery;
|
|
18871
|
+
}
|
|
18850
18872
|
function generateDistinctQuerySnowflake(stringFields, query) {
|
|
18851
18873
|
const distinctQueries = stringFields.map((field) => {
|
|
18852
18874
|
return `SELECT '${field}' AS "field", ARRAY_AGG(DISTINCT "${field}") AS "string_values" FROM querytable`;
|
|
@@ -18888,7 +18910,7 @@ function generateMinMaxDateRangeQueries(columnFields, query, databaseType) {
|
|
|
18888
18910
|
const min2 = "MIN";
|
|
18889
18911
|
const cast = databaseType.toLowerCase() === "snowflake" ? "::TIMESTAMP_TZ" : "";
|
|
18890
18912
|
const distinctQueries = columnFields.map((field) => {
|
|
18891
|
-
const wrappedField = ["postgresql", "clickhouse"].includes(
|
|
18913
|
+
const wrappedField = ["postgresql", "redshift", "clickhouse"].includes(
|
|
18892
18914
|
databaseType.toLowerCase()
|
|
18893
18915
|
) ? processColumnReference(field, databaseType) : field;
|
|
18894
18916
|
return `SELECT '${field}' AS ${processColumnReference("field", databaseType)}, ${min2}(${wrappedField})${cast} AS ${processColumnReference("min_range", databaseType)}, ${max2}(${wrappedField})${cast} AS ${processColumnReference("max_range", databaseType)} FROM querytable`;
|
|
@@ -53860,6 +53882,7 @@ function generateSuggestionsByDatasource(monaco, range, databaseType) {
|
|
|
53860
53882
|
switch (databaseType.toLowerCase()) {
|
|
53861
53883
|
case "snowflake":
|
|
53862
53884
|
case "postgresql":
|
|
53885
|
+
case "redshift":
|
|
53863
53886
|
return [
|
|
53864
53887
|
...POSTGRES_DATASOURCE_SUGGESTIONS(monaco, range),
|
|
53865
53888
|
...GENERIC_DATASOURCE_SUGGESTIONS(monaco, range)
|
|
@@ -66742,9 +66765,8 @@ function useReport(reportIdArg, options = {}) {
|
|
|
66742
66765
|
}, [schemaForReportBuilderState, schemaForeignKeyMap]);
|
|
66743
66766
|
const reportBuilderBaseTableNamesKey = (sourceReport?.reportBuilderState?.tables ?? []).map((t) => String(t?.name ?? "").trim()).filter(Boolean).join("\0") ?? "";
|
|
66744
66767
|
const selectedBaseTableNamesForFieldOptions = useMemo33(() => {
|
|
66745
|
-
|
|
66746
|
-
|
|
66747
|
-
return fromPicker;
|
|
66768
|
+
if (Array.isArray(schemaDatasourceIds)) {
|
|
66769
|
+
return schemaDatasourceIds.map((id) => String(id ?? "").trim()).filter(Boolean);
|
|
66748
66770
|
}
|
|
66749
66771
|
return (sourceReport?.reportBuilderState?.tables ?? []).map((t) => String(t?.name ?? "").trim()).filter(Boolean);
|
|
66750
66772
|
}, [schemaDatasourceIdsKey, reportBuilderBaseTableNamesKey]);
|
|
@@ -68094,7 +68116,7 @@ function useReport(reportIdArg, options = {}) {
|
|
|
68094
68116
|
prevPivotStateForColumnExpansionRef.current = pivotState;
|
|
68095
68117
|
}, [pivotState]);
|
|
68096
68118
|
const effectiveReportBuilderTables = useMemo33(() => {
|
|
68097
|
-
const explicitBaseFromSchemaPicker = Array.isArray(schemaDatasourceIds)
|
|
68119
|
+
const explicitBaseFromSchemaPicker = Array.isArray(schemaDatasourceIds) ? schemaDatasourceIds.map((tableName) => String(tableName ?? "").trim()).filter(Boolean).map((name2) => ({ name: name2 })) : null;
|
|
68098
68120
|
const baseTablesForMerge = explicitBaseFromSchemaPicker ?? baseReportBuilderTables;
|
|
68099
68121
|
return mergeReportBuilderTables(
|
|
68100
68122
|
baseTablesForMerge,
|
|
@@ -68128,6 +68150,12 @@ function useReport(reportIdArg, options = {}) {
|
|
|
68128
68150
|
() => baseReportBuilderTables.map((table2) => String(table2.name ?? "").trim()).filter((name2) => Boolean(name2)),
|
|
68129
68151
|
[baseReportBuilderTables]
|
|
68130
68152
|
);
|
|
68153
|
+
const datasourcePickerValues = useMemo33(() => {
|
|
68154
|
+
if (Array.isArray(schemaDatasourceIds)) {
|
|
68155
|
+
return schemaDatasourceIds.map((tableName) => String(tableName ?? "").trim()).filter((name2) => Boolean(name2));
|
|
68156
|
+
}
|
|
68157
|
+
return baseReportBuilderTableNames;
|
|
68158
|
+
}, [baseReportBuilderTableNames, schemaDatasourceIdsKey]);
|
|
68131
68159
|
const normalizeQueryBuilderFieldNameForConfig = useMemo33(() => {
|
|
68132
68160
|
const configEntries = Object.entries(queryBuilderFieldConfigByName);
|
|
68133
68161
|
const preferredTableNames = /* @__PURE__ */ new Set([
|
|
@@ -70924,7 +70952,7 @@ function useReport(reportIdArg, options = {}) {
|
|
|
70924
70952
|
}
|
|
70925
70953
|
if (effectiveNextState.datasources !== void 0) {
|
|
70926
70954
|
const nextIds = effectiveNextState.datasources.map((tableName) => String(tableName ?? "").trim()).filter(Boolean);
|
|
70927
|
-
next.schemaDatasourceIds = nextIds
|
|
70955
|
+
next.schemaDatasourceIds = nextIds;
|
|
70928
70956
|
if (nextIds.length > 0) {
|
|
70929
70957
|
pruneFormStateToExplicitDatasourceTables(next, nextIds, {
|
|
70930
70958
|
sourceReport,
|
|
@@ -70977,6 +71005,15 @@ function useReport(reportIdArg, options = {}) {
|
|
|
70977
71005
|
}
|
|
70978
71006
|
}
|
|
70979
71007
|
}
|
|
71008
|
+
} else {
|
|
71009
|
+
next.queryColumns = [];
|
|
71010
|
+
next.groupRowsBy = void 0;
|
|
71011
|
+
next.groupColumnsBy = void 0;
|
|
71012
|
+
next.aggregationState = [];
|
|
71013
|
+
next.aggregationTablesByIndex = [];
|
|
71014
|
+
next.pivotSort = void 0;
|
|
71015
|
+
next.regularSort = void 0;
|
|
71016
|
+
next.queryFilters = { combinator: "and", rules: [] };
|
|
70980
71017
|
}
|
|
70981
71018
|
}
|
|
70982
71019
|
if (effectiveNextState.columns !== void 0) {
|
|
@@ -70997,6 +71034,8 @@ function useReport(reportIdArg, options = {}) {
|
|
|
70997
71034
|
if (replacesDatasourceTableScope) {
|
|
70998
71035
|
next.queryColumns = normalizedDisplayColumns;
|
|
70999
71036
|
next.schemaDatasourceIds = [...requestedDisplayTableNames];
|
|
71037
|
+
} else if (effectiveNextState.datasources !== void 0 && normalizedDisplayColumns.length === 0) {
|
|
71038
|
+
next.queryColumns = [];
|
|
71000
71039
|
} else {
|
|
71001
71040
|
next.queryColumns = mergeReportBuilderColumns(
|
|
71002
71041
|
next.queryColumns,
|
|
@@ -71504,6 +71543,7 @@ function useReport(reportIdArg, options = {}) {
|
|
|
71504
71543
|
reportId: effectiveReportId || void 0,
|
|
71505
71544
|
/* ── Selected value + pick list (same naming pattern throughout) ── */
|
|
71506
71545
|
datasources,
|
|
71546
|
+
datasourcePickerValues,
|
|
71507
71547
|
datasourceOptions,
|
|
71508
71548
|
groupRowsBy,
|
|
71509
71549
|
groupRowsByOptions,
|