Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ilm navigation #81664

Merged
merged 19 commits into from Nov 10, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,55 @@
/*
* 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.
*/

import { act } from 'react-dom/test-utils';
import { registerTestBed, TestBed, TestBedConfig } from '../../../../../test_utils';
import { App } from '../../../public/application/app';
import { TestSubjects } from '../helpers';

const getTestBedConfig = (initialEntries: string[]): TestBedConfig => ({
memoryRouter: {
initialEntries,
},
defaultProps: {
getUrlForApp: () => {},
navigateToApp: () => {},
},
});

const initTestBed = (initialEntries: string[]) =>
registerTestBed(App, getTestBedConfig(initialEntries))();

export interface AppTestBed extends TestBed<TestSubjects> {
actions: {
clickPolicyNameLink: () => void;
clickCreatePolicyButton: () => void;
};
}

export const setup = async (initialEntries: string[]): Promise<AppTestBed> => {
const testBed = await initTestBed(initialEntries);

const clickPolicyNameLink = async () => {
const { component, find } = testBed;
await act(async () => {
find('policyTablePolicyNameLink').simulate('click', { button: 0 });
});
component.update();
};

const clickCreatePolicyButton = async () => {
const { component, find } = testBed;
await act(async () => {
find('createPolicyButton').simulate('click', { button: 0 });
});
component.update();
};

return {
...testBed,
actions: { clickPolicyNameLink, clickCreatePolicyButton },
};
};
@@ -0,0 +1,84 @@
/*
* 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.
*/

import { AppTestBed, setup } from './app.helpers';
import { setupEnvironment } from '../helpers/setup_environment';
import { SPECIAL_CHARS_NAME, getDefaultHotPhasePolicy } from '../edit_policy/constants';
import { act } from 'react-dom/test-utils';

window.scrollTo = jest.fn();

describe('<App />', () => {
let testBed: AppTestBed;
const { server, httpRequestsMockHelpers } = setupEnvironment();
afterAll(() => {
server.restore();
});

describe('navigation with special characters', () => {
beforeAll(async () => {
httpRequestsMockHelpers.setLoadPolicies([getDefaultHotPhasePolicy(SPECIAL_CHARS_NAME)]);
});

test('when clicked on policy name in table', async () => {
await act(async () => {
testBed = await setup(['/']);
});

const { component, actions } = testBed;
component.update();

await actions.clickPolicyNameLink();
component.update();

expect(testBed.find('policyTitle').text()).toBe(
`Edit index lifecycle policy ${SPECIAL_CHARS_NAME}`
);
});

test('when creating a new policy', async () => {
await act(async () => {
testBed = await setup(['/']);
});

const { component, actions } = testBed;
component.update();

await actions.clickCreatePolicyButton();
component.update();

expect(testBed.find('policyTitle').text()).toBe(`Create an index lifecycle policy`);
});

test('when loading edit policy page url', async () => {
await act(async () => {
testBed = await setup([`/policies/edit/${encodeURIComponent(SPECIAL_CHARS_NAME)}`]);
});

const { component } = testBed;
component.update();

expect(testBed.find('policyTitle').text()).toBe(
`Edit index lifecycle policy ${SPECIAL_CHARS_NAME}`
);
});

test('when loading edit policy page url with double encoding', async () => {
await act(async () => {
testBed = await setup([
encodeURI(`/policies/edit/${encodeURIComponent(SPECIAL_CHARS_NAME)}`),
]);
});

const { component } = testBed;
component.update();

expect(testBed.find('policyTitle').text()).toBe(
`Edit index lifecycle policy ${SPECIAL_CHARS_NAME}`
);
});
});
});
Expand Up @@ -7,6 +7,9 @@
import { PolicyFromES } from '../../../common/types';

export const POLICY_NAME = 'my_policy';
// navigation doesn't work for % with other special chars or sequence %25
// https://github.com/elastic/kibana/pull/81664
export const SPECIAL_CHARS_NAME = 'test?#';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is only consumed in app.test.ts, and I find it's a bit easier to understand the tests if this value is colocated with the code that consumes it. Can we define it there until it needs to be used in multiple files, at which point we can extract it to a common location like this file?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, what do you think of using the full set of reserved characters that the API allows?

export const SPECIAL_CHARS_NAME = 'test?#$+=&@:';

export const SNAPSHOT_POLICY_NAME = 'my_snapshot_policy';
export const NEW_SNAPSHOT_POLICY_NAME = 'my_new_snapshot_policy';

Expand Down Expand Up @@ -96,6 +99,23 @@ export const DELETE_PHASE_POLICY: PolicyFromES = {
name: POLICY_NAME,
};

export const getDefaultHotPhasePolicy = (policyName: string): PolicyFromES => ({
version: 1,
modified_date: Date.now().toString(),
policy: {
name: policyName,
phases: {
hot: {
min_age: '123ms',
actions: {
rollover: {},
},
},
},
},
name: policyName,
});

