Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Mappings editor] Add UI/UX to mappings core #48876

Merged
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
aa758ea
Add field settings json editor
sebelga Oct 9, 2019
1f9f91a
Add isUnmounted ref to form hook
sebelga Oct 9, 2019
2c7774c
Update contract for json editor update
sebelga Oct 9, 2019
25b9195
Update useMemo with correct defaultValue
sebelga Oct 9, 2019
5218f21
Fix uniqueName validator and wrap NameParameter in its own component
sebelga Oct 10, 2019
aef2e76
Merge feature/mappings-editor branch
sebelga Oct 10, 2019
b33915c
Move child field create at the bottom of the list
sebelga Oct 10, 2019
c0c706e
Add initial UI style
sebelga Oct 11, 2019
6c9b504
Add indent tree + toggle of child fields
sebelga Oct 11, 2019
db04fab
Fix nested css indicator position
sebelga Oct 11, 2019
d49c3f4
First pass at styling CreateField component
sebelga Oct 11, 2019
0b46696
Add css to hide actions until mouse over
sebelga Oct 11, 2019
9bd825b
Merge branch 'feature/mappings-editor' into feature/mappings-editor-a…
sebelga Oct 15, 2019
bf787d7
Improve fields list styling
sebelga Oct 15, 2019
a546907
Highlight field being edited
sebelga Oct 16, 2019
072f1de
Add path meta to fields
sebelga Oct 16, 2019
991aa75
Update button style in flyout footer
sebelga Oct 17, 2019
4805217
Update document fields header + add field button
sebelga Oct 17, 2019
8f61a98
Show the <CreateField /> component on empty list
sebelga Oct 17, 2019
92e1cb8
Add subType select to "create" and "edit"
sebelga Oct 17, 2019
d42d9da
Add click outside handler to automatically submit field create form
sebelga Oct 17, 2019
a9b078a
Tidy styling when the list has only 1 level depth
sebelga Oct 18, 2019
69594d9
Add multi-field button for "text" and "keyword"
sebelga Oct 18, 2019
858c60b
Create stateless component for FieldListItem
sebelga Oct 18, 2019
ed0a64d
Fix toggle regression
sebelga Oct 18, 2019
97b8608
Fix left indent on multi-field
sebelga Oct 18, 2019
c9d3c41
Fix calculate maxNestedDepth
sebelga Oct 18, 2019
07de5ca
Add Tree component to display fields + subFields to be deleted
sebelga Oct 18, 2019
67e4c0d
Add "isMultiField" flag to NormalizedField
sebelga Oct 21, 2019
c60c6b9
Update style for multi-fields
sebelga Oct 21, 2019
7891368
Display tree of fields to be deleted in modal
sebelga Oct 22, 2019
c96bf96
Remove Field settings Json editor
sebelga Oct 22, 2019
1eeaf1c
Use label for type (in badge) instead of value
sebelga Oct 22, 2019
72620fc
Merge branch 'feature/mappings-editor' into feature/mappings-editor-a…
sebelga Oct 22, 2019
a5c7e9e
Fix tests
sebelga Oct 22, 2019
78eb451
Make CR changes to styling
sebelga Oct 22, 2019
092f522
Add CR style change
sebelga Oct 22, 2019
88624eb
Increase flyout width from 400 to 720
sebelga Oct 23, 2019
b8a199e
Hide "Add multi-field" button when in create or edit mode
sebelga Oct 23, 2019
25b006c
Refactor reducer for edit field
sebelga Oct 23, 2019
b839f2a
Add toggle also to multi-field
sebelga Oct 23, 2019
9de4faf
Refactor: rename "useState" --> "useMappingsState"
sebelga Oct 23, 2019
9a00d11
Make CR changes
sebelga Oct 23, 2019
ae69039
Fix paddingLeft styling
sebelga Oct 23, 2019
e03962d
Fix display modal to confirm type change
sebelga Oct 23, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -118,7 +118,7 @@ export function useForm<T extends object = FormData>(

if (!areAllFieldsValidated) {
// If *not* all the fiels have been validated, the validity of the form is unknown, thus still "undefined"
return;
return undefined;
}

const isFormValid = fieldsArray.every(isFieldValid);
Expand Down
Expand Up @@ -51,7 +51,9 @@ export class Subject<T> {
}

next(value: T) {
this.value = value;
this.callbacks.forEach(fn => fn(value));
if (value !== this.value) {
this.value = value;
this.callbacks.forEach(fn => fn(value));
}
}
}
@@ -0,0 +1,195 @@

