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:
parent
71a57454c7
commit
712ab004c5
16 changed files with 561 additions and 12 deletions
|
@ -305,6 +305,10 @@ export const exampleResult = {
|
||||||
urlField: 'myLink',
|
urlField: 'myLink',
|
||||||
color: '#e3e3e3',
|
color: '#e3e3e3',
|
||||||
descriptionField: 'about',
|
descriptionField: 'about',
|
||||||
|
typeField: 'otherType',
|
||||||
|
mediaTypeField: 'otherMediaType',
|
||||||
|
createdByField: 'otherCreatedBy',
|
||||||
|
updatedByField: 'otherUpdatedBy',
|
||||||
detailFields: [
|
detailFields: [
|
||||||
{ fieldName: 'cats', label: 'Felines' },
|
{ fieldName: 'cats', label: 'Felines' },
|
||||||
{ fieldName: 'dogs', label: 'Canines' },
|
{ fieldName: 'dogs', label: 'Canines' },
|
||||||
|
|
|
@ -204,6 +204,10 @@ export interface SearchResultConfig {
|
||||||
titleField: string | null;
|
titleField: string | null;
|
||||||
subtitleField: string | null;
|
subtitleField: string | null;
|
||||||
descriptionField: string | null;
|
descriptionField: string | null;
|
||||||
|
typeField: string | null;
|
||||||
|
mediaTypeField: string | null;
|
||||||
|
createdByField: string | null;
|
||||||
|
updatedByField: string | null;
|
||||||
urlField: string | null;
|
urlField: string | null;
|
||||||
color: string;
|
color: string;
|
||||||
detailFields: DetailField[];
|
detailFields: DetailField[];
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
|
@ -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;
|
||||||
|
};
|
|
@ -6,3 +6,4 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { toSentenceSerial } from './to_sentence_serial';
|
export { toSentenceSerial } from './to_sentence_serial';
|
||||||
|
export { getAsLocalDateTimeString } from './get_as_local_datetime_string';
|
||||||
|
|
|
@ -22,6 +22,23 @@
|
||||||
0 0 20px $euiColorLightestShade;
|
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
|
// Wrapper
|
||||||
.custom-source-display-settings {
|
.custom-source-display-settings {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
@ -73,6 +90,28 @@
|
||||||
color: $euiColorDarkShade;
|
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 {
|
.example-result-content-placeholder {
|
||||||
|
|
|
@ -52,6 +52,10 @@ describe('DisplaySettingsLogic', () => {
|
||||||
urlFieldHover: false,
|
urlFieldHover: false,
|
||||||
subtitleFieldHover: false,
|
subtitleFieldHover: false,
|
||||||
descriptionFieldHover: false,
|
descriptionFieldHover: false,
|
||||||
|
typeFieldHover: false,
|
||||||
|
mediaTypeFieldHover: false,
|
||||||
|
createdByFieldHover: false,
|
||||||
|
updatedByFieldHover: false,
|
||||||
fieldOptions: [],
|
fieldOptions: [],
|
||||||
optionalFieldOptions: [
|
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', () => {
|
it('setDetailFields', () => {
|
||||||
const result = {
|
const result = {
|
||||||
destination: {
|
destination: {
|
||||||
|
@ -286,6 +334,36 @@ describe('DisplaySettingsLogic', () => {
|
||||||
|
|
||||||
expect(DisplaySettingsLogic.values.urlFieldHover).toEqual(!defaultValues.urlFieldHover);
|
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', () => {
|
describe('listeners', () => {
|
||||||
|
|
|
@ -55,6 +55,10 @@ interface DisplaySettingsActions {
|
||||||
setUrlField(urlField: string): string;
|
setUrlField(urlField: string): string;
|
||||||
setSubtitleField(subtitleField: string | null): string | null;
|
setSubtitleField(subtitleField: string | null): string | null;
|
||||||
setDescriptionField(descriptionField: 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;
|
setColorField(hex: string): string;
|
||||||
setDetailFields(result: DropResult): { result: DropResult };
|
setDetailFields(result: DropResult): { result: DropResult };
|
||||||
openEditDetailField(editFieldIndex: number | null): number | null;
|
openEditDetailField(editFieldIndex: number | null): number | null;
|
||||||
|
@ -70,6 +74,10 @@ interface DisplaySettingsActions {
|
||||||
toggleTitleFieldHover(): void;
|
toggleTitleFieldHover(): void;
|
||||||
toggleSubtitleFieldHover(): void;
|
toggleSubtitleFieldHover(): void;
|
||||||
toggleDescriptionFieldHover(): void;
|
toggleDescriptionFieldHover(): void;
|
||||||
|
toggleTypeFieldHover(): void;
|
||||||
|
toggleMediaTypeFieldHover(): void;
|
||||||
|
toggleCreatedByFieldHover(): void;
|
||||||
|
toggleUpdatedByFieldHover(): void;
|
||||||
toggleUrlFieldHover(): void;
|
toggleUrlFieldHover(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +97,10 @@ interface DisplaySettingsValues {
|
||||||
urlFieldHover: boolean;
|
urlFieldHover: boolean;
|
||||||
subtitleFieldHover: boolean;
|
subtitleFieldHover: boolean;
|
||||||
descriptionFieldHover: boolean;
|
descriptionFieldHover: boolean;
|
||||||
|
typeFieldHover: boolean;
|
||||||
|
mediaTypeFieldHover: boolean;
|
||||||
|
createdByFieldHover: boolean;
|
||||||
|
updatedByFieldHover: boolean;
|
||||||
fieldOptions: OptionValue[];
|
fieldOptions: OptionValue[];
|
||||||
optionalFieldOptions: OptionValue[];
|
optionalFieldOptions: OptionValue[];
|
||||||
availableFieldOptions: OptionValue[];
|
availableFieldOptions: OptionValue[];
|
||||||
|
@ -100,6 +112,10 @@ export const defaultSearchResultConfig = {
|
||||||
subtitleField: '',
|
subtitleField: '',
|
||||||
descriptionField: '',
|
descriptionField: '',
|
||||||
urlField: '',
|
urlField: '',
|
||||||
|
typeField: '',
|
||||||
|
mediaTypeField: '',
|
||||||
|
createdByField: '',
|
||||||
|
updatedByField: '',
|
||||||
color: '#000000',
|
color: '#000000',
|
||||||
detailFields: [],
|
detailFields: [],
|
||||||
};
|
};
|
||||||
|
@ -115,7 +131,11 @@ export const DisplaySettingsLogic = kea<
|
||||||
setTitleField: (titleField: string) => titleField,
|
setTitleField: (titleField: string) => titleField,
|
||||||
setUrlField: (urlField: string) => urlField,
|
setUrlField: (urlField: string) => urlField,
|
||||||
setSubtitleField: (subtitleField: string | null) => subtitleField,
|
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,
|
setColorField: (hex: string) => hex,
|
||||||
setDetailFields: (result: DropResult) => ({ result }),
|
setDetailFields: (result: DropResult) => ({ result }),
|
||||||
openEditDetailField: (editFieldIndex: number | null) => editFieldIndex,
|
openEditDetailField: (editFieldIndex: number | null) => editFieldIndex,
|
||||||
|
@ -128,6 +148,10 @@ export const DisplaySettingsLogic = kea<
|
||||||
toggleTitleFieldHover: () => true,
|
toggleTitleFieldHover: () => true,
|
||||||
toggleSubtitleFieldHover: () => true,
|
toggleSubtitleFieldHover: () => true,
|
||||||
toggleDescriptionFieldHover: () => true,
|
toggleDescriptionFieldHover: () => true,
|
||||||
|
toggleTypeFieldHover: () => true,
|
||||||
|
toggleMediaTypeFieldHover: () => true,
|
||||||
|
toggleCreatedByFieldHover: () => true,
|
||||||
|
toggleUpdatedByFieldHover: () => true,
|
||||||
toggleUrlFieldHover: () => true,
|
toggleUrlFieldHover: () => true,
|
||||||
initializeDisplaySettings: () => true,
|
initializeDisplaySettings: () => true,
|
||||||
setServerData: () => true,
|
setServerData: () => true,
|
||||||
|
@ -181,6 +205,19 @@ export const DisplaySettingsLogic = kea<
|
||||||
...searchResultConfig,
|
...searchResultConfig,
|
||||||
descriptionField,
|
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 }),
|
setColorField: (searchResultConfig, color) => ({ ...searchResultConfig, color }),
|
||||||
setDetailFields: (searchResultConfig, { result: { destination, source } }) => {
|
setDetailFields: (searchResultConfig, { result: { destination, source } }) => {
|
||||||
const detailFields = cloneDeep(searchResultConfig.detailFields);
|
const detailFields = cloneDeep(searchResultConfig.detailFields);
|
||||||
|
@ -273,7 +310,31 @@ export const DisplaySettingsLogic = kea<
|
||||||
descriptionFieldHover: [
|
descriptionFieldHover: [
|
||||||
false,
|
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,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,16 +13,12 @@ import { useValues } from 'kea';
|
||||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
|
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
|
||||||
|
|
||||||
import { URL_LABEL } from '../../../../constants';
|
import { URL_LABEL } from '../../../../constants';
|
||||||
|
import { getAsLocalDateTimeString } from '../../../../utils';
|
||||||
|
|
||||||
import { CustomSourceIcon } from './custom_source_icon';
|
import { CustomSourceIcon } from './custom_source_icon';
|
||||||
import { DisplaySettingsLogic } from './display_settings_logic';
|
import { DisplaySettingsLogic } from './display_settings_logic';
|
||||||
import { TitleField } from './title_field';
|
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 = () => {
|
export const ExampleResultDetailCard: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
sourceName,
|
sourceName,
|
||||||
|
|
|
@ -41,4 +41,26 @@ describe('ExampleSearchResultGroup', () => {
|
||||||
|
|
||||||
expect(wrapper.find('[data-test-subj="DefaultDescriptionLabel"]')).toHaveLength(1);
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { useValues } from 'kea';
|
||||||
import { isColorDark, hexToRgb } from '@elastic/eui';
|
import { isColorDark, hexToRgb } from '@elastic/eui';
|
||||||
|
|
||||||
import { DESCRIPTION_LABEL } from '../../../../constants';
|
import { DESCRIPTION_LABEL } from '../../../../constants';
|
||||||
|
import { getAsLocalDateTimeString } from '../../../../utils';
|
||||||
|
|
||||||
import { CustomSourceIcon } from './custom_source_icon';
|
import { CustomSourceIcon } from './custom_source_icon';
|
||||||
import { DisplaySettingsLogic } from './display_settings_logic';
|
import { DisplaySettingsLogic } from './display_settings_logic';
|
||||||
|
@ -22,10 +23,23 @@ import { TitleField } from './title_field';
|
||||||
export const ExampleSearchResultGroup: React.FC = () => {
|
export const ExampleSearchResultGroup: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
sourceName,
|
sourceName,
|
||||||
searchResultConfig: { titleField, subtitleField, descriptionField, color },
|
searchResultConfig: {
|
||||||
|
titleField,
|
||||||
|
subtitleField,
|
||||||
|
descriptionField,
|
||||||
|
typeField,
|
||||||
|
mediaTypeField,
|
||||||
|
createdByField,
|
||||||
|
updatedByField,
|
||||||
|
color,
|
||||||
|
},
|
||||||
titleFieldHover,
|
titleFieldHover,
|
||||||
subtitleFieldHover,
|
subtitleFieldHover,
|
||||||
descriptionFieldHover,
|
descriptionFieldHover,
|
||||||
|
typeFieldHover,
|
||||||
|
mediaTypeFieldHover,
|
||||||
|
createdByFieldHover,
|
||||||
|
updatedByFieldHover,
|
||||||
exampleDocuments,
|
exampleDocuments,
|
||||||
} = useValues(DisplaySettingsLogic);
|
} = useValues(DisplaySettingsLogic);
|
||||||
|
|
||||||
|
@ -72,6 +86,60 @@ export const ExampleSearchResultGroup: React.FC = () => {
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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
|
||||||
|
{updatedByField && result[updatedByField] && (
|
||||||
|
<span
|
||||||
|
className={classNames('example-result-content__tag-content', {
|
||||||
|
'example-result-field-hover': updatedByFieldHover,
|
||||||
|
})}
|
||||||
|
data-test-subj="UpdatedByField"
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
by {result[updatedByField]}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{getAsLocalDateTimeString(result.last_updated as string) ||
|
||||||
|
result.last_updated}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -41,4 +41,26 @@ describe('ExampleStandoutResult', () => {
|
||||||
|
|
||||||
expect(wrapper.find('[data-test-subj="DefaultDescriptionLabel"]')).toHaveLength(1);
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { useValues } from 'kea';
|
||||||
import { isColorDark, hexToRgb } from '@elastic/eui';
|
import { isColorDark, hexToRgb } from '@elastic/eui';
|
||||||
|
|
||||||
import { DESCRIPTION_LABEL } from '../../../../constants';
|
import { DESCRIPTION_LABEL } from '../../../../constants';
|
||||||
|
import { getAsLocalDateTimeString } from '../../../../utils';
|
||||||
|
|
||||||
import { CustomSourceIcon } from './custom_source_icon';
|
import { CustomSourceIcon } from './custom_source_icon';
|
||||||
import { DisplaySettingsLogic } from './display_settings_logic';
|
import { DisplaySettingsLogic } from './display_settings_logic';
|
||||||
|
@ -22,10 +23,23 @@ import { TitleField } from './title_field';
|
||||||
export const ExampleStandoutResult: React.FC = () => {
|
export const ExampleStandoutResult: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
sourceName,
|
sourceName,
|
||||||
searchResultConfig: { titleField, subtitleField, descriptionField, color },
|
searchResultConfig: {
|
||||||
|
titleField,
|
||||||
|
subtitleField,
|
||||||
|
descriptionField,
|
||||||
|
typeField,
|
||||||
|
mediaTypeField,
|
||||||
|
createdByField,
|
||||||
|
updatedByField,
|
||||||
|
color,
|
||||||
|
},
|
||||||
titleFieldHover,
|
titleFieldHover,
|
||||||
subtitleFieldHover,
|
subtitleFieldHover,
|
||||||
descriptionFieldHover,
|
descriptionFieldHover,
|
||||||
|
typeFieldHover,
|
||||||
|
mediaTypeFieldHover,
|
||||||
|
createdByFieldHover,
|
||||||
|
updatedByFieldHover,
|
||||||
exampleDocuments,
|
exampleDocuments,
|
||||||
} = useValues(DisplaySettingsLogic);
|
} = useValues(DisplaySettingsLogic);
|
||||||
|
|
||||||
|
@ -66,6 +80,55 @@ export const ExampleStandoutResult: React.FC = () => {
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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
|
||||||
|
{updatedByField && result[updatedByField] && (
|
||||||
|
<span
|
||||||
|
className={classNames('example-result-content__tag-content', {
|
||||||
|
'example-result-field-hover': updatedByFieldHover,
|
||||||
|
})}
|
||||||
|
data-test-subj="UpdatedByField"
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
by {result[updatedByField]}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{getAsLocalDateTimeString(result.last_updated as string) || result.last_updated}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -41,6 +41,10 @@ describe('SearchResults', () => {
|
||||||
const setDescriptionField = jest.fn();
|
const setDescriptionField = jest.fn();
|
||||||
const setUrlField = jest.fn();
|
const setUrlField = jest.fn();
|
||||||
const setColorField = jest.fn();
|
const setColorField = jest.fn();
|
||||||
|
const setTypeField = jest.fn();
|
||||||
|
const setMediaTypeField = jest.fn();
|
||||||
|
const setCreatedByField = jest.fn();
|
||||||
|
const setUpdatedByField = jest.fn();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setMockActions({
|
setMockActions({
|
||||||
|
@ -52,6 +56,10 @@ describe('SearchResults', () => {
|
||||||
setDescriptionField,
|
setDescriptionField,
|
||||||
setUrlField,
|
setUrlField,
|
||||||
setColorField,
|
setColorField,
|
||||||
|
setTypeField,
|
||||||
|
setMediaTypeField,
|
||||||
|
setCreatedByField,
|
||||||
|
setUpdatedByField,
|
||||||
});
|
});
|
||||||
setMockValues({
|
setMockValues({
|
||||||
searchResultConfig,
|
searchResultConfig,
|
||||||
|
@ -103,6 +111,42 @@ describe('SearchResults', () => {
|
||||||
expect(setDescriptionField).toHaveBeenCalledWith(searchResultConfig.descriptionField);
|
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', () => {
|
it('handles blank fallbacks', () => {
|
||||||
setMockValues({
|
setMockValues({
|
||||||
searchResultConfig: { detailFields: [] },
|
searchResultConfig: { detailFields: [] },
|
||||||
|
@ -116,9 +160,25 @@ describe('SearchResults', () => {
|
||||||
wrapper
|
wrapper
|
||||||
.find('[data-test-subj="DescriptionFieldSelect"]')
|
.find('[data-test-subj="DescriptionFieldSelect"]')
|
||||||
.simulate('change', { target: { value: LEAVE_UNASSIGNED_FIELD } });
|
.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(wrapper.find('[data-test-subj="UrlFieldSelect"]').prop('value')).toEqual('');
|
||||||
expect(setSubtitleField).toHaveBeenCalledWith(null);
|
expect(setSubtitleField).toHaveBeenCalledWith(null);
|
||||||
expect(setDescriptionField).toHaveBeenCalledWith(null);
|
expect(setDescriptionField).toHaveBeenCalledWith(null);
|
||||||
|
expect(setTypeField).toHaveBeenCalledWith(null);
|
||||||
|
expect(setMediaTypeField).toHaveBeenCalledWith(null);
|
||||||
|
expect(setCreatedByField).toHaveBeenCalledWith(null);
|
||||||
|
expect(setUpdatedByField).toHaveBeenCalledWith(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -42,15 +42,33 @@ export const SearchResults: React.FC = () => {
|
||||||
toggleTitleFieldHover,
|
toggleTitleFieldHover,
|
||||||
toggleSubtitleFieldHover,
|
toggleSubtitleFieldHover,
|
||||||
toggleDescriptionFieldHover,
|
toggleDescriptionFieldHover,
|
||||||
|
toggleTypeFieldHover,
|
||||||
|
toggleMediaTypeFieldHover,
|
||||||
|
toggleCreatedByFieldHover,
|
||||||
|
toggleUpdatedByFieldHover,
|
||||||
setTitleField,
|
setTitleField,
|
||||||
setSubtitleField,
|
setSubtitleField,
|
||||||
setDescriptionField,
|
setDescriptionField,
|
||||||
|
setTypeField,
|
||||||
|
setMediaTypeField,
|
||||||
|
setCreatedByField,
|
||||||
|
setUpdatedByField,
|
||||||
setUrlField,
|
setUrlField,
|
||||||
setColorField,
|
setColorField,
|
||||||
} = useActions(DisplaySettingsLogic);
|
} = useActions(DisplaySettingsLogic);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
searchResultConfig: { titleField, descriptionField, subtitleField, urlField, color },
|
searchResultConfig: {
|
||||||
|
titleField,
|
||||||
|
descriptionField,
|
||||||
|
subtitleField,
|
||||||
|
typeField,
|
||||||
|
mediaTypeField,
|
||||||
|
createdByField,
|
||||||
|
updatedByField,
|
||||||
|
urlField,
|
||||||
|
color,
|
||||||
|
},
|
||||||
fieldOptions,
|
fieldOptions,
|
||||||
optionalFieldOptions,
|
optionalFieldOptions,
|
||||||
} = useValues(DisplaySettingsLogic);
|
} = useValues(DisplaySettingsLogic);
|
||||||
|
@ -136,6 +154,82 @@ export const SearchResults: React.FC = () => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</EuiFormRow>
|
</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>
|
</EuiForm>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem>
|
<EuiFlexItem>
|
||||||
|
|
|
@ -45,9 +45,13 @@ const displayFieldSchema = schema.object({
|
||||||
|
|
||||||
const displaySettingsSchema = schema.object({
|
const displaySettingsSchema = schema.object({
|
||||||
titleField: schema.maybe(schema.string()),
|
titleField: schema.maybe(schema.string()),
|
||||||
subtitleField: schema.maybe(schema.string()),
|
subtitleField: schema.nullable(schema.string()),
|
||||||
descriptionField: schema.maybe(schema.string()),
|
descriptionField: schema.nullable(schema.string()),
|
||||||
urlField: schema.maybe(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(),
|
color: schema.string(),
|
||||||
urlFieldIsLinkable: schema.boolean(),
|
urlFieldIsLinkable: schema.boolean(),
|
||||||
detailFields: schema.oneOf([schema.arrayOf(displayFieldSchema), displayFieldSchema]),
|
detailFields: schema.oneOf([schema.arrayOf(displayFieldSchema), displayFieldSchema]),
|
||||||
|
|
Loading…
Reference in a new issue