@adaptabletools/adaptable-cjs
Version:
Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
254 lines (253 loc) • 16.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.baseClassName = void 0;
exports.ExpressionEditor = ExpressionEditor;
const tslib_1 = require("tslib");
const React = tslib_1.__importStar(require("react"));
const react_1 = require("react");
const DocumentationLinkConstants_1 = require("../../Utilities/Constants/DocumentationLinkConstants");
const ModuleConstants_1 = require("../../Utilities/Constants/ModuleConstants");
const ArrayExtensions_1 = tslib_1.__importDefault(require("../../Utilities/Extensions/ArrayExtensions"));
const StringExtensions_1 = tslib_1.__importDefault(require("../../Utilities/Extensions/StringExtensions"));
const AdaptableInput_1 = tslib_1.__importDefault(require("../../View/Components/AdaptableInput"));
const ButtonInfo_1 = require("../../View/Components/Buttons/ButtonInfo");
const CheckBox_1 = require("../CheckBox");
const CodeBlock_1 = require("../CodeBlock");
const HelpBlock_1 = tslib_1.__importDefault(require("../HelpBlock"));
const Tabs_1 = require("../Tabs");
const DataTableEditor_1 = require("./DataTableEditor");
const EditorContext_1 = require("./EditorContext");
const EditorInput_1 = tslib_1.__importDefault(require("./EditorInput"));
const EditorInputWithWhereClause_1 = tslib_1.__importDefault(require("./EditorInputWithWhereClause"));
const ExpressionFunctionDocumentation_1 = require("./ExpressionFunctionDocumentation");
const NamedQueryContext_1 = require("./NamedQueryContext");
const NamedQueryEditor_1 = require("./NamedQueryEditor");
const QueryBuilder_1 = require("./QueryBuilder");
const Flex_1 = require("../Flex");
const twMerge_1 = require("../../twMerge");
exports.baseClassName = `ab-ExpressionEditor`;
function ExpressionEditor(props) {
const { type, module } = props;
const [selectedFunction, setSelectedFunction] = (0, react_1.useState)(null);
const [expressionText, setExpressionText] = (0, react_1.useState)(props.value);
const { namedQuery, setNamedQuery } = (0, NamedQueryContext_1.useNamedQueryContext)();
const textAreaRef = (0, react_1.useRef)(null);
const allowSaveNamedQuery = props.allowSaveNamedQuery ?? type === 'boolean';
const editorInput = type === 'observable' || type === 'aggregatedBoolean' ? (React.createElement(EditorInputWithWhereClause_1.default, { type: type, module: module, value: props.value, onChange: (value) => {
setExpressionText(value);
props.onChange(value);
}, testData: props.initialData, api: props.api })) : (
// 'boolean','scalar','aggregatedScalar'/'cumulativeAggregatedScalar'/'quantileAggregatedScalar'
(React.createElement(
EditorInput_1.default,
{ type: type, module: module, value: props.value, onChange: (value) => {
setExpressionText(value);
props.onChange(value);
}, testData: props.initialData, isFullExpression: props.isFullExpression, api: props.api }
)));
const adaptableFields = props.api.expressionApi.internalApi.getAvailableFields();
// currently only boolean and scalar expressions support nested calculated columns (calc cols which reference other calc cols)
const queryableColumns = type === 'scalar' || type === 'boolean' || type === 'aggregatedScalar'
? props.columns
: props.columns.filter((c) => !c.isCalculatedColumn);
const showDocumentationLinks = props.api.internalApi.isDocumentationLinksDisplayed();
// @Bogdan i did this to be sure but i think we can get rid of this as the button does the same (and better IMO)
const showNamedQueryStuff = false;
const queryDocumentationLink = queryDocumentationLinks[type];
const saveAsNamedQueryElement = allowSaveNamedQuery &&
props.api.internalApi
.getModuleService()
.getModuleById(ModuleConstants_1.NamedQueryModuleId)
.isModuleEditable() && (React.createElement(Flex_1.Flex, { flexDirection: "row", className: "twa:mb-2 twa:text-3" },
React.createElement(CheckBox_1.CheckBox, { checked: !!namedQuery, onChange: (checked) => {
if (checked) {
setNamedQuery({
Name: '',
BooleanExpression: expressionText,
});
}
else {
setNamedQuery(false);
}
}, className: "twa:mr-2 twa:items-center" }, "Save as new Named Query"),
namedQuery ? (React.createElement(AdaptableInput_1.default, { className: "twa:flex-1", onChange: (event) => {
const { target: { value }, } = event;
setNamedQuery({
Name: value,
BooleanExpression: expressionText,
});
} })) : null));
const editorElement = (React.createElement(Flex_1.Flex, { className: (0, twMerge_1.twMerge)(`${exports.baseClassName} twa:flex-1 twa:pl-2 twa:overflow-auto`, props.className), "data-name": "expression-editor-wrapper", style: props.style },
React.createElement(Flex_1.Flex, { className: "twa:flex-1", style: { minHeight: 0 }, flexDirection: "column", onFocus: (event) => {
if (event.target.tagName === 'TEXTAREA') {
textAreaRef.current = event.target;
}
} },
React.createElement(Flex_1.Flex, { className: "twa:flex-1 twa:text-2", flexDirection: "row", "data-name": "expression-editor", style: { minHeight: 0 } },
React.createElement(Flex_1.Flex, { className: "twa:flex-1 twa:pr-2 twa:overflow-auto twa:min-h-0", "data-name": "expression-builder", flexDirection: "column" },
editorInput,
StringExtensions_1.default.IsNotNullOrEmpty(expressionText?.trim()) && (React.createElement(ExpressionFunctionDocumentation_1.ExpressionFunctionDocumentation, { expressionFunction: selectedFunction })),
/* displayed for advanced queries (observable&Aggregation) to give the users a starting point */
StringExtensions_1.default.IsNullOrEmpty(expressionText?.trim()) && renderQueryHints(type),
showDocumentationLinks && (React.createElement(HelpBlock_1.default, { "data-name": "query-documentation", className: "twa:my-2 twa:p-2 twa:text-3" },
React.createElement(ButtonInfo_1.ButtonInfo, { className: "twa:mr-2", onClick: () => window.open(queryDocumentationLink, '_blank') }),
"See documentation for more details and examples")),
React.createElement(Flex_1.Box, { className: "twa:flex-1" }),
showNamedQueryStuff && saveAsNamedQueryElement),
React.createElement(Flex_1.Box, { className: `${exports.baseClassName}__sidebar twa:pb-2 twa:px-2 twa:overflow-auto twa:h-full twa:w-[280px]`, "data-name": "expression-sidebar" },
React.createElement(Flex_1.Box, { className: "twa:h-full twa:p-2 twa:rounded-standard twa:shadow-md twa:bg-defaultbackground twa:text-text-on-defaultbackground" },
React.createElement(Tabs_1.Tabs, { className: "twa:h-full" },
React.createElement(Tabs_1.Tabs.Tab, { value: "column" }, "Columns"),
React.createElement(Tabs_1.Tabs.Tab, { value: "field" }, "Fields"),
React.createElement(Tabs_1.Tabs.Tab, { value: "named-query" }, "Named Queries"),
React.createElement(Tabs_1.Tabs.Content, { className: "twa:flex-1 twa:h-full" },
React.createElement(DataTableEditor_1.DataTableEditor, { type: "column", dataFormatter: (value) => `[${value}]`, fields: queryableColumns.map((column) => ({
label: column.friendlyName,
value: column.columnId,
dataType: column.dataType,
readOnly: column.readOnly,
})), data: props.initialData })),
React.createElement(Tabs_1.Tabs.Content, null, adaptableFields && adaptableFields.length > 0 ? (React.createElement(DataTableEditor_1.DataTableEditor, { type: "field", labels: {
showIds: 'Show Field path',
filterPlaceholder: 'Filter fields...',
}, dataFormatter: (value) => `FIELD("${value}")`, data: props.initialData, fields: adaptableFields?.map((field) => ({
label: field.label,
value: field.name,
dataType: field.dataType,
readOnly: true,
})) })) : (React.createElement(Flex_1.Box, { className: "twa:p-2" }, "No Data Fields provided"))),
React.createElement(Tabs_1.Tabs.Content, null, ArrayExtensions_1.default.IsNullOrEmpty(props.namedQueries) ? (React.createElement("div", null,
React.createElement(Flex_1.Box, { className: "twa:p-2" }, "No Named Queries defined"),
React.createElement(Flex_1.Box, { className: "twa:text-2 twa:p-2" },
"Named Queries are saved Expressions which can be referenced in other Expressions using the ",
React.createElement("i", null, "QUERY"),
" keyword",
' '))) : (React.createElement(NamedQueryEditor_1.NamedQueryEditor, { namedQueries: props.namedQueries }))))))))));
const queryBuilderElement = (React.createElement(QueryBuilder_1.QueryBuilder, { module: module, query: expressionText, onChange: (query) => {
setExpressionText(query);
props.onChange(query);
}, getFields: (type) => {
return props.fields
.filter((field) => {
if (!type) {
return (field.dataType === 'text' ||
field.dataType === 'number' ||
field.dataType === 'boolean' ||
field.dataType === 'date');
}
else {
return field.dataType === type;
}
})
.map((field) => ({
value: field.name,
label: field.label,
type: field.dataType,
}));
}, getColumns: (type) => {
return props.columns
.filter((column) => {
if (!type) {
return (column.dataType === 'text' ||
column.dataType === 'number' ||
column.dataType === 'boolean' ||
column.dataType === 'date');
}
else {
return column.dataType === type;
}
})
.map((col) => ({
value: col.columnId,
label: col.friendlyName ?? col.columnId,
type: col.dataType,
}));
} }));
const showQueryBuilder = props.showQueryBuilder ?? false;
const showExpressionEditor = props.showExpressionEditor ?? true;
return (React.createElement(EditorContext_1.ExpressionEditorContext.Provider, { value: {
selectedFunction,
setSelectedFunction,
textAreaRef,
} }, (() => {
switch (true) {
case showQueryBuilder && showExpressionEditor:
return (React.createElement(Tabs_1.Tabs, { "data-name": "editor-selector-tabs", className: "twa:mb-2 twa:p-2 twa:min-h-0 twa:flex-1" },
React.createElement(Tabs_1.Tabs.Tab, { value: "editor" }, "Expression Editor"),
React.createElement(Tabs_1.Tabs.Tab, { value: "ui" }, "Query Builder"),
React.createElement(Tabs_1.Tabs.Content, null, editorElement),
React.createElement(Tabs_1.Tabs.Content, null,
React.createElement(Flex_1.Flex, { flexDirection: "column", className: "twa:h-full" },
queryBuilderElement,
React.createElement(Flex_1.Box, { className: "twa:flex-1" }),
showNamedQueryStuff && saveAsNamedQueryElement))));
case showQueryBuilder:
return (React.createElement(Flex_1.Flex, { flexDirection: "column", className: "twa:p-2 twa:h-full" }, queryBuilderElement));
case showExpressionEditor:
return (React.createElement(Flex_1.Flex, { flexDirection: "column", className: "twa:p-2 twa:h-full" }, editorElement));
default:
return null;
}
})()));
}
const renderQueryHints = (type) => {
const examples = [];
if (type === 'observable') {
examples.push({
code: "ROW_CHANGE( COUNT([ItemCount],3), TIMEFRAME('5m'))",
description: 'The Item Count value in a specific Row changes 3 times within a 5 minute timeframe',
});
examples.push({
code: "GRID_CHANGE( MAX([OrderCost]), TIMEFRAME('1h')) WHERE [CustomerReference] = 'TRADH'",
description: "An Order Cost cell contains its highest value in the whole Grid within the last hour - for rows where Cust Ref is 'TRADH'",
});
}
if (type === 'aggregatedBoolean') {
examples.push({
code: 'SUM([PnL]) > 50000000',
description: "The sum of the 'PnL' column values in all rows is greater than 5 Million",
});
examples.push({
code: "SUM([PnL]) > '5B' WHERE QUERY('CurrencyDollar')",
description: "The sum of the 'PnL' column values in all rows where named query 'CurrencyDollar' is evaluated to TRUE is greater than 5 Billions",
});
}
if (type === 'aggregatedScalar') {
examples.push({
code: 'AVG([stargazers_count], GROUP_BY([language]))',
description: 'Average popularity (number of stars) of all the Github repositories, grouped by programming language',
}, {
code: 'MAX([PnL]), GROUP_BY([currency], [country])',
description: "The maximum 'PnL' value, grouped by currency and country",
});
}
if (type === 'cumulativeAggregatedScalar') {
examples.push({
code: 'CUMUL(AVG([stargazers_count], GROUP_BY([language])), OVER([TradeDate]))',
description: "The cumulative average popularity (number of stars) of all the Github repositories, grouped by programming language in the order given by the 'TradeDate' column",
}, {
code: 'CUMUL( SUM([PnL]), OVER([TradeDate]))',
description: "The cumulative sum of all 'PnL' columns in the order given by the 'TradeDate' column",
});
}
return examples.length ? (React.createElement(Flex_1.Box, { "data-name": "expression-hints", className: "twa:my-2 twa:p-2 twa:bg-primary twa:rounded-standard" },
React.createElement(Flex_1.Box, null,
React.createElement(Flex_1.Box, { className: "twa:mb-2" },
React.createElement("b", null, "Examples"),
" (click on each to see its explanation):"),
examples.map((example, index) => (React.createElement("details", { key: index, className: "twa:mb-2" },
React.createElement(Flex_1.Flex, { className: "twa:mr-2 twa:rounded-standard twa:mb-1 twa:text-2", alignItems: "center", as: "summary" },
React.createElement(CodeBlock_1.CodeBlock, null,
" ",
example.code)),
React.createElement(Flex_1.Box, { className: "twa:ml-3 twa:italic twa:text-2" }, example.description))))))) : null;
};
const queryDocumentationLinks = {
boolean: DocumentationLinkConstants_1.BooleanQueryDocsLink,
scalar: DocumentationLinkConstants_1.ScalarQueryDocsLink,
observable: DocumentationLinkConstants_1.ObservableQueryDocsLink,
aggregatedBoolean: DocumentationLinkConstants_1.AggregatedBooleanQueryDocsLink,
aggregatedScalar: DocumentationLinkConstants_1.AggregatedScalarQueryDocsLink,
cumulativeAggregatedScalar: DocumentationLinkConstants_1.CumulativeAggregatedScalarQueryDocsLink,
quantileAggregatedScalar: DocumentationLinkConstants_1.QuantileAggregatedScalarQueryDocsLink,
};