import { css, cx } from '@emotion/css'; import React, { ReactNode, useState } from 'react'; import { DataQuery, DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { DataSourcePicker } from '@grafana/runtime'; import { Icon, Input, FieldValidationMessage, useStyles2 } from '@grafana/ui'; export interface Props { query: TQuery; queries: TQuery[]; disabled?: boolean; dataSource: DataSourceInstanceSettings; renderExtras?: () => ReactNode; onChangeDataSource?: (settings: DataSourceInstanceSettings) => void; onChange: (query: TQuery) => void; onClick: (e: React.MouseEvent) => void; collapsedText: string | null; alerting?: boolean; } export const QueryEditorRowHeader = (props: Props) => { const { query, queries, onChange, collapsedText, renderExtras, disabled } = props; const styles = useStyles2(getStyles); const [isEditing, setIsEditing] = useState(false); const [validationError, setValidationError] = useState(null); const onEditQuery = (event: React.SyntheticEvent) => { setIsEditing(true); }; const onEndEditName = (newName: string) => { setIsEditing(false); // Ignore change if invalid if (validationError) { setValidationError(null); return; } if (query.refId !== newName) { onChange({ ...query, refId: newName, }); } }; const onInputChange = (event: React.SyntheticEvent) => { const newName = event.currentTarget.value.trim(); if (newName.length === 0) { setValidationError('An empty query name is not allowed'); return; } for (const otherQuery of queries) { if (otherQuery !== query && newName === otherQuery.refId) { setValidationError('Query name already exists'); return; } } if (validationError) { setValidationError(null); } }; const onEditQueryBlur = (event: React.SyntheticEvent) => { onEndEditName(event.currentTarget.value.trim()); }; const onKeyDown = (event: React.KeyboardEvent) => { if (event.key === 'Enter') { onEndEditName((event.target as any).value); } }; const onFocus = (event: React.FocusEvent) => { event.target.select(); }; return ( <>
{!isEditing && ( )} {isEditing && ( <> {validationError && {validationError}} )} {renderDataSource(props, styles)} {renderExtras &&
{renderExtras()}
} {disabled && Disabled}
{collapsedText &&
{collapsedText}
} ); }; const renderDataSource = ( props: Props, styles: ReturnType ): ReactNode => { const { alerting, dataSource, onChangeDataSource } = props; if (!onChangeDataSource) { return ({dataSource.name}); } return (
); }; const getStyles = (theme: GrafanaTheme2) => { return { wrapper: css` label: Wrapper; display: flex; align-items: center; margin-left: ${theme.spacing(0.5)}; overflow: hidden; `, queryNameWrapper: css` display: flex; cursor: pointer; border: 1px solid transparent; border-radius: ${theme.shape.borderRadius(2)}; align-items: center; padding: 0 0 0 ${theme.spacing(0.5)}; margin: 0; background: transparent; overflow: hidden; &:hover { background: ${theme.colors.action.hover}; border: 1px dashed ${theme.colors.border.strong}; } &:focus { border: 2px solid ${theme.colors.primary.border}; } &:hover, &:focus { .query-name-edit-icon { visibility: visible; } } `, queryName: css` font-weight: ${theme.typography.fontWeightMedium}; color: ${theme.colors.primary.text}; cursor: pointer; overflow: hidden; margin-left: ${theme.spacing(0.5)}; `, queryEditIcon: cx( css` margin-left: ${theme.spacing(2)}; visibility: hidden; `, 'query-name-edit-icon' ), queryNameInput: css` max-width: 300px; margin: -4px 0; `, collapsedText: css` font-weight: ${theme.typography.fontWeightRegular}; font-size: ${theme.typography.bodySmall.fontSize}; color: ${theme.colors.text.secondary}; padding-left: ${theme.spacing(1)}; align-items: center; overflow: hidden; font-style: italic; white-space: nowrap; text-overflow: ellipsis; `, contextInfo: css` font-size: ${theme.typography.bodySmall.fontSize}; font-style: italic; color: ${theme.colors.text.secondary}; padding-left: 10px; padding-right: 10px; `, itemWrapper: css` display: flex; margin-left: 4px; `, }; };