Skip to content

Commit

Permalink
Add Drag Across Axis Functionality to Vis Builder
Browse files Browse the repository at this point in the history
Signed-off-by: Suchit Sahoo <[email protected]>
  • Loading branch information
LDrago27 committed Jun 28, 2024
1 parent e74ed2c commit a602190
Show file tree
Hide file tree
Showing 14 changed files with 698 additions and 186 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,48 @@

import { EuiForm } from '@elastic/eui';
import React from 'react';
import { useVisualizationType } from '../../utils/use';
import { useTypedSelector } from '../../utils/state_management';

import './config_panel.scss';
import { mapSchemaToAggPanel } from './schema_to_dropbox';
import { SecondaryPanel } from './secondary_panel';
import { Schemas } from '../../../../../vis_default_editor/public';
import {
AggConfig,
AggConfigs,
CreateAggConfigParams,
} from '../../../../../data/common/search/aggs';
import { IndexPattern, TimeRange } from '../../../../../data/public';

export function ConfigPanel() {
const vizType = useVisualizationType();
const editingState = useTypedSelector(
(state) => state.visualization.activeVisualization?.draftAgg
);
const schemas = vizType.ui.containerConfig.data.schemas;
export interface AggProps {
indexPattern: IndexPattern | undefined;
aggConfigs: AggConfigs | undefined;
aggs: AggConfig[];
timeRange: TimeRange;
}

export interface ConfigPanelProps {
schemas: Schemas;
editingState?: CreateAggConfigParams;
aggProps: AggProps;
schemaDisplayStates: object;
setSchemaDisplayStates: React.Dispatch<React.SetStateAction<object>>;
}

export function ConfigPanel({
schemas,
editingState,
aggProps,
schemaDisplayStates,
setSchemaDisplayStates,
}: ConfigPanelProps) {
if (!schemas) return null;

const mainPanel = mapSchemaToAggPanel(schemas);
const mainPanel = mapSchemaToAggPanel(

Check warning on line 44 in src/plugins/vis_builder/public/application/components/data_tab/config_panel.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/application/components/data_tab/config_panel.tsx#L44

Added line #L44 was not covered by tests
schemas,
aggProps,
schemaDisplayStates,
setSchemaDisplayStates
);

return (
<EuiForm className={`vbConfig ${editingState ? 'showSecondary' : ''}`}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
border-bottom: none;
}

&__droppable {
min-height: 1px;
}

&__container {
display: grid;
grid-gap: calc($euiSizeXS / 2);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@
import { i18n } from '@osd/i18n';
import {
EuiButtonIcon,
EuiDragDropContext,
EuiDraggable,
EuiDroppable,
EuiFormRow,
EuiPanel,
EuiText,
DropResult,
} from '@elastic/eui';
import React, { useCallback, useState } from 'react';
import { IDropAttributes, IDropState } from '../../utils/drag_drop';
Expand Down Expand Up @@ -51,25 +49,13 @@ const DropboxComponent = ({
onAddField,
onDeleteField,
onEditField,
onReorderField,
limit = 1,
isValidDropTarget,
canDrop,
dropProps,
}: DropboxProps) => {
const prefersReducedMotion = usePrefersReducedMotion();
const [closing, setClosing] = useState<boolean | string>(false);
const handleDragEnd = useCallback(
({ source, destination }: DropResult) => {
if (!destination) return;

onReorderField({
sourceAggId: fields[source.index].id,
destinationAggId: fields[destination.index].id,
});
},
[fields, onReorderField]
);

const animateDelete = useCallback(
(id: string) => {
Expand All @@ -86,71 +72,72 @@ const DropboxComponent = ({
);

return (
<EuiDragDropContext onDragEnd={handleDragEnd}>
<EuiFormRow label={boxLabel} className="dropBox" fullWidth>
<div className="dropBox__container">
<EuiDroppable droppableId={dropboxId}>
{fields.map(({ id, label }, index) => (
<EuiDraggable
className={`dropBox__draggable ${id === closing && 'closing'}`}
key={id}
draggableId={id}
index={index}
>
<EuiPanel
key={index}
paddingSize="s"
className="dropBox__field"
data-test-subj={`dropBoxField-${dropboxId}-${index}`}
>
<EuiText size="s" className="dropBox__field_text" onClick={() => onEditField(id)}>
<a role="button" tabIndex={0}>
{label}
</a>
</EuiText>
<EuiButtonIcon
color="subdued"
iconType="cross"
aria-label="clear-field"
iconSize="s"
onClick={() => animateDelete(id)}
data-test-subj="dropBoxRemoveBtn"
/>
</EuiPanel>
</EuiDraggable>
))}
</EuiDroppable>
{fields.length < limit && (
<EuiPanel
data-test-subj={`dropBoxAddField-${dropboxId}`}
className={`dropBox__field dropBox__dropTarget ${
isValidDropTarget ? 'validField' : ''
} ${canDrop ? 'canDrop' : ''}`}
{...(isValidDropTarget && dropProps)}
<EuiFormRow label={boxLabel} className="dropBox" fullWidth>
<div className="dropBox__container">
<EuiDroppable
className="dropBox__droppable"
droppableId={dropboxId}
isCombineEnabled={true}
>
{fields.map(({ id, label }, index) => (
<EuiDraggable

Check warning on line 83 in src/plugins/vis_builder/public/application/components/data_tab/dropbox.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/application/components/data_tab/dropbox.tsx#L83

Added line #L83 was not covered by tests
className={`dropBox__draggable ${id === closing && 'closing'}`}
key={id}
draggableId={id}
index={index}
>
<EuiText size="s">
{i18n.translate('visBuilder.dropbox.addField.title', {
defaultMessage: 'Click or drop to add',
})}
</EuiText>
<EuiButtonIcon
iconType="plusInCircle"
aria-label="clear-field"
iconSize="s"
onClick={() => onAddField()}
data-test-subj="dropBoxAddBtn"
/>
</EuiPanel>
)}
</div>
</EuiFormRow>
</EuiDragDropContext>
<EuiPanel
key={index}
paddingSize="s"
className="dropBox__field"
data-test-subj={`dropBoxField-${dropboxId}-${index}`}
>
<EuiText size="s" className="dropBox__field_text" onClick={() => onEditField(id)}>

Check warning on line 95 in src/plugins/vis_builder/public/application/components/data_tab/dropbox.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/application/components/data_tab/dropbox.tsx#L95

Added line #L95 was not covered by tests
<a role="button" tabIndex={0}>
{label}
</a>
</EuiText>
<EuiButtonIcon
color="subdued"
iconType="cross"
aria-label="clear-field"
iconSize="s"
onClick={() => animateDelete(id)}

Check warning on line 105 in src/plugins/vis_builder/public/application/components/data_tab/dropbox.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/application/components/data_tab/dropbox.tsx#L105

Added line #L105 was not covered by tests
data-test-subj="dropBoxRemoveBtn"
/>
</EuiPanel>
</EuiDraggable>
))}
</EuiDroppable>
{fields.length < limit && (
<EuiPanel
data-test-subj={`dropBoxAddField-${dropboxId}`}
className={`dropBox__field dropBox__dropTarget ${
isValidDropTarget ? 'validField' : ''
} ${canDrop ? 'canDrop' : ''}`}
{...(isValidDropTarget && dropProps)}
>
<EuiText size="s">
{i18n.translate('visBuilder.dropbox.addField.title', {
defaultMessage: 'Click or drop to add',
})}
</EuiText>
<EuiButtonIcon
iconType="plusInCircle"
aria-label="clear-field"
iconSize="s"
onClick={() => onAddField()}

Check warning on line 129 in src/plugins/vis_builder/public/application/components/data_tab/dropbox.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/application/components/data_tab/dropbox.tsx#L129

Added line #L129 was not covered by tests
data-test-subj="dropBoxAddBtn"
/>
</EuiPanel>
)}
</div>
</EuiFormRow>
);
};

const Dropbox = React.memo((dropBox: UseDropboxProps) => {
const props = useDropbox(dropBox);

return <DropboxComponent {...props} />;
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { render, screen } from '@testing-library/react';
import { IndexPatternField } from '../../../../../data/public';

import { DraggableFieldButton } from './field';
import { DropResult, EuiDragDropContext, EuiDroppable } from '@elastic/eui';

describe('visBuilder field', function () {
describe('DraggableFieldButton', () => {
Expand All @@ -28,7 +29,13 @@ describe('visBuilder field', function () {
'bytes'
),
};
render(<DraggableFieldButton {...props} />);
render(
<EuiDragDropContext onDragEnd={(result: DropResult) => {}}>
<EuiDroppable droppableId="1">
<DraggableFieldButton index={1} {...props} />
</EuiDroppable>
</EuiDragDropContext>
);

const button = screen.getByTestId('field-bytes-showDetails');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
*/

import React, { useState } from 'react';
import { EuiPopover } from '@elastic/eui';
import { EuiDraggable, EuiPopover } from '@elastic/eui';

import { IndexPatternField } from '../../../../../data/public';
import {
Expand All @@ -46,10 +46,11 @@ import './field.scss';
export interface FieldProps {
field: IndexPatternField;
getDetails: (field) => FieldDetails;
id: number;
}

// TODO: Add field sections (Available fields, popular fields from src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx)
export const Field = ({ field, getDetails }: FieldProps) => {
export const Field = ({ field, getDetails, id }: FieldProps) => {
const [infoIsOpen, setOpen] = useState(false);

function togglePopover() {
Expand All @@ -60,7 +61,14 @@ export const Field = ({ field, getDetails }: FieldProps) => {
<EuiPopover
ownFocus
display="block"
button={<DraggableFieldButton isActive={infoIsOpen} onClick={togglePopover} field={field} />}
button={
<DraggableFieldButton
isActive={infoIsOpen}
onClick={togglePopover}
field={field}
index={id}
/>
}
isOpen={infoIsOpen}
closePopover={() => setOpen(false)}
anchorPosition="rightUp"
Expand All @@ -77,9 +85,15 @@ export const Field = ({ field, getDetails }: FieldProps) => {
export interface DraggableFieldButtonProps extends Partial<FieldButtonProps> {
dragValue?: IndexPatternField['name'] | null | typeof COUNT_FIELD;
field: Partial<IndexPatternField> & Pick<IndexPatternField, 'displayName' | 'name' | 'type'>;
index: number;
}

export const DraggableFieldButton = ({ dragValue, field, ...rest }: DraggableFieldButtonProps) => {
export const DraggableFieldButton = ({
dragValue,
field,
index,
...rest
}: DraggableFieldButtonProps) => {
const { name, displayName, type, scripted = false } = field;
const [dragProps] = useDrag({
namespace: 'field-data',
Expand Down Expand Up @@ -109,5 +123,14 @@ export const DraggableFieldButton = ({ dragValue, field, ...rest }: DraggableFie
onClick: () => {},
};

return <FieldButton {...defaultProps} {...rest} {...dragProps} />;
return (
<EuiDraggable
draggableId={name}
index={index}
disableInteractiveElementBlocking={true}
shouldRespectForcePress
>
<FieldButton {...defaultProps} {...rest} {...dragProps} />
</EuiDraggable>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
padding: $euiSizeS;

&__fieldGroups {
@include euiYScrollWithShadows;

overflow-y: auto;
margin-right: -$euiSizeS;
padding-right: $euiSizeS;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { FilterManager, IndexPatternField } from '../../../../../data/public';
import { FieldGroup } from './field_selector';
import { DropResult, EuiDragDropContext, EuiDroppable } from '@elastic/eui';

const mockUseIndexPatterns = jest.fn(() => ({ selected: 'mockIndexPattern' }));
const mockUseOnAddFilter = jest.fn();
Expand Down Expand Up @@ -68,7 +69,13 @@ describe('visBuilder sidebar field selector', function () {
...defaultProps,
fields: ['bytes', 'machine.ram', 'memory', 'phpmemory'].map(getFields),
};
const { container } = render(<FieldGroup {...props} />);
const { container } = render(
<EuiDragDropContext onDragEnd={(result: DropResult) => {}}>
<EuiDroppable droppableId="1">
<FieldGroup {...props} />
</EuiDroppable>
</EuiDragDropContext>
);

expect(container).toHaveTextContent(props.header);
expect(container).toHaveTextContent(props.fields.length.toString());
Expand Down
Loading

0 comments on commit a602190

Please sign in to comment.