UNPKG

@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
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, };