import { css } from '@emotion/css'; import { DataSourceApi, GrafanaTheme2 } from '@grafana/data'; import { Stack } from '@grafana/experimental'; import { Button, Icon, Tooltip, useStyles2 } from '@grafana/ui'; import React from 'react'; import { Draggable } from 'react-beautiful-dnd'; import { VisualQueryModeller, QueryBuilderOperation, QueryBuilderOperationParamValue, QueryBuilderOperationDef, QueryBuilderOperationParamDef, } from '../shared/types'; import { OperationHeader } from './OperationHeader'; import { getOperationParamEditor } from './OperationParamEditor'; import { getOperationParamId } from './operationUtils'; export interface Props { operation: QueryBuilderOperation; index: number; query: any; datasource: DataSourceApi; queryModeller: VisualQueryModeller; onChange: (index: number, update: QueryBuilderOperation) => void; onRemove: (index: number) => void; onRunQuery: () => void; } export function OperationEditor({ operation, index, onRemove, onChange, onRunQuery, queryModeller, query, datasource, }: Props) { const styles = useStyles2(getStyles); const def = queryModeller.getOperationDef(operation.id); if (!def) { return Operation {operation.id} not found; } const onParamValueChanged = (paramIdx: number, value: QueryBuilderOperationParamValue) => { const update: QueryBuilderOperation = { ...operation, params: [...operation.params] }; update.params[paramIdx] = value; callParamChangedThenOnChange(def, update, index, paramIdx, onChange); }; const onAddRestParam = () => { const update: QueryBuilderOperation = { ...operation, params: [...operation.params, ''] }; callParamChangedThenOnChange(def, update, index, operation.params.length, onChange); }; const onRemoveRestParam = (paramIdx: number) => { const update: QueryBuilderOperation = { ...operation, params: [...operation.params.slice(0, paramIdx), ...operation.params.slice(paramIdx + 1)], }; callParamChangedThenOnChange(def, update, index, paramIdx, onChange); }; const operationElements: React.ReactNode[] = []; for (let paramIndex = 0; paramIndex < operation.params.length; paramIndex++) { const paramDef = def.params[Math.min(def.params.length - 1, paramIndex)]; const Editor = getOperationParamEditor(paramDef); operationElements.push(
{!paramDef.hideName && (
{paramDef.description && ( )}
)}
{paramDef.restParam && (operation.params.length > def.params.length || paramDef.optional) && (
); } // Handle adding button for rest params let restParam: React.ReactNode | undefined; if (def.params.length > 0) { const lastParamDef = def.params[def.params.length - 1]; if (lastParamDef.restParam) { restParam = renderAddRestParamButton(lastParamDef, onAddRestParam, index, operation.params.length, styles); } } return ( {(provided) => (
{operationElements}
{restParam} {index < query.operations.length - 1 && (
)}
)} ); } function renderAddRestParamButton( paramDef: QueryBuilderOperationParamDef, onAddRestParam: () => void, operationIndex: number, paramIndex: number, styles: OperationEditorStyles ) { return (
); } function callParamChangedThenOnChange( def: QueryBuilderOperationDef, operation: QueryBuilderOperation, operationIndex: number, paramIndex: number, onChange: (index: number, update: QueryBuilderOperation) => void ) { if (def.paramChangedHandler) { onChange(operationIndex, def.paramChangedHandler(paramIndex, operation, def)); } else { onChange(operationIndex, operation); } } const getStyles = (theme: GrafanaTheme2) => { return { card: css({ background: theme.colors.background.primary, border: `1px solid ${theme.colors.border.medium}`, display: 'flex', flexDirection: 'column', cursor: 'grab', borderRadius: theme.shape.borderRadius(1), marginBottom: theme.spacing(1), position: 'relative', }), infoIcon: css({ marginLeft: theme.spacing(0.5), color: theme.colors.text.secondary, ':hover': { color: theme.colors.text.primary, }, }), body: css({ margin: theme.spacing(1, 1, 0.5, 1), display: 'table', }), paramRow: css({ label: 'paramRow', display: 'table-row', verticalAlign: 'middle', }), paramName: css({ display: 'table-cell', padding: theme.spacing(0, 1, 0, 0), fontSize: theme.typography.bodySmall.fontSize, fontWeight: theme.typography.fontWeightMedium, verticalAlign: 'middle', height: '32px', }), paramValue: css({ label: 'paramValue', display: 'table-cell', verticalAlign: 'middle', }), restParam: css({ padding: theme.spacing(0, 1, 1, 1), }), arrow: css({ position: 'absolute', top: '0', right: '-18px', display: 'flex', }), arrowLine: css({ height: '2px', width: '8px', backgroundColor: theme.colors.border.strong, position: 'relative', top: '14px', }), arrowArrow: css({ width: 0, height: 0, borderTop: `5px solid transparent`, borderBottom: `5px solid transparent`, borderLeft: `7px solid ${theme.colors.border.strong}`, position: 'relative', top: '10px', }), }; }; type OperationEditorStyles = ReturnType;