Skip to content

Commit

Permalink
View request flyout (#127156)
Browse files Browse the repository at this point in the history
* wip: create code scaffold for component

* Implement new flyout in ingest pipelines

* Fix linter and i18n issues

* Add base tests for new component

* Wire everything together

* Fix linter issues

* Finish writing tests

* fix linting issues

* Refactor hook and fix small dependencies bug

* commit using @elastic.co

* Refactor out hook and fix linter issues

* Enhance tests and fix typo

* Refactor component name and fix tests

* update snapshot

* Address first round of CR

* Update snapshot

* Refactor apirequestflyout to consume applicationStart only

* Fix import order
  • Loading branch information
sabarasaba authored Mar 22, 2022
1 parent d8a1827 commit e9d0769
Show file tree
Hide file tree
Showing 18 changed files with 440 additions and 123 deletions.
2 changes: 1 addition & 1 deletion src/plugins/es_ui_shared/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
"static/forms/components",
"static/forms/helpers/field_validators/types"
],
"requiredBundles": ["data"]
"requiredBundles": ["data", "kibanaReact"]
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export { ViewApiRequestFlyout } from './view_api_request_flyout';
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { act } from 'react-dom/test-utils';
import { mountWithI18nProvider } from '@kbn/test-jest-helpers';
import { findTestSubject, takeMountedSnapshot } from '@elastic/eui/lib/test';
import { compressToEncodedURIComponent } from 'lz-string';

import { ViewApiRequestFlyout } from './view_api_request_flyout';
import type { UrlService } from 'src/plugins/share/common/url_service';
import { ApplicationStart } from 'src/core/public';
import { applicationServiceMock } from 'src/core/public/mocks';

const payload = {
title: 'Test title',
description: 'Test description',
request: 'Hello world',
closeFlyout: jest.fn(),
};

const urlServiceMock = {
locators: {
get: jest.fn().mockReturnValue({
useUrl: jest.fn().mockImplementation((value) => {
return `devToolsUrl_${value?.loadFrom}`;
}),
}),
},
} as any as UrlService;

const applicationMock = {
...applicationServiceMock.createStartContract(),
capabilities: {
dev_tools: {
show: true,
},
},
} as any as ApplicationStart;

