Port PR 3746 from ent-search (#103765)

* Port the changes as is with no modifications

* Fix accessibility errors

* Rename variable

* Fix Stylelint issues and remove unused CSS

* Extract getAsLocalDatetimeString as a util function and use it everywhere

* Update backend schema

Also replace schema.maybe with schema.nullable. Previously assigning
"Leave unassigned" value to subtitle and description caused a server error,
because we were receiving null for these values that server did not expect.

* Update exampleResult mock

* Add tests for DisplaySettingsLogic

* Add tests for ExampleSearchResultGroup

* Add tests for ExampleStandoutResult

* Add tests for SearchResults

* Add missed null fallback type
This commit is contained in:
Vadim Yakhin 2021-06-29 22:11:09 -03:00 committed by GitHub
parent 71a57454c7
commit 712ab004c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 561 additions and 12 deletions

View file

@ -305,6 +305,10 @@ export const exampleResult = {
urlField: 'myLink',
color: '#e3e3e3',
descriptionField: 'about',
typeField: 'otherType',
mediaTypeField: 'otherMediaType',
createdByField: 'otherCreatedBy',
updatedByField: 'otherUpdatedBy',
detailFields: [
{ fieldName: 'cats', label: 'Felines' },
{ fieldName: 'dogs', label: 'Canines' },

View file

@ -204,6 +204,10 @@ export interface SearchResultConfig {
titleField: string | null;
subtitleField: string | null;
descriptionField: string | null;
typeField: string | null;
mediaTypeField: string | null;
createdByField: string | null;
updatedByField: string | null;
urlField: string | null;
color: string;
detailFields: DetailField[];

View file

@ -0,0 +1,22 @@
/*
* 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 { getAsLocalDateTimeString } from './';
describe('getAsLocalDateTimeString', () => {
it('returns localized date if string can be parsed as date', () => {
const date = '2021-06-28';
expect(getAsLocalDateTimeString(date)).toEqual(new Date(Date.parse(date)).toLocaleString());
});
it('returns null if string cannot be parsed as date', () => {
const date = 'foo';
expect(getAsLocalDateTimeString(date)).toEqual(null);
});
});

View file

@ -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 const getAsLocalDateTimeString = (str: string) => {
const dateValue = Date.parse(str);
return dateValue ? new Date(dateValue).toLocaleString() : null;
};

View file

@ -6,3 +6,4 @@
*/
export { toSentenceSerial } from './to_sentence_serial';
export { getAsLocalDateTimeString } from './get_as_local_datetime_string';

View file

@ -22,6 +22,23 @@
0 0 20px $euiColorLightestShade;
}
@mixin searchResultTag {
height: 20px;
border-radius: 2px;
display: inline-flex;
align-items: center;
padding: 0 .25rem;
background: #E9EDF2;
color: #647487;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .075em;
white-space: nowrap;
z-index: 1;
margin-right: 5px;
}
// Wrapper
.custom-source-display-settings {
font-size: 16px;
@ -73,6 +90,28 @@
color: $euiColorDarkShade;
}
}
&__tag {
@include searchResultTag;
}
&__tag-content {
display: inline-flex;
height: 20px;
flex-shrink: 0;
align-items: center;
}
&__meta {
position: relative;
z-index: 1;
display: flex;
flex-flow: row nowrap;
align-items: center;
margin-top: .5rem;
font-size: .8em;
overflow: hidden;
}
}
.example-result-content-placeholder {

View file

@ -52,6 +52,10 @@ describe('DisplaySettingsLogic', () => {
urlFieldHover: false,
subtitleFieldHover: false,
descriptionFieldHover: false,
typeFieldHover: false,
mediaTypeFieldHover: false,
createdByFieldHover: false,
updatedByFieldHover: false,
fieldOptions: [],
optionalFieldOptions: [
{
@ -182,6 +186,50 @@ describe('DisplaySettingsLogic', () => {
});
});
it('setTypeField', () => {
const TYPE = 'new type';
DisplaySettingsLogic.actions.setServerResponseData(serverProps);
DisplaySettingsLogic.actions.setTypeField(TYPE);
expect(DisplaySettingsLogic.values.searchResultConfig).toEqual({
...searchResultConfig,
typeField: TYPE,
});
});
it('setMediaTypeField', () => {
const MEDIA_TYPE = 'new media type';
DisplaySettingsLogic.actions.setServerResponseData(serverProps);
DisplaySettingsLogic.actions.setMediaTypeField(MEDIA_TYPE);
expect(DisplaySettingsLogic.values.searchResultConfig).toEqual({
...searchResultConfig,
mediaTypeField: MEDIA_TYPE,
});
});
it('setCreatedByField', () => {
const CREATED_BY = 'new created by';
DisplaySettingsLogic.actions.setServerResponseData(serverProps);
DisplaySettingsLogic.actions.setCreatedByField(CREATED_BY);
expect(DisplaySettingsLogic.values.searchResultConfig).toEqual({
...searchResultConfig,
createdByField: CREATED_BY,
});
});
it('setUpdatedByField', () => {
const UPDATED_BY = 'new updated by';
DisplaySettingsLogic.actions.setServerResponseData(serverProps);
DisplaySettingsLogic.actions.setUpdatedByField(UPDATED_BY);
expect(DisplaySettingsLogic.values.searchResultConfig).toEqual({
...searchResultConfig,
updatedByField: UPDATED_BY,
});
});
it('setDetailFields', () => {
const result = {
destination: {
@ -286,6 +334,36 @@ describe('DisplaySettingsLogic', () => {
expect(DisplaySettingsLogic.values.urlFieldHover).toEqual(!defaultValues.urlFieldHover);
});
it('toggleTypeFieldHover', () => {
DisplaySettingsLogic.actions.toggleTypeFieldHover();
expect(DisplaySettingsLogic.values.typeFieldHover).toEqual(!defaultValues.typeFieldHover);
});
it('toggleMediaTypeFieldHover', () => {
DisplaySettingsLogic.actions.toggleMediaTypeFieldHover();
expect(DisplaySettingsLogic.values.mediaTypeFieldHover).toEqual(
!defaultValues.mediaTypeFieldHover
);
});
it('toggleCreatedByFieldHover', () => {
DisplaySettingsLogic.actions.toggleCreatedByFieldHover();
expect(DisplaySettingsLogic.values.createdByFieldHover).toEqual(
!defaultValues.createdByFieldHover
);
});
it('toggleUpdatedByFieldHover', () => {
DisplaySettingsLogic.actions.toggleUpdatedByFieldHover();
expect(DisplaySettingsLogic.values.updatedByFieldHover).toEqual(
!defaultValues.updatedByFieldHover
);
});
});
describe('listeners', () => {

View file

@ -55,6 +55,10 @@ interface DisplaySettingsActions {
setUrlField(urlField: string): string;
setSubtitleField(subtitleField: string | null): string | null;
setDescriptionField(descriptionField: string | null): string | null;
setTypeField(typeField: string | null): string | null;
setMediaTypeField(mediaTypeField: string | null): string | null;
setCreatedByField(createdByField: string | null): string | null;
setUpdatedByField(updatedByField: string | null): string | null;
setColorField(hex: string): string;
setDetailFields(result: DropResult): { result: DropResult };
openEditDetailField(editFieldIndex: number | null): number | null;
@ -70,6 +74,10 @@ interface DisplaySettingsActions {
toggleTitleFieldHover(): void;
toggleSubtitleFieldHover(): void;
toggleDescriptionFieldHover(): void;
toggleTypeFieldHover(): void;
toggleMediaTypeFieldHover(): void;
toggleCreatedByFieldHover(): void;
toggleUpdatedByFieldHover(): void;
toggleUrlFieldHover(): void;
}
@ -89,6 +97,10 @@ interface DisplaySettingsValues {
urlFieldHover: boolean;
subtitleFieldHover: boolean;
descriptionFieldHover: boolean;
typeFieldHover: boolean;
mediaTypeFieldHover: boolean;
createdByFieldHover: boolean;
updatedByFieldHover: boolean;
fieldOptions: OptionValue[];
optionalFieldOptions: OptionValue[];
availableFieldOptions: OptionValue[];
@ -100,6 +112,10 @@ export const defaultSearchResultConfig = {
subtitleField: '',
descriptionField: '',
urlField: '',
typeField: '',
mediaTypeField: '',
createdByField: '',
updatedByField: '',
color: '#000000',
detailFields: [],
};
@ -115,7 +131,11 @@ export const DisplaySettingsLogic = kea<
setTitleField: (titleField: string) => titleField,
setUrlField: (urlField: string) => urlField,
setSubtitleField: (subtitleField: string | null) => subtitleField,
setDescriptionField: (descriptionField: string) => descriptionField,
setDescriptionField: (descriptionField: string | null) => descriptionField,
setTypeField: (typeField: string | null) => typeField,
setMediaTypeField: (mediaTypeField: string | null) => mediaTypeField,
setCreatedByField: (createdByField: string | null) => createdByField,
setUpdatedByField: (updatedByField: string | null) => updatedByField,
setColorField: (hex: string) => hex,
setDetailFields: (result: DropResult) => ({ result }),
openEditDetailField: (editFieldIndex: number | null) => editFieldIndex,
@ -128,6 +148,10 @@ export const DisplaySettingsLogic = kea<
toggleTitleFieldHover: () => true,
toggleSubtitleFieldHover: () => true,
toggleDescriptionFieldHover: () => true,
toggleTypeFieldHover: () => true,
toggleMediaTypeFieldHover: () => true,
toggleCreatedByFieldHover: () => true,
toggleUpdatedByFieldHover: () => true,
toggleUrlFieldHover: () => true,
initializeDisplaySettings: () => true,
setServerData: () => true,
@ -181,6 +205,19 @@ export const DisplaySettingsLogic = kea<
...searchResultConfig,
descriptionField,
}),
setTypeField: (searchResultConfig, typeField) => ({ ...searchResultConfig, typeField }),
setMediaTypeField: (searchResultConfig, mediaTypeField) => ({
...searchResultConfig,
mediaTypeField,
}),
setCreatedByField: (searchResultConfig, createdByField) => ({
...searchResultConfig,
createdByField,
}),
setUpdatedByField: (searchResultConfig, updatedByField) => ({
...searchResultConfig,
updatedByField,
}),
setColorField: (searchResultConfig, color) => ({ ...searchResultConfig, color }),
setDetailFields: (searchResultConfig, { result: { destination, source } }) => {
const detailFields = cloneDeep(searchResultConfig.detailFields);
@ -273,7 +310,31 @@ export const DisplaySettingsLogic = kea<
descriptionFieldHover: [
false,
{
toggleDescriptionFieldHover: (addFieldModalVisible) => !addFieldModalVisible,
toggleDescriptionFieldHover: (descriptionFieldHover) => !descriptionFieldHover,
},
],
typeFieldHover: [
false,
{
toggleTypeFieldHover: (typeFieldHover) => !typeFieldHover,
},
],
mediaTypeFieldHover: [
false,
{
toggleMediaTypeFieldHover: (mediaTypeFieldHover) => !mediaTypeFieldHover,
},
],
createdByFieldHover: [
false,
{
toggleCreatedByFieldHover: (createdByFieldHover) => !createdByFieldHover,
},
],
updatedByFieldHover: [
false,
{
toggleUpdatedByFieldHover: (updatedByFieldHover) => !updatedByFieldHover,
},
],
},

View file

@ -13,16 +13,12 @@ import { useValues } from 'kea';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
import { URL_LABEL } from '../../../../constants';
import { getAsLocalDateTimeString } from '../../../../utils';
import { CustomSourceIcon } from './custom_source_icon';
import { DisplaySettingsLogic } from './display_settings_logic';
import { TitleField } from './title_field';
const getAsLocalDateTimeString = (str: string) => {
const dateValue = Date.parse(str);
return dateValue ? new Date(dateValue).toLocaleString() : null;
};
export const ExampleResultDetailCard: React.FC = () => {
const {
sourceName,

View file

@ -41,4 +41,26 @@ describe('ExampleSearchResultGroup', () => {
expect(wrapper.find('[data-test-subj="DefaultDescriptionLabel"]')).toHaveLength(1);
});
it('renders optional fields if they exist in result', () => {
setMockValues({
...exampleResult,
exampleDocuments: [
{
myLink: 'http://foo',
otherTitle: 'foo',
otherType: 'File',
otherMediaType: 'PDF',
otherCreatedBy: 'bar',
otherUpdatedBy: 'baz',
},
],
});
const wrapper = shallow(<ExampleSearchResultGroup />);
expect(wrapper.find('[data-test-subj="CreatedByField"]')).toHaveLength(1);
expect(wrapper.find('[data-test-subj="UpdatedByField"]')).toHaveLength(1);
expect(wrapper.find('[data-test-subj="TypeField"]')).toHaveLength(1);
expect(wrapper.find('[data-test-subj="MediaTypeField"]')).toHaveLength(1);
});
});

View file

@ -13,6 +13,7 @@ import { useValues } from 'kea';
import { isColorDark, hexToRgb } from '@elastic/eui';
import { DESCRIPTION_LABEL } from '../../../../constants';
import { getAsLocalDateTimeString } from '../../../../utils';
import { CustomSourceIcon } from './custom_source_icon';
import { DisplaySettingsLogic } from './display_settings_logic';
@ -22,10 +23,23 @@ import { TitleField } from './title_field';
export const ExampleSearchResultGroup: React.FC = () => {
const {
sourceName,
searchResultConfig: { titleField, subtitleField, descriptionField, color },
searchResultConfig: {
titleField,
subtitleField,
descriptionField,
typeField,
mediaTypeField,
createdByField,
updatedByField,
color,
},
titleFieldHover,
subtitleFieldHover,
descriptionFieldHover,
typeFieldHover,
mediaTypeFieldHover,
createdByFieldHover,
updatedByFieldHover,
exampleDocuments,
} = useValues(DisplaySettingsLogic);
@ -72,6 +86,60 @@ export const ExampleSearchResultGroup: React.FC = () => {
</span>
)}
</div>
{createdByField && result[createdByField] && (
<div
className={classNames('example-result-content__subtitle', {
'example-result-field-hover': createdByFieldHover,
})}
data-test-subj="CreatedByField"
>
Created by {result[createdByField]}
</div>
)}
<div className="example-result-content__meta">
{typeField && result[typeField] && (
<div
className={classNames('example-result-content__tag', {
'example-result-field-hover': typeFieldHover,
})}
data-test-subj="TypeField"
>
<span className="example-search-result__tag-content">
{result[typeField]}
</span>
</div>
)}
{mediaTypeField && result[mediaTypeField] && (
<div
className={classNames('example-result-content__tag', {
'example-result-field-hover': mediaTypeFieldHover,
})}
data-test-subj="MediaTypeField"
>
<span className="example-search-result__tag-content">
{result[mediaTypeField]}
</span>
</div>
)}
<div className="example-result-content__tag-content">
<span>
Last updated&nbsp;
{updatedByField && result[updatedByField] && (
<span
className={classNames('example-result-content__tag-content', {
'example-result-field-hover': updatedByFieldHover,
})}
data-test-subj="UpdatedByField"
>
{' '}
by {result[updatedByField]}&nbsp;
</span>
)}
{getAsLocalDateTimeString(result.last_updated as string) ||
result.last_updated}
</span>
</div>
</div>
</div>
</div>
))}

View file

@ -41,4 +41,26 @@ describe('ExampleStandoutResult', () => {
expect(wrapper.find('[data-test-subj="DefaultDescriptionLabel"]')).toHaveLength(1);
});
it('renders optional fields if they exist in result', () => {
setMockValues({
...exampleResult,
exampleDocuments: [
{
myLink: 'http://foo',
otherTitle: 'foo',
otherType: 'File',
otherMediaType: 'PDF',
otherCreatedBy: 'bar',
otherUpdatedBy: 'baz',
},
],
});
const wrapper = shallow(<ExampleStandoutResult />);
expect(wrapper.find('[data-test-subj="CreatedByField"]')).toHaveLength(1);
expect(wrapper.find('[data-test-subj="UpdatedByField"]')).toHaveLength(1);
expect(wrapper.find('[data-test-subj="TypeField"]')).toHaveLength(1);
expect(wrapper.find('[data-test-subj="MediaTypeField"]')).toHaveLength(1);
});
});

View file

@ -13,6 +13,7 @@ import { useValues } from 'kea';
import { isColorDark, hexToRgb } from '@elastic/eui';
import { DESCRIPTION_LABEL } from '../../../../constants';
import { getAsLocalDateTimeString } from '../../../../utils';
import { CustomSourceIcon } from './custom_source_icon';
import { DisplaySettingsLogic } from './display_settings_logic';
@ -22,10 +23,23 @@ import { TitleField } from './title_field';
export const ExampleStandoutResult: React.FC = () => {
const {
sourceName,
searchResultConfig: { titleField, subtitleField, descriptionField, color },
searchResultConfig: {
titleField,
subtitleField,
descriptionField,
typeField,
mediaTypeField,
createdByField,
updatedByField,
color,
},
titleFieldHover,
subtitleFieldHover,
descriptionFieldHover,
typeFieldHover,
mediaTypeFieldHover,
createdByFieldHover,
updatedByFieldHover,
exampleDocuments,
} = useValues(DisplaySettingsLogic);
@ -66,6 +80,55 @@ export const ExampleStandoutResult: React.FC = () => {
</span>
)}
</div>
{createdByField && result[createdByField] && (
<div
className={classNames('example-result-content__subtitle', {
'example-result-field-hover': createdByFieldHover,
})}
data-test-subj="CreatedByField"
>
Created by {result[createdByField]}
</div>
)}
<div className="example-result-content__meta">
{typeField && result[typeField] && (
<div
className={classNames('example-result-content__tag', {
'example-result-field-hover': typeFieldHover,
})}
data-test-subj="TypeField"
>
<span className="example-search-result__tag-content">{result[typeField]}</span>
</div>
)}
{mediaTypeField && result[mediaTypeField] && (
<div
className={classNames('example-result-content__tag', {
'example-result-field-hover': mediaTypeFieldHover,
})}
data-test-subj="MediaTypeField"
>
<span className="example-search-result__tag-content">{result[mediaTypeField]}</span>
</div>
)}
<div className="example-result-content__tag-content">
<span>
Last updated&nbsp;
{updatedByField && result[updatedByField] && (
<span
className={classNames('example-result-content__tag-content', {
'example-result-field-hover': updatedByFieldHover,
})}
data-test-subj="UpdatedByField"
>
{' '}
by {result[updatedByField]}&nbsp;
</span>
)}
{getAsLocalDateTimeString(result.last_updated as string) || result.last_updated}
</span>
</div>
</div>
</div>
</div>
</div>

View file

@ -41,6 +41,10 @@ describe('SearchResults', () => {
const setDescriptionField = jest.fn();
const setUrlField = jest.fn();
const setColorField = jest.fn();
const setTypeField = jest.fn();
const setMediaTypeField = jest.fn();
const setCreatedByField = jest.fn();
const setUpdatedByField = jest.fn();
beforeEach(() => {
setMockActions({
@ -52,6 +56,10 @@ describe('SearchResults', () => {
setDescriptionField,
setUrlField,
setColorField,
setTypeField,
setMediaTypeField,
setCreatedByField,
setUpdatedByField,
});
setMockValues({
searchResultConfig,
@ -103,6 +111,42 @@ describe('SearchResults', () => {
expect(setDescriptionField).toHaveBeenCalledWith(searchResultConfig.descriptionField);
});
it('calls setTypeField on change', () => {
const wrapper = shallow(<SearchResults />);
wrapper
.find('[data-test-subj="TypeFieldSelect"]')
.simulate('change', { target: { value: searchResultConfig.typeField } });
expect(setTypeField).toHaveBeenCalledWith(searchResultConfig.typeField);
});
it('calls setMediaTypeField on change', () => {
const wrapper = shallow(<SearchResults />);
wrapper
.find('[data-test-subj="MediaTypeFieldSelect"]')
.simulate('change', { target: { value: searchResultConfig.mediaTypeField } });
expect(setMediaTypeField).toHaveBeenCalledWith(searchResultConfig.mediaTypeField);
});
it('calls setCreatedByField on change', () => {
const wrapper = shallow(<SearchResults />);
wrapper
.find('[data-test-subj="CreatedByFieldSelect"]')
.simulate('change', { target: { value: searchResultConfig.createdByField } });
expect(setCreatedByField).toHaveBeenCalledWith(searchResultConfig.createdByField);
});
it('calls setUpdatedByField on change', () => {
const wrapper = shallow(<SearchResults />);
wrapper
.find('[data-test-subj="UpdatedByFieldSelect"]')
.simulate('change', { target: { value: searchResultConfig.updatedByField } });
expect(setUpdatedByField).toHaveBeenCalledWith(searchResultConfig.updatedByField);
});
it('handles blank fallbacks', () => {
setMockValues({
searchResultConfig: { detailFields: [] },
@ -116,9 +160,25 @@ describe('SearchResults', () => {
wrapper
.find('[data-test-subj="DescriptionFieldSelect"]')
.simulate('change', { target: { value: LEAVE_UNASSIGNED_FIELD } });
wrapper
.find('[data-test-subj="TypeFieldSelect"]')
.simulate('change', { target: { value: LEAVE_UNASSIGNED_FIELD } });
wrapper
.find('[data-test-subj="MediaTypeFieldSelect"]')
.simulate('change', { target: { value: LEAVE_UNASSIGNED_FIELD } });
wrapper
.find('[data-test-subj="CreatedByFieldSelect"]')
.simulate('change', { target: { value: LEAVE_UNASSIGNED_FIELD } });
wrapper
.find('[data-test-subj="UpdatedByFieldSelect"]')
.simulate('change', { target: { value: LEAVE_UNASSIGNED_FIELD } });
expect(wrapper.find('[data-test-subj="UrlFieldSelect"]').prop('value')).toEqual('');
expect(setSubtitleField).toHaveBeenCalledWith(null);
expect(setDescriptionField).toHaveBeenCalledWith(null);
expect(setTypeField).toHaveBeenCalledWith(null);
expect(setMediaTypeField).toHaveBeenCalledWith(null);
expect(setCreatedByField).toHaveBeenCalledWith(null);
expect(setUpdatedByField).toHaveBeenCalledWith(null);
});
});

View file

@ -42,15 +42,33 @@ export const SearchResults: React.FC = () => {
toggleTitleFieldHover,
toggleSubtitleFieldHover,
toggleDescriptionFieldHover,
toggleTypeFieldHover,
toggleMediaTypeFieldHover,
toggleCreatedByFieldHover,
toggleUpdatedByFieldHover,
setTitleField,
setSubtitleField,
setDescriptionField,
setTypeField,
setMediaTypeField,
setCreatedByField,
setUpdatedByField,
setUrlField,
setColorField,
} = useActions(DisplaySettingsLogic);
const {
searchResultConfig: { titleField, descriptionField, subtitleField, urlField, color },
searchResultConfig: {
titleField,
descriptionField,
subtitleField,
typeField,
mediaTypeField,
createdByField,
updatedByField,
urlField,
color,
},
fieldOptions,
optionalFieldOptions,
} = useValues(DisplaySettingsLogic);
@ -136,6 +154,82 @@ export const SearchResults: React.FC = () => {
}
/>
</EuiFormRow>
<EuiFormRow
label="Type"
helpText="This area is optional"
onMouseOver={toggleTypeFieldHover}
onFocus={toggleTypeFieldHover}
onMouseOut={toggleTypeFieldHover}
onBlur={toggleTypeFieldHover}
>
<EuiSelect
options={optionalFieldOptions}
className="field-selector"
hasNoInitialSelection
data-test-subj="TypeFieldSelect"
value={typeField || LEAVE_UNASSIGNED_FIELD}
onChange={({ target: { value } }) =>
setTypeField(value === LEAVE_UNASSIGNED_FIELD ? null : value)
}
/>
</EuiFormRow>
<EuiFormRow
label="Media Type"
helpText="This area is optional"
onMouseOver={toggleMediaTypeFieldHover}
onFocus={toggleMediaTypeFieldHover}
onMouseOut={toggleMediaTypeFieldHover}
onBlur={toggleMediaTypeFieldHover}
>
<EuiSelect
options={optionalFieldOptions}
className="field-selector"
hasNoInitialSelection
data-test-subj="MediaTypeFieldSelect"
value={mediaTypeField || LEAVE_UNASSIGNED_FIELD}
onChange={({ target: { value } }) =>
setMediaTypeField(value === LEAVE_UNASSIGNED_FIELD ? null : value)
}
/>
</EuiFormRow>
<EuiFormRow
label="Created By"
helpText="This area is optional"
onMouseOver={toggleCreatedByFieldHover}
onFocus={toggleCreatedByFieldHover}
onMouseOut={toggleCreatedByFieldHover}
onBlur={toggleCreatedByFieldHover}
>
<EuiSelect
options={optionalFieldOptions}
className="field-selector"
hasNoInitialSelection
data-test-subj="CreatedByFieldSelect"
value={createdByField || LEAVE_UNASSIGNED_FIELD}
onChange={({ target: { value } }) =>
setCreatedByField(value === LEAVE_UNASSIGNED_FIELD ? null : value)
}
/>
</EuiFormRow>
<EuiFormRow
label="Updated By"
helpText="This area is optional"
onMouseOver={toggleUpdatedByFieldHover}
onFocus={toggleUpdatedByFieldHover}
onMouseOut={toggleUpdatedByFieldHover}
onBlur={toggleUpdatedByFieldHover}
>
<EuiSelect
options={optionalFieldOptions}
className="field-selector"
hasNoInitialSelection
data-test-subj="UpdatedByFieldSelect"
value={updatedByField || LEAVE_UNASSIGNED_FIELD}
onChange={({ target: { value } }) =>
setUpdatedByField(value === LEAVE_UNASSIGNED_FIELD ? null : value)
}
/>
</EuiFormRow>
</EuiForm>
</EuiFlexItem>
<EuiFlexItem>

View file

@ -45,9 +45,13 @@ const displayFieldSchema = schema.object({
const displaySettingsSchema = schema.object({
titleField: schema.maybe(schema.string()),
subtitleField: schema.maybe(schema.string()),
descriptionField: schema.maybe(schema.string()),
subtitleField: schema.nullable(schema.string()),
descriptionField: schema.nullable(schema.string()),
urlField: schema.maybe(schema.string()),
typeField: schema.nullable(schema.string()),
mediaTypeField: schema.nullable(schema.string()),
createdByField: schema.nullable(schema.string()),
updatedByField: schema.nullable(schema.string()),
color: schema.string(),
urlFieldIsLinkable: schema.boolean(),
detailFields: schema.oneOf([schema.arrayOf(displayFieldSchema), displayFieldSchema]),