mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
[ES|QL] Index editor cell validations (#224728)
## Summary Related issue https://github.com/elastic/kibana/issues/207330 Adds simple type validations when editing/adding a new cell in the index editor. <img width="1271" alt="image" src="https://github.com/user-attachments/assets/6ec9fae4-f0d6-4ab2-b32e-64dc1594c36a" />
This commit is contained in:
parent
210280580b
commit
531d80aae4
5 changed files with 121 additions and 13 deletions
|
@ -113,8 +113,15 @@ const DataGrid: React.FC<ESQLDataGridProps> = (props) => {
|
|||
);
|
||||
|
||||
const CellValueRenderer = useMemo(() => {
|
||||
return getCellValueRenderer(rows, editingCell, savingDocs, setEditingCell, onValueChange);
|
||||
}, [rows, editingCell, setEditingCell, onValueChange, savingDocs]);
|
||||
return getCellValueRenderer(
|
||||
rows,
|
||||
props.columns,
|
||||
editingCell,
|
||||
savingDocs,
|
||||
setEditingCell,
|
||||
onValueChange
|
||||
);
|
||||
}, [rows, props.columns, editingCell, setEditingCell, onValueChange, savingDocs]);
|
||||
|
||||
const externalCustomRenderers: CustomCellRenderer = useMemo(() => {
|
||||
return activeColumns.reduce((acc, columnId) => {
|
||||
|
|
|
@ -69,7 +69,7 @@ export const RowColumnCreator = ({ columns }: { columns: DatatableColumn[] }) =>
|
|||
onChange={updateRow(column.id)}
|
||||
autoFocus={index === 0}
|
||||
css={css`
|
||||
min-width: ${230}px;
|
||||
min-width: ${180}px;
|
||||
`}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -7,13 +7,17 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { EuiFieldText } from '@elastic/eui';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { DatatableColumn } from '@kbn/expressions-plugin/common';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { getInputComponentForType } from './value_inputs_factory';
|
||||
import { KibanaContextExtra } from '../types';
|
||||
|
||||
interface ValueInputProps {
|
||||
value?: string;
|
||||
columnName?: string;
|
||||
columns?: DatatableColumn[];
|
||||
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
||||
onEnter?: (value: string) => void;
|
||||
onChange?: (value: string) => void;
|
||||
|
@ -24,33 +28,55 @@ interface ValueInputProps {
|
|||
export const ValueInput = ({
|
||||
value = '',
|
||||
columnName = '',
|
||||
columns,
|
||||
onBlur,
|
||||
onEnter,
|
||||
onChange,
|
||||
autoFocus = false,
|
||||
className = '',
|
||||
}: ValueInputProps) => {
|
||||
const {
|
||||
services: { notifications },
|
||||
} = useKibana<KibanaContextExtra>();
|
||||
const [editValue, setEditValue] = useState(value);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const columnType = useMemo(() => {
|
||||
if (!columns || !columnName) return;
|
||||
const col = columns.find((c) => c.name === columnName);
|
||||
return col?.meta?.type;
|
||||
}, [columns, columnName]);
|
||||
|
||||
const onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
// Perform Validations
|
||||
if (error) {
|
||||
notifications.toasts.addDanger({
|
||||
title: error,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
onEnter?.(editValue);
|
||||
}
|
||||
};
|
||||
|
||||
const onBlurHandler = (event: React.FocusEvent<HTMLInputElement>) => {
|
||||
if (onBlur) {
|
||||
onBlur(event);
|
||||
if (error) {
|
||||
notifications.toasts.addDanger({
|
||||
title: error,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Perform Validations ?
|
||||
onBlur?.(event);
|
||||
};
|
||||
|
||||
const InputComponent = useMemo(() => getInputComponentForType(columnType), [columnType]);
|
||||
|
||||
return (
|
||||
<EuiFieldText
|
||||
<InputComponent
|
||||
autoFocus={autoFocus}
|
||||
compressed
|
||||
placeholder={columnName}
|
||||
label={columnName}
|
||||
value={editValue}
|
||||
aria-label={i18n.translate('indexEditor.cellValueInput.aria', {
|
||||
defaultMessage: 'Value for {columnName}',
|
||||
|
@ -60,6 +86,7 @@ export const ValueInput = ({
|
|||
setEditValue(e.target.value);
|
||||
onChange?.(e.target.value);
|
||||
}}
|
||||
onError={setError}
|
||||
onBlur={onBlurHandler}
|
||||
onKeyDown={onKeyDown}
|
||||
className={className}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
|
|||
import { type DataGridCellValueElementProps } from '@kbn/unified-data-table';
|
||||
import type { DataTableRecord } from '@kbn/discover-utils';
|
||||
import { isNil } from 'lodash';
|
||||
import type { DatatableColumn } from '@kbn/expressions-plugin/common';
|
||||
import type { PendingSave } from '../index_update_service';
|
||||
import { ValueInput } from './value_input';
|
||||
|
||||
|
@ -20,6 +21,7 @@ export type OnCellValueChange = (docId: string, update: any) => void;
|
|||
export const getCellValueRenderer =
|
||||
(
|
||||
rows: DataTableRecord[],
|
||||
columns: DatatableColumn[],
|
||||
editingCell: { row: number | null; col: string | null },
|
||||
savingDocs: PendingSave | undefined,
|
||||
onEditStart: (update: { row: number | null; col: string | null }) => void,
|
||||
|
@ -40,9 +42,8 @@ export const getCellValueRenderer =
|
|||
cellValue = pendingSaveValue;
|
||||
} else if (row.flattened) {
|
||||
// Otherwise, use the value from the row
|
||||
cellValue = row.flattened[columnId];
|
||||
cellValue = row.flattened[columnId]?.toString();
|
||||
}
|
||||
|
||||
if (cellValue == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -60,6 +61,7 @@ export const getCellValueRenderer =
|
|||
onValueChange(docId!, { [columnId]: value });
|
||||
}}
|
||||
columnName={columnId}
|
||||
columns={columns}
|
||||
value={cellValue}
|
||||
autoFocus
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { EuiFieldText, EuiFieldNumber } from '@elastic/eui';
|
||||
import { DatatableColumnType } from '@kbn/expressions-plugin/common';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export interface ValueInputProps {
|
||||
onError?: (error: string | null) => void;
|
||||
value: string;
|
||||
label?: string;
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
||||
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
autoFocus?: boolean;
|
||||
className?: string;
|
||||
isInvalid?: boolean;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export const StringInput: React.FC<ValueInputProps> = ({ onError, ...restOfProps }) => (
|
||||
<EuiFieldText compressed {...restOfProps} />
|
||||
);
|
||||
|
||||
export const NumberInput: React.FC<ValueInputProps> = ({ onError, ...restOfProps }) => (
|
||||
<EuiFieldNumber compressed {...restOfProps} />
|
||||
);
|
||||
|
||||
export const BooleanInput: React.FC<ValueInputProps> = ({ onError, onChange, ...restOfProps }) => {
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
|
||||
const onChangeHandler = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (!['true', 'false'].includes(e.target.value.toLowerCase())) {
|
||||
const booleanError = i18n.translate('indexEditor.cellValueInput.validation.boolean', {
|
||||
defaultMessage: 'Value must be true or false',
|
||||
});
|
||||
setError(booleanError);
|
||||
onError?.(booleanError);
|
||||
} else {
|
||||
setError(null);
|
||||
onError?.(null);
|
||||
}
|
||||
onChange?.(e);
|
||||
},
|
||||
[onError, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFieldText compressed {...restOfProps} onChange={onChangeHandler} isInvalid={!!error} />
|
||||
);
|
||||
};
|
||||
|
||||
export function getInputComponentForType(
|
||||
type: DatatableColumnType | undefined
|
||||
): React.FC<ValueInputProps> {
|
||||
switch (type) {
|
||||
case 'number':
|
||||
return NumberInput;
|
||||
case 'boolean':
|
||||
return BooleanInput;
|
||||
default:
|
||||
return StringInput;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue