@finos/legend-query-builder 4.10.2 → 4.11.1
Sign up to get free protection for your applications and to get access to all the features.
- package/lib/__lib__/QueryBuilderDocumentation.d.ts +2 -1
- package/lib/__lib__/QueryBuilderDocumentation.d.ts.map +1 -1
- package/lib/__lib__/QueryBuilderDocumentation.js +1 -0
- package/lib/__lib__/QueryBuilderDocumentation.js.map +1 -1
- package/lib/components/QueryBuilderResultPanel.d.ts.map +1 -1
- package/lib/components/QueryBuilderResultPanel.js +111 -16
- package/lib/components/QueryBuilderResultPanel.js.map +1 -1
- package/lib/index.css +17 -1
- package/lib/index.css.map +1 -1
- package/lib/package.json +1 -1
- package/lib/stores/QueryBuilderResultState.d.ts +8 -0
- package/lib/stores/QueryBuilderResultState.d.ts.map +1 -1
- package/lib/stores/QueryBuilderResultState.js +10 -0
- package/lib/stores/QueryBuilderResultState.js.map +1 -1
- package/lib/stores/QueryBuilderState.d.ts +2 -0
- package/lib/stores/QueryBuilderState.d.ts.map +1 -1
- package/lib/stores/QueryBuilderState.js +6 -0
- package/lib/stores/QueryBuilderState.js.map +1 -1
- package/package.json +5 -5
- package/src/__lib__/QueryBuilderDocumentation.ts +1 -0
- package/src/components/QueryBuilderResultPanel.tsx +194 -28
- package/src/stores/QueryBuilderResultState.ts +18 -0
- package/src/stores/QueryBuilderState.ts +7 -0
@@ -37,6 +37,8 @@ import {
|
|
37
37
|
ModalFooterButton,
|
38
38
|
ModalHeader,
|
39
39
|
PanelDivider,
|
40
|
+
SquareIcon,
|
41
|
+
CheckSquareIcon,
|
40
42
|
} from '@finos/legend-art';
|
41
43
|
import { format as formatSQL } from 'sql-formatter';
|
42
44
|
import { observer } from 'mobx-react-lite';
|
@@ -53,6 +55,7 @@ import {
|
|
53
55
|
EnumValueExplicitReference,
|
54
56
|
RelationalExecutionActivities,
|
55
57
|
getTDSRowRankByColumnInAsc,
|
58
|
+
PRIMITIVE_TYPE,
|
56
59
|
} from '@finos/legend-graph';
|
57
60
|
import {
|
58
61
|
ActionAlertActionType,
|
@@ -100,6 +103,7 @@ import { PARAMETER_SUBMIT_ACTION } from '../stores/shared/LambdaParameterState.j
|
|
100
103
|
import { QUERY_BUILDER_TEST_ID } from '../__lib__/QueryBuilderTesting.js';
|
101
104
|
import {
|
102
105
|
DataGrid,
|
106
|
+
type DataGridColumnApi,
|
103
107
|
type DataGridCellRendererParams,
|
104
108
|
type DataGridColumnDefinition,
|
105
109
|
} from '@finos/legend-lego/data-grid';
|
@@ -119,6 +123,8 @@ import {
|
|
119
123
|
} from '../stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.js';
|
120
124
|
import { QueryUsageViewer } from './QueryUsageViewer.js';
|
121
125
|
import { DEFAULT_LOCALE } from '../graph-manager/QueryBuilderConst.js';
|
126
|
+
import { DocumentationLink } from '@finos/legend-lego/application';
|
127
|
+
import { QUERY_BUILDER_DOCUMENTATION_KEY } from '../__lib__/QueryBuilderDocumentation.js';
|
122
128
|
|
123
129
|
export const tryToFormatSql = (sql: string): string => {
|
124
130
|
try {
|
@@ -732,6 +738,40 @@ const QueryResultCellRenderer = observer(
|
|
732
738
|
},
|
733
739
|
);
|
734
740
|
|
741
|
+
const getColumnCustomizations = (
|
742
|
+
result: TDSExecutionResult,
|
743
|
+
columnName: string,
|
744
|
+
): object | undefined => {
|
745
|
+
const columnType = result.builder.columns.find(
|
746
|
+
(col) => col.name === columnName,
|
747
|
+
)?.type;
|
748
|
+
switch (columnType) {
|
749
|
+
case PRIMITIVE_TYPE.STRING:
|
750
|
+
return {
|
751
|
+
filter: 'agTextColumnFilter',
|
752
|
+
allowedAggFuncs: ['count'],
|
753
|
+
};
|
754
|
+
case PRIMITIVE_TYPE.DATE:
|
755
|
+
case PRIMITIVE_TYPE.DATETIME:
|
756
|
+
case PRIMITIVE_TYPE.STRICTDATE:
|
757
|
+
return {
|
758
|
+
filter: 'agDateColumnFilter',
|
759
|
+
allowedAggFuncs: ['count'],
|
760
|
+
};
|
761
|
+
case PRIMITIVE_TYPE.DECIMAL:
|
762
|
+
case PRIMITIVE_TYPE.INTEGER:
|
763
|
+
case PRIMITIVE_TYPE.FLOAT:
|
764
|
+
return {
|
765
|
+
filter: 'agNumberColumnFilter',
|
766
|
+
allowedAggFuncs: ['count', 'sum', 'max', 'min', 'avg'],
|
767
|
+
};
|
768
|
+
default:
|
769
|
+
return {
|
770
|
+
allowedAggFuncs: ['count'],
|
771
|
+
};
|
772
|
+
}
|
773
|
+
};
|
774
|
+
|
735
775
|
const QueryBuilderGridResult = observer(
|
736
776
|
(props: {
|
737
777
|
executionResult: TDSExecutionResult;
|
@@ -739,7 +779,56 @@ const QueryBuilderGridResult = observer(
|
|
739
779
|
}) => {
|
740
780
|
const { executionResult, queryBuilderState } = props;
|
741
781
|
|
782
|
+
const [columnAPi, setColumnApi] = useState<DataGridColumnApi | undefined>(
|
783
|
+
undefined,
|
784
|
+
);
|
742
785
|
const resultState = queryBuilderState.resultState;
|
786
|
+
const isAdvancedModeEnabled = queryBuilderState.isAdvancedModeEnabled;
|
787
|
+
const colDefs = isAdvancedModeEnabled
|
788
|
+
? executionResult.result.columns.map((colName) => {
|
789
|
+
const col = {
|
790
|
+
minWidth: 50,
|
791
|
+
sortable: true,
|
792
|
+
resizable: true,
|
793
|
+
field: colName,
|
794
|
+
flex: 1,
|
795
|
+
enablePivot: true,
|
796
|
+
enableRowGroup: true,
|
797
|
+
enableValue: true,
|
798
|
+
...getColumnCustomizations(executionResult, colName),
|
799
|
+
} as DataGridColumnDefinition;
|
800
|
+
const persistedColumn = resultState.gridConfig.columns.find(
|
801
|
+
(c) => c.colId === colName,
|
802
|
+
);
|
803
|
+
if (persistedColumn) {
|
804
|
+
if (persistedColumn.width) {
|
805
|
+
col.width = persistedColumn.width;
|
806
|
+
}
|
807
|
+
col.pinned = persistedColumn.pinned ?? null;
|
808
|
+
col.rowGroup = persistedColumn.rowGroup ?? false;
|
809
|
+
col.rowGroupIndex = persistedColumn.rowGroupIndex ?? null;
|
810
|
+
col.aggFunc = persistedColumn.aggFunc ?? null;
|
811
|
+
col.pivot = persistedColumn.pivot ?? false;
|
812
|
+
col.hide = persistedColumn.hide ?? false;
|
813
|
+
}
|
814
|
+
return col;
|
815
|
+
})
|
816
|
+
: executionResult.result.columns.map(
|
817
|
+
(colName) =>
|
818
|
+
({
|
819
|
+
minWidth: 50,
|
820
|
+
sortable: true,
|
821
|
+
resizable: true,
|
822
|
+
field: colName,
|
823
|
+
flex: 1,
|
824
|
+
cellRenderer: QueryResultCellRenderer,
|
825
|
+
cellRendererParams: {
|
826
|
+
resultState: resultState,
|
827
|
+
tdsExecutionResult: executionResult,
|
828
|
+
},
|
829
|
+
}) as DataGridColumnDefinition,
|
830
|
+
);
|
831
|
+
const sideBar = isAdvancedModeEnabled ? ['columns', 'filters'] : null;
|
743
832
|
|
744
833
|
const rowData = executionResult.result.rows.map((_row, rowIdx) => {
|
745
834
|
const row: PlainObject = {};
|
@@ -754,6 +843,15 @@ const QueryBuilderGridResult = observer(
|
|
754
843
|
row.rowNumber = rowIdx;
|
755
844
|
return row;
|
756
845
|
});
|
846
|
+
const onSaveGridColumnState = (): void => {
|
847
|
+
if (!columnAPi) {
|
848
|
+
return;
|
849
|
+
}
|
850
|
+
resultState.setGridConfig({
|
851
|
+
columns: columnAPi.getColumnState(),
|
852
|
+
isPivotModeEnabled: columnAPi.isPivotMode(),
|
853
|
+
});
|
854
|
+
};
|
757
855
|
|
758
856
|
return (
|
759
857
|
<div className="query-builder__result__values__table">
|
@@ -762,34 +860,57 @@ const QueryBuilderGridResult = observer(
|
|
762
860
|
'ag-theme-balham-dark query-builder__result__tds-grid',
|
763
861
|
)}
|
764
862
|
>
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
863
|
+
{isAdvancedModeEnabled ? (
|
864
|
+
<DataGrid
|
865
|
+
rowData={rowData}
|
866
|
+
onGridReady={(params): void => {
|
867
|
+
setColumnApi(params.columnApi);
|
868
|
+
params.columnApi.setPivotMode(
|
869
|
+
resultState.gridConfig.isPivotModeEnabled,
|
870
|
+
);
|
871
|
+
}}
|
872
|
+
gridOptions={{
|
873
|
+
suppressScrollOnNewData: true,
|
874
|
+
getRowId: (data) => data.data.rowNumber,
|
875
|
+
rowSelection: 'multiple',
|
876
|
+
pivotPanelShow: 'always',
|
877
|
+
rowGroupPanelShow: 'always',
|
878
|
+
}}
|
879
|
+
// NOTE: when column definition changed, we need to force refresh the cell to make sure the cell renderer is updated
|
880
|
+
// See https://stackoverflow.com/questions/56341073/how-to-refresh-an-ag-grid-when-a-change-occurs-inside-a-custom-cell-renderer-com
|
881
|
+
onRowDataUpdated={(params) => {
|
882
|
+
params.api.refreshCells({ force: true });
|
883
|
+
}}
|
884
|
+
suppressFieldDotNotation={true}
|
885
|
+
suppressContextMenu={!isAdvancedModeEnabled}
|
886
|
+
columnDefs={colDefs}
|
887
|
+
sideBar={sideBar}
|
888
|
+
onColumnVisible={onSaveGridColumnState}
|
889
|
+
onColumnPinned={onSaveGridColumnState}
|
890
|
+
onColumnResized={onSaveGridColumnState}
|
891
|
+
onColumnRowGroupChanged={onSaveGridColumnState}
|
892
|
+
onColumnValueChanged={onSaveGridColumnState}
|
893
|
+
onColumnPivotChanged={onSaveGridColumnState}
|
894
|
+
onColumnPivotModeChanged={onSaveGridColumnState}
|
895
|
+
/>
|
896
|
+
) : (
|
897
|
+
<DataGrid
|
898
|
+
rowData={rowData}
|
899
|
+
gridOptions={{
|
900
|
+
suppressScrollOnNewData: true,
|
901
|
+
getRowId: (data) => data.data.rowNumber,
|
902
|
+
rowSelection: 'multiple',
|
903
|
+
}}
|
904
|
+
// NOTE: when column definition changed, we need to force refresh the cell to make sure the cell renderer is updated
|
905
|
+
// See https://stackoverflow.com/questions/56341073/how-to-refresh-an-ag-grid-when-a-change-occurs-inside-a-custom-cell-renderer-com
|
906
|
+
onRowDataUpdated={(params) => {
|
907
|
+
params.api.refreshCells({ force: true });
|
908
|
+
}}
|
909
|
+
suppressFieldDotNotation={true}
|
910
|
+
suppressContextMenu={!isAdvancedModeEnabled}
|
911
|
+
columnDefs={colDefs}
|
912
|
+
/>
|
913
|
+
)}
|
793
914
|
</div>
|
794
915
|
</div>
|
795
916
|
);
|
@@ -906,6 +1027,11 @@ export const QueryBuilderResultPanel = observer(
|
|
906
1027
|
const isQueryValid =
|
907
1028
|
!queryBuilderState.isQuerySupported || isSupportedQueryValid;
|
908
1029
|
|
1030
|
+
const isQueryValidForAdvancedMode =
|
1031
|
+
isQueryValid &&
|
1032
|
+
queryBuilderState.fetchStructureState.implementation instanceof
|
1033
|
+
QueryBuilderTDSState;
|
1034
|
+
|
909
1035
|
const runQuery = (): void => {
|
910
1036
|
resultState.setSelectedCells([]);
|
911
1037
|
resultState.pressedRunQuery.inProgress();
|
@@ -936,6 +1062,7 @@ export const QueryBuilderResultPanel = observer(
|
|
936
1062
|
);
|
937
1063
|
|
938
1064
|
const allowSettingPreviewLimit = queryBuilderState.isQuerySupported;
|
1065
|
+
const allowSettingAdvancedMode = queryBuilderState.isQuerySupported;
|
939
1066
|
|
940
1067
|
const copyExpression = (value: string): void => {
|
941
1068
|
applicationStore.clipboardService
|
@@ -1008,6 +1135,13 @@ export const QueryBuilderResultPanel = observer(
|
|
1008
1135
|
}
|
1009
1136
|
};
|
1010
1137
|
|
1138
|
+
const toggleIsAdvancedModeEnabled = (): void => {
|
1139
|
+
resultState.setExecutionResult(undefined);
|
1140
|
+
queryBuilderState.setIsAdvancedModeEnabled(
|
1141
|
+
!queryBuilderState.isAdvancedModeEnabled,
|
1142
|
+
);
|
1143
|
+
};
|
1144
|
+
|
1011
1145
|
return (
|
1012
1146
|
<div
|
1013
1147
|
data-testid={QUERY_BUILDER_TEST_ID.QUERY_BUILDER_RESULT_PANEL}
|
@@ -1084,6 +1218,38 @@ export const QueryBuilderResultPanel = observer(
|
|
1084
1218
|
)}
|
1085
1219
|
</div>
|
1086
1220
|
<div className="panel__header__actions query-builder__result__header__actions">
|
1221
|
+
{allowSettingAdvancedMode && (
|
1222
|
+
<div className="query-builder__result__advanced__mode">
|
1223
|
+
<div className="query-builder__result__advanced__mode__label">
|
1224
|
+
Advanced Mode
|
1225
|
+
<DocumentationLink
|
1226
|
+
title="The grid in advanced mode performs all operations like grouping, sorting, filtering, etc after initial query execution locally withought reaching out to server. This limits the number of rows to smaller number so they can fit in memory"
|
1227
|
+
documentationKey={
|
1228
|
+
QUERY_BUILDER_DOCUMENTATION_KEY.QUESTION_HOW_TO_USE_ADVANCED_GRID_MODE
|
1229
|
+
}
|
1230
|
+
/>
|
1231
|
+
</div>
|
1232
|
+
<button
|
1233
|
+
className={clsx(
|
1234
|
+
'query-builder__result__advanced__mode__toggler__btn',
|
1235
|
+
{
|
1236
|
+
'query-builder__result__advanced__mode__toggler__btn--toggled':
|
1237
|
+
queryBuilderState.isAdvancedModeEnabled,
|
1238
|
+
},
|
1239
|
+
)}
|
1240
|
+
disabled={!isQueryValidForAdvancedMode}
|
1241
|
+
onClick={toggleIsAdvancedModeEnabled}
|
1242
|
+
tabIndex={-1}
|
1243
|
+
>
|
1244
|
+
{queryBuilderState.isAdvancedModeEnabled ? (
|
1245
|
+
<CheckSquareIcon />
|
1246
|
+
) : (
|
1247
|
+
<SquareIcon />
|
1248
|
+
)}
|
1249
|
+
</button>
|
1250
|
+
</div>
|
1251
|
+
)}
|
1252
|
+
|
1087
1253
|
{allowSettingPreviewLimit && (
|
1088
1254
|
<div className="query-builder__result__limit">
|
1089
1255
|
<div className="query-builder__result__limit__label">
|
@@ -48,6 +48,7 @@ import type { LambdaFunctionBuilderOption } from './QueryBuilderValueSpecificati
|
|
48
48
|
import { QueryBuilderTelemetryHelper } from '../__lib__/QueryBuilderTelemetryHelper.js';
|
49
49
|
import { QUERY_BUILDER_EVENT } from '../__lib__/QueryBuilderEvent.js';
|
50
50
|
import { ExecutionPlanState } from './execution-plan/ExecutionPlanState.js';
|
51
|
+
import type { DataGridColumnState } from '@finos/legend-lego/data-grid';
|
51
52
|
|
52
53
|
export const DEFAULT_LIMIT = 1000;
|
53
54
|
|
@@ -67,6 +68,11 @@ export interface QueryBuilderTDSResultCellCoordinate {
|
|
67
68
|
colIndex: number;
|
68
69
|
}
|
69
70
|
|
71
|
+
type QueryBuilderDataGridConfig = {
|
72
|
+
columns: DataGridColumnState[];
|
73
|
+
isPivotModeEnabled: boolean;
|
74
|
+
};
|
75
|
+
|
70
76
|
export class QueryBuilderResultState {
|
71
77
|
readonly queryBuilderState: QueryBuilderState;
|
72
78
|
readonly exportDataState = ActionState.create();
|
@@ -86,6 +92,8 @@ export class QueryBuilderResultState {
|
|
86
92
|
mousedOverCell: QueryBuilderTDSResultCellData | null = null;
|
87
93
|
isSelectingCells: boolean;
|
88
94
|
|
95
|
+
gridConfig!: QueryBuilderDataGridConfig;
|
96
|
+
|
89
97
|
constructor(queryBuilderState: QueryBuilderState) {
|
90
98
|
makeObservable(this, {
|
91
99
|
executionResult: observable,
|
@@ -99,6 +107,8 @@ export class QueryBuilderResultState {
|
|
99
107
|
isRunningQuery: observable,
|
100
108
|
isSelectingCells: observable,
|
101
109
|
isQueryUsageViewerOpened: observable,
|
110
|
+
gridConfig: observable,
|
111
|
+
setGridConfig: action,
|
102
112
|
setIsSelectingCells: action,
|
103
113
|
setIsRunningQuery: action,
|
104
114
|
setExecutionResult: action,
|
@@ -117,6 +127,10 @@ export class QueryBuilderResultState {
|
|
117
127
|
this.isSelectingCells = false;
|
118
128
|
|
119
129
|
this.selectedCells = [];
|
130
|
+
this.gridConfig = {
|
131
|
+
columns: [],
|
132
|
+
isPivotModeEnabled: false,
|
133
|
+
};
|
120
134
|
this.queryBuilderState = queryBuilderState;
|
121
135
|
this.executionPlanState = new ExecutionPlanState(
|
122
136
|
this.queryBuilderState.applicationStore,
|
@@ -124,6 +138,10 @@ export class QueryBuilderResultState {
|
|
124
138
|
);
|
125
139
|
}
|
126
140
|
|
141
|
+
setGridConfig(val: QueryBuilderDataGridConfig): void {
|
142
|
+
this.gridConfig = val;
|
143
|
+
}
|
144
|
+
|
127
145
|
setIsSelectingCells(val: boolean): void {
|
128
146
|
this.isSelectingCells = val;
|
129
147
|
}
|
@@ -131,6 +131,7 @@ export abstract class QueryBuilderState implements CommandRegistrar {
|
|
131
131
|
isCheckingEntitlments = false;
|
132
132
|
isCalendarEnabled = false;
|
133
133
|
isQueryChatOpened = false;
|
134
|
+
isAdvancedModeEnabled = false;
|
134
135
|
|
135
136
|
class?: Class | undefined;
|
136
137
|
executionContextState: QueryBuilderExecutionContextState;
|
@@ -166,6 +167,7 @@ export abstract class QueryBuilderState implements CommandRegistrar {
|
|
166
167
|
executionContextState: observable,
|
167
168
|
class: observable,
|
168
169
|
isQueryChatOpened: observable,
|
170
|
+
isAdvancedModeEnabled: observable,
|
169
171
|
|
170
172
|
sideBarClassName: computed,
|
171
173
|
isQuerySupported: computed,
|
@@ -178,6 +180,7 @@ export abstract class QueryBuilderState implements CommandRegistrar {
|
|
178
180
|
setIsCheckingEntitlments: action,
|
179
181
|
setClass: action,
|
180
182
|
setIsQueryChatOpened: action,
|
183
|
+
setIsAdvancedModeEnabled: action,
|
181
184
|
|
182
185
|
resetQueryResult: action,
|
183
186
|
resetQueryContent: action,
|
@@ -260,6 +263,10 @@ export abstract class QueryBuilderState implements CommandRegistrar {
|
|
260
263
|
this.isQueryChatOpened = val;
|
261
264
|
}
|
262
265
|
|
266
|
+
setIsAdvancedModeEnabled(val: boolean): void {
|
267
|
+
this.isAdvancedModeEnabled = val;
|
268
|
+
}
|
269
|
+
|
263
270
|
setInternalize(val: QueryBuilderInternalizeState | undefined): void {
|
264
271
|
this.internalizeState = val;
|
265
272
|
}
|