Skip to content

Commit

Permalink
[Enterprise Search] Add beta notification (#103535)
Browse files Browse the repository at this point in the history
* Set up new BetaNotification component

* Update shared page template to append new beta notification item to side nav

NOTE: I'm mutating the array because:
- returning a new instance leads to a lot of really annoying type errors
- the side nav's we're getting are entirely static with predictable items & and always come from us anyway
- this is eventually going to get removed, and I'm optimizing for easy-to-remove code

* Add beta notification to error connecting state

- to help users/SDH cases where users cannot connect at all

* Fix type error

- sideNav itself can be undefined but not `sideNav.items`
  • Loading branch information
Constance authored Jun 28, 2021
1 parent f3a5ff2 commit d655c05
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { useValues } from 'kea';
import { EuiEmptyPrompt, EuiCode } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';

import { KibanaLogic } from '../../shared/kibana';
import { KibanaLogic } from '../kibana';
import { BetaNotification } from '../layout/beta';
import { EuiButtonTo } from '../react_router_helpers';

import './error_state_prompt.scss';
Expand Down Expand Up @@ -92,14 +93,15 @@ export const ErrorStatePrompt: React.FC = () => {
</ol>
</>
}
actions={
actions={[
<EuiButtonTo iconType="help" fill to="/setup_guide">
<FormattedMessage
id="xpack.enterpriseSearch.errorConnectingState.setupGuideCta"
defaultMessage="Review setup guide"
/>
</EuiButtonTo>
}
</EuiButtonTo>,
<BetaNotification buttonProps={{ size: 'l', flush: undefined }} />,
]}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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.
*/

@include euiBreakpoint('m', 'l', 'xl') {
.kbnPageTemplateSolutionNav {
position: relative;
min-height: 100%;

// Nested to override EUI specificity
.betaNotificationSideNavItem {
margin-top: $euiSizeL;
}
}

.betaNotificationWrapper {
position: absolute;
bottom: 3px; // Without this 3px buffer, the popover won't render to the right
}
}

.betaNotification {
width: 350px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* 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 '../../__mocks__/enterprise_search_url.mock';

import React from 'react';

import { shallow, ShallowWrapper } from 'enzyme';

import { EuiPopover, EuiPopoverTitle, EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';

import { shallowWithIntl } from '../../test_helpers';

import { BetaNotification, appendBetaNotificationItem } from './beta';

describe('BetaNotification', () => {
const getToggleButton = (wrapper: ShallowWrapper) => {
return shallow(<div>{wrapper.prop('button')}</div>).childAt(0);
};

it('renders', () => {
const wrapper = shallow(<BetaNotification />);

expect(wrapper.type()).toEqual(EuiPopover);
expect(wrapper.find(EuiPopoverTitle).prop('children')).toEqual(
'Enterprise Search in Kibana is a beta user interface'
);
});

describe('open/close popover state', () => {
const wrapper = shallow(<BetaNotification />);

it('is initially closed', () => {
expect(wrapper.find(EuiPopover).prop('isOpen')).toBe(false);
});

it('opens the popover when the toggle button is pressed', () => {
getToggleButton(wrapper).simulate('click');

expect(wrapper.find(EuiPopover).prop('isOpen')).toBe(true);
});

it('closes the popover', () => {
wrapper.prop('closePopover')();

expect(wrapper.find(EuiPopover).prop('isOpen')).toBe(false);
});
});

describe('toggle button props', () => {
it('defaults to a size of xs and flush', () => {
const wrapper = shallow(<BetaNotification />);
const toggleButton = getToggleButton(wrapper);

expect(toggleButton.prop('size')).toEqual('xs');
expect(toggleButton.prop('flush')).toEqual('both');
});

it('passes down custom button props', () => {
const wrapper = shallow(<BetaNotification buttonProps={{ size: 'l' }} />);
const toggleButton = getToggleButton(wrapper);

expect(toggleButton.prop('size')).toEqual('l');
});
});

describe('links', () => {
const wrapper = shallowWithIntl(<BetaNotification />);
const links = wrapper.find(FormattedMessage).dive();

it('renders a documentation link', () => {
const docLink = links.find(EuiLink).first();

expect(docLink.prop('href')).toContain('/user-interfaces.html');
});

it('renders a link back to the standalone UI', () => {
const switchLink = links.find(EuiLink).last();

expect(switchLink.prop('href')).toBe('http://localhost:3002');
});
});
});

describe('appendBetaNotificationItem', () => {
const mockSideNav = {
name: 'Hello world',
items: [
{ id: '1', name: 'Link 1' },
{ id: '2', name: 'Link 2' },
],
};

it('inserts a beta notification into a side nav items array', () => {
appendBetaNotificationItem(mockSideNav);

expect(mockSideNav).toEqual({
name: 'Hello world',
items: [
{ id: '1', name: 'Link 1' },
{ id: '2', name: 'Link 2' },
{
id: 'beta',
name: '',
className: 'betaNotificationSideNavItem',
renderItem: expect.any(Function),
},
],
});
});

it('renders the BetaNotification component as a side nav item', () => {
const SideNavItem = (mockSideNav.items[2] as any).renderItem;
const wrapper = shallow(<SideNavItem />);

expect(wrapper.hasClass('betaNotificationWrapper')).toBe(true);
expect(wrapper.find(BetaNotification)).toHaveLength(1);
});

it('does nothing if no side nav was passed', () => {
const mockEmptySideNav = undefined;
appendBetaNotificationItem(mockEmptySideNav);

expect(mockEmptySideNav).toEqual(undefined);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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, { useState } from 'react';

import {
EuiPopover,
EuiPopoverTitle,
EuiPopoverFooter,
EuiButtonEmpty,
EuiButtonEmptyProps,
EuiText,
EuiLink,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';

import { KibanaPageTemplateProps } from '../../../../../../../src/plugins/kibana_react/public';

import { docLinks } from '../doc_links';
import { getEnterpriseSearchUrl } from '../enterprise_search_url';

import './beta.scss';

interface Props {
buttonProps?: EuiButtonEmptyProps;
}

export const BetaNotification: React.FC<Props> = ({ buttonProps }) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const togglePopover = () => setIsPopoverOpen((isOpen) => !isOpen);
const closePopover = () => setIsPopoverOpen(false);

return (
<EuiPopover
button={
<EuiButtonEmpty
size="xs"
flush="both"
iconType="alert"
{...buttonProps}
onClick={togglePopover}
>
{i18n.translate('xpack.enterpriseSearch.beta.buttonLabel', {
defaultMessage: 'This is a beta user interface',
})}
</EuiButtonEmpty>
}
isOpen={isPopoverOpen}
closePopover={closePopover}
anchorPosition="rightDown"
repositionOnScroll
>
<EuiPopoverTitle>
{i18n.translate('xpack.enterpriseSearch.beta.popover.title', {
defaultMessage: 'Enterprise Search in Kibana is a beta user interface',
})}
</EuiPopoverTitle>
<div className="betaNotification">
<EuiText size="s">
<p>
{i18n.translate('xpack.enterpriseSearch.beta.popover.description', {
defaultMessage:
'The Kibana interface for Enterprise Search is a beta feature. It is subject to change and is not covered by the same level of support as generally available features. This interface will become the sole management panel for Enterprise Search with the 8.0 release. Until then, the standalone Enterprise Search UI remains available and supported.',
})}
</p>
</EuiText>
</div>
<EuiPopoverFooter>
<FormattedMessage
id="xpack.enterpriseSearch.beta.popover.footerDetail"
defaultMessage="{learnMoreLink} or {standaloneUILink}."
values={{
learnMoreLink: (
<EuiLink
href={`${docLinks.enterpriseSearchBase}/user-interfaces.html`}
target="_blank"
>
Learn more
</EuiLink>
),
standaloneUILink: (
<EuiLink href={getEnterpriseSearchUrl()}>switch to the Enterprise Search UI</EuiLink>
),
}}
/>
</EuiPopoverFooter>
</EuiPopover>
);
};

export const appendBetaNotificationItem = (sideNav: KibanaPageTemplateProps['solutionNav']) => {
if (sideNav) {
sideNav.items.push({
id: 'beta',
name: '',
className: 'betaNotificationSideNavItem',
renderItem: () => (
<div className="betaNotificationWrapper">
<BetaNotification />
</div>
),
});
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import { setMockValues } from '../../__mocks__/kea_logic';

jest.mock('./beta', () => ({ appendBetaNotificationItem: jest.fn() })); // Mostly adding this to get tests passing. Should be removed once we're out of beta

import React from 'react';

import { shallow } from 'enzyme';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { HttpLogic } from '../http';
import { BreadcrumbTrail } from '../kibana_chrome/generate_breadcrumbs';
import { Loading } from '../loading';

import { appendBetaNotificationItem } from './beta';

import './page_template.scss';

/*
Expand Down Expand Up @@ -61,6 +63,8 @@ export const EnterpriseSearchPageTemplate: React.FC<PageTemplateProps> = ({
const hasCustomEmptyState = !!emptyState;
const showCustomEmptyState = hasCustomEmptyState && isEmptyState;

appendBetaNotificationItem(solutionNav);

return (
<KibanaPageTemplate
restrictWidth={false}
Expand Down

0 comments on commit d655c05

Please sign in to comment.