/*
[1] When the <CreateField /> component is embedded inside the tree, we need
to add some extra indent to make room for the child "L" bullet on the left.

[2] By default all content have a padding left to leave some room for the "L" bullet
unless "--toggle" is added. In that case we don't need padding as the toggle will add it.

[3] We need to compensate from the -4px margin added by the euiFlexGroup to make sure that the
border-bottom is always visible, even when mouseovering and changing the background color.
*/

.mappings-editor {
&__fields-list-item {
&--dotted-line {
> .mappings-editor__fields-list-item__field {
border-bottom-style: dashed;
}
}

&__field {
border-bottom: $euiBorderThin;
margin-top: 4px; // [3]

&:hover {
background-color: $euiColorLightestShade;
.mappings-editor__fields-list-item__actions,
.mappings-editor__fields-list-item__multi-field-button {
opacity: 1;
}
}

&--selected {
background-color: $euiColorLightestShade;
&:hover {
background-color: $euiColorLightestShade;
}
}

&--dim {
opacity: 0.3;

&:hover {
background-color: initial;
}
}
}

&__wrapper {
padding-left: $euiSizeXS;

&--indent {
padding-left: $euiSize;
}
}

&__content {
height: $euiSizeXL * 2;
position: relative;

&--indent {
padding-left: $euiSizeXL;
}
}

&__toggle {
padding-left: $euiSizeXS;
width: $euiSizeL;
}

&__actions,
&__multi-field-button {
opacity: 0;
padding-right: $euiSizeS;
transition: opacity $euiAnimSpeedNormal $euiAnimSlightResistance;
}
}

&__create-field-wrapper {
background-color: $euiColorLightestShade;
border-right: $euiBorderThin;
border-bottom: $euiBorderThin;
border-left: $euiBorderThin;
padding: $euiSize;
}

&__create-field-content {
position: relative;
}

&__edit-field {
&__section {
border-bottom: $euiBorderThin;
padding: $euiSizeXL 0;

&:first-child {
padding-top: 0;
}
}
}
}

