mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Lens] don't allow to drag and drop a single element (#141793)
* [wip][Lens] don't allow to drag and drop a single element * do some cleanup * push some changes * update field_inputs * some cleanup * fix styles * push some code * push some changes * fix some tests * push some changes * add padding * fix styles * first attempt at cleaning up markup and styles * change the name of color to bgColor, pass isDragging state * Update default_bucket_container.tsx more overlooked copy * fix JEST * Update x-pack/plugins/lens/public/shared_components/drag_drop_bucket/fields_bucket_container.tsx Co-authored-by: Michael Marcialis <michael@marcial.is> * Update x-pack/plugins/lens/public/shared_components/drag_drop_bucket/default_bucket_container.tsx Co-authored-by: Michael Marcialis <michael@marcial.is> * Update x-pack/plugins/lens/public/shared_components/drag_drop_bucket/default_bucket_container.tsx Co-authored-by: Michael Marcialis <michael@marcial.is> * Update x-pack/plugins/lens/public/shared_components/drag_drop_bucket/default_bucket_container.tsx Co-authored-by: Michael Marcialis <michael@marcial.is> * Update x-pack/plugins/lens/public/shared_components/drag_drop_bucket/default_bucket_container.tsx Co-authored-by: Michael Marcialis <michael@marcial.is> * Update x-pack/plugins/lens/public/shared_components/drag_drop_bucket/buckets.tsx Co-authored-by: Michael Marcialis <michael@marcial.is> * Update x-pack/plugins/lens/public/shared_components/drag_drop_bucket/buckets.tsx Co-authored-by: Michael Marcialis <michael@marcial.is> * Update x-pack/plugins/lens/public/shared_components/drag_drop_bucket/fields_bucket_container.tsx Co-authored-by: Michael Marcialis <michael@marcial.is> * Update x-pack/plugins/lens/public/shared_components/drag_drop_bucket/fields_bucket_container.tsx Co-authored-by: Michael Marcialis <michael@marcial.is> * Update x-pack/plugins/lens/public/shared_components/drag_drop_bucket/default_bucket_container.tsx Co-authored-by: Michael Marcialis <michael@marcial.is> * fix JEST Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com> Co-authored-by: Michael Marcialis <michael.marcialis@elastic.co> Co-authored-by: Marta Bondyra <marta.bondyra@elastic.co> Co-authored-by: Michael Marcialis <michael@marcial.is>
This commit is contained in:
parent
6157f0be86
commit
067484b9b3
18 changed files with 494 additions and 437 deletions
|
@ -399,8 +399,8 @@ describe('filters', () => {
|
|||
);
|
||||
|
||||
instance
|
||||
.find('[data-test-subj="lns-customBucketContainer-remove"]')
|
||||
.at(2)
|
||||
.find('[data-test-subj="lns-customBucketContainer-remove-1"]')
|
||||
.at(0)
|
||||
.simulate('click');
|
||||
expect(updateLayerSpy).toHaveBeenCalledWith({
|
||||
...layer,
|
||||
|
|
|
@ -235,13 +235,14 @@ export const FilterList = ({
|
|||
droppableId="FILTERS_DROPPABLE_AREA"
|
||||
items={localFilters}
|
||||
>
|
||||
{localFilters?.map((filter: FilterValue, idx: number) => {
|
||||
{localFilters?.map((filter, idx, arrayRef) => {
|
||||
const isInvalid = !isQueryValid(filter.input, indexPattern);
|
||||
const id = filter.id;
|
||||
|
||||
return (
|
||||
<DraggableBucketContainer
|
||||
id={filter.id}
|
||||
key={filter.id}
|
||||
id={id}
|
||||
key={id}
|
||||
idx={idx}
|
||||
isInvalid={isInvalid}
|
||||
invalidMessage={i18n.translate('xpack.lens.indexPattern.filters.isInvalid', {
|
||||
|
@ -251,7 +252,8 @@ export const FilterList = ({
|
|||
removeTitle={i18n.translate('xpack.lens.indexPattern.filters.removeFilter', {
|
||||
defaultMessage: 'Remove a filter',
|
||||
})}
|
||||
isNotRemovable={localFilters.length === 1}
|
||||
isNotRemovable={arrayRef.length === 1}
|
||||
isNotDraggable={arrayRef.length === 1}
|
||||
>
|
||||
<FilterPopover
|
||||
data-test-subj="indexPattern-filters-existingFilterContainer"
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import './advanced_editor.scss';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
|
@ -220,7 +220,7 @@ export const AdvancedRangeEditor = ({
|
|||
[localRanges]
|
||||
);
|
||||
|
||||
const addNewRange = () => {
|
||||
const addNewRange = useCallback(() => {
|
||||
const newRangeId = generateId();
|
||||
|
||||
setLocalRanges([
|
||||
|
@ -228,13 +228,13 @@ export const AdvancedRangeEditor = ({
|
|||
{
|
||||
id: newRangeId,
|
||||
from: localRanges[localRanges.length - 1].to,
|
||||
to: Infinity,
|
||||
to: Number.POSITIVE_INFINITY,
|
||||
label: '',
|
||||
},
|
||||
]);
|
||||
|
||||
setActiveRangeId(newRangeId);
|
||||
};
|
||||
}, [localRanges]);
|
||||
|
||||
const changeActiveRange = (rangeId: string) => {
|
||||
let newActiveRangeId = rangeId;
|
||||
|
@ -264,11 +264,10 @@ export const AdvancedRangeEditor = ({
|
|||
<>
|
||||
<DragDropBuckets
|
||||
onDragEnd={setLocalRanges}
|
||||
onDragStart={() => {}}
|
||||
droppableId="RANGES_DROPPABLE_AREA"
|
||||
items={localRanges}
|
||||
>
|
||||
{localRanges.map((range: LocalRangeType, idx: number) => (
|
||||
{localRanges.map((range, idx, arrayRef) => (
|
||||
<DraggableBucketContainer
|
||||
key={range.id}
|
||||
idx={idx}
|
||||
|
@ -278,20 +277,21 @@ export const AdvancedRangeEditor = ({
|
|||
defaultMessage: 'This range is invalid',
|
||||
})}
|
||||
onRemoveClick={() => {
|
||||
const newRanges = localRanges.filter((_, i) => i !== idx);
|
||||
const newRanges = arrayRef.filter((_, i) => i !== idx);
|
||||
setLocalRanges(newRanges);
|
||||
}}
|
||||
removeTitle={i18n.translate('xpack.lens.indexPattern.ranges.deleteRange', {
|
||||
defaultMessage: 'Delete range',
|
||||
})}
|
||||
isNotRemovable={localRanges.length === 1}
|
||||
isNotRemovable={arrayRef.length === 1}
|
||||
isNotDraggable={arrayRef.length < 2}
|
||||
>
|
||||
<RangePopover
|
||||
range={range}
|
||||
isOpen={range.id === activeRangeId}
|
||||
triggerClose={() => changeActiveRange('')}
|
||||
setRange={(newRange: LocalRangeType) => {
|
||||
const newRanges = [...localRanges];
|
||||
const newRanges = [...arrayRef];
|
||||
if (newRange.id === newRanges[idx].id) {
|
||||
newRanges[idx] = newRange;
|
||||
} else {
|
||||
|
@ -320,9 +320,7 @@ export const AdvancedRangeEditor = ({
|
|||
))}
|
||||
</DragDropBuckets>
|
||||
<NewBucketButton
|
||||
onClick={() => {
|
||||
addNewRange();
|
||||
}}
|
||||
onClick={addNewRange}
|
||||
label={i18n.translate('xpack.lens.indexPattern.ranges.addRange', {
|
||||
defaultMessage: 'Add range',
|
||||
})}
|
||||
|
|
|
@ -797,8 +797,8 @@ describe('ranges', () => {
|
|||
// This series of act closures are made to make it work properly the update flush
|
||||
act(() => {
|
||||
instance
|
||||
.find('[data-test-subj="lns-customBucketContainer-remove"]')
|
||||
.last()
|
||||
.find('[data-test-subj="lns-customBucketContainer-remove-1"]')
|
||||
.at(0)
|
||||
.simulate('click');
|
||||
});
|
||||
|
||||
|
|
|
@ -5,24 +5,16 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiDraggable,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
htmlIdGenerator,
|
||||
EuiPanel,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { htmlIdGenerator } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ExistingFieldsMap, IndexPattern } from '../../../../types';
|
||||
import {
|
||||
DragDropBuckets,
|
||||
FieldsBucketContainer,
|
||||
NewBucketButton,
|
||||
TooltipWrapper,
|
||||
useDebouncedValue,
|
||||
DraggableBucketContainer,
|
||||
} from '../../../../shared_components';
|
||||
import { FieldSelect } from '../../../dimension_panel/field_select';
|
||||
import type { TermsIndexPatternColumn } from './types';
|
||||
|
@ -61,9 +53,6 @@ export function FieldInputs({
|
|||
operationSupportMatrix,
|
||||
invalidFields,
|
||||
}: FieldInputsProps) {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
const onChangeWrapped = useCallback(
|
||||
(values: WrappedValue[]) =>
|
||||
onChange(values.filter(removeNewEmptyField).map(({ value }) => value)),
|
||||
|
@ -97,154 +86,90 @@ export function FieldInputs({
|
|||
);
|
||||
|
||||
const disableActions =
|
||||
(localValues.length === 2 && localValues.some(({ isNew }) => isNew)) ||
|
||||
localValues.length === 1;
|
||||
localValues.length === 1 || localValues.filter(({ isNew }) => !isNew).length < 2;
|
||||
const localValuesFilled = localValues.filter(({ isNew }) => !isNew);
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: isDragging ? 'transparent' : euiTheme.colors.lightestShade,
|
||||
borderRadius: euiTheme.size.xs,
|
||||
marginBottom: euiTheme.size.xs,
|
||||
<DragDropBuckets
|
||||
onDragEnd={(updatedValues: WrappedValue[]) => {
|
||||
handleInputChange(updatedValues);
|
||||
}}
|
||||
droppableId="TOP_TERMS_DROPPABLE_AREA"
|
||||
items={localValues}
|
||||
bgColor="subdued"
|
||||
>
|
||||
<DragDropBuckets
|
||||
onDragEnd={(updatedValues: WrappedValue[]) => {
|
||||
handleInputChange(updatedValues);
|
||||
setIsDragging(false);
|
||||
}}
|
||||
className="lnsIndexPatternDimensionEditor__droppable"
|
||||
onDragStart={() => {
|
||||
setIsDragging(true);
|
||||
}}
|
||||
droppableId="TOP_TERMS_DROPPABLE_AREA"
|
||||
items={localValues}
|
||||
>
|
||||
{localValues.map(({ id, value, isNew }, index) => {
|
||||
// need to filter the available fields for multiple terms
|
||||
// * a scripted field should be removed
|
||||
// * a field of unsupported type should be removed
|
||||
// * a field that has been used
|
||||
// * a scripted field was used in a singular term, should be marked as invalid for multi-terms
|
||||
const filteredOperationByField = Object.keys(operationSupportMatrix.operationByField)
|
||||
.filter((key) => {
|
||||
if (key === value) {
|
||||
return true;
|
||||
}
|
||||
const field = indexPattern.getFieldByName(key);
|
||||
if (index === 0) {
|
||||
return !rawValuesLookup.has(key) && field && supportedTypes.has(field.type);
|
||||
} else {
|
||||
return (
|
||||
!rawValuesLookup.has(key) &&
|
||||
field &&
|
||||
!field.scripted &&
|
||||
supportedTypes.has(field.type)
|
||||
);
|
||||
}
|
||||
})
|
||||
.reduce<OperationSupportMatrix['operationByField']>((memo, key) => {
|
||||
memo[key] = operationSupportMatrix.operationByField[key];
|
||||
return memo;
|
||||
}, {});
|
||||
{localValues.map(({ id, value, isNew }, index, arrayRef) => {
|
||||
// need to filter the available fields for multiple terms
|
||||
// * a scripted field should be removed
|
||||
// * a field of unsupported type should be removed
|
||||
// * a field that has been used
|
||||
// * a scripted field was used in a singular term, should be marked as invalid for multi-terms
|
||||
const filteredOperationByField = Object.keys(operationSupportMatrix.operationByField)
|
||||
.filter((key) => {
|
||||
if (key === value) {
|
||||
return true;
|
||||
}
|
||||
const field = indexPattern.getFieldByName(key);
|
||||
if (index === 0) {
|
||||
return !rawValuesLookup.has(key) && field && supportedTypes.has(field.type);
|
||||
} else {
|
||||
return (
|
||||
!rawValuesLookup.has(key) &&
|
||||
field &&
|
||||
!field.scripted &&
|
||||
supportedTypes.has(field.type)
|
||||
);
|
||||
}
|
||||
})
|
||||
.reduce<OperationSupportMatrix['operationByField']>((memo, key) => {
|
||||
memo[key] = operationSupportMatrix.operationByField[key];
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
const shouldShowError = Boolean(
|
||||
value &&
|
||||
((indexPattern.getFieldByName(value)?.scripted && localValuesFilled.length > 1) ||
|
||||
invalidFields?.includes(value))
|
||||
);
|
||||
return (
|
||||
<EuiDraggable
|
||||
spacing="none"
|
||||
index={index}
|
||||
draggableId={value || 'newField'}
|
||||
key={id}
|
||||
disableInteractiveElementBlocking
|
||||
>
|
||||
{(provided) => (
|
||||
<EuiPanel paddingSize="xs" hasShadow={false} color="transparent">
|
||||
<EuiFlexGroup gutterSize="none" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
className="lnsIndexPatternDimensionEditor__droppableItem"
|
||||
>
|
||||
<EuiIcon
|
||||
size="s"
|
||||
color="text"
|
||||
type="grab"
|
||||
title={i18n.translate('xpack.lens.indexPattern.terms.dragToReorder', {
|
||||
defaultMessage: 'Drag to reorder',
|
||||
})}
|
||||
data-test-subj={`indexPattern-terms-dragToReorder-${index}`}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={true}
|
||||
style={{ minWidth: 0 }}
|
||||
className="lnsIndexPatternDimensionEditor__droppableItem"
|
||||
>
|
||||
<FieldSelect
|
||||
fieldIsInvalid={shouldShowError}
|
||||
currentIndexPattern={indexPattern}
|
||||
existingFields={existingFields[indexPattern.title]}
|
||||
operationByField={filteredOperationByField}
|
||||
selectedOperationType={column.operationType}
|
||||
selectedField={value}
|
||||
autoFocus={isNew}
|
||||
onChoose={(choice) => {
|
||||
onFieldSelectChange(choice, index);
|
||||
}}
|
||||
isInvalid={shouldShowError}
|
||||
data-test-subj={
|
||||
localValues.length !== 1
|
||||
? `indexPattern-dimension-field-${index}`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<TooltipWrapper
|
||||
tooltipContent={i18n.translate(
|
||||
'xpack.lens.indexPattern.terms.deleteButtonDisabled',
|
||||
{
|
||||
defaultMessage:
|
||||
'This function requires a minimum of one field defined',
|
||||
}
|
||||
)}
|
||||
condition={disableActions}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
iconType="trash"
|
||||
color="danger"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.lens.indexPattern.terms.deleteButtonAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Delete',
|
||||
}
|
||||
)}
|
||||
title={i18n.translate(
|
||||
'xpack.lens.indexPattern.terms.deleteButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Delete',
|
||||
}
|
||||
)}
|
||||
onClick={() => {
|
||||
handleInputChange(localValues.filter((_, i) => i !== index));
|
||||
}}
|
||||
data-test-subj={`indexPattern-terms-removeField-${index}`}
|
||||
isDisabled={disableActions && !isNew}
|
||||
/>
|
||||
</TooltipWrapper>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
)}
|
||||
</EuiDraggable>
|
||||
);
|
||||
})}
|
||||
</DragDropBuckets>
|
||||
</div>
|
||||
const shouldShowError = Boolean(
|
||||
value &&
|
||||
((indexPattern.getFieldByName(value)?.scripted && localValuesFilled.length > 1) ||
|
||||
invalidFields?.includes(value))
|
||||
);
|
||||
const itemId = (value ?? 'newField') + id;
|
||||
|
||||
return (
|
||||
<DraggableBucketContainer
|
||||
id={itemId}
|
||||
key={itemId}
|
||||
idx={index}
|
||||
onRemoveClick={() => {
|
||||
handleInputChange(arrayRef.filter((_, i) => i !== index));
|
||||
}}
|
||||
removeTitle={i18n.translate('xpack.lens.indexPattern.terms.deleteButtonLabel', {
|
||||
defaultMessage: 'Delete',
|
||||
})}
|
||||
isNotRemovable={disableActions && !isNew}
|
||||
isNotDraggable={arrayRef.length < 2}
|
||||
data-test-subj={`indexPattern-terms`}
|
||||
Container={FieldsBucketContainer}
|
||||
isInsidePanel={true}
|
||||
>
|
||||
<FieldSelect
|
||||
fieldIsInvalid={shouldShowError}
|
||||
currentIndexPattern={indexPattern}
|
||||
existingFields={existingFields[indexPattern.title]}
|
||||
operationByField={filteredOperationByField}
|
||||
selectedOperationType={column.operationType}
|
||||
selectedField={value}
|
||||
autoFocus={isNew}
|
||||
onChoose={(choice) => {
|
||||
onFieldSelectChange(choice, index);
|
||||
}}
|
||||
isInvalid={shouldShowError}
|
||||
data-test-subj={
|
||||
localValues.length !== 1 ? `indexPattern-dimension-field-${index}` : undefined
|
||||
}
|
||||
/>
|
||||
</DraggableBucketContainer>
|
||||
);
|
||||
})}
|
||||
</DragDropBuckets>
|
||||
<NewBucketButton
|
||||
onClick={() => {
|
||||
handleInputChange([...localValues, { id: generateId(), value: undefined, isNew: true }]);
|
||||
|
|
|
@ -63,14 +63,13 @@ describe('buckets shared components', () => {
|
|||
it('should render invalid component', () => {
|
||||
const instance = mount(<DraggableBucketContainer {...defaultProps} isInvalid />);
|
||||
const iconProps = instance.find(EuiIcon).first().props();
|
||||
expect(iconProps.color).toEqual('danger');
|
||||
expect(iconProps.color).toEqual('#BD271E');
|
||||
expect(iconProps.type).toEqual('alert');
|
||||
expect(iconProps.title).toEqual('invalid');
|
||||
});
|
||||
it('should call onRemoveClick when remove icon is clicked', () => {
|
||||
const instance = mount(<DraggableBucketContainer {...defaultProps} />);
|
||||
const removeIcon = instance
|
||||
.find('[data-test-subj="lns-customBucketContainer-remove"]')
|
||||
.find('[data-test-subj="lns-customBucketContainer-remove-0"]')
|
||||
.first();
|
||||
removeIcon.simulate('click');
|
||||
expect(defaultProps.onRemoveClick).toHaveBeenCalled();
|
||||
|
|
|
@ -5,162 +5,110 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import type { Assign } from '@kbn/utility-types';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPanel,
|
||||
EuiButtonIcon,
|
||||
EuiIcon,
|
||||
EuiDragDropContext,
|
||||
euiDragDropReorder,
|
||||
EuiDraggable,
|
||||
EuiDroppable,
|
||||
EuiButtonEmpty,
|
||||
EuiPanelProps,
|
||||
EuiDragDropContext,
|
||||
DragDropContextProps,
|
||||
euiDragDropReorder,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
|
||||
export const NewBucketButton = ({
|
||||
label,
|
||||
onClick,
|
||||
['data-test-subj']: dataTestSubj,
|
||||
isDisabled,
|
||||
className,
|
||||
}: {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
'data-test-subj'?: string;
|
||||
isDisabled?: boolean;
|
||||
className?: string;
|
||||
}) => (
|
||||
<EuiButtonEmpty
|
||||
data-test-subj={dataTestSubj ?? 'lns-newBucket-add'}
|
||||
size="xs"
|
||||
iconType="plusInCircle"
|
||||
onClick={onClick}
|
||||
isDisabled={isDisabled}
|
||||
flush="left"
|
||||
className={className}
|
||||
>
|
||||
{label}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
|
||||
interface BucketContainerProps {
|
||||
isInvalid?: boolean;
|
||||
invalidMessage: string;
|
||||
onRemoveClick: () => void;
|
||||
removeTitle: string;
|
||||
isNotRemovable?: boolean;
|
||||
children: React.ReactNode;
|
||||
dataTestSubj?: string;
|
||||
}
|
||||
|
||||
const BucketContainer = ({
|
||||
isInvalid,
|
||||
invalidMessage,
|
||||
onRemoveClick,
|
||||
removeTitle,
|
||||
children,
|
||||
dataTestSubj,
|
||||
isNotRemovable,
|
||||
}: BucketContainerProps) => {
|
||||
return (
|
||||
<EuiPanel paddingSize="none" data-test-subj={dataTestSubj} hasShadow={false} hasBorder>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false}>{/* Empty for spacing */}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon
|
||||
size="s"
|
||||
color={isInvalid ? 'danger' : 'subdued'}
|
||||
type={isInvalid ? 'alert' : 'grab'}
|
||||
title={
|
||||
isInvalid
|
||||
? invalidMessage
|
||||
: i18n.translate('xpack.lens.customBucketContainer.dragToReorder', {
|
||||
defaultMessage: 'Drag to reorder',
|
||||
})
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={true}>{children}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
iconSize="s"
|
||||
iconType="cross"
|
||||
color="danger"
|
||||
data-test-subj="lns-customBucketContainer-remove"
|
||||
onClick={onRemoveClick}
|
||||
aria-label={removeTitle}
|
||||
title={removeTitle}
|
||||
disabled={isNotRemovable}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
import { DefaultBucketContainer } from './default_bucket_container';
|
||||
import type { BucketContainerProps } from './types';
|
||||
|
||||
export const DraggableBucketContainer = ({
|
||||
id,
|
||||
idx,
|
||||
children,
|
||||
isInsidePanel,
|
||||
Container = DefaultBucketContainer,
|
||||
...bucketContainerProps
|
||||
}: {
|
||||
id: string;
|
||||
idx: number;
|
||||
children: React.ReactNode;
|
||||
} & BucketContainerProps) => {
|
||||
}: Assign<
|
||||
Omit<BucketContainerProps, 'draggableProvided'>,
|
||||
{
|
||||
id: string;
|
||||
children: React.ReactNode;
|
||||
isInsidePanel?: boolean;
|
||||
Container?: React.FunctionComponent<BucketContainerProps>;
|
||||
}
|
||||
>) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
return (
|
||||
<EuiDraggable
|
||||
style={{ marginBottom: 4 }}
|
||||
spacing="none"
|
||||
index={idx}
|
||||
draggableId={id}
|
||||
customDragHandle={true}
|
||||
index={bucketContainerProps.idx}
|
||||
isDragDisabled={bucketContainerProps.isNotDraggable}
|
||||
style={!isInsidePanel ? { marginBottom: euiTheme.size.xs } : {}}
|
||||
spacing="none"
|
||||
hasInteractiveChildren
|
||||
disableInteractiveElementBlocking
|
||||
>
|
||||
{(provided) => <BucketContainer {...bucketContainerProps}>{children}</BucketContainer>}
|
||||
{(provided, state) => (
|
||||
<Container
|
||||
draggableProvided={provided}
|
||||
isDragging={state?.isDragging ?? false}
|
||||
{...bucketContainerProps}
|
||||
>
|
||||
{children}
|
||||
</Container>
|
||||
)}
|
||||
</EuiDraggable>
|
||||
);
|
||||
};
|
||||
|
||||
interface DraggableLocation {
|
||||
droppableId: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export const DragDropBuckets = ({
|
||||
export function DragDropBuckets<T = unknown>({
|
||||
items,
|
||||
onDragStart,
|
||||
onDragEnd,
|
||||
droppableId,
|
||||
children,
|
||||
className,
|
||||
bgColor,
|
||||
}: {
|
||||
items: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
onDragStart: () => void;
|
||||
onDragEnd: (items: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
items: T[];
|
||||
droppableId: string;
|
||||
children: React.ReactElement[];
|
||||
className?: string;
|
||||
}) => {
|
||||
const handleDragEnd = ({
|
||||
source,
|
||||
destination,
|
||||
}: {
|
||||
source?: DraggableLocation;
|
||||
destination?: DraggableLocation;
|
||||
}) => {
|
||||
if (source && destination) {
|
||||
const newItems = euiDragDropReorder(items, source.index, destination.index);
|
||||
onDragEnd(newItems);
|
||||
}
|
||||
};
|
||||
onDragStart?: () => void;
|
||||
onDragEnd?: (items: T[]) => void;
|
||||
bgColor?: EuiPanelProps['color'];
|
||||
}) {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
const handleDragEnd: DragDropContextProps['onDragEnd'] = useCallback(
|
||||
({ source, destination }) => {
|
||||
setIsDragging(false);
|
||||
if (source && destination) {
|
||||
onDragEnd?.(euiDragDropReorder(items, source.index, destination.index));
|
||||
}
|
||||
},
|
||||
[items, onDragEnd]
|
||||
);
|
||||
|
||||
const handleDragStart: DragDropContextProps['onDragStart'] = useCallback(() => {
|
||||
setIsDragging(true);
|
||||
onDragStart?.();
|
||||
}, [onDragStart]);
|
||||
|
||||
return (
|
||||
<EuiDragDropContext onDragEnd={handleDragEnd} onDragStart={onDragStart}>
|
||||
<EuiDroppable droppableId={droppableId} spacing="none" className={className}>
|
||||
{children}
|
||||
</EuiDroppable>
|
||||
<EuiDragDropContext onDragEnd={handleDragEnd} onDragStart={handleDragStart}>
|
||||
<EuiPanel
|
||||
paddingSize="none"
|
||||
color={isDragging ? 'success' : bgColor}
|
||||
hasShadow={false}
|
||||
hasBorder={false}
|
||||
>
|
||||
<EuiDroppable
|
||||
droppableId={droppableId}
|
||||
spacing={bgColor ? 'm' : 'none'}
|
||||
style={{ backgroundColor: 'transparent' }}
|
||||
>
|
||||
{children}
|
||||
</EuiDroppable>
|
||||
</EuiPanel>
|
||||
</EuiDragDropContext>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiPanel,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import type { BucketContainerProps } from './types';
|
||||
import { TooltipWrapper } from '../tooltip_wrapper';
|
||||
|
||||
export const DefaultBucketContainer = ({
|
||||
idx,
|
||||
isInvalid,
|
||||
invalidMessage,
|
||||
onRemoveClick,
|
||||
removeTitle,
|
||||
children,
|
||||
draggableProvided,
|
||||
isNotRemovable,
|
||||
isNotDraggable,
|
||||
'data-test-subj': dataTestSubj = 'lns-customBucketContainer',
|
||||
}: BucketContainerProps) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
return (
|
||||
<EuiPanel
|
||||
paddingSize="none"
|
||||
hasShadow={false}
|
||||
hasBorder={true}
|
||||
data-test-subj={dataTestSubj}
|
||||
style={{ padding: '0 ' + euiTheme.size.xs }}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false} {...(draggableProvided?.dragHandleProps ?? {})}>
|
||||
<TooltipWrapper
|
||||
tooltipContent={i18n.translate('xpack.lens.fieldsBucketContainer.dragHandleDisabled', {
|
||||
defaultMessage: 'Reordering requires more than one item.',
|
||||
})}
|
||||
condition={isNotDraggable ?? true}
|
||||
>
|
||||
<EuiIcon
|
||||
size="s"
|
||||
color={
|
||||
euiTheme.colors[isInvalid ? 'danger' : isNotDraggable ? 'disabled' : 'subduedText']
|
||||
}
|
||||
type={isInvalid ? 'alert' : 'grab'}
|
||||
aria-label={
|
||||
isInvalid
|
||||
? invalidMessage
|
||||
: i18n.translate('xpack.lens.customBucketContainer.dragToReorder', {
|
||||
defaultMessage: 'Drag to reorder',
|
||||
})
|
||||
}
|
||||
data-test-subj={`${dataTestSubj}-dragToReorder-${idx}`}
|
||||
/>
|
||||
</TooltipWrapper>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={true}>{children}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<TooltipWrapper
|
||||
tooltipContent={i18n.translate(
|
||||
'xpack.lens.fieldsBucketContainer.deleteButtonDisabled',
|
||||
{
|
||||
defaultMessage: 'A minimum of one item is required.',
|
||||
}
|
||||
)}
|
||||
condition={isNotRemovable ?? false}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
iconSize="s"
|
||||
iconType="trash"
|
||||
color="danger"
|
||||
onClick={onRemoveClick}
|
||||
aria-label={removeTitle}
|
||||
disabled={isNotRemovable}
|
||||
data-test-subj={`${dataTestSubj}-remove-${idx}`}
|
||||
/>
|
||||
</TooltipWrapper>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiPanel,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { TooltipWrapper } from '..';
|
||||
import type { BucketContainerProps } from './types';
|
||||
|
||||
export const FieldsBucketContainer = ({
|
||||
idx,
|
||||
onRemoveClick,
|
||||
removeTitle,
|
||||
children,
|
||||
draggableProvided,
|
||||
isNotRemovable,
|
||||
isNotDraggable,
|
||||
isDragging,
|
||||
'data-test-subj': dataTestSubj = 'lns-fieldsBucketContainer',
|
||||
}: BucketContainerProps) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
return (
|
||||
<EuiPanel
|
||||
paddingSize="xs"
|
||||
hasShadow={isDragging}
|
||||
color={isDragging ? 'plain' : 'transparent'}
|
||||
data-test-subj={dataTestSubj}
|
||||
>
|
||||
<EuiFlexGroup direction={'row'} gutterSize="s" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false} {...(draggableProvided?.dragHandleProps ?? {})}>
|
||||
<TooltipWrapper
|
||||
tooltipContent={i18n.translate('xpack.lens.fieldsBucketContainer.dragHandleDisabled', {
|
||||
defaultMessage: 'Reordering requires more than one item.',
|
||||
})}
|
||||
condition={isNotDraggable ?? true}
|
||||
>
|
||||
<EuiIcon
|
||||
size="s"
|
||||
color={euiTheme.colors[isNotDraggable ? 'disabled' : 'text']}
|
||||
type="grab"
|
||||
aria-label={i18n.translate('xpack.lens.fieldsBucketContainer.dragToReorder', {
|
||||
defaultMessage: 'Drag to reorder',
|
||||
})}
|
||||
data-test-subj={`${dataTestSubj}-dragToReorder-${idx}`}
|
||||
/>
|
||||
</TooltipWrapper>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={true} style={{ minWidth: 0 }}>
|
||||
{children}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<TooltipWrapper
|
||||
tooltipContent={i18n.translate(
|
||||
'xpack.lens.fieldsBucketContainer.deleteButtonDisabled',
|
||||
{
|
||||
defaultMessage: 'A minimum of one item is required.',
|
||||
}
|
||||
)}
|
||||
condition={isNotRemovable ?? false}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
iconType="trash"
|
||||
color="danger"
|
||||
aria-label={removeTitle}
|
||||
onClick={onRemoveClick}
|
||||
data-test-subj={`${dataTestSubj}-removeField-${idx}`}
|
||||
isDisabled={isNotRemovable}
|
||||
/>
|
||||
</TooltipWrapper>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { NewBucketButton } from './new_bucket_button';
|
||||
export { FieldsBucketContainer } from './fields_bucket_container';
|
||||
|
||||
export * from './buckets';
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiButtonEmpty } from '@elastic/eui';
|
||||
|
||||
interface NewBucketButtonProps {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
isDisabled?: boolean;
|
||||
className?: string;
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
export const NewBucketButton = ({
|
||||
label,
|
||||
onClick,
|
||||
isDisabled,
|
||||
className,
|
||||
'data-test-subj': dataTestSubj = 'lns-newBucket-add',
|
||||
}: NewBucketButtonProps) => (
|
||||
<EuiButtonEmpty
|
||||
data-test-subj={dataTestSubj}
|
||||
size="xs"
|
||||
iconType="plusInCircle"
|
||||
onClick={onClick}
|
||||
isDisabled={isDisabled}
|
||||
flush="left"
|
||||
className={className}
|
||||
>
|
||||
{label}
|
||||
</EuiButtonEmpty>
|
||||
);
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { DraggableProvided } from 'react-beautiful-dnd';
|
||||
|
||||
export interface BucketContainerProps {
|
||||
children: React.ReactNode;
|
||||
removeTitle: string;
|
||||
idx: number;
|
||||
onRemoveClick: () => void;
|
||||
isDragging?: boolean;
|
||||
draggableProvided?: DraggableProvided;
|
||||
isInvalid?: boolean;
|
||||
invalidMessage?: string;
|
||||
isNotRemovable?: boolean;
|
||||
isNotDraggable?: boolean;
|
||||
'data-test-subj'?: string;
|
||||
}
|
|
@ -17,7 +17,8 @@ export {
|
|||
NewBucketButton,
|
||||
DraggableBucketContainer,
|
||||
DragDropBuckets,
|
||||
} from './drag_drop_bucket/buckets';
|
||||
FieldsBucketContainer,
|
||||
} from './drag_drop_bucket';
|
||||
export { RangeInputField } from './range_input_field';
|
||||
export {
|
||||
BucketAxisBoundsControl,
|
||||
|
|
|
@ -14,11 +14,6 @@
|
|||
margin-top: $euiSizeXS;
|
||||
}
|
||||
|
||||
.lnsConfigPanelAnnotations__droppable {
|
||||
padding: $euiSizeXS;
|
||||
border-radius: $euiBorderRadiusSmall;
|
||||
}
|
||||
|
||||
.lnsConfigPanelAnnotations__fieldPicker {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,19 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
htmlIdGenerator,
|
||||
EuiButtonIcon,
|
||||
EuiDraggable,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiPanel,
|
||||
useEuiTheme,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { htmlIdGenerator, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { QueryPointEventAnnotationConfig } from '@kbn/event-annotation-plugin/common';
|
||||
import type { ExistingFieldsMap, IndexPattern } from '../../../../types';
|
||||
import {
|
||||
|
@ -28,8 +18,12 @@ import {
|
|||
useDebouncedValue,
|
||||
NewBucketButton,
|
||||
DragDropBuckets,
|
||||
DraggableBucketContainer,
|
||||
FieldsBucketContainer,
|
||||
} from '../../../../shared_components';
|
||||
|
||||
export const MAX_TOOLTIP_FIELDS_SIZE = 2;
|
||||
|
||||
const generateId = htmlIdGenerator();
|
||||
const supportedTypes = new Set(['string', 'boolean', 'number', 'ip', 'date']);
|
||||
|
||||
|
@ -60,8 +54,6 @@ export function TooltipSection({
|
|||
existingFields,
|
||||
invalidFields,
|
||||
}: FieldInputsProps) {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const onChangeWrapped = useCallback(
|
||||
(values: WrappedValue[]) => {
|
||||
setConfig({
|
||||
|
@ -108,6 +100,7 @@ export function TooltipSection({
|
|||
label={i18n.translate('xpack.lens.xyChart.annotation.tooltip.addField', {
|
||||
defaultMessage: 'Add field',
|
||||
})}
|
||||
isDisabled={localValues.length > MAX_TOOLTIP_FIELDS_SIZE}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -132,7 +125,7 @@ export function TooltipSection({
|
|||
);
|
||||
}
|
||||
const currentExistingField = existingFields[indexPattern.title];
|
||||
const disableActions = localValues.length === 2 && localValues.some(({ isNew }) => isNew);
|
||||
|
||||
const options = indexPattern.fields
|
||||
.filter(
|
||||
({ displayName, type }) =>
|
||||
|
@ -154,107 +147,62 @@ export function TooltipSection({
|
|||
)
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
const isDragDisabled = localValues.length < 2;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: isDragging ? 'transparent' : euiTheme.colors.lightestShade,
|
||||
borderRadius: euiTheme.border.radius.small,
|
||||
<DragDropBuckets
|
||||
onDragEnd={(updatedValues: WrappedValue[]) => {
|
||||
handleInputChange(updatedValues);
|
||||
}}
|
||||
droppableId="ANNOTATION_TOOLTIP_DROPPABLE_AREA"
|
||||
items={localValues}
|
||||
bgColor="subdued"
|
||||
>
|
||||
<DragDropBuckets
|
||||
onDragEnd={(updatedValues: WrappedValue[]) => {
|
||||
handleInputChange(updatedValues);
|
||||
setIsDragging(false);
|
||||
}}
|
||||
onDragStart={() => {
|
||||
setIsDragging(true);
|
||||
}}
|
||||
droppableId="ANNOTATION_TOOLTIP_DROPPABLE_AREA"
|
||||
items={localValues}
|
||||
className="lnsConfigPanelAnnotations__droppable"
|
||||
>
|
||||
{localValues.map(({ id, value, isNew }, index) => {
|
||||
const fieldIsValid = value ? Boolean(indexPattern.getFieldByName(value)) : true;
|
||||
return (
|
||||
<EuiDraggable
|
||||
spacing="none"
|
||||
index={index}
|
||||
draggableId={value || 'newField'}
|
||||
key={id}
|
||||
disableInteractiveElementBlocking
|
||||
isDragDisabled={isDragDisabled}
|
||||
>
|
||||
{(provided) => (
|
||||
<EuiPanel paddingSize="xs" hasShadow={false} color="transparent">
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false}>{/* Empty for spacing */}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon
|
||||
size="s"
|
||||
color={isDragDisabled ? euiTheme.colors.disabled : 'subdued'}
|
||||
type="grab"
|
||||
title={i18n.translate(
|
||||
'xpack.lens.xyChart.annotation..tooltip.dragToReorder',
|
||||
{
|
||||
defaultMessage: 'Drag to reorder',
|
||||
}
|
||||
)}
|
||||
data-test-subj={`lnsXY-annotation-tooltip-dragToReorder-${index}`}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={true} style={{ minWidth: 0 }}>
|
||||
<FieldPicker
|
||||
selectedOptions={
|
||||
value
|
||||
? [
|
||||
{
|
||||
label: value,
|
||||
value: { type: 'field', field: value },
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
options={options}
|
||||
onChoose={function (choice: FieldOptionValue | undefined): void {
|
||||
onFieldSelectChange(choice, index);
|
||||
}}
|
||||
fieldIsInvalid={!fieldIsValid}
|
||||
className="lnsConfigPanelAnnotations__fieldPicker"
|
||||
data-test-subj={`lnsXY-annotation-tooltip-field-picker--${index}`}
|
||||
autoFocus={isNew && value == null}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
iconType="trash"
|
||||
color="danger"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.lens.indexPattern.terms.deleteButtonAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Delete',
|
||||
}
|
||||
)}
|
||||
title={i18n.translate('xpack.lens.indexPattern.terms.deleteButtonLabel', {
|
||||
defaultMessage: 'Delete',
|
||||
})}
|
||||
onClick={() => {
|
||||
handleInputChange(localValues.filter((_, i) => i !== index));
|
||||
}}
|
||||
data-test-subj={`lnsXY-annotation-tooltip-removeField-${index}`}
|
||||
isDisabled={disableActions && !isNew}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
)}
|
||||
</EuiDraggable>
|
||||
);
|
||||
})}
|
||||
</DragDropBuckets>
|
||||
</div>
|
||||
{localValues.map(({ id, value, isNew }, index, arrayRef) => {
|
||||
const fieldIsValid = value ? Boolean(indexPattern.getFieldByName(value)) : true;
|
||||
|
||||
return (
|
||||
<DraggableBucketContainer
|
||||
id={(value ?? 'newField') + id}
|
||||
key={(value ?? 'newField') + id}
|
||||
idx={index}
|
||||
onRemoveClick={() => {
|
||||
handleInputChange(arrayRef.filter((_, i) => i !== index));
|
||||
}}
|
||||
removeTitle={i18n.translate(
|
||||
'xpack.lens.xyChart.annotation.tooltip.deleteButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Delete',
|
||||
}
|
||||
)}
|
||||
isNotDraggable={arrayRef.length < 2}
|
||||
Container={FieldsBucketContainer}
|
||||
isInsidePanel={true}
|
||||
data-test-subj={`lnsXY-annotation-tooltip-${index}`}
|
||||
>
|
||||
<FieldPicker
|
||||
selectedOptions={
|
||||
value
|
||||
? [
|
||||
{
|
||||
label: value,
|
||||
value: { type: 'field', field: value },
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
options={options}
|
||||
onChoose={(choice) => {
|
||||
onFieldSelectChange(choice, index);
|
||||
}}
|
||||
fieldIsInvalid={!fieldIsValid}
|
||||
className="lnsConfigPanelAnnotations__fieldPicker"
|
||||
data-test-subj={`lnsXY-annotation-tooltip-field-picker--${index}`}
|
||||
autoFocus={isNew && value == null}
|
||||
/>
|
||||
</DraggableBucketContainer>
|
||||
);
|
||||
})}
|
||||
</DragDropBuckets>
|
||||
{newBucketButton}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -17838,10 +17838,7 @@
|
|||
"xpack.lens.indexPattern.terms.addaFilter": "Ajouter un champ",
|
||||
"xpack.lens.indexPattern.terms.addRegex": "Utiliser une expression régulière",
|
||||
"xpack.lens.indexPattern.terms.advancedSettings": "Avancé",
|
||||
"xpack.lens.indexPattern.terms.deleteButtonAriaLabel": "Supprimer",
|
||||
"xpack.lens.indexPattern.terms.deleteButtonDisabled": "Cette fonction nécessite au minimum un champ défini.",
|
||||
"xpack.lens.indexPattern.terms.deleteButtonLabel": "Supprimer",
|
||||
"xpack.lens.indexPattern.terms.dragToReorder": "Faire glisser pour réorganiser",
|
||||
"xpack.lens.indexPattern.terms.exclude": "Exclure les valeurs",
|
||||
"xpack.lens.indexPattern.terms.include": "Inclure les valeurs",
|
||||
"xpack.lens.indexPattern.terms.includeExcludePatternPlaceholder": "Entrer une expression régulière pour filtrer les valeurs",
|
||||
|
|
|
@ -17821,10 +17821,7 @@
|
|||
"xpack.lens.indexPattern.terms.addaFilter": "フィールドの追加",
|
||||
"xpack.lens.indexPattern.terms.addRegex": "正規表現を使用",
|
||||
"xpack.lens.indexPattern.terms.advancedSettings": "高度な設定",
|
||||
"xpack.lens.indexPattern.terms.deleteButtonAriaLabel": "削除",
|
||||
"xpack.lens.indexPattern.terms.deleteButtonDisabled": "この関数には定義された1つのフィールドの最小値が必須です",
|
||||
"xpack.lens.indexPattern.terms.deleteButtonLabel": "削除",
|
||||
"xpack.lens.indexPattern.terms.dragToReorder": "ドラッグして並べ替え",
|
||||
"xpack.lens.indexPattern.terms.exclude": "値を除外",
|
||||
"xpack.lens.indexPattern.terms.include": "値を含める",
|
||||
"xpack.lens.indexPattern.terms.includeExcludePatternPlaceholder": "値をフィルターするには正規表現を入力します",
|
||||
|
|
|
@ -17846,10 +17846,7 @@
|
|||
"xpack.lens.indexPattern.terms.addaFilter": "添加字段",
|
||||
"xpack.lens.indexPattern.terms.addRegex": "使用正则表达式",
|
||||
"xpack.lens.indexPattern.terms.advancedSettings": "高级",
|
||||
"xpack.lens.indexPattern.terms.deleteButtonAriaLabel": "删除",
|
||||
"xpack.lens.indexPattern.terms.deleteButtonDisabled": "此函数需要至少定义一个字段",
|
||||
"xpack.lens.indexPattern.terms.deleteButtonLabel": "删除",
|
||||
"xpack.lens.indexPattern.terms.dragToReorder": "拖动以重新排序",
|
||||
"xpack.lens.indexPattern.terms.exclude": "排除值",
|
||||
"xpack.lens.indexPattern.terms.include": "包括值",
|
||||
"xpack.lens.indexPattern.terms.includeExcludePatternPlaceholder": "输入正则表达式以筛选值",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue