@adaptabletools/adaptable
Version:
Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
254 lines (253 loc) • 15.5 kB
JavaScript
import * as React from 'react';
import { useRef, useState } from 'react';
import { Box, Flex, Text } from 'rebass';
import { AggregatedBooleanQueryDocsLink, AggregatedScalarQueryDocsLink, BooleanQueryDocsLink, CumulativeAggregatedScalarQueryDocsLink, ObservableQueryDocsLink, QuantileAggregatedScalarQueryDocsLink, ScalarQueryDocsLink, } from '../../Utilities/Constants/DocumentationLinkConstants';
import { NamedQueryModuleId } from '../../Utilities/Constants/ModuleConstants';
import ArrayExtensions from '../../Utilities/Extensions/ArrayExtensions';
import StringExtensions from '../../Utilities/Extensions/StringExtensions';
import AdaptableInput from '../../View/Components/AdaptableInput';
import { ButtonInfo } from '../../View/Components/Buttons/ButtonInfo';
import { CheckBox } from '../CheckBox';
import { CodeBlock } from '../CodeBlock';
import HelpBlock from '../HelpBlock';
import Panel from '../Panel';
import { Tabs } from '../Tabs';
import { DataTableEditor } from './DataTableEditor';
import { ExpressionEditorContext } from './EditorContext';
import EditorInput from './EditorInput';
import EditorInputWithWhereClause from './EditorInputWithWhereClause';
import { ExpressionFunctionDocumentation } from './ExpressionFunctionDocumentation';
import { useNamedQueryContext } from './NamedQueryContext';
import { NamedQueryEditor } from './NamedQueryEditor';
import { QueryBuilder } from './QueryBuilder';
export const baseClassName = `ab-ExpressionEditor`;
export function ExpressionEditor(props) {
const { type, module } = props;
const [selectedFunction, setSelectedFunction] = useState(null);
const [expressionText, setExpressionText] = useState(props.value);
const { namedQuery, setNamedQuery } = useNamedQueryContext();
const textAreaRef = useRef(null);
const allowSaveNamedQuery = props.allowSaveNamedQuery ?? type === 'boolean';
const editorInput = type === 'observable' || type === 'aggregatedBoolean' ? (React.createElement(EditorInputWithWhereClause, { 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,
{ 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(NamedQueryModuleId)
.isModuleEditable() && (React.createElement(Flex, { flexDirection: "row", mb: 2, fontSize: 3 },
React.createElement(CheckBox, { checked: !!namedQuery, onChange: (checked) => {
if (checked) {
setNamedQuery({
Name: '',
BooleanExpression: expressionText,
});
}
else {
setNamedQuery(false);
}
}, mr: 2, style: { alignItems: 'center' } }, "Save as new Named Query"),
namedQuery ? (React.createElement(AdaptableInput, { style: { flex: 1 }, onChange: (event) => {
const { target: { value }, } = event;
setNamedQuery({
Name: value,
BooleanExpression: expressionText,
});
} })) : null));
const editorElement = (React.createElement(Flex, { className: baseClassName, flex: 1, "data-name": "expression-editor-wrapper", pl: 2, style: props.style },
React.createElement(Flex, { flex: 1, style: { minHeight: 0 }, flexDirection: "column", onFocus: (event) => {
if (event.target.tagName === 'TEXTAREA') {
textAreaRef.current = event.target;
}
} },
React.createElement(Flex, { flex: 1, flexDirection: "row", "data-name": "expression-editor", fontSize: 2, style: { minHeight: 0 } },
React.createElement(Flex, { flex: 1, paddingRight: 2, style: { overflow: 'auto' }, "data-name": "expression-builder", flexDirection: "column" },
editorInput,
StringExtensions.IsNotNullOrEmpty(expressionText?.trim()) && (React.createElement(ExpressionFunctionDocumentation, { expressionFunction: selectedFunction })),
/* displayed for advanced queries (observable&Aggregation) to give the users a starting point */
StringExtensions.IsNullOrEmpty(expressionText?.trim()) && renderQueryHints(type),
showDocumentationLinks && (React.createElement(HelpBlock, { "data-name": "query-documentation", mt: 2, mb: 2, fontSize: 3 },
React.createElement(ButtonInfo, { mr: 2, onClick: () => window.open(queryDocumentationLink, '_blank') }),
"See documentation for more details and examples")),
React.createElement(Box, { flex: 1 }),
showNamedQueryStuff && saveAsNamedQueryElement),
React.createElement(Box, { className: `${baseClassName}__sidebar`, "data-name": "expression-sidebar", pb: 2, paddingLeft: 2, pr: 2 },
React.createElement(Panel, { bodyProps: { style: { height: '100%' } }, style: { height: '100%' } },
React.createElement(Tabs, { style: { height: '100%' }, variant: "" },
React.createElement(Tabs.Tab, { value: "column" }, "Columns"),
React.createElement(Tabs.Tab, { value: "field" }, "Fields"),
React.createElement(Tabs.Tab, { value: "named-query" }, "Named Queries"),
React.createElement(Tabs.Content, { flex: 1, style: { height: '100%' } },
React.createElement(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.Content, null, adaptableFields && adaptableFields.length > 0 ? (React.createElement(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(Text, { p: 2 }, "No Data Fields provided"))),
React.createElement(Tabs.Content, null, ArrayExtensions.IsNullOrEmpty(props.namedQueries) ? (React.createElement("div", null,
React.createElement(Text, { p: 2 }, "No Named Queries defined"),
React.createElement(Text, { fontSize: 2, padding: 2, 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, { namedQueries: props.namedQueries }))))))))));
const queryBuilderElement = (React.createElement(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(ExpressionEditorContext.Provider, { value: {
selectedFunction,
setSelectedFunction,
textAreaRef,
} }, (() => {
switch (true) {
case showQueryBuilder && showExpressionEditor:
return (React.createElement(Tabs, { "data-name": "editor-selector-tabs", mb: 2, p: 2, minHeight: 0, flex: 1 },
React.createElement(Tabs.Tab, { value: "editor" }, "Expression Editor"),
React.createElement(Tabs.Tab, { value: "ui" }, "Query Builder"),
React.createElement(Tabs.Content, null, editorElement),
React.createElement(Tabs.Content, null,
React.createElement(Flex, { flexDirection: "column", height: "100%" },
queryBuilderElement,
React.createElement(Box, { flex: 1 }),
showNamedQueryStuff && saveAsNamedQueryElement))));
case showQueryBuilder:
return (React.createElement(Flex, { flexDirection: "column", p: 2, height: "100%" }, queryBuilderElement));
case showExpressionEditor:
return (React.createElement(Flex, { flexDirection: "column", p: 2, height: "100%" }, 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(Box, { "data-name": "expression-hints", my: 2, p: 2, style: {
background: 'var(--ab-color-primary)',
borderRadius: 'var(--ab__border-radius)',
} },
React.createElement(Box, null,
React.createElement(Text, { marginBottom: 2 },
React.createElement("b", null, "Examples"),
" (click on each to see its explanation):"),
examples.map((example, index) => (React.createElement("details", { key: index, style: { marginBottom: 'var(--ab-space-2)' } },
React.createElement(Flex, { style: {
borderRadius: 'var(--ab__border-radius)',
}, marginRight: 2, marginBottom: 1, color: 'text-on-secondary', fontSize: 'var( --ab-font-size-2)', alignItems: "center", as: "summary" },
React.createElement(CodeBlock, null,
" ",
example.code)),
React.createElement(Text, { fontSize: 2, marginLeft: 3, style: { fontStyle: 'italic' } }, example.description))))))) : null;
};
const queryDocumentationLinks = {
boolean: BooleanQueryDocsLink,
scalar: ScalarQueryDocsLink,
observable: ObservableQueryDocsLink,
aggregatedBoolean: AggregatedBooleanQueryDocsLink,
aggregatedScalar: AggregatedScalarQueryDocsLink,
cumulativeAggregatedScalar: CumulativeAggregatedScalarQueryDocsLink,
quantileAggregatedScalar: QuantileAggregatedScalarQueryDocsLink,
};