describe('ViewApiRequestFlyout', () => {
test('is rendered', () => {
const component = mountWithI18nProvider(<ViewApiRequestFlyout {...payload} />);
expect(takeMountedSnapshot(component)).toMatchSnapshot();
});

describe('props', () => {
test('on closeFlyout', async () => {
const component = mountWithI18nProvider(<ViewApiRequestFlyout {...payload} />);

await act(async () => {
findTestSubject(component, 'apiRequestFlyoutClose').simulate('click');
});

expect(payload.closeFlyout).toBeCalled();
});

test('doesnt have openInConsole when some optional props are not supplied', async () => {
const component = mountWithI18nProvider(<ViewApiRequestFlyout {...payload} />);

const openInConsole = findTestSubject(component, 'apiRequestFlyoutOpenInConsoleButton');
expect(openInConsole.length).toEqual(0);

// Flyout should *not* be wrapped with RedirectAppLinks
const redirectWrapper = findTestSubject(component, 'apiRequestFlyoutRedirectWrapper');
expect(redirectWrapper.length).toEqual(0);
});

test('has openInConsole when all optional props are supplied', async () => {
const encodedRequest = compressToEncodedURIComponent(payload.request);
const component = mountWithI18nProvider(
<ViewApiRequestFlyout
{...payload}
application={applicationMock}
urlService={urlServiceMock}
/>
);

const openInConsole = findTestSubject(component, 'apiRequestFlyoutOpenInConsoleButton');
expect(openInConsole.length).toEqual(1);
expect(openInConsole.props().href).toEqual(`devToolsUrl_data:text/plain,${encodedRequest}`);

// Flyout should be wrapped with RedirectAppLinks
const redirectWrapper = findTestSubject(component, 'apiRequestFlyoutRedirectWrapper');
expect(redirectWrapper.length).toEqual(1);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { compressToEncodedURIComponent } from 'lz-string';

import {
EuiFlyout,
EuiFlyoutProps,
EuiFlyoutHeader,
EuiTitle,
EuiFlyoutBody,
EuiFlyoutFooter,
EuiButtonEmpty,
EuiText,
EuiSpacer,
EuiCodeBlock,
EuiCopy,
} from '@elastic/eui';
import type { UrlService } from 'src/plugins/share/common/url_service';
import { ApplicationStart, APP_WRAPPER_CLASS } from '../../../../../core/public';
import { RedirectAppLinks } from '../../../../kibana_react/public';

type FlyoutProps = Omit<EuiFlyoutProps, 'onClose'>;
interface ViewApiRequestFlyoutProps {
title: string;
description: string;
request: string;
closeFlyout: () => void;
flyoutProps?: FlyoutProps;
application?: ApplicationStart;
urlService?: UrlService;
}

export const ApiRequestFlyout: React.FunctionComponent<ViewApiRequestFlyoutProps> = ({
title,
description,
request,
closeFlyout,
flyoutProps,
urlService,
application,
}) => {
const getUrlParams = undefined;
const canShowDevtools = !!application?.capabilities?.dev_tools?.show;
const devToolsDataUri = compressToEncodedURIComponent(request);

// Generate a console preview link if we have a valid locator
const consolePreviewLink = urlService?.locators.get('CONSOLE_APP_LOCATOR')?.useUrl(
{
loadFrom: `data:text/plain,${devToolsDataUri}`,
},
getUrlParams,
[request]
);

// Check if both the Dev Tools UI and the Console UI are enabled.
const shouldShowDevToolsLink = canShowDevtools && consolePreviewLink !== undefined;

return (
<EuiFlyout onClose={closeFlyout} data-test-subj="apiRequestFlyout" {...flyoutProps}>
<EuiFlyoutHeader>
<EuiTitle>
<h2 data-test-subj="apiRequestFlyoutTitle">{title}</h2>
</EuiTitle>
</EuiFlyoutHeader>

<EuiFlyoutBody>
<EuiText>
<p data-test-subj="apiRequestFlyoutDescription">{description}</p>
</EuiText>
<EuiSpacer />

<EuiSpacer size="s" />
<div className="eui-textRight">
<EuiCopy textToCopy={request}>
{(copy) => (
<EuiButtonEmpty
size="xs"
flush="right"
iconType="copyClipboard"
onClick={copy}
data-test-subj="apiRequestFlyoutCopyClipboardButton"
>
<FormattedMessage
id="esUi.viewApiRequest.copyToClipboardButton"
defaultMessage="Copy to clipboard"
/>
</EuiButtonEmpty>
)}
</EuiCopy>
{shouldShowDevToolsLink && (
<EuiButtonEmpty
size="xs"
flush="right"
iconType="wrench"
href={consolePreviewLink}
data-test-subj="apiRequestFlyoutOpenInConsoleButton"
>
<FormattedMessage
id="esUi.viewApiRequest.openInConsoleButton"
defaultMessage="Open in Console"
/>
</EuiButtonEmpty>
)}
</div>
<EuiSpacer size="s" />
<EuiCodeBlock language="json" data-test-subj="apiRequestFlyoutBody">
{request}
</EuiCodeBlock>
</EuiFlyoutBody>

<EuiFlyoutFooter>
<EuiButtonEmpty
iconType="cross"
onClick={closeFlyout}
flush="left"
data-test-subj="apiRequestFlyoutClose"
>
<FormattedMessage id="esUi.viewApiRequest.closeButtonLabel" defaultMessage="Close" />
</EuiButtonEmpty>
</EuiFlyoutFooter>
</EuiFlyout>
);
};

export const ViewApiRequestFlyout = (props: ViewApiRequestFlyoutProps) => {
if (props.application) {
return (
<RedirectAppLinks
application={props.application}
className={APP_WRAPPER_CLASS}
data-test-subj="apiRequestFlyoutRedirectWrapper"
>
<ApiRequestFlyout {...props} />
</RedirectAppLinks>
);
}

return <ApiRequestFlyout {...props} />;
};
1 change: 1 addition & 0 deletions src/plugins/es_ui_shared/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type { EuiCodeEditorProps } from './components/code_editor';
export { EuiCodeEditor } from './components/code_editor';
export type { Frequency } from './components/cron_editor';
export { CronEditor } from './components/cron_editor';
export { ViewApiRequestFlyout } from './components/view_api_request_flyout';

export type {
SendRequestConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ export type PipelineFormTestSubjects =
| 'onFailureEditor'
| 'testPipelineButton'
| 'showRequestLink'
| 'requestFlyout'
| 'requestFlyout.title'
| 'apiRequestFlyout'
| 'apiRequestFlyout.apiRequestFlyoutTitle'
| 'testPipelineFlyout'
| 'testPipelineFlyout.title'
| 'documentationLink';
Loading

0 comments on commit e9d0769

Please sign in to comment.