.mappings-editor__fields-list {
.mappings-editor__fields-list .mappings-editor__fields-list-item__content,
.mappings-editor__create-field-content {
&::before {
border-bottom: 1px solid $euiColorMediumShade;
content: '';
left: $euiSize;
position: absolute;
top: 50%;
width: $euiSizeS;
}
&::after {
border-left: 1px solid $euiColorMediumShade;
content: '';
left: $euiSize;
position: absolute;
top: calc(50% - #{$euiSizeS});
height: $euiSizeS;
}
}

.mappings-editor__create-field-wrapper {
&--multi-field {
.mappings-editor__create-field-content {
&::before, &::after {
content: none;
}
}
}
}

.mappings-editor__create-field-content {
padding-left: $euiSizeXXL - $euiSizeXS; // [1]
}

.mappings-editor__fields-list .mappings-editor__fields-list-item__content {
padding-left: $euiSizeXL; // [2]

&--toggle, &--multi-field {
&::before, &::after {
content: none;
}
}

&--toggle {
padding-left: 0;
}

&--multi-field {
padding-left: $euiSizeS;
}
}
}

ul.tree {
padding: 0;
margin: 0;
list-style-type: none;
position: relative;
padding-top: $euiSizeXS;

li.tree-item {
list-style-type: none;
border-left: $euiBorderThin;
margin-left: $euiSizeL;
padding-bottom: $euiSizeS;

div {
padding-left: $euiSizeL;
position: relative;

&::before {
content:'';
position: absolute;
top: 0;
left: -1px;
bottom: 50%;
width: $euiSize;
border: $euiBorderThin;
border-top: none;
border-right: none;
}
}
}

> li.tree-item:first-child {
padding-top: $euiSizeS;
}
> li.tree-item:last-child {
border-left-color: transparent;
padding-bottom: 0;
}
}
Expand Up @@ -33,7 +33,7 @@ export const ConfigurationForm = React.memo(({ defaultValue }: Props) => {
}, [form]);

return (
<Form form={form} className="mappings-editor">
<Form form={form} className="mappings-editor__configuration">
<FormRow title="Configuration" description="Global settings for the index mappings">
<UseField
path="dynamic"
Expand Down
Expand Up @@ -4,11 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useMemo } from 'react';
import { EuiButton, EuiSpacer } from '@elastic/eui';
import React, { useEffect } from 'react';
import { EuiButtonEmpty, EuiSpacer } from '@elastic/eui';

import { useState, useDispatch } from '../../mappings_state';
import { validateUniqueName } from '../../lib';
import { FieldsList, CreateField, EditField } from './fields';

export const DocumentFields = () => {
Expand All @@ -21,37 +20,35 @@ export const DocumentFields = () => {
const getField = (fieldId: string) => byId[fieldId];
const fields = rootLevelFields.map(getField);

const uniqueNameValidatorCreate = useMemo(() => {
return validateUniqueName({ rootLevelFields, byId });
}, [byId, rootLevelFields]);

const uniqueNameValidatorEdit = useMemo(() => {
if (fieldToEdit === undefined) {
return;
}
return validateUniqueName({ rootLevelFields, byId }, byId[fieldToEdit!].source.name);
}, [byId, rootLevelFields, fieldToEdit]);

const addField = () => {
dispatch({ type: 'documentField.createField' });
};

useEffect(() => {
if (status === 'idle' && fields.length === 0) {
addField();
}
}, [fields, status]);

const renderCreateField = () => {
// The "fieldToAddFieldTo" is undefined when adding to the top level "properties" object.
if (status !== 'creatingField' || fieldToAddFieldTo !== undefined) {
const showCreateField = status === 'creatingField' && fieldToAddFieldTo === undefined;

if (!showCreateField) {
return null;
}
return <CreateField uniqueNameValidator={uniqueNameValidatorCreate} />;

return <CreateField isCancelable={fields.length > 0} />;
};

const renderAddFieldButton = () => {
if (status !== 'idle') {
return null;
}
const isDisabled = status !== 'idle';
return (
<>
<EuiSpacer />
<EuiButton onClick={addField}>Add field</EuiButton>
<EuiButtonEmpty disabled={isDisabled} onClick={addField} iconType="plusInCircleFilled">
Add field
</EuiButtonEmpty>
</>
);
};
Expand All @@ -61,7 +58,7 @@ export const DocumentFields = () => {
return null;
}
const field = byId[fieldToEdit!];
return <EditField field={field} uniqueNameValidator={uniqueNameValidatorEdit!} />;
return <EditField field={field} />;
};

return (
Expand Down
Expand Up @@ -4,12 +4,25 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { EuiTitle } from '@elastic/eui';
import { EuiTitle, EuiText, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

export const DocumentFieldsHeaders = () => {
return (
<EuiTitle size="s">
<h2>Document fields</h2>
</EuiTitle>
<>
<EuiTitle size="s">
<h2>
{i18n.translate('xpack.idxMgmt.mappingsEditor.documentFieldsTitle', {
defaultMessage: 'Document fields',
})}
</h2>
</EuiTitle>
<EuiSpacer size="s" />
<EuiText size="s" color="subdued">
{i18n.translate('xpack.idxMgmt.mappingsEditor.documentFieldsDescription', {
defaultMessage: 'Define which fields the documents of your index will contain.',
})}
</EuiText>
</>
);
};
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export * from './name_parameter';
@@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';

import { TextField, UseField, FieldConfig } from '../../../shared_imports';
import { validateUniqueName } from '../../../lib';
import { PARAMETERS_DEFINITION } from '../../../constants';
import { useState } from '../../../mappings_state';

export const NameParameter = () => {
const {
fields: { rootLevelFields, byId },
documentFields: { fieldToAddFieldTo, fieldToEdit },
} = useState();
jloleysens marked this conversation as resolved.
Show resolved Hide resolved
const { validations, ...rest } = PARAMETERS_DEFINITION.name.fieldConfig as FieldConfig;

const initialName = fieldToEdit ? byId[fieldToEdit].source.name : undefined;
const parentId = fieldToEdit ? byId[fieldToEdit].parentId : fieldToAddFieldTo;
const uniqueNameValidator = validateUniqueName({ rootLevelFields, byId }, initialName, parentId);

const nameConfig: FieldConfig = {
...rest,
validations: [
...validations!,
{
validator: uniqueNameValidator,
},
],
};

return <UseField path="name" config={nameConfig} component={TextField} />;
};