export const POLICY_WITH_NODE_ATTR_AND_OFF_ALLOCATION: PolicyFromES = {
version: 1,
modified_date: Date.now().toString(),
Expand Down
Expand Up @@ -18,6 +18,7 @@ import {
POLICY_WITH_INCLUDE_EXCLUDE,
POLICY_WITH_NODE_ATTR_AND_OFF_ALLOCATION,
POLICY_WITH_NODE_ROLE_ALLOCATION,
getDefaultHotPhasePolicy,
} from './constants';

window.scrollTo = jest.fn();
Expand All @@ -32,7 +33,7 @@ describe('<EditPolicy />', () => {
describe('hot phase', () => {
describe('serialization', () => {
beforeEach(async () => {
httpRequestsMockHelpers.setLoadPolicies([DEFAULT_POLICY]);
httpRequestsMockHelpers.setLoadPolicies([getDefaultHotPhasePolicy('test')]);
httpRequestsMockHelpers.setLoadSnapshotPolicies([]);

await act(async () => {
Expand Down
Expand Up @@ -17,4 +17,7 @@ export type TestSubjects =
| 'hot-selectedMaxDocuments'
| 'hot-selectedMaxAge'
| 'hot-selectedMaxAgeUnits'
| 'policyTablePolicyNameLink'
| 'policyTitle'
| 'createPolicyButton'
| string;
Expand Up @@ -14,31 +14,41 @@ import { EditPolicy } from './sections/edit_policy';
import { PolicyTable } from './sections/policy_table';
import { trackUiMetric } from './services/ui_metric';

export const App = ({
export const AppWithRouter = ({
history,
navigateToApp,
getUrlForApp,
}: {
history: ScopedHistory;
navigateToApp: ApplicationStart['navigateToApp'];
getUrlForApp: ApplicationStart['getUrlForApp'];
}) => (
<Router history={history}>
<App navigateToApp={navigateToApp} getUrlForApp={getUrlForApp} />
</Router>
);

export const App = ({
navigateToApp,
getUrlForApp,
}: {
navigateToApp: ApplicationStart['navigateToApp'];
getUrlForApp: ApplicationStart['getUrlForApp'];
}) => {
useEffect(() => trackUiMetric(METRIC_TYPE.LOADED, UIM_APP_LOAD), []);

return (
<Router history={history}>
<Switch>
<Redirect exact from="/" to="/policies" />
<Route
exact
path={`/policies`}
render={(props) => <PolicyTable {...props} navigateToApp={navigateToApp} />}
/>
<Route
path={`/policies/edit/:policyName?`}
render={(props) => <EditPolicy {...props} getUrlForApp={getUrlForApp} />}
/>
</Switch>
</Router>
<Switch>
<Redirect exact from="/" to="/policies" />
<Route
exact
path={`/policies`}
render={(props) => <PolicyTable {...props} navigateToApp={navigateToApp} />}
/>
<Route
path={`/policies/edit/:policyName?`}
render={(props) => <EditPolicy {...props} getUrlForApp={getUrlForApp} />}
/>
</Switch>
);
};
Expand Up @@ -12,7 +12,7 @@ import { CloudSetup } from '../../../cloud/public';

import { KibanaContextProvider } from '../shared_imports';

import { App } from './app';
import { AppWithRouter } from './app';

export const renderApp = (
element: Element,
Expand All @@ -25,7 +25,11 @@ export const renderApp = (
render(
<I18nContext>
<KibanaContextProvider services={{ cloud }}>
<App history={history} navigateToApp={navigateToApp} getUrlForApp={getUrlForApp} />
<AppWithRouter
history={history}
navigateToApp={navigateToApp}
getUrlForApp={getUrlForApp}
/>
</KibanaContextProvider>
</I18nContext>,
element
Expand Down
Expand Up @@ -76,12 +76,22 @@ export const EditPolicy: React.FunctionComponent<Props & RouteComponentProps<Rou
);
}

let decodedPolicyName = policyName;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a comment here that this code can be removed if we upgrade to react-router 6 and history 5?

if (policyName) {
try {
decodedPolicyName = decodeURIComponent(policyName);
} catch (e) {
// %25 is automatically decoded to %, so decoding it again will throw an error
// that have to be ignored (https://github.com/elastic/kibana/pull/81664)
}
}

return (
<PresentationComponent
policies={policies}
history={history}
getUrlForApp={getUrlForApp}
policyName={policyName}
policyName={decodedPolicyName}
/>
);
};
Expand Up @@ -213,7 +213,7 @@ export const EditPolicy: React.FunctionComponent<Props> = ({
verticalPosition="center"
horizontalPosition="center"
>
<EuiTitle size="l">
<EuiTitle size="l" data-test-subj="policyTitle">
<h1>
{isNewPolicy
? i18n.translate('xpack.indexLifecycleMgmt.editPolicy.createPolicyMessage', {
Expand Down