[Workplace Search] Port 4 PRs from ent-search to kibana (#103547)

* Poprt #3567 to Kibana

https://github.com/elastic/ent-search/pull/3567

* Poer #3582 to Kibana

https://github.com/elastic/ent-search/pull/3582

Also adds missing i18n

* Port #3634 to Kibana

https://github.com/elastic/ent-search/pull/3634

* Port #3758 to Kibana

* Rename var
This commit is contained in:
Scotty Bollinger 2021-06-28 18:20:12 -05:00 committed by GitHub
parent 6feea1a506
commit 6085f90e2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 124 additions and 33 deletions

View file

@ -24,6 +24,7 @@ export const contentSources = [
errorReason: null,
allowsReauth: true,
boost: 1,
activities: [],
},
{
id: '124',
@ -38,6 +39,7 @@ export const contentSources = [
errorReason: null,
allowsReauth: true,
boost: 0.5,
activities: [],
},
];

View file

@ -0,0 +1,44 @@
/*
* 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 { i18n } from '@kbn/i18n';
export const SOURCE_ROW_REAUTHENTICATE_STATUS_LINK_LABEL = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.sourceRow.reauthenticateStatusLinkLabel',
{
defaultMessage: 'Re-authenticate',
}
);
export const SOURCE_ROW_REMOTE_LABEL = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.sourceRow.remoteLabel',
{
defaultMessage: 'Remote',
}
);
export const SOURCE_ROW_REMOTE_TOOLTIP = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.sourceRow.remoteTooltip',
{
defaultMessage:
"Remote sources rely on the source's search service directly, and no content is indexed with Workplace Search. Speed and integrity of results are functions of the third-party service's health and performance.",
}
);
export const SOURCE_ROW_SEARCHABLE_TOGGLE_LABEL = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.sourceRow.searchableToggleLabel',
{
defaultMessage: 'Source searchable toggle',
}
);
export const SOURCE_ROW_DETAILS_LABEL = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.sourceRow.detailsLabel',
{
defaultMessage: 'Details',
}
);

View file

@ -35,15 +35,23 @@ describe('SourceRow', () => {
expect(onToggle).toHaveBeenCalled();
});
it('renders "Fix" link', () => {
it('renders "Re-authenticate" link', () => {
const source = {
...contentSources[0],
status: 'error',
errorReason: 'OAuth access token could not be refreshed',
activities: [
{
status: 'error',
details: [],
event: '',
time: '',
},
],
};
const wrapper = shallow(<SourceRow isOrganization source={source} />);
expect(wrapper.contains('Fix')).toBeTruthy();
expect(wrapper.contains('Re-authenticate')).toBeTruthy();
});
it('renders loading icon when indexing', () => {

View file

@ -34,6 +34,14 @@ import { SourceIcon } from '../source_icon';
import './source_row.scss';
import {
SOURCE_ROW_REAUTHENTICATE_STATUS_LINK_LABEL,
SOURCE_ROW_REMOTE_LABEL,
SOURCE_ROW_REMOTE_TOOLTIP,
SOURCE_ROW_SEARCHABLE_TOGGLE_LABEL,
SOURCE_ROW_DETAILS_LABEL,
} from './constants';
// i18n is not needed here because this is only used to check against the server error, which
// is not translated by the Kibana team at this time.
const CREDENTIALS_REFRESH_NEEDED_PREFIX = 'OAuth access token could not be refreshed';
@ -61,6 +69,7 @@ export const SourceRow: React.FC<SourceRowProps> = ({
isFederatedSource,
errorReason,
allowsReauth,
activities,
},
onSearchableToggle,
isOrganization,
@ -68,32 +77,29 @@ export const SourceRow: React.FC<SourceRowProps> = ({
}) => {
const isIndexing = status === statuses.INDEXING;
const hasError = status === statuses.ERROR || status === statuses.DISCONNECTED;
const showFix =
isOrganization &&
const showReauthenticate =
hasError &&
allowsReauth &&
errorReason?.startsWith(CREDENTIALS_REFRESH_NEEDED_PREFIX);
errorReason?.startsWith(CREDENTIALS_REFRESH_NEEDED_PREFIX) &&
activities[0]?.status?.toLowerCase() === statuses.ERROR;
const rowClass = classNames({ 'source-row--error': hasError });
const fixLink = (
const reauthenticateLink = (
<EuiLinkTo
to={getSourcesPath(
`${ADD_SOURCE_PATH}/${serviceType}/reauthenticate?sourceId=${id}`,
isOrganization
)}
>
Fix
{SOURCE_ROW_REAUTHENTICATE_STATUS_LINK_LABEL}
</EuiLinkTo>
);
const remoteTooltip = (
<>
<span>Remote</span>
<EuiToolTip
position="top"
content="Remote sources rely on the source's search service directly, and no content is indexed with Workplace Search. Speed and integrity of results are functions of the third-party service's health and performance."
>
<span>{SOURCE_ROW_REMOTE_LABEL}</span>
<EuiToolTip position="top" content={SOURCE_ROW_REMOTE_TOOLTIP}>
<EuiIcon type="questionInCircle" />
</EuiToolTip>
</>
@ -143,7 +149,7 @@ export const SourceRow: React.FC<SourceRowProps> = ({
onChange={(e: EuiSwitchEvent) => onSearchableToggle(id, e.target.checked)}
disabled={!supportedByLicense}
compressed
label="Source Searchable Toggle"
label={SOURCE_ROW_SEARCHABLE_TOGGLE_LABEL}
showLabel={false}
data-test-subj="SourceSearchableToggle"
/>
@ -151,7 +157,7 @@ export const SourceRow: React.FC<SourceRowProps> = ({
)}
<EuiTableRowCell align="right">
<EuiFlexGroup justifyContent="flexEnd" alignItems="center" gutterSize="s">
{showFix && <EuiFlexItem grow={false}>{fixLink}</EuiFlexItem>}
{showReauthenticate && <EuiFlexItem grow={false}>{reauthenticateLink}</EuiFlexItem>}
<EuiFlexItem grow={false}>
{showDetails && (
<EuiLinkTo
@ -159,7 +165,7 @@ export const SourceRow: React.FC<SourceRowProps> = ({
data-test-subj="SourceDetailsLink"
to={getContentSourcePath(SOURCE_DETAILS_PATH, id, !!isOrganization)}
>
Details
{SOURCE_ROW_DETAILS_LABEL}
</EuiLinkTo>
)}
</EuiFlexItem>

View file

@ -109,6 +109,7 @@ export interface ContentSourceDetails extends ContentSource {
errorReason: string | null;
allowsReauth: boolean;
boost: number;
activities: SourceActivity[];
}
interface DescriptionList {

View file

@ -14,6 +14,8 @@ import React from 'react';
import { shallow } from 'enzyme';
import { EuiText } from '@elastic/eui';
import { ExampleResultDetailCard } from './example_result_detail_card';
describe('ExampleResultDetailCard', () => {
@ -36,4 +38,18 @@ describe('ExampleResultDetailCard', () => {
expect(wrapper.find('[data-test-subj="DefaultUrlLabel"]')).toHaveLength(1);
});
it('shows formatted value when date can be parsed', () => {
const date = '2021-06-28';
setMockValues({
...exampleResult,
searchResultConfig: { detailFields: [{ fieldName: 'date', label: 'Date' }] },
exampleDocuments: [{ date }],
});
const wrapper = shallow(<ExampleResultDetailCard />);
expect(wrapper.find(EuiText).children().text()).toContain(
new Date(Date.parse(date)).toLocaleString()
);
});
});

View file

@ -18,6 +18,11 @@ 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,
@ -60,20 +65,25 @@ export const ExampleResultDetailCard: React.FC = () => {
</div>
<div className="example-result-detail-card__content">
{detailFields.length > 0 ? (
detailFields.map(({ fieldName, label }, index) => (
<div
data-test-subj="DetailField"
className="example-result-detail-card__field"
key={index}
>
<EuiTitle size="xs">
<h4>{label}</h4>
</EuiTitle>
<EuiText size="s" color="subdued">
<div className="eui-textBreakWord">{result[fieldName]}</div>
</EuiText>
</div>
))
detailFields.map(({ fieldName, label }, index) => {
const value = result[fieldName] as string;
const dateValue = getAsLocalDateTimeString(value);
return (
<div
className="example-result-detail-card__field"
key={index}
data-test-subj="DetailField"
>
<EuiTitle size="xs">
<h4>{label}</h4>
</EuiTitle>
<EuiText size="s" color="subdued">
<div className="eui-textBreakWord">{dateValue || value}</div>
</EuiText>
</div>
);
})
) : (
<EuiSpacer size="m" />
)}

View file

@ -198,12 +198,16 @@ export const Overview: React.FC = () => {
{!custom && (
<EuiTableRowCell>
<EuiText size="xs">
{status} {activityDetails && <StatusItem details={activityDetails} />}
<small>
{status} {activityDetails && <StatusItem details={activityDetails} />}
</small>
</EuiText>
</EuiTableRowCell>
)}
<EuiTableRowCell align="right">
<EuiText size="xs">{time}</EuiText>
<EuiText size="xs">
<small>{time}</small>
</EuiText>
</EuiTableRowCell>
</EuiTableRow>
))}
@ -453,7 +457,7 @@ export const Overview: React.FC = () => {
<ViewContentHeader title="Source overview" />
<EuiFlexGroup gutterSize="xl" alignItems="flexStart">
<EuiFlexItem>
<EuiFlexItem grow={8}>
<EuiFlexGroup gutterSize="xl" direction="column">
<EuiFlexItem>
<DocumentSummary data-test-subj="DocumentSummary" />
@ -465,7 +469,7 @@ export const Overview: React.FC = () => {
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexItem grow={7}>
<EuiFlexGroup gutterSize="m" direction="column">
<EuiFlexItem>{groups.length > 0 && groupsSummary}</EuiFlexItem>
{details.length > 0 && <EuiFlexItem>{detailsSummary}</EuiFlexItem>}