UNPKG

@adaptabletools/adaptable

Version:

Powerful AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements

194 lines (193 loc) 14 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useRef, useState } from 'react'; 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 { 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'; import { Box, Flex } from '../Flex'; import { twMerge } from '../../twMerge'; 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' ? (_jsx(EditorInputWithWhereClause, { type: type, module: module, value: props.value, onChange: (value) => { setExpressionText(value); props.onChange(value); }, testData: props.initialData, api: props.api })) : (_jsx(EditorInput, { type: type, module: module, columnScope: props.columnScope, 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(); const queryableColumns = type === 'scalar' || type === 'boolean' || type === 'aggregatedScalar' ? props.columns : props.columns.filter((c) => !c.isCalculatedColumn); const showDocumentationLinks = props.api.internalApi.isDocumentationLinksDisplayed(); const showNamedQueryStuff = false; const queryDocumentationLink = queryDocumentationLinks[type]; const saveAsNamedQueryElement = allowSaveNamedQuery && props.api.internalApi .getModuleService() .getModuleById(NamedQueryModuleId) .isModuleEditable() && (_jsxs(Flex, { flexDirection: "row", className: "twa:mb-2 twa:text-3", children: [_jsx(CheckBox, { checked: !!namedQuery, onChange: (checked) => { if (checked) { setNamedQuery({ Name: '', BooleanExpression: expressionText, }); } else { setNamedQuery(false); } }, className: "twa:mr-2 twa:items-center", children: "Save as new Named Query" }), namedQuery ? (_jsx(AdaptableInput, { className: "twa:flex-1", onChange: (event) => { const { target: { value }, } = event; setNamedQuery({ Name: value, BooleanExpression: expressionText, }); } })) : null] })); const editorElement = (_jsx(Flex, { className: twMerge(`${baseClassName} twa:flex-1 twa:pl-2 twa:overflow-auto`, props.className), "data-name": "expression-editor-wrapper", style: props.style, children: _jsx(Flex, { className: "twa:flex-1", style: { minHeight: 0 }, flexDirection: "column", onFocus: (event) => { if (event.target.tagName === 'TEXTAREA') { textAreaRef.current = event.target; } }, children: _jsxs(Flex, { className: "twa:flex-1 twa:text-2", flexDirection: "row", "data-name": "expression-editor", style: { minHeight: 0 }, children: [_jsxs(Flex, { className: "twa:flex-1 twa:pr-2 twa:overflow-auto twa:min-h-0", "data-name": "expression-builder", flexDirection: "column", children: [editorInput, StringExtensions.IsNotNullOrEmpty(expressionText?.trim()) && (_jsx(ExpressionFunctionDocumentation, { expressionFunction: selectedFunction })), StringExtensions.IsNullOrEmpty(expressionText?.trim()) && renderQueryHints(type), showDocumentationLinks && (_jsxs(HelpBlock, { "data-name": "query-documentation", className: "twa:my-2 twa:p-2 twa:text-3", children: [_jsx(ButtonInfo, { className: "twa:mr-2", onClick: () => window.open(queryDocumentationLink, '_blank') }), "See documentation for more details and examples"] })), _jsx(Box, { className: "twa:flex-1" }), showNamedQueryStuff && saveAsNamedQueryElement] }), _jsx(Box, { className: `${baseClassName}__sidebar twa:pb-2 twa:px-2 twa:overflow-auto twa:h-full twa:w-[280px]`, "data-name": "expression-sidebar", children: _jsx(Box, { className: "twa:h-full twa:p-2 twa:rounded-standard twa:shadow-md twa:bg-background twa:text-foreground", children: _jsxs(Tabs, { className: "twa:h-full", children: [_jsx(Tabs.Tab, { value: "column", children: "Columns" }), _jsx(Tabs.Tab, { value: "field", children: "Fields" }), _jsx(Tabs.Tab, { value: "named-query", children: "Named Queries" }), _jsx(Tabs.Content, { className: "twa:flex-1 twa:h-full", children: _jsx(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 }) }), _jsx(Tabs.Content, { children: adaptableFields && adaptableFields.length > 0 ? (_jsx(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, })) })) : (_jsx(Box, { className: "twa:p-2", children: "No Data Fields provided" })) }), _jsx(Tabs.Content, { children: ArrayExtensions.IsNullOrEmpty(props.namedQueries) ? (_jsxs("div", { children: [_jsx(Box, { className: "twa:p-2", children: "No Named Queries defined" }), _jsxs(Box, { className: "twa:text-2 twa:p-2", children: ["Named Queries are saved Expressions which can be referenced in other Expressions using the ", _jsx("i", { children: "QUERY" }), " keyword", ' '] })] })) : (_jsx(NamedQueryEditor, { namedQueries: props.namedQueries })) })] }) }) })] }) }) })); const queryBuilderElement = (_jsx(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 (_jsx(ExpressionEditorContext.Provider, { value: { selectedFunction, setSelectedFunction, textAreaRef, }, children: (() => { switch (true) { case showQueryBuilder && showExpressionEditor: return (_jsxs(Tabs, { "data-name": "editor-selector-tabs", className: "twa:mb-2 twa:p-2 twa:min-h-0 twa:flex-1", children: [_jsx(Tabs.Tab, { value: "editor", children: "Expression Editor" }), _jsx(Tabs.Tab, { value: "ui", children: "Query Builder" }), _jsx(Tabs.Content, { children: editorElement }), _jsx(Tabs.Content, { children: _jsxs(Flex, { flexDirection: "column", className: "twa:h-full", children: [queryBuilderElement, _jsx(Box, { className: "twa:flex-1" }), showNamedQueryStuff && saveAsNamedQueryElement] }) })] })); case showQueryBuilder: return (_jsx(Flex, { flexDirection: "column", className: "twa:p-2 twa:h-full", children: queryBuilderElement })); case showExpressionEditor: return (_jsx(Flex, { flexDirection: "column", className: "twa:p-2 twa:h-full", children: 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 ? (_jsx(Box, { "data-name": "expression-hints", className: "twa:my-2 twa:p-2 twa:bg-primary twa:rounded-standard", children: _jsxs(Box, { children: [_jsxs(Box, { className: "twa:mb-2", children: [_jsx("b", { children: "Examples" }), " (click on each to see its explanation):"] }), examples.map((example, index) => (_jsxs("details", { className: "twa:mb-2", children: [_jsx(Flex, { className: "twa:mr-2 twa:rounded-standard twa:mb-1 twa:text-2", alignItems: "center", as: "summary", children: _jsxs(CodeBlock, { children: [" ", example.code] }) }), _jsx(Box, { className: "twa:ml-3 twa:italic twa:text-2", children: example.description })] }, index)))] }) })) : null; }; const queryDocumentationLinks = { boolean: BooleanQueryDocsLink, scalar: ScalarQueryDocsLink, observable: ObservableQueryDocsLink, aggregatedBoolean: AggregatedBooleanQueryDocsLink, aggregatedScalar: AggregatedScalarQueryDocsLink, cumulativeAggregatedScalar: CumulativeAggregatedScalarQueryDocsLink, quantileAggregatedScalar: QuantileAggregatedScalarQueryDocsLink, };