Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
.appSearchResult {
display: flex;
display: grid;
grid-template-columns: 1fr auto;
grid-template-rows: 1fr auto;
grid-template-areas:
'content actions'
'toggle actions';
overflow: hidden; // Prevents child background-colors from clipping outside of panel border-radius

&__content {
grid-area: content;
width: 100%;
padding: $euiSize;
overflow: hidden;
color: $euiTextColor;
}

&__hiddenFieldsIndicator {
&__hiddenFieldsToggle {
grid-area: toggle;
display: flex;
justify-content: center;
padding: $euiSizeS;
border-top: $euiBorderThin;
font-size: $euiFontSizeXS;
color: $euiColorDarkShade;
margin-top: $euiSizeS;
color: $euiColorPrimary;

&:hover,
&:focus {
background-color: $euiPageBackgroundColor;
}

.euiIcon {
margin-left: $euiSizeXS;
}
}

&__actionButtons {
grid-area: actions;
display: flex;
flex-wrap: no-wrap;
}

&__actionButton {
Expand All @@ -22,10 +48,27 @@
border-left: $euiBorderThin;

&:hover,
&:focus,
&:active {
&:focus {
background-color: $euiPageBackgroundColor;
cursor: pointer;
}
}
}

/**
* CSS for hover specific logic
* It's mildly horrific, so I pulled it out to its own section here
*/

.appSearchResult--link {
&:hover,
&:focus {
@include euiSlightShadowHover;
}
}
.appSearchResult__content--link:hover {
cursor: pointer;

& ~ .appSearchResult__actionButtons .appSearchResult__actionButton--link {
background-color: $euiPageBackgroundColor;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ describe('Result', () => {
it('renders', () => {
const wrapper = shallow(<Result {...props} />);
expect(wrapper.find(EuiPanel).exists()).toBe(true);
expect(wrapper.find(EuiPanel).prop('title')).toEqual('Document 1');
});

it('should render a ResultField for each field except id and _meta', () => {
Expand Down Expand Up @@ -76,16 +77,20 @@ describe('Result', () => {
describe('document detail link', () => {
it('will render a link if shouldLinkToDetailPage is true', () => {
const wrapper = shallow(<Result {...props} shouldLinkToDetailPage={true} />);
expect(wrapper.find(ReactRouterHelper).prop('to')).toEqual('/engines/my-engine/documents/1');
expect(wrapper.find('article.appSearchResult__content').exists()).toBe(false);
expect(wrapper.find('a.appSearchResult__content').exists()).toBe(true);
wrapper.find(ReactRouterHelper).forEach((link) => {
expect(link.prop('to')).toEqual('/engines/my-engine/documents/1');
});
expect(wrapper.hasClass('appSearchResult--link')).toBe(true);
expect(wrapper.find('.appSearchResult__content--link').exists()).toBe(true);
expect(wrapper.find('.appSearchResult__actionButton--link').exists()).toBe(true);
});

it('will not render a link if shouldLinkToDetailPage is not set', () => {
const wrapper = shallow(<Result {...props} />);
expect(wrapper.find(ReactRouterHelper).exists()).toBe(false);
expect(wrapper.find('article.appSearchResult__content').exists()).toBe(true);
expect(wrapper.find('a.appSearchResult__content').exists()).toBe(false);
expect(wrapper.hasClass('appSearchResult--link')).toBe(false);
expect(wrapper.find('.appSearchResult__content--link').exists()).toBe(false);
expect(wrapper.find('.appSearchResult__actionButton--link').exists()).toBe(false);
});
});

Expand Down Expand Up @@ -140,18 +145,16 @@ describe('Result', () => {
wrapper = shallow(<Result {...propsWithMoreFields} />);
});

it('renders a collapse button', () => {
expect(wrapper.find('[data-test-subj="CollapseResult"]').exists()).toBe(false);
it('renders a hidden fields toggle button', () => {
expect(wrapper.find('.appSearchResult__hiddenFieldsToggle').exists()).toBe(true);
});

it('does not render an expand button', () => {
expect(wrapper.find('[data-test-subj="ExpandResult"]').exists()).toBe(true);
it('renders a collapse icon', () => {
expect(wrapper.find('[data-test-subj="CollapseResult"]').exists()).toBe(false);
});

it('renders a hidden fields indicator', () => {
expect(wrapper.find('.appSearchResult__hiddenFieldsIndicator').text()).toEqual(
'1 more fields'
);
it('does not render an expand icon', () => {
expect(wrapper.find('[data-test-subj="ExpandResult"]').exists()).toBe(true);
});

it('shows no more than 5 fields', () => {
Expand All @@ -164,20 +167,22 @@ describe('Result', () => {

beforeAll(() => {
wrapper = shallow(<Result {...propsWithMoreFields} />);
expect(wrapper.find('.appSearchResult__actionButton').exists()).toBe(true);
wrapper.find('.appSearchResult__actionButton').simulate('click');
expect(wrapper.find('.appSearchResult__hiddenFieldsToggle').exists()).toBe(true);
wrapper.find('.appSearchResult__hiddenFieldsToggle').simulate('click');
});

it('renders a collapse button', () => {
expect(wrapper.find('[data-test-subj="CollapseResult"]').exists()).toBe(true);
it('renders correct toggle text', () => {
expect(wrapper.find('.appSearchResult__hiddenFieldsToggle').text()).toEqual(
'Hide additional fields<EuiIcon />'
);
});

it('does not render an expand button', () => {
expect(wrapper.find('[data-test-subj="ExpandResult"]').exists()).toBe(false);
it('renders a collapse icon', () => {
expect(wrapper.find('[data-test-subj="CollapseResult"]').exists()).toBe(true);
});

it('does not render a hidden fields indicator', () => {
expect(wrapper.find('.appSearchResult__hiddenFieldsIndicator').exists()).toBe(false);
it('does not render an expand icon', () => {
expect(wrapper.find('[data-test-subj="ExpandResult"]').exists()).toBe(false);
});

it('shows all fields', () => {
Expand All @@ -190,23 +195,23 @@ describe('Result', () => {

beforeAll(() => {
wrapper = shallow(<Result {...propsWithMoreFields} />);
expect(wrapper.find('.appSearchResult__actionButton').exists()).toBe(true);
wrapper.find('.appSearchResult__actionButton').simulate('click');
wrapper.find('.appSearchResult__actionButton').simulate('click');
expect(wrapper.find('.appSearchResult__hiddenFieldsToggle').exists()).toBe(true);
wrapper.find('.appSearchResult__hiddenFieldsToggle').simulate('click');
wrapper.find('.appSearchResult__hiddenFieldsToggle').simulate('click');
});

it('renders a collapse button', () => {
expect(wrapper.find('[data-test-subj="CollapseResult"]').exists()).toBe(false);
it('renders correct toggle text', () => {
expect(wrapper.find('.appSearchResult__hiddenFieldsToggle').text()).toEqual(
'Show 1 additional field<EuiIcon />'
);
});

it('does not render an expand button', () => {
expect(wrapper.find('[data-test-subj="ExpandResult"]').exists()).toBe(true);
it('renders a collapse icon', () => {
expect(wrapper.find('[data-test-subj="CollapseResult"]').exists()).toBe(false);
});

it('renders a hidden fields indicator', () => {
expect(wrapper.find('.appSearchResult__hiddenFieldsIndicator').text()).toEqual(
'1 more fields'
);
it('does not render an expand icon', () => {
expect(wrapper.find('[data-test-subj="ExpandResult"]').exists()).toBe(true);
});

it('shows no more than 5 fields', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import React, { useState, useMemo } from 'react';
import classNames from 'classnames';

import './result.scss';

Expand Down Expand Up @@ -49,23 +50,31 @@ export const Result: React.FC<Props> = ({
if (schemaForTypeHighlights) return schemaForTypeHighlights[fieldName];
};

const documentLink = getDocumentDetailRoute(resultMeta.engine, resultMeta.id);
const conditionallyLinkedArticle = (children: React.ReactNode) => {
return shouldLinkToDetailPage ? (
<ReactRouterHelper to={getDocumentDetailRoute(resultMeta.engine, resultMeta.id)}>
<a className="appSearchResult__content">{children}</a>
<ReactRouterHelper to={documentLink}>
<article className="appSearchResult__content appSearchResult__content--link">
{children}
</article>
</ReactRouterHelper>
) : (
<article className="appSearchResult__content">{children}</article>
);
};

const classes = classNames('appSearchResult', {
'appSearchResult--link': shouldLinkToDetailPage,
});

return (
<EuiPanel
paddingSize="none"
className="appSearchResult"
className={classes}
data-test-subj="AppSearchResult"
title={i18n.translate('xpack.enterpriseSearch.appSearch.result.title', {
defaultMessage: 'View document details',
defaultMessage: 'Document {id}',
values: { id: result[ID].raw },
})}
>
{conditionallyLinkedArticle(
Expand All @@ -75,53 +84,57 @@ export const Result: React.FC<Props> = ({
showScore={!!showScore}
isMetaEngine={isMetaEngine}
/>
<div className="appSearchResult__body">
{resultFields
.slice(0, isOpen ? resultFields.length : RESULT_CUTOFF)
.map(([field, value]: [string, FieldValue]) => (
<ResultField
key={field}
field={field}
raw={value.raw}
snippet={value.snippet}
type={typeForField(field)}
/>
))}
</div>
{numResults > RESULT_CUTOFF && !isOpen && (
<footer className="appSearchResult__hiddenFieldsIndicator">
{i18n.translate('xpack.enterpriseSearch.appSearch.result.numberOfAdditionalFields', {
defaultMessage: '{numberOfAdditionalFields} more fields',
values: {
numberOfAdditionalFields: numResults - RESULT_CUTOFF,
},
})}
</footer>
)}
{resultFields
.slice(0, isOpen ? resultFields.length : RESULT_CUTOFF)
.map(([field, value]: [string, FieldValue]) => (
<ResultField
key={field}
field={field}
raw={value.raw}
snippet={value.snippet}
type={typeForField(field)}
/>
))}
</>
)}
{numResults > RESULT_CUTOFF && (
<button
type="button"
className="appSearchResult__actionButton"
className="appSearchResult__hiddenFieldsToggle"
onClick={() => setIsOpen(!isOpen)}
aria-label={
isOpen
? i18n.translate('xpack.enterpriseSearch.appSearch.result.hideAdditionalFields', {
defaultMessage: 'Hide additional fields',
})
: i18n.translate('xpack.enterpriseSearch.appSearch.result.showAdditionalFields', {
defaultMessage: 'Show additional fields',
})
}
>
{isOpen ? (
<EuiIcon data-test-subj="CollapseResult" type="arrowUp" />
) : (
<EuiIcon data-test-subj="ExpandResult" type="arrowDown" />
)}
{isOpen
? i18n.translate('xpack.enterpriseSearch.appSearch.result.hideAdditionalFields', {
defaultMessage: 'Hide additional fields',
})
: i18n.translate('xpack.enterpriseSearch.appSearch.result.showAdditionalFields', {
defaultMessage:
'Show {numberOfAdditionalFields, number} additional {numberOfAdditionalFields, plural, one {field} other {fields}}',
values: {
numberOfAdditionalFields: numResults - RESULT_CUTOFF,
},
})}
<EuiIcon
type={isOpen ? 'arrowUp' : 'arrowDown'}
data-test-subj={isOpen ? 'CollapseResult' : 'ExpandResult'}
/>
</button>
)}
<div className="appSearchResult__actionButtons">
{shouldLinkToDetailPage && (
<ReactRouterHelper to={documentLink}>
<a
className="appSearchResult__actionButton appSearchResult__actionButton--link"
aria-label={i18n.translate(
'xpack.enterpriseSearch.appSearch.result.documentDetailLink',
{ defaultMessage: 'Visit document details' }
)}
>
<EuiIcon type="popout" />
</a>
</ReactRouterHelper>
)}
</div>
</EuiPanel>
);
};