Changes APM home page to use route-based tabs (#25891)
* Changed home page to use route based history tabs, abstracted history tabs to component with tests * Spreads location on tab links to preserve query string etc * Adds ts-ignore to 'fix' problem with TS not finding EuiTab and EuiTabs modules in EUI exports * Fixes breadcrumbs and service redirect * Removes commented code
This commit is contained in:
parent
1b3d6ae0b3
commit
453e1f1a2d
|
@ -5,8 +5,12 @@
|
|||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import { EuiTabbedContent } from '@elastic/eui';
|
||||
import { EuiTab, EuiTabs } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import {
|
||||
HistoryTabs,
|
||||
IHistoryTab
|
||||
} from 'x-pack/plugins/apm/public/components/shared/HistoryTabs';
|
||||
// @ts-ignore
|
||||
import { KueryBar } from '../../shared/KueryBar';
|
||||
import { SetupInstructionsLink } from '../../shared/SetupInstructionsLink';
|
||||
|
@ -16,6 +20,19 @@ import { HeaderContainer } from '../../shared/UIComponents';
|
|||
import { ServiceOverview } from '../ServiceOverview';
|
||||
import { TraceOverview } from '../TraceOverview';
|
||||
|
||||
const homeTabs: IHistoryTab[] = [
|
||||
{
|
||||
path: '/services',
|
||||
name: 'Services',
|
||||
component: ServiceOverview
|
||||
},
|
||||
{
|
||||
path: '/traces',
|
||||
name: 'Traces',
|
||||
component: TraceOverview
|
||||
}
|
||||
];
|
||||
|
||||
export function Home() {
|
||||
return (
|
||||
<div>
|
||||
|
@ -24,17 +41,7 @@ export function Home() {
|
|||
<SetupInstructionsLink />
|
||||
</HeaderContainer>
|
||||
<KueryBar />
|
||||
<EuiTabbedContent
|
||||
className="k6Tab--large"
|
||||
tabs={[
|
||||
{
|
||||
id: 'services_overview',
|
||||
name: 'Services',
|
||||
content: <ServiceOverview />
|
||||
},
|
||||
{ id: 'traces_overview', name: 'Traces', content: <TraceOverview /> }
|
||||
]}
|
||||
/>
|
||||
<HistoryTabs tabs={homeTabs} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,19 +9,18 @@ exports[`Home component should render 1`] = `
|
|||
<SetupInstructionsLink />
|
||||
</styled.div>
|
||||
<Connect(KueryBarView) />
|
||||
<EuiTabbedContent
|
||||
className="k6Tab--large"
|
||||
<withRouter(HistoryTabsWithoutRouter)
|
||||
tabs={
|
||||
Array [
|
||||
Object {
|
||||
"content": <Connect(ServiceOverview) />,
|
||||
"id": "services_overview",
|
||||
"component": [Function],
|
||||
"name": "Services",
|
||||
"path": "/services",
|
||||
},
|
||||
Object {
|
||||
"content": <Connect(TraceOverview) />,
|
||||
"id": "traces_overview",
|
||||
"component": [Function],
|
||||
"name": "Traces",
|
||||
"path": "/traces",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -20,19 +20,33 @@ import { Home } from './Home';
|
|||
|
||||
interface BreadcrumbArgs {
|
||||
match: {
|
||||
params: StringMap<any>;
|
||||
params: StringMap;
|
||||
};
|
||||
}
|
||||
|
||||
interface RenderArgs {
|
||||
location: StringMap<any>;
|
||||
location: StringMap;
|
||||
match: {
|
||||
params: StringMap;
|
||||
};
|
||||
}
|
||||
|
||||
const renderAsRedirectTo = (to: string) => {
|
||||
return ({ location }: RenderArgs) => (
|
||||
<Redirect
|
||||
to={{
|
||||
...location,
|
||||
pathname: to
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const routes = [
|
||||
{
|
||||
exact: true,
|
||||
path: '/',
|
||||
component: Home,
|
||||
render: renderAsRedirectTo('/services'),
|
||||
breadcrumb: 'APM'
|
||||
},
|
||||
{
|
||||
|
@ -56,20 +70,26 @@ export const routes = [
|
|||
breadcrumb: 'Invalid License',
|
||||
render: () => <div>Invalid license</div>
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/services',
|
||||
component: Home,
|
||||
breadcrumb: 'Services'
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/traces',
|
||||
component: Home,
|
||||
breadcrumb: 'Traces'
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/:serviceName',
|
||||
breadcrumb: ({ match }: BreadcrumbArgs) => match.params.serviceName,
|
||||
render: ({ location }: RenderArgs) => {
|
||||
return (
|
||||
<Redirect
|
||||
to={{
|
||||
...location,
|
||||
pathname: `${location.pathname}/transactions`
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
render: (props: RenderArgs) =>
|
||||
renderAsRedirectTo(`/${props.match.params.serviceName}/transactions`)(
|
||||
props
|
||||
)
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
// @ts-ignore otherwise TS complains "Module ''@elastic/eui'' has no exported member 'EuiTab'"
|
||||
import { EuiTab } from '@elastic/eui';
|
||||
import { mount, ReactWrapper, shallow, ShallowWrapper } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import {
|
||||
HistoryTabs,
|
||||
HistoryTabsProps,
|
||||
HistoryTabsWithoutRouter,
|
||||
IHistoryTab
|
||||
} from '..';
|
||||
|
||||
describe('HistoryTabs', () => {
|
||||
let mockLocation: any;
|
||||
let mockHistory: any;
|
||||
let testTabs: IHistoryTab[];
|
||||
let testProps: HistoryTabsProps;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLocation = {
|
||||
pathname: ''
|
||||
};
|
||||
mockHistory = {
|
||||
push: jest.fn()
|
||||
};
|
||||
|
||||
const Content = (props: { name: string }) => <div>{props.name}</div>;
|
||||
|
||||
testTabs = [
|
||||
{
|
||||
name: 'One',
|
||||
path: '/one',
|
||||
component: () => <Content name="one" />
|
||||
},
|
||||
{
|
||||
name: 'Two',
|
||||
path: '/two',
|
||||
component: () => <Content name="two" />
|
||||
},
|
||||
{
|
||||
name: 'Three',
|
||||
path: '/three',
|
||||
component: () => <Content name="three" />
|
||||
}
|
||||
];
|
||||
|
||||
testProps = ({
|
||||
location: mockLocation,
|
||||
history: mockHistory,
|
||||
tabs: testTabs
|
||||
} as unknown) as HistoryTabsProps;
|
||||
});
|
||||
|
||||
it('should render correctly', () => {
|
||||
mockLocation.pathname = '/two';
|
||||
const wrapper = shallow(<HistoryTabsWithoutRouter {...testProps} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
const tabs: ShallowWrapper<EuiTab> = wrapper.find(EuiTab);
|
||||
expect(tabs.at(0).props().isSelected).toEqual(false);
|
||||
expect(tabs.at(1).props().isSelected).toEqual(true);
|
||||
expect(tabs.at(2).props().isSelected).toEqual(false);
|
||||
});
|
||||
|
||||
it('should change the selected item on tab click', () => {
|
||||
const wrapper = mount(
|
||||
<MemoryRouter initialEntries={['/two']}>
|
||||
<HistoryTabs tabs={testTabs} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(wrapper.find('Content')).toMatchSnapshot();
|
||||
|
||||
wrapper
|
||||
.find(EuiTab)
|
||||
.at(2)
|
||||
.simulate('click');
|
||||
|
||||
const tabs: ReactWrapper<EuiTab> = wrapper.find(EuiTab);
|
||||
expect(tabs.at(0).props().isSelected).toEqual(false);
|
||||
expect(tabs.at(1).props().isSelected).toEqual(false);
|
||||
expect(tabs.at(2).props().isSelected).toEqual(true);
|
||||
|
||||
expect(wrapper.find('Content')).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`HistoryTabs should change the selected item on tab click 1`] = `
|
||||
<Content
|
||||
name="two"
|
||||
>
|
||||
<div>
|
||||
two
|
||||
</div>
|
||||
</Content>
|
||||
`;
|
||||
|
||||
exports[`HistoryTabs should change the selected item on tab click 2`] = `
|
||||
<Content
|
||||
name="three"
|
||||
>
|
||||
<div>
|
||||
three
|
||||
</div>
|
||||
</Content>
|
||||
`;
|
||||
|
||||
exports[`HistoryTabs should render correctly 1`] = `
|
||||
<React.Fragment>
|
||||
<EuiTabs
|
||||
expand={false}
|
||||
size="m"
|
||||
>
|
||||
<EuiTab
|
||||
disabled={false}
|
||||
isSelected={false}
|
||||
key="/one--One"
|
||||
onClick={[Function]}
|
||||
>
|
||||
One
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
disabled={false}
|
||||
isSelected={true}
|
||||
key="/two--Two"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Two
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
disabled={false}
|
||||
isSelected={false}
|
||||
key="/three--Three"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Three
|
||||
</EuiTab>
|
||||
</EuiTabs>
|
||||
<Route
|
||||
component={[Function]}
|
||||
key="/one"
|
||||
path="/one"
|
||||
/>
|
||||
<Route
|
||||
component={[Function]}
|
||||
key="/two"
|
||||
path="/two"
|
||||
/>
|
||||
<Route
|
||||
component={[Function]}
|
||||
key="/three"
|
||||
path="/three"
|
||||
/>
|
||||
</React.Fragment>
|
||||
`;
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
// @ts-ignore otherwise TS complains "Module ''@elastic/eui'' has no exported member 'EuiTab'"
|
||||
import { EuiTab, EuiTabs } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { Route, RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
|
||||
export interface IHistoryTab {
|
||||
path: string;
|
||||
name: string;
|
||||
component: React.SFC | React.ComponentClass;
|
||||
}
|
||||
|
||||
export interface HistoryTabsProps extends RouteComponentProps {
|
||||
tabs: IHistoryTab[];
|
||||
}
|
||||
|
||||
const HistoryTabsWithoutRouter = ({
|
||||
tabs,
|
||||
history,
|
||||
location
|
||||
}: HistoryTabsProps) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<EuiTabs>
|
||||
{tabs.map(tab => (
|
||||
<EuiTab
|
||||
onClick={() => history.push({ ...location, pathname: tab.path })}
|
||||
isSelected={location.pathname === tab.path}
|
||||
key={`${tab.path}--${tab.name}`}
|
||||
>
|
||||
{tab.name}
|
||||
</EuiTab>
|
||||
))}
|
||||
</EuiTabs>
|
||||
{tabs.map(tab => (
|
||||
<Route path={tab.path} component={tab.component} key={tab.path} />
|
||||
))}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const HistoryTabs = withRouter(HistoryTabsWithoutRouter);
|
||||
|
||||
export { HistoryTabsWithoutRouter, HistoryTabs };
|
Loading…
Reference in a new issue