mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Workplace Search] Add a download diagnostics button when error is shown (#121017)
* Factor out DownloadDiagnosticsButton for reuse * Add logic for rendering button * Add button to layout * Fix test name * Better method naming
This commit is contained in:
parent
34711539fa
commit
5e34758d43
9 changed files with 180 additions and 51 deletions
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 { setMockValues } from '../../../../__mocks__/kea_logic';
|
||||
import { fullContentSources } from '../../../__mocks__/content_sources.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
|
||||
import { DownloadDiagnosticsButton } from './download_diagnostics_button';
|
||||
|
||||
describe('DownloadDiagnosticsButton', () => {
|
||||
const label = 'foo123';
|
||||
const contentSource = fullContentSources[0];
|
||||
const buttonLoading = false;
|
||||
const isOrganization = true;
|
||||
|
||||
const mockValues = {
|
||||
contentSource,
|
||||
buttonLoading,
|
||||
isOrganization,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setMockValues(mockValues);
|
||||
});
|
||||
|
||||
it('renders the Download diagnostics button with org href', () => {
|
||||
const wrapper = shallow(<DownloadDiagnosticsButton label={label} />);
|
||||
|
||||
expect(wrapper.find(EuiButton).prop('href')).toEqual(
|
||||
'/internal/workplace_search/org/sources/123/download_diagnostics'
|
||||
);
|
||||
});
|
||||
|
||||
it('renders the Download diagnostics button with account href', () => {
|
||||
setMockValues({ ...mockValues, isOrganization: false });
|
||||
const wrapper = shallow(<DownloadDiagnosticsButton label={label} />);
|
||||
|
||||
expect(wrapper.find(EuiButton).prop('href')).toEqual(
|
||||
'/internal/workplace_search/account/sources/123/download_diagnostics'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
|
||||
import { HttpLogic } from '../../../../shared/http';
|
||||
import { AppLogic } from '../../../app_logic';
|
||||
|
||||
import { SourceLogic } from '../source_logic';
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
}
|
||||
|
||||
export const DownloadDiagnosticsButton: React.FC<Props> = ({ label }) => {
|
||||
const { http } = useValues(HttpLogic);
|
||||
const { isOrganization } = useValues(AppLogic);
|
||||
const {
|
||||
contentSource: { id, serviceType },
|
||||
buttonLoading,
|
||||
} = useValues(SourceLogic);
|
||||
|
||||
const diagnosticsPath = isOrganization
|
||||
? http.basePath.prepend(`/internal/workplace_search/org/sources/${id}/download_diagnostics`)
|
||||
: http.basePath.prepend(
|
||||
`/internal/workplace_search/account/sources/${id}/download_diagnostics`
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiButton
|
||||
target="_blank"
|
||||
href={diagnosticsPath}
|
||||
isLoading={buttonLoading}
|
||||
data-test-subj="DownloadDiagnosticsButton"
|
||||
download={`${id}_${serviceType}_${Date.now()}_diagnostics.json`}
|
||||
>
|
||||
{label}
|
||||
</EuiButton>
|
||||
);
|
||||
};
|
|
@ -18,6 +18,7 @@ import { EuiCallOut } from '@elastic/eui';
|
|||
|
||||
import { WorkplaceSearchPageTemplate, PersonalDashboardLayout } from '../../../components/layout';
|
||||
|
||||
import { DownloadDiagnosticsButton } from './download_diagnostics_button';
|
||||
import { SourceInfoCard } from './source_info_card';
|
||||
import { SourceLayout } from './source_layout';
|
||||
|
||||
|
@ -26,6 +27,7 @@ describe('SourceLayout', () => {
|
|||
const mockValues = {
|
||||
contentSource,
|
||||
dataLoading: false,
|
||||
diagnosticDownloadButtonVisible: false,
|
||||
isOrganization: true,
|
||||
};
|
||||
|
||||
|
@ -87,4 +89,14 @@ describe('SourceLayout', () => {
|
|||
|
||||
expect(wrapper.find(EuiCallOut)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders DownloadDiagnosticsButton', () => {
|
||||
setMockValues({
|
||||
...mockValues,
|
||||
diagnosticDownloadButtonVisible: true,
|
||||
});
|
||||
const wrapper = shallow(<SourceLayout />);
|
||||
|
||||
expect(wrapper.find(DownloadDiagnosticsButton)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,12 +19,14 @@ import { WorkplaceSearchPageTemplate, PersonalDashboardLayout } from '../../../c
|
|||
import { NAV } from '../../../constants';
|
||||
|
||||
import {
|
||||
DOWNLOAD_DIAGNOSTIC_BUTTON,
|
||||
SOURCE_DISABLED_CALLOUT_TITLE,
|
||||
SOURCE_DISABLED_CALLOUT_DESCRIPTION,
|
||||
SOURCE_DISABLED_CALLOUT_BUTTON,
|
||||
} from '../constants';
|
||||
import { SourceLogic } from '../source_logic';
|
||||
|
||||
import { DownloadDiagnosticsButton } from './download_diagnostics_button';
|
||||
import { SourceInfoCard } from './source_info_card';
|
||||
|
||||
export const SourceLayout: React.FC<PageTemplateProps> = ({
|
||||
|
@ -32,7 +34,7 @@ export const SourceLayout: React.FC<PageTemplateProps> = ({
|
|||
pageChrome = [],
|
||||
...props
|
||||
}) => {
|
||||
const { contentSource, dataLoading } = useValues(SourceLogic);
|
||||
const { contentSource, dataLoading, diagnosticDownloadButtonVisible } = useValues(SourceLogic);
|
||||
const { isOrganization } = useValues(AppLogic);
|
||||
|
||||
const { name, createdAt, serviceType, isFederatedSource, supportedByLicense } = contentSource;
|
||||
|
@ -61,6 +63,13 @@ export const SourceLayout: React.FC<PageTemplateProps> = ({
|
|||
</>
|
||||
);
|
||||
|
||||
const downloadDiagnosticButton = (
|
||||
<>
|
||||
<DownloadDiagnosticsButton label={DOWNLOAD_DIAGNOSTIC_BUTTON} />
|
||||
<EuiSpacer size="xl" />
|
||||
</>
|
||||
);
|
||||
|
||||
const Layout = isOrganization ? WorkplaceSearchPageTemplate : PersonalDashboardLayout;
|
||||
|
||||
return (
|
||||
|
@ -69,6 +78,7 @@ export const SourceLayout: React.FC<PageTemplateProps> = ({
|
|||
{...props}
|
||||
pageChrome={[NAV.SOURCES, name || '...', ...pageChrome]}
|
||||
>
|
||||
{diagnosticDownloadButtonVisible && downloadDiagnosticButton}
|
||||
{!supportedByLicense && callout}
|
||||
{pageHeader}
|
||||
{children}
|
||||
|
|
|
@ -18,6 +18,7 @@ import { EuiConfirmModal } from '@elastic/eui';
|
|||
|
||||
import { SourceConfigFields } from '../../../components/shared/source_config_fields';
|
||||
|
||||
import { DownloadDiagnosticsButton } from './download_diagnostics_button';
|
||||
import { SourceSettings } from './source_settings';
|
||||
|
||||
describe('SourceSettings', () => {
|
||||
|
@ -48,6 +49,7 @@ describe('SourceSettings', () => {
|
|||
const wrapper = shallow(<SourceSettings />);
|
||||
|
||||
expect(wrapper.find('form')).toHaveLength(1);
|
||||
expect(wrapper.find(DownloadDiagnosticsButton)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('handles form submission', () => {
|
||||
|
@ -104,36 +106,4 @@ describe('SourceSettings', () => {
|
|||
sourceConfigData.configuredFields.publicKey
|
||||
);
|
||||
});
|
||||
|
||||
describe('DownloadDiagnosticsButton', () => {
|
||||
it('renders for org with correct href', () => {
|
||||
const wrapper = shallow(<SourceSettings />);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="DownloadDiagnosticsButton"]').prop('href')).toEqual(
|
||||
'/internal/workplace_search/org/sources/123/download_diagnostics'
|
||||
);
|
||||
});
|
||||
|
||||
it('renders for account with correct href', () => {
|
||||
setMockValues({
|
||||
...mockValues,
|
||||
isOrganization: false,
|
||||
});
|
||||
const wrapper = shallow(<SourceSettings />);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="DownloadDiagnosticsButton"]').prop('href')).toEqual(
|
||||
'/internal/workplace_search/account/sources/123/download_diagnostics'
|
||||
);
|
||||
});
|
||||
|
||||
it('renders with the correct download file name', () => {
|
||||
jest.spyOn(global.Date, 'now').mockImplementationOnce(() => new Date('1970-01-01').valueOf());
|
||||
|
||||
const wrapper = shallow(<SourceSettings />);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="DownloadDiagnosticsButton"]').prop('download')).toEqual(
|
||||
'123_custom_0_diagnostics.json'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,7 +23,6 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { HttpLogic } from '../../../../shared/http';
|
||||
import { EuiButtonEmptyTo } from '../../../../shared/react_router_helpers';
|
||||
import { AppLogic } from '../../../app_logic';
|
||||
import { ContentSection } from '../../../components/shared/content_section';
|
||||
|
@ -61,11 +60,11 @@ import {
|
|||
import { staticSourceData } from '../source_data';
|
||||
import { SourceLogic } from '../source_logic';
|
||||
|
||||
import { DownloadDiagnosticsButton } from './download_diagnostics_button';
|
||||
|
||||
import { SourceLayout } from './source_layout';
|
||||
|
||||
export const SourceSettings: React.FC = () => {
|
||||
const { http } = useValues(HttpLogic);
|
||||
|
||||
const {
|
||||
updateContentSource,
|
||||
removeContentSource,
|
||||
|
@ -110,12 +109,6 @@ export const SourceSettings: React.FC = () => {
|
|||
|
||||
const { clientId, clientSecret, publicKey, consumerKey, baseUrl } = configuredFields || {};
|
||||
|
||||
const diagnosticsPath = isOrganization
|
||||
? http.basePath.prepend(`/internal/workplace_search/org/sources/${id}/download_diagnostics`)
|
||||
: http.basePath.prepend(
|
||||
`/internal/workplace_search/account/sources/${id}/download_diagnostics`
|
||||
);
|
||||
|
||||
const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => setValue(e.target.value);
|
||||
|
||||
const submitNameChange = (e: FormEvent) => {
|
||||
|
@ -241,15 +234,7 @@ export const SourceSettings: React.FC = () => {
|
|||
</ContentSection>
|
||||
)}
|
||||
<ContentSection title={SYNC_DIAGNOSTICS_TITLE} description={SYNC_DIAGNOSTICS_DESCRIPTION}>
|
||||
<EuiButton
|
||||
target="_blank"
|
||||
href={diagnosticsPath}
|
||||
isLoading={buttonLoading}
|
||||
data-test-subj="DownloadDiagnosticsButton"
|
||||
download={`${id}_${serviceType}_${Date.now()}_diagnostics.json`}
|
||||
>
|
||||
{SYNC_DIAGNOSTICS_BUTTON}
|
||||
</EuiButton>
|
||||
<DownloadDiagnosticsButton label={SYNC_DIAGNOSTICS_BUTTON} />
|
||||
</ContentSection>
|
||||
<ContentSection title={SOURCE_REMOVE_TITLE} description={SOURCE_REMOVE_DESCRIPTION}>
|
||||
<EuiButton
|
||||
|
|
|
@ -370,6 +370,13 @@ export const SYNC_DIAGNOSTICS_BUTTON = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const DOWNLOAD_DIAGNOSTIC_BUTTON = i18n.translate(
|
||||
'xpack.enterpriseSearch.workplaceSearch.sources.downloadDiagnosticButton',
|
||||
{
|
||||
defaultMessage: 'Download diagnostic bundle',
|
||||
}
|
||||
);
|
||||
|
||||
export const SOURCE_NAME_LABEL = i18n.translate(
|
||||
'xpack.enterpriseSearch.workplaceSearch.sources.sourceName.label',
|
||||
{
|
||||
|
|
|
@ -40,6 +40,7 @@ describe('SourceLogic', () => {
|
|||
dataLoading: true,
|
||||
sectionLoading: true,
|
||||
buttonLoading: false,
|
||||
diagnosticDownloadButtonVisible: false,
|
||||
contentMeta: DEFAULT_META,
|
||||
contentFilterValue: '',
|
||||
isConfigurationUpdateButtonLoading: false,
|
||||
|
@ -125,6 +126,12 @@ describe('SourceLogic', () => {
|
|||
|
||||
expect(SourceLogic.values.buttonLoading).toEqual(false);
|
||||
});
|
||||
|
||||
it('showDiagnosticDownloadButton', () => {
|
||||
SourceLogic.actions.showDiagnosticDownloadButton();
|
||||
|
||||
expect(SourceLogic.values.diagnosticDownloadButtonVisible).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listeners', () => {
|
||||
|
@ -183,6 +190,27 @@ describe('SourceLogic', () => {
|
|||
expect(flashAPIErrors).toHaveBeenCalledWith('error');
|
||||
});
|
||||
|
||||
it('handles error message with diagnostic bundle error message', async () => {
|
||||
const showDiagnosticDownloadButtonSpy = jest.spyOn(
|
||||
SourceLogic.actions,
|
||||
'showDiagnosticDownloadButton'
|
||||
);
|
||||
|
||||
// For contenst source errors, the API returns the source errors in an error property in the success
|
||||
// response. We don't reject here because we still render the content source with the error.
|
||||
const promise = Promise.resolve({
|
||||
...contentSource,
|
||||
errors: [
|
||||
'The database is on fire. [Check diagnostic bundle for details - Message id: 123]',
|
||||
],
|
||||
});
|
||||
http.get.mockReturnValue(promise);
|
||||
SourceLogic.actions.initializeSource(contentSource.id);
|
||||
await promise;
|
||||
|
||||
expect(showDiagnosticDownloadButtonSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('404s', () => {
|
||||
const mock404 = Promise.reject({ response: { status: 404 } });
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ export interface SourceActions {
|
|||
setButtonNotLoading(): void;
|
||||
setStagedPrivateKey(stagedPrivateKey: string | null): string | null;
|
||||
setConfigurationUpdateButtonNotLoading(): void;
|
||||
showDiagnosticDownloadButton(): void;
|
||||
}
|
||||
|
||||
interface SourceValues {
|
||||
|
@ -59,6 +60,7 @@ interface SourceValues {
|
|||
dataLoading: boolean;
|
||||
sectionLoading: boolean;
|
||||
buttonLoading: boolean;
|
||||
diagnosticDownloadButtonVisible: boolean;
|
||||
contentItems: SourceContentItem[];
|
||||
contentMeta: Meta;
|
||||
contentFilterValue: string;
|
||||
|
@ -108,6 +110,7 @@ export const SourceLogic = kea<MakeLogicType<SourceValues, SourceActions>>({
|
|||
setButtonNotLoading: () => false,
|
||||
setStagedPrivateKey: (stagedPrivateKey: string) => stagedPrivateKey,
|
||||
setConfigurationUpdateButtonNotLoading: () => false,
|
||||
showDiagnosticDownloadButton: true,
|
||||
},
|
||||
reducers: {
|
||||
contentSource: [
|
||||
|
@ -147,6 +150,13 @@ export const SourceLogic = kea<MakeLogicType<SourceValues, SourceActions>>({
|
|||
setSearchResults: () => false,
|
||||
},
|
||||
],
|
||||
diagnosticDownloadButtonVisible: [
|
||||
false,
|
||||
{
|
||||
showDiagnosticDownloadButton: () => true,
|
||||
initializeSource: () => false,
|
||||
},
|
||||
],
|
||||
contentItems: [
|
||||
[],
|
||||
{
|
||||
|
@ -200,6 +210,9 @@ export const SourceLogic = kea<MakeLogicType<SourceValues, SourceActions>>({
|
|||
}
|
||||
if (response.errors) {
|
||||
setErrorMessage(response.errors);
|
||||
if (errorsHaveDiagnosticBundleString(response.errors as unknown as string[])) {
|
||||
actions.showDiagnosticDownloadButton();
|
||||
}
|
||||
} else {
|
||||
clearFlashMessages();
|
||||
}
|
||||
|
@ -343,3 +356,8 @@ const setPage = (state: Meta, page: number) => ({
|
|||
current: page,
|
||||
},
|
||||
});
|
||||
|
||||
const errorsHaveDiagnosticBundleString = (errors: string[]) => {
|
||||
const ERROR_SUBSTRING = 'Check diagnostic bundle for details';
|
||||
return errors.find((e) => e.includes(ERROR_SUBSTRING));
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue