mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[ILM] Migrate Hot phase to Form Lib (#80012)
* wip * added missing shared_imports file to index * initial migration of hot phase to form lib - tests are now broken - need to break up the hot_phase file in to meaningful parts - duplicated set_priority and forcemerge components * Big refactor - moved a lot of files around - removed the need for the state to track whether rollover is set * Integrate form lib serialization with existing serialization - refactor serializePolicy -> legacySerializePolicy - updated serialization of form lib to factor in pre-existing policy values. These should be interacted with in a non-lossy way. * wip on fixing jest tests and some other refactors * fix jest tests and other refactors * delete existing hot phase serialization and tests * beginning of serializer test for hot phase * added serialization tests for form lib components * fix some i18n issues * fixed delete phase integration test * move hot phase serialization test to pre-existing test location * fix another jest test issue * fix ui metric tracking for setting input priority in hot phase * refactor use rollover switch to form lib component and update validation for number segments in force merge * readded missing validation 🤦🏼♂️ * fix type check issues and setting of rollover enabled 🙄 * migrate all form lib components to spreading all rest props in EuiFormRow * added comment to test helper function * refactor test helper setPhaseIndexPriorityFormLib -> setPhaseIndexPriority * refactor to use form schema * Removed use of UseMultiFields component - also fix missing "key" on react component in unrelated file - fixed ordering of JSON in test file - also removed default value from form schema so that when a value is not set for max size, max docs or max age it will remain unset in future policies * update json flyout behaviour * fix json policy serialization * Fix type and i18n issues * do not use form.subscribe * add missing key value in cells Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
668a4d4366
commit
30fc4bed7d
56 changed files with 1908 additions and 1303 deletions
|
@ -39,8 +39,8 @@ export const CheckBoxField = ({ field, euiFieldProps = {}, ...rest }: Props) =>
|
|||
error={errorMessage}
|
||||
isInvalid={isInvalid}
|
||||
fullWidth
|
||||
data-test-subj={rest['data-test-subj']}
|
||||
describedByIds={rest.idAria ? [rest.idAria] : undefined}
|
||||
{...rest}
|
||||
>
|
||||
<EuiCheckbox
|
||||
label={field.label}
|
||||
|
|
|
@ -87,8 +87,8 @@ export const ComboBoxField = ({ field, euiFieldProps = {}, ...rest }: Props) =>
|
|||
error={errorMessage}
|
||||
isInvalid={isInvalid}
|
||||
fullWidth
|
||||
data-test-subj={rest['data-test-subj']}
|
||||
describedByIds={rest.idAria ? [rest.idAria] : undefined}
|
||||
{...rest}
|
||||
>
|
||||
<EuiComboBox
|
||||
noSuggestions
|
||||
|
|
|
@ -39,8 +39,8 @@ export const MultiSelectField = ({ field, euiFieldProps = {}, ...rest }: Props)
|
|||
error={errorMessage}
|
||||
isInvalid={isInvalid}
|
||||
fullWidth
|
||||
data-test-subj={rest['data-test-subj']}
|
||||
describedByIds={rest.idAria ? [rest.idAria] : undefined}
|
||||
{...rest}
|
||||
>
|
||||
<EuiSelectable
|
||||
allowExclusions={false}
|
||||
|
|
|
@ -39,8 +39,8 @@ export const NumericField = ({ field, euiFieldProps = {}, ...rest }: Props) => {
|
|||
error={errorMessage}
|
||||
isInvalid={isInvalid}
|
||||
fullWidth
|
||||
data-test-subj={rest['data-test-subj']}
|
||||
describedByIds={rest.idAria ? [rest.idAria] : undefined}
|
||||
{...rest}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
isInvalid={isInvalid}
|
||||
|
|
|
@ -39,8 +39,8 @@ export const RadioGroupField = ({ field, euiFieldProps = {}, ...rest }: Props) =
|
|||
error={errorMessage}
|
||||
isInvalid={isInvalid}
|
||||
fullWidth
|
||||
data-test-subj={rest['data-test-subj']}
|
||||
describedByIds={rest.idAria ? [rest.idAria] : undefined}
|
||||
{...rest}
|
||||
>
|
||||
<EuiRadioGroup
|
||||
idSelected={field.value as string}
|
||||
|
|
|
@ -50,8 +50,8 @@ export const RangeField = ({ field, euiFieldProps = {}, ...rest }: Props) => {
|
|||
error={errorMessage}
|
||||
isInvalid={isInvalid}
|
||||
fullWidth
|
||||
data-test-subj={rest['data-test-subj']}
|
||||
describedByIds={rest.idAria ? [rest.idAria] : undefined}
|
||||
{...rest}
|
||||
>
|
||||
<EuiRange
|
||||
value={field.value as number}
|
||||
|
|
|
@ -44,8 +44,8 @@ export const SelectField = ({ field, euiFieldProps, ...rest }: Props) => {
|
|||
error={errorMessage}
|
||||
isInvalid={isInvalid}
|
||||
fullWidth
|
||||
data-test-subj={rest['data-test-subj']}
|
||||
describedByIds={rest.idAria ? [rest.idAria] : undefined}
|
||||
{...rest}
|
||||
>
|
||||
<EuiSelect
|
||||
fullWidth
|
||||
|
|
|
@ -42,8 +42,8 @@ export const SuperSelectField = ({ field, euiFieldProps = { options: [] }, ...re
|
|||
error={errorMessage}
|
||||
isInvalid={isInvalid}
|
||||
fullWidth
|
||||
data-test-subj={rest['data-test-subj']}
|
||||
describedByIds={rest.idAria ? [rest.idAria] : undefined}
|
||||
{...rest}
|
||||
>
|
||||
<EuiSuperSelect
|
||||
fullWidth
|
||||
|
|
|
@ -39,8 +39,8 @@ export const TextAreaField = ({ field, euiFieldProps = {}, ...rest }: Props) =>
|
|||
error={errorMessage}
|
||||
isInvalid={isInvalid}
|
||||
fullWidth
|
||||
data-test-subj={rest['data-test-subj']}
|
||||
describedByIds={rest.idAria ? [rest.idAria] : undefined}
|
||||
{...rest}
|
||||
>
|
||||
<EuiTextArea
|
||||
isInvalid={isInvalid}
|
||||
|
|
|
@ -39,8 +39,8 @@ export const TextField = ({ field, euiFieldProps = {}, ...rest }: Props) => {
|
|||
error={errorMessage}
|
||||
isInvalid={isInvalid}
|
||||
fullWidth
|
||||
data-test-subj={rest['data-test-subj']}
|
||||
describedByIds={rest.idAria ? [rest.idAria] : undefined}
|
||||
{...rest}
|
||||
>
|
||||
<EuiFieldText
|
||||
isInvalid={isInvalid}
|
||||
|
|
|
@ -46,8 +46,8 @@ export const ToggleField = ({ field, euiFieldProps = {}, ...rest }: Props) => {
|
|||
error={errorMessage}
|
||||
isInvalid={isInvalid}
|
||||
fullWidth
|
||||
data-test-subj={rest['data-test-subj']}
|
||||
describedByIds={rest.idAria ? [rest.idAria] : undefined}
|
||||
{...rest}
|
||||
>
|
||||
<EuiSwitch
|
||||
label={field.label}
|
||||
|
|
|
@ -552,7 +552,9 @@ exports[`extend index management ilm summary extension should return extension w
|
|||
2018-12-07 13:02:55
|
||||
</dd>
|
||||
</EuiDescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle
|
||||
key="phaseDefinition_title"
|
||||
>
|
||||
<dt
|
||||
className="euiDescriptionList__title"
|
||||
>
|
||||
|
|
|
@ -10,6 +10,23 @@ export const POLICY_NAME = 'my_policy';
|
|||
export const SNAPSHOT_POLICY_NAME = 'my_snapshot_policy';
|
||||
export const NEW_SNAPSHOT_POLICY_NAME = 'my_new_snapshot_policy';
|
||||
|
||||
export const DEFAULT_POLICY: PolicyFromES = {
|
||||
version: 1,
|
||||
modified_date: Date.now().toString(),
|
||||
policy: {
|
||||
name: '',
|
||||
phases: {
|
||||
hot: {
|
||||
min_age: '123ms',
|
||||
actions: {
|
||||
rollover: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
name: '',
|
||||
};
|
||||
|
||||
export const DELETE_PHASE_POLICY: PolicyFromES = {
|
||||
version: 1,
|
||||
modified_date: Date.now().toString(),
|
||||
|
@ -19,8 +36,12 @@ export const DELETE_PHASE_POLICY: PolicyFromES = {
|
|||
min_age: '0ms',
|
||||
actions: {
|
||||
rollover: {
|
||||
max_age: '30d',
|
||||
max_size: '50gb',
|
||||
},
|
||||
set_priority: {
|
||||
priority: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { registerTestBed, TestBed, TestBedConfig } from '../../../../../test_utils';
|
||||
import { registerTestBed, TestBedConfig } from '../../../../../test_utils';
|
||||
|
||||
import { POLICY_NAME } from './constants';
|
||||
import { TestSubjects } from '../helpers';
|
||||
|
@ -43,39 +43,110 @@ const testBedConfig: TestBedConfig = {
|
|||
},
|
||||
};
|
||||
|
||||
const initTestBed = registerTestBed(EditPolicy, testBedConfig);
|
||||
const initTestBed = registerTestBed<TestSubjects>(EditPolicy, testBedConfig);
|
||||
|
||||
export interface EditPolicyTestBed extends TestBed<TestSubjects> {
|
||||
actions: {
|
||||
setWaitForSnapshotPolicy: (snapshotPolicyName: string) => void;
|
||||
savePolicy: () => void;
|
||||
};
|
||||
}
|
||||
type SetupReturn = ReturnType<typeof setup>;
|
||||
|
||||
export const setup = async (): Promise<EditPolicyTestBed> => {
|
||||
export type EditPolicyTestBed = SetupReturn extends Promise<infer U> ? U : SetupReturn;
|
||||
|
||||
export const setup = async () => {
|
||||
const testBed = await initTestBed();
|
||||
|
||||
const { find, component } = testBed;
|
||||
|
||||
const setWaitForSnapshotPolicy = async (snapshotPolicyName: string) => {
|
||||
const { component } = testBed;
|
||||
act(() => {
|
||||
testBed.find('snapshotPolicyCombobox').simulate('change', [{ label: snapshotPolicyName }]);
|
||||
find('snapshotPolicyCombobox').simulate('change', [{ label: snapshotPolicyName }]);
|
||||
});
|
||||
component.update();
|
||||
};
|
||||
|
||||
const savePolicy = async () => {
|
||||
const { component, find } = testBed;
|
||||
await act(async () => {
|
||||
find('savePolicyButton').simulate('click');
|
||||
});
|
||||
component.update();
|
||||
};
|
||||
|
||||
const toggleRollover = async (checked: boolean) => {
|
||||
await act(async () => {
|
||||
find('rolloverSwitch').simulate('click', { target: { checked } });
|
||||
});
|
||||
component.update();
|
||||
};
|
||||
|
||||
const setMaxSize = async (value: string, units?: string) => {
|
||||
await act(async () => {
|
||||
find('hot-selectedMaxSizeStored').simulate('change', { target: { value } });
|
||||
if (units) {
|
||||
find('hot-selectedMaxSizeStoredUnits.select').simulate('change', {
|
||||
target: { value: units },
|
||||
});
|
||||
}
|
||||
});
|
||||
component.update();
|
||||
};
|
||||
|
||||
const setMaxDocs = async (value: string) => {
|
||||
await act(async () => {
|
||||
find('hot-selectedMaxDocuments').simulate('change', { target: { value } });
|
||||
});
|
||||
component.update();
|
||||
};
|
||||
|
||||
const setMaxAge = async (value: string, units?: string) => {
|
||||
await act(async () => {
|
||||
find('hot-selectedMaxAge').simulate('change', { target: { value } });
|
||||
if (units) {
|
||||
find('hot-selectedMaxAgeUnits.select').simulate('change', { target: { value: units } });
|
||||
}
|
||||
});
|
||||
component.update();
|
||||
};
|
||||
|
||||
const toggleForceMerge = (phase: string) => async (checked: boolean) => {
|
||||
await act(async () => {
|
||||
find(`${phase}-forceMergeSwitch`).simulate('click', { target: { checked } });
|
||||
});
|
||||
component.update();
|
||||
};
|
||||
|
||||
const setForcemergeSegmentsCount = (phase: string) => async (value: string) => {
|
||||
await act(async () => {
|
||||
find(`${phase}-selectedForceMergeSegments`).simulate('change', { target: { value } });
|
||||
});
|
||||
component.update();
|
||||
};
|
||||
|
||||
const setBestCompression = (phase: string) => async (checked: boolean) => {
|
||||
await act(async () => {
|
||||
find(`${phase}-bestCompression`).simulate('click', { target: { checked } });
|
||||
});
|
||||
component.update();
|
||||
};
|
||||
|
||||
const setIndexPriority = (phase: string) => async (value: string) => {
|
||||
await act(async () => {
|
||||
find(`${phase}-phaseIndexPriority`).simulate('change', { target: { value } });
|
||||
});
|
||||
component.update();
|
||||
};
|
||||
|
||||
return {
|
||||
...testBed,
|
||||
actions: {
|
||||
setWaitForSnapshotPolicy,
|
||||
savePolicy,
|
||||
hot: {
|
||||
setMaxSize,
|
||||
setMaxDocs,
|
||||
setMaxAge,
|
||||
toggleRollover,
|
||||
toggleForceMerge: toggleForceMerge('hot'),
|
||||
setForcemergeSegments: setForcemergeSegmentsCount('hot'),
|
||||
setBestCompression: setBestCompression('hot'),
|
||||
setIndexPriority: setIndexPriority('hot'),
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -10,7 +10,12 @@ import { setupEnvironment } from '../helpers/setup_environment';
|
|||
import { EditPolicyTestBed, setup } from './edit_policy.helpers';
|
||||
|
||||
import { API_BASE_PATH } from '../../../common/constants';
|
||||
import { DELETE_PHASE_POLICY, NEW_SNAPSHOT_POLICY_NAME, SNAPSHOT_POLICY_NAME } from './constants';
|
||||
import {
|
||||
DELETE_PHASE_POLICY,
|
||||
NEW_SNAPSHOT_POLICY_NAME,
|
||||
SNAPSHOT_POLICY_NAME,
|
||||
DEFAULT_POLICY,
|
||||
} from './constants';
|
||||
|
||||
window.scrollTo = jest.fn();
|
||||
|
||||
|
@ -21,6 +26,83 @@ describe('<EditPolicy />', () => {
|
|||
server.restore();
|
||||
});
|
||||
|
||||
describe('hot phase', () => {
|
||||
describe('serialization', () => {
|
||||
beforeEach(async () => {
|
||||
httpRequestsMockHelpers.setLoadPolicies([DEFAULT_POLICY]);
|
||||
httpRequestsMockHelpers.setLoadSnapshotPolicies([]);
|
||||
|
||||
await act(async () => {
|
||||
testBed = await setup();
|
||||
});
|
||||
|
||||
const { component } = testBed;
|
||||
component.update();
|
||||
});
|
||||
|
||||
test('setting all values', async () => {
|
||||
const { actions } = testBed;
|
||||
|
||||
await actions.hot.setMaxSize('123', 'mb');
|
||||
await actions.hot.setMaxDocs('123');
|
||||
await actions.hot.setMaxAge('123', 'h');
|
||||
await actions.hot.toggleForceMerge(true);
|
||||
await actions.hot.setForcemergeSegments('123');
|
||||
await actions.hot.setBestCompression(true);
|
||||
await actions.hot.setIndexPriority('123');
|
||||
|
||||
await actions.savePolicy();
|
||||
const latestRequest = server.requests[server.requests.length - 1];
|
||||
expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"name": "my_policy",
|
||||
"phases": Object {
|
||||
"hot": Object {
|
||||
"actions": Object {
|
||||
"forcemerge": Object {
|
||||
"index_codec": "best_compression",
|
||||
"max_num_segments": 123,
|
||||
},
|
||||
"rollover": Object {
|
||||
"max_age": "123h",
|
||||
"max_docs": 123,
|
||||
"max_size": "123mb",
|
||||
},
|
||||
"set_priority": Object {
|
||||
"priority": 123,
|
||||
},
|
||||
},
|
||||
"min_age": "0ms",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('disabling rollover', async () => {
|
||||
const { actions } = testBed;
|
||||
await actions.hot.toggleRollover(false);
|
||||
await actions.savePolicy();
|
||||
const latestRequest = server.requests[server.requests.length - 1];
|
||||
expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"name": "my_policy",
|
||||
"phases": Object {
|
||||
"hot": Object {
|
||||
"actions": Object {
|
||||
"set_priority": Object {
|
||||
"priority": 100,
|
||||
},
|
||||
},
|
||||
"min_age": "0ms",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete phase', () => {
|
||||
beforeEach(async () => {
|
||||
httpRequestsMockHelpers.setLoadPolicies([DELETE_PHASE_POLICY]);
|
||||
|
|
|
@ -9,4 +9,12 @@ export type TestSubjects =
|
|||
| 'savePolicyButton'
|
||||
| 'customPolicyCallout'
|
||||
| 'noPoliciesCallout'
|
||||
| 'policiesErrorCallout';
|
||||
| 'policiesErrorCallout'
|
||||
| 'rolloverSwitch'
|
||||
| 'rolloverSettingsRequired'
|
||||
| 'hot-selectedMaxSizeStored'
|
||||
| 'hot-selectedMaxSizeStoredUnits'
|
||||
| 'hot-selectedMaxDocuments'
|
||||
| 'hot-selectedMaxAge'
|
||||
| 'hot-selectedMaxAgeUnits'
|
||||
| string;
|
||||
|
|
|
@ -33,15 +33,12 @@ import {
|
|||
positiveNumbersAboveZeroErrorMessage,
|
||||
positiveNumberRequiredMessage,
|
||||
numberRequiredMessage,
|
||||
maximumAgeRequiredMessage,
|
||||
maximumSizeRequiredMessage,
|
||||
policyNameRequiredMessage,
|
||||
policyNameStartsWithUnderscoreErrorMessage,
|
||||
policyNameContainsCommaErrorMessage,
|
||||
policyNameContainsSpaceErrorMessage,
|
||||
policyNameMustBeDifferentErrorMessage,
|
||||
policyNameAlreadyUsedErrorMessage,
|
||||
maximumDocumentsRequiredMessage,
|
||||
} from '../../public/application/services/policies/policy_validation';
|
||||
import { editPolicyHelpers } from './helpers';
|
||||
|
||||
|
@ -116,8 +113,10 @@ const expectedErrorMessages = (rendered: ReactWrapper, expectedMessages: string[
|
|||
expect(foundErrorMessage).toBe(true);
|
||||
});
|
||||
};
|
||||
const noRollover = (rendered: ReactWrapper) => {
|
||||
findTestSubject(rendered, 'rolloverSwitch').simulate('click');
|
||||
const noRollover = async (rendered: ReactWrapper) => {
|
||||
await act(async () => {
|
||||
findTestSubject(rendered, 'rolloverSwitch').simulate('click');
|
||||
});
|
||||
rendered.update();
|
||||
};
|
||||
const getNodeAttributeSelect = (rendered: ReactWrapper, phase: string) => {
|
||||
|
@ -133,7 +132,7 @@ const setPhaseAfter = (rendered: ReactWrapper, phase: string, after: string | nu
|
|||
afterInput.simulate('change', { target: { value: after } });
|
||||
rendered.update();
|
||||
};
|
||||
const setPhaseIndexPriority = (
|
||||
const setPhaseIndexPriorityLegacy = (
|
||||
rendered: ReactWrapper,
|
||||
phase: string,
|
||||
priority: string | number
|
||||
|
@ -142,12 +141,43 @@ const setPhaseIndexPriority = (
|
|||
priorityInput.simulate('change', { target: { value: priority } });
|
||||
rendered.update();
|
||||
};
|
||||
const save = (rendered: ReactWrapper) => {
|
||||
const setPhaseIndexPriority = async (
|
||||
rendered: ReactWrapper,
|
||||
phase: string,
|
||||
priority: string | number
|
||||
) => {
|
||||
const priorityInput = findTestSubject(rendered, `${phase}-phaseIndexPriority`);
|
||||
await act(async () => {
|
||||
priorityInput.simulate('change', { target: { value: priority } });
|
||||
});
|
||||
rendered.update();
|
||||
};
|
||||
const save = async (rendered: ReactWrapper) => {
|
||||
const saveButton = findTestSubject(rendered, 'savePolicyButton');
|
||||
saveButton.simulate('click');
|
||||
await act(async () => {
|
||||
saveButton.simulate('click');
|
||||
});
|
||||
rendered.update();
|
||||
};
|
||||
describe('edit policy', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
/**
|
||||
* The form lib has a short delay (setTimeout) before running and rendering
|
||||
* any validation errors. This helper advances timers and can trigger component
|
||||
* state changes.
|
||||
*/
|
||||
const waitForFormLibValidation = () => {
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(1000);
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
component = (
|
||||
<KibanaContextProvider services={{ cloud: { isCloudEnabled: false } as CloudSetup }}>
|
||||
|
@ -166,27 +196,27 @@ describe('edit policy', () => {
|
|||
httpRequestsMockHelpers.setPoliciesResponse(policies);
|
||||
});
|
||||
describe('top level form', () => {
|
||||
test('should show error when trying to save empty form', () => {
|
||||
test('should show error when trying to save empty form', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [policyNameRequiredMessage]);
|
||||
});
|
||||
test('should show error when trying to save policy name with space', () => {
|
||||
test('should show error when trying to save policy name with space', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'my policy');
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [policyNameContainsSpaceErrorMessage]);
|
||||
});
|
||||
test('should show error when trying to save policy name that is already used', () => {
|
||||
test('should show error when trying to save policy name that is already used', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'testy0');
|
||||
rendered.update();
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [policyNameAlreadyUsedErrorMessage]);
|
||||
});
|
||||
test('should show error when trying to save as new policy but using the same name', () => {
|
||||
test('should show error when trying to save as new policy but using the same name', async () => {
|
||||
component = (
|
||||
<EditPolicy
|
||||
policyName={'testy0'}
|
||||
|
@ -199,42 +229,46 @@ describe('edit policy', () => {
|
|||
findTestSubject(rendered, 'saveAsNewSwitch').simulate('click');
|
||||
rendered.update();
|
||||
setPolicyName(rendered, 'testy0');
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [policyNameMustBeDifferentErrorMessage]);
|
||||
});
|
||||
test('should show error when trying to save policy name with comma', () => {
|
||||
test('should show error when trying to save policy name with comma', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'my,policy');
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [policyNameContainsCommaErrorMessage]);
|
||||
});
|
||||
test('should show error when trying to save policy name starting with underscore', () => {
|
||||
test('should show error when trying to save policy name starting with underscore', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, '_mypolicy');
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [policyNameStartsWithUnderscoreErrorMessage]);
|
||||
});
|
||||
test('should show correct json in policy flyout', () => {
|
||||
test('should show correct json in policy flyout', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
findTestSubject(rendered, 'requestButton').simulate('click');
|
||||
|
||||
await act(async () => {
|
||||
findTestSubject(rendered, 'requestButton').simulate('click');
|
||||
});
|
||||
rendered.update();
|
||||
const json = rendered.find(`code`).text();
|
||||
const expected = `PUT _ilm/policy/<policyName>\n${JSON.stringify(
|
||||
{
|
||||
policy: {
|
||||
phases: {
|
||||
hot: {
|
||||
min_age: '0ms',
|
||||
actions: {
|
||||
rollover: {
|
||||
max_age: '30d',
|
||||
max_size: '50gb',
|
||||
},
|
||||
set_priority: {
|
||||
priority: 100,
|
||||
},
|
||||
rollover: {
|
||||
max_size: '50gb',
|
||||
max_age: '30d',
|
||||
},
|
||||
},
|
||||
min_age: '0ms',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -246,55 +280,66 @@ describe('edit policy', () => {
|
|||
});
|
||||
});
|
||||
describe('hot phase', () => {
|
||||
test('should show errors when trying to save with no max size and no max age', () => {
|
||||
test('should show errors when trying to save with no max size and no max age', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
expect(findTestSubject(rendered, 'rolloverSettingsRequired').exists()).toBeFalsy();
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
const maxSizeInput = rendered.find(`input#hot-selectedMaxSizeStored`);
|
||||
maxSizeInput.simulate('change', { target: { value: '' } });
|
||||
const maxAgeInput = rendered.find(`input#hot-selectedMaxAge`);
|
||||
maxAgeInput.simulate('change', { target: { value: '' } });
|
||||
const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored');
|
||||
await act(async () => {
|
||||
maxSizeInput.simulate('change', { target: { value: '' } });
|
||||
});
|
||||
waitForFormLibValidation();
|
||||
const maxAgeInput = findTestSubject(rendered, 'hot-selectedMaxAge');
|
||||
await act(async () => {
|
||||
maxAgeInput.simulate('change', { target: { value: '' } });
|
||||
});
|
||||
waitForFormLibValidation();
|
||||
rendered.update();
|
||||
save(rendered);
|
||||
expectedErrorMessages(rendered, [
|
||||
maximumSizeRequiredMessage,
|
||||
maximumAgeRequiredMessage,
|
||||
maximumDocumentsRequiredMessage,
|
||||
]);
|
||||
await save(rendered);
|
||||
expect(findTestSubject(rendered, 'rolloverSettingsRequired').exists()).toBeTruthy();
|
||||
});
|
||||
test('should show number above 0 required error when trying to save with -1 for max size', () => {
|
||||
test('should show number above 0 required error when trying to save with -1 for max size', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
const maxSizeInput = rendered.find(`input#hot-selectedMaxSizeStored`);
|
||||
maxSizeInput.simulate('change', { target: { value: -1 } });
|
||||
const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored');
|
||||
await act(async () => {
|
||||
maxSizeInput.simulate('change', { target: { value: '-1' } });
|
||||
});
|
||||
waitForFormLibValidation();
|
||||
rendered.update();
|
||||
save(rendered);
|
||||
expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]);
|
||||
});
|
||||
test('should show number above 0 required error when trying to save with 0 for max size', () => {
|
||||
test('should show number above 0 required error when trying to save with 0 for max size', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
const maxSizeInput = rendered.find(`input#hot-selectedMaxSizeStored`);
|
||||
maxSizeInput.simulate('change', { target: { value: 0 } });
|
||||
const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored');
|
||||
await act(async () => {
|
||||
maxSizeInput.simulate('change', { target: { value: '-1' } });
|
||||
});
|
||||
waitForFormLibValidation();
|
||||
rendered.update();
|
||||
save(rendered);
|
||||
expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]);
|
||||
});
|
||||
test('should show number above 0 required error when trying to save with -1 for max age', () => {
|
||||
test('should show number above 0 required error when trying to save with -1 for max age', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
const maxSizeInput = rendered.find(`input#hot-selectedMaxAge`);
|
||||
maxSizeInput.simulate('change', { target: { value: -1 } });
|
||||
const maxAgeInput = findTestSubject(rendered, 'hot-selectedMaxAge');
|
||||
await act(async () => {
|
||||
maxAgeInput.simulate('change', { target: { value: '-1' } });
|
||||
});
|
||||
waitForFormLibValidation();
|
||||
rendered.update();
|
||||
save(rendered);
|
||||
expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]);
|
||||
});
|
||||
test('should show number above 0 required error when trying to save with 0 for max age', () => {
|
||||
test('should show number above 0 required error when trying to save with 0 for max age', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
const maxSizeInput = rendered.find(`input#hot-selectedMaxAge`);
|
||||
maxSizeInput.simulate('change', { target: { value: 0 } });
|
||||
const maxAgeInput = findTestSubject(rendered, 'hot-selectedMaxAge');
|
||||
await act(async () => {
|
||||
maxAgeInput.simulate('change', { target: { value: '0' } });
|
||||
});
|
||||
waitForFormLibValidation();
|
||||
rendered.update();
|
||||
save(rendered);
|
||||
expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]);
|
||||
});
|
||||
test('should show forcemerge input when rollover enabled', () => {
|
||||
|
@ -302,22 +347,27 @@ describe('edit policy', () => {
|
|||
setPolicyName(rendered, 'mypolicy');
|
||||
expect(findTestSubject(rendered, 'hot-forceMergeSwitch').exists()).toBeTruthy();
|
||||
});
|
||||
test('should hide forcemerge input when rollover is disabled', () => {
|
||||
test('should hide forcemerge input when rollover is disabled', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
waitForFormLibValidation();
|
||||
rendered.update();
|
||||
expect(findTestSubject(rendered, 'hot-forceMergeSwitch').exists()).toBeFalsy();
|
||||
});
|
||||
test('should show positive number required above zero error when trying to save hot phase with 0 for force merge', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
findTestSubject(rendered, 'hot-forceMergeSwitch').simulate('click');
|
||||
act(() => {
|
||||
findTestSubject(rendered, 'hot-forceMergeSwitch').simulate('click');
|
||||
});
|
||||
rendered.update();
|
||||
const forcemergeInput = findTestSubject(rendered, 'hot-selectedForceMergeSegments');
|
||||
forcemergeInput.simulate('change', { target: { value: '0' } });
|
||||
await act(async () => {
|
||||
forcemergeInput.simulate('change', { target: { value: '0' } });
|
||||
});
|
||||
waitForFormLibValidation();
|
||||
rendered.update();
|
||||
save(rendered);
|
||||
expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]);
|
||||
});
|
||||
test('should show positive number above 0 required error when trying to save hot phase with -1 for force merge', async () => {
|
||||
|
@ -326,18 +376,22 @@ describe('edit policy', () => {
|
|||
findTestSubject(rendered, 'hot-forceMergeSwitch').simulate('click');
|
||||
rendered.update();
|
||||
const forcemergeInput = findTestSubject(rendered, 'hot-selectedForceMergeSegments');
|
||||
forcemergeInput.simulate('change', { target: { value: '-1' } });
|
||||
await act(async () => {
|
||||
forcemergeInput.simulate('change', { target: { value: '-1' } });
|
||||
});
|
||||
waitForFormLibValidation();
|
||||
rendered.update();
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]);
|
||||
});
|
||||
test('should show positive number required error when trying to save with -1 for index priority', () => {
|
||||
test('should show positive number required error when trying to save with -1 for index priority', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
setPhaseIndexPriority(rendered, 'hot', '-1');
|
||||
save(rendered);
|
||||
expectedErrorMessages(rendered, [positiveNumberRequiredMessage]);
|
||||
await setPhaseIndexPriority(rendered, 'hot', '-1');
|
||||
waitForFormLibValidation();
|
||||
rendered.update();
|
||||
expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]);
|
||||
});
|
||||
});
|
||||
describe('warm phase', () => {
|
||||
|
@ -351,44 +405,44 @@ describe('edit policy', () => {
|
|||
|
||||
test('should show number required error when trying to save empty warm phase', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
setPhaseAfter(rendered, 'warm', '');
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [numberRequiredMessage]);
|
||||
});
|
||||
test('should allow 0 for phase timing', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
setPhaseAfter(rendered, 'warm', '0');
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, []);
|
||||
});
|
||||
test('should show positive number required error when trying to save warm phase with -1 for after', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
setPhaseAfter(rendered, 'warm', '-1');
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [positiveNumberRequiredMessage]);
|
||||
});
|
||||
test('should show positive number required error when trying to save warm phase with -1 for index priority', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
setPhaseAfter(rendered, 'warm', '1');
|
||||
setPhaseIndexPriority(rendered, 'warm', '-1');
|
||||
save(rendered);
|
||||
setPhaseIndexPriorityLegacy(rendered, 'warm', '-1');
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [positiveNumberRequiredMessage]);
|
||||
});
|
||||
test('should show positive number required above zero error when trying to save warm phase with 0 for shrink', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
findTestSubject(rendered, 'shrinkSwitch').simulate('click');
|
||||
|
@ -397,12 +451,12 @@ describe('edit policy', () => {
|
|||
const shrinkInput = rendered.find('input#warm-selectedPrimaryShardCount');
|
||||
shrinkInput.simulate('change', { target: { value: '0' } });
|
||||
rendered.update();
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]);
|
||||
});
|
||||
test('should show positive number above 0 required error when trying to save warm phase with -1 for shrink', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
setPhaseAfter(rendered, 'warm', '1');
|
||||
|
@ -411,12 +465,12 @@ describe('edit policy', () => {
|
|||
const shrinkInput = rendered.find('input#warm-selectedPrimaryShardCount');
|
||||
shrinkInput.simulate('change', { target: { value: '-1' } });
|
||||
rendered.update();
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]);
|
||||
});
|
||||
test('should show positive number required above zero error when trying to save warm phase with 0 for force merge', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
setPhaseAfter(rendered, 'warm', '1');
|
||||
|
@ -425,12 +479,12 @@ describe('edit policy', () => {
|
|||
const forcemergeInput = findTestSubject(rendered, 'warm-selectedForceMergeSegments');
|
||||
forcemergeInput.simulate('change', { target: { value: '0' } });
|
||||
rendered.update();
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]);
|
||||
});
|
||||
test('should show positive number above 0 required error when trying to save warm phase with -1 for force merge', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
setPhaseAfter(rendered, 'warm', '1');
|
||||
|
@ -439,13 +493,13 @@ describe('edit policy', () => {
|
|||
const forcemergeInput = findTestSubject(rendered, 'warm-selectedForceMergeSegments');
|
||||
forcemergeInput.simulate('change', { target: { value: '-1' } });
|
||||
rendered.update();
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]);
|
||||
});
|
||||
test('should show spinner for node attributes input when loading', async () => {
|
||||
server.respondImmediately = false;
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy();
|
||||
|
@ -459,7 +513,7 @@ describe('edit policy', () => {
|
|||
isUsingDeprecatedDataRoleConfig: false,
|
||||
});
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
|
||||
|
@ -469,7 +523,7 @@ describe('edit policy', () => {
|
|||
});
|
||||
test('should show node attributes input when attributes exist', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
|
||||
|
@ -481,7 +535,7 @@ describe('edit policy', () => {
|
|||
});
|
||||
test('should show view node attributes link when attribute selected and show flyout when clicked', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
|
||||
|
@ -508,7 +562,7 @@ describe('edit policy', () => {
|
|||
isUsingDeprecatedDataRoleConfig: false,
|
||||
});
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
|
||||
|
@ -521,7 +575,7 @@ describe('edit policy', () => {
|
|||
isUsingDeprecatedDataRoleConfig: false,
|
||||
});
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
|
||||
|
@ -534,7 +588,7 @@ describe('edit policy', () => {
|
|||
isUsingDeprecatedDataRoleConfig: false,
|
||||
});
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
|
||||
|
@ -551,26 +605,26 @@ describe('edit policy', () => {
|
|||
});
|
||||
test('should allow 0 for phase timing', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'cold');
|
||||
setPhaseAfter(rendered, 'cold', '0');
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, []);
|
||||
});
|
||||
test('should show positive number required error when trying to save cold phase with -1 for after', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'cold');
|
||||
setPhaseAfter(rendered, 'cold', '-1');
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [positiveNumberRequiredMessage]);
|
||||
});
|
||||
test('should show spinner for node attributes input when loading', async () => {
|
||||
server.respondImmediately = false;
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'cold');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy();
|
||||
|
@ -584,7 +638,7 @@ describe('edit policy', () => {
|
|||
isUsingDeprecatedDataRoleConfig: false,
|
||||
});
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'cold');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
|
||||
|
@ -594,7 +648,7 @@ describe('edit policy', () => {
|
|||
});
|
||||
test('should show node attributes input when attributes exist', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'cold');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
|
||||
|
@ -606,7 +660,7 @@ describe('edit policy', () => {
|
|||
});
|
||||
test('should show view node attributes link when attribute selected and show flyout when clicked', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'cold');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
|
||||
|
@ -628,12 +682,12 @@ describe('edit policy', () => {
|
|||
});
|
||||
test('should show positive number required error when trying to save with -1 for index priority', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'cold');
|
||||
setPhaseAfter(rendered, 'cold', '1');
|
||||
setPhaseIndexPriority(rendered, 'cold', '-1');
|
||||
save(rendered);
|
||||
setPhaseIndexPriorityLegacy(rendered, 'cold', '-1');
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [positiveNumberRequiredMessage]);
|
||||
});
|
||||
test('should show default allocation warning when no node roles are found', async () => {
|
||||
|
@ -643,7 +697,7 @@ describe('edit policy', () => {
|
|||
isUsingDeprecatedDataRoleConfig: false,
|
||||
});
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'cold');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
|
||||
|
@ -656,7 +710,7 @@ describe('edit policy', () => {
|
|||
isUsingDeprecatedDataRoleConfig: false,
|
||||
});
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'cold');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
|
||||
|
@ -669,7 +723,7 @@ describe('edit policy', () => {
|
|||
isUsingDeprecatedDataRoleConfig: false,
|
||||
});
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'cold');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
|
||||
|
@ -679,20 +733,20 @@ describe('edit policy', () => {
|
|||
describe('delete phase', () => {
|
||||
test('should allow 0 for phase timing', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'delete');
|
||||
setPhaseAfter(rendered, 'delete', '0');
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, []);
|
||||
});
|
||||
test('should show positive number required error when trying to save delete phase with -1 for after', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'delete');
|
||||
setPhaseAfter(rendered, 'delete', '-1');
|
||||
save(rendered);
|
||||
await save(rendered);
|
||||
expectedErrorMessages(rendered, [positiveNumberRequiredMessage]);
|
||||
});
|
||||
});
|
||||
|
@ -707,7 +761,7 @@ describe('edit policy', () => {
|
|||
isUsingDeprecatedDataRoleConfig: true,
|
||||
});
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
|
||||
|
@ -746,7 +800,7 @@ describe('edit policy', () => {
|
|||
isUsingDeprecatedDataRoleConfig: true,
|
||||
});
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
|
||||
|
@ -767,7 +821,7 @@ describe('edit policy', () => {
|
|||
isUsingDeprecatedDataRoleConfig: false,
|
||||
});
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'warm');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
|
||||
|
@ -785,7 +839,7 @@ describe('edit policy', () => {
|
|||
isUsingDeprecatedDataRoleConfig: false,
|
||||
});
|
||||
const rendered = mountWithIntl(component);
|
||||
noRollover(rendered);
|
||||
await noRollover(rendered);
|
||||
setPolicyName(rendered, 'mypolicy');
|
||||
await activatePhase(rendered, 'cold');
|
||||
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
|
||||
|
|
|
@ -107,10 +107,9 @@ export interface ForcemergeAction {
|
|||
index_codec?: 'best_compression';
|
||||
}
|
||||
|
||||
export interface Policy {
|
||||
export interface LegacyPolicy {
|
||||
name: string;
|
||||
phases: {
|
||||
hot: HotPhase;
|
||||
warm: WarmPhase;
|
||||
cold: ColdPhase;
|
||||
delete: DeletePhase;
|
||||
|
@ -155,18 +154,6 @@ export interface PhaseWithForcemergeAction {
|
|||
bestCompressionEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface HotPhase
|
||||
extends CommonPhaseSettings,
|
||||
PhaseWithIndexPriority,
|
||||
PhaseWithForcemergeAction {
|
||||
rolloverEnabled: boolean;
|
||||
selectedMaxSizeStored: string;
|
||||
selectedMaxSizeStoredUnits: string;
|
||||
selectedMaxDocuments: string;
|
||||
selectedMaxAge: string;
|
||||
selectedMaxAgeUnits: string;
|
||||
}
|
||||
|
||||
export interface WarmPhase
|
||||
extends CommonPhaseSettings,
|
||||
PhaseWithMinAge,
|
||||
|
|
|
@ -5,4 +5,5 @@
|
|||
*/
|
||||
|
||||
export * from './policy';
|
||||
|
||||
export * from './ui_metric';
|
||||
|
|
|
@ -8,22 +8,24 @@ import {
|
|||
SerializedPhase,
|
||||
ColdPhase,
|
||||
DeletePhase,
|
||||
HotPhase,
|
||||
WarmPhase,
|
||||
SerializedPolicy,
|
||||
} from '../../../common/types';
|
||||
|
||||
export const defaultNewHotPhase: HotPhase = {
|
||||
phaseEnabled: true,
|
||||
rolloverEnabled: true,
|
||||
selectedMaxAge: '30',
|
||||
selectedMaxAgeUnits: 'd',
|
||||
selectedMaxSizeStored: '50',
|
||||
selectedMaxSizeStoredUnits: 'gb',
|
||||
forceMergeEnabled: false,
|
||||
selectedForceMergeSegments: '',
|
||||
bestCompressionEnabled: false,
|
||||
phaseIndexPriority: '100',
|
||||
selectedMaxDocuments: '',
|
||||
export const defaultSetPriority: string = '100';
|
||||
|
||||
export const defaultPolicy: SerializedPolicy = {
|
||||
name: '',
|
||||
phases: {
|
||||
hot: {
|
||||
actions: {
|
||||
rollover: {
|
||||
max_age: '30d',
|
||||
max_size: '50gb',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const defaultNewWarmPhase: WarmPhase = {
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* PLEASE NOTE: This component is currently duplicated. A version of this component wired up with
|
||||
* the form lib lives in ./phases/shared
|
||||
*/
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiDescribedFormGroup,
|
||||
|
|
|
@ -9,7 +9,7 @@ import { EuiFormRow, EuiFormRowProps } from '@elastic/eui';
|
|||
|
||||
type Props = EuiFormRowProps & {
|
||||
isShowingErrors: boolean;
|
||||
errors?: string[];
|
||||
errors?: string | string[] | null;
|
||||
};
|
||||
|
||||
export const ErrableFormRow: React.FunctionComponent<Props> = ({
|
||||
|
@ -18,8 +18,13 @@ export const ErrableFormRow: React.FunctionComponent<Props> = ({
|
|||
children,
|
||||
...rest
|
||||
}) => {
|
||||
const _errors = errors ? (Array.isArray(errors) ? errors : [errors]) : undefined;
|
||||
return (
|
||||
<EuiFormRow isInvalid={errors && isShowingErrors && errors.length > 0} error={errors} {...rest}>
|
||||
<EuiFormRow
|
||||
isInvalid={isShowingErrors || (_errors && _errors.length > 0)}
|
||||
error={errors}
|
||||
{...rest}
|
||||
>
|
||||
<Fragment>
|
||||
{Children.map(children, (child) =>
|
||||
cloneElement(child as ReactElement, {
|
||||
|
|
|
@ -22,3 +22,5 @@ export {
|
|||
} from './data_tier_allocation';
|
||||
export { DescribedFormField } from './described_form_field';
|
||||
export { Forcemerge } from './forcemerge';
|
||||
|
||||
export * from './phases';
|
||||
|
|
|
@ -212,7 +212,7 @@ export const MinAgeInput = <Phase extends PhaseWithMinAge>({
|
|||
<EuiFieldNumber
|
||||
id={`${phase}-${selectedMinimumAgeProperty}`}
|
||||
value={phaseData.selectedMinimumAge}
|
||||
onChange={async (e) => {
|
||||
onChange={(e) => {
|
||||
setPhaseData(selectedMinimumAgeProperty, e.target.value);
|
||||
}}
|
||||
min={0}
|
||||
|
|
|
@ -10,8 +10,11 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { EuiFieldNumber, EuiDescribedFormGroup, EuiSwitch, EuiTextColor } from '@elastic/eui';
|
||||
|
||||
import { ColdPhase as ColdPhaseInterface, Phases } from '../../../../../common/types';
|
||||
import { PhaseValidationErrors } from '../../../services/policies/policy_validation';
|
||||
import { ColdPhase as ColdPhaseInterface, Phases } from '../../../../../../common/types';
|
||||
|
||||
import { useFormData } from '../../../../../shared_imports';
|
||||
|
||||
import { PhaseValidationErrors } from '../../../../services/policies/policy_validation';
|
||||
|
||||
import {
|
||||
LearnMoreLink,
|
||||
|
@ -22,9 +25,9 @@ import {
|
|||
SetPriorityInput,
|
||||
MinAgeInput,
|
||||
DescribedFormField,
|
||||
} from '../components';
|
||||
} from '../';
|
||||
|
||||
import { DataTierAllocationField } from './shared';
|
||||
import { DataTierAllocationField, useRolloverPath } from './shared';
|
||||
|
||||
const i18nTexts = {
|
||||
freezeLabel: i18n.translate('xpack.indexLifecycleMgmt.coldPhase.freezeIndexLabel', {
|
||||
|
@ -46,15 +49,17 @@ interface Props {
|
|||
phaseData: ColdPhaseInterface;
|
||||
isShowingErrors: boolean;
|
||||
errors?: PhaseValidationErrors<ColdPhaseInterface>;
|
||||
hotPhaseRolloverEnabled: boolean;
|
||||
}
|
||||
export const ColdPhase: FunctionComponent<Props> = ({
|
||||
setPhaseData,
|
||||
phaseData,
|
||||
errors,
|
||||
isShowingErrors,
|
||||
hotPhaseRolloverEnabled,
|
||||
}) => {
|
||||
const [{ [useRolloverPath]: hotPhaseRolloverEnabled }] = useFormData({
|
||||
watch: [useRolloverPath],
|
||||
});
|
||||
|
||||
return (
|
||||
<div id="coldPhaseContent" aria-live="polite" role="region">
|
||||
<>
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* 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 React, { FunctionComponent, Fragment } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiDescribedFormGroup, EuiSwitch, EuiTextColor, EuiFormRow } from '@elastic/eui';
|
||||
|
||||
import { DeletePhase as DeletePhaseInterface, Phases } from '../../../../../../common/types';
|
||||
|
||||
import { useFormData } from '../../../../../shared_imports';
|
||||
|
||||
import { PhaseValidationErrors } from '../../../../services/policies/policy_validation';
|
||||
|
||||
import {
|
||||
ActiveBadge,
|
||||
LearnMoreLink,
|
||||
OptionalLabel,
|
||||
PhaseErrorMessage,
|
||||
MinAgeInput,
|
||||
SnapshotPolicies,
|
||||
} from '../';
|
||||
import { useRolloverPath } from './shared';
|
||||
|
||||
const deleteProperty: keyof Phases = 'delete';
|
||||
const phaseProperty = (propertyName: keyof DeletePhaseInterface) => propertyName;
|
||||
|
||||
interface Props {
|
||||
setPhaseData: (key: keyof DeletePhaseInterface & string, value: string | boolean) => void;
|
||||
phaseData: DeletePhaseInterface;
|
||||
isShowingErrors: boolean;
|
||||
errors?: PhaseValidationErrors<DeletePhaseInterface>;
|
||||
getUrlForApp: (
|
||||
appId: string,
|
||||
options?: {
|
||||
path?: string;
|
||||
absolute?: boolean;
|
||||
}
|
||||
) => string;
|
||||
}
|
||||
|
||||
export const DeletePhase: FunctionComponent<Props> = ({
|
||||
setPhaseData,
|
||||
phaseData,
|
||||
errors,
|
||||
isShowingErrors,
|
||||
getUrlForApp,
|
||||
}) => {
|
||||
const [{ [useRolloverPath]: hotPhaseRolloverEnabled }] = useFormData({
|
||||
watch: [useRolloverPath],
|
||||
});
|
||||
|
||||
return (
|
||||
<div id="deletePhaseContent" aria-live="polite" role="region">
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<div>
|
||||
<h2 className="eui-displayInlineBlock eui-alignMiddle">
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.deletePhase.deletePhaseLabel"
|
||||
defaultMessage="Delete phase"
|
||||
/>
|
||||
</h2>{' '}
|
||||
{phaseData.phaseEnabled && !isShowingErrors ? <ActiveBadge /> : null}
|
||||
<PhaseErrorMessage isShowingErrors={isShowingErrors} />
|
||||
</div>
|
||||
}
|
||||
titleSize="s"
|
||||
description={
|
||||
<Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.deletePhase.deletePhaseDescriptionText"
|
||||
defaultMessage="You no longer need your index. You can define when it is safe to delete it."
|
||||
/>
|
||||
</p>
|
||||
<EuiSwitch
|
||||
data-test-subj="enablePhaseSwitch-delete"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.deletePhase.activateWarmPhaseSwitchLabel"
|
||||
defaultMessage="Activate delete phase"
|
||||
/>
|
||||
}
|
||||
id={`${deleteProperty}-${phaseProperty('phaseEnabled')}`}
|
||||
checked={phaseData.phaseEnabled}
|
||||
onChange={(e) => {
|
||||
setPhaseData(phaseProperty('phaseEnabled'), e.target.checked);
|
||||
}}
|
||||
aria-controls="deletePhaseContent"
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
{phaseData.phaseEnabled ? (
|
||||
<MinAgeInput<DeletePhaseInterface>
|
||||
errors={errors}
|
||||
phaseData={phaseData}
|
||||
phase={deleteProperty}
|
||||
isShowingErrors={isShowingErrors}
|
||||
setPhaseData={setPhaseData}
|
||||
rolloverEnabled={hotPhaseRolloverEnabled}
|
||||
/>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</EuiDescribedFormGroup>
|
||||
{phaseData.phaseEnabled ? (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.deletePhase.waitForSnapshotTitle"
|
||||
defaultMessage="Wait for snapshot policy"
|
||||
/>
|
||||
</h3>
|
||||
}
|
||||
description={
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.deletePhase.waitForSnapshotDescription"
|
||||
defaultMessage="Specify a snapshot policy to be executed before the deletion of the index. This ensures that a snapshot of the deleted index is available."
|
||||
/>{' '}
|
||||
<LearnMoreLink docPath="ilm-wait-for-snapshot.html" />
|
||||
</EuiTextColor>
|
||||
}
|
||||
titleSize="xs"
|
||||
fullWidth
|
||||
>
|
||||
<EuiFormRow
|
||||
id="deletePhaseWaitForSnapshot"
|
||||
label={
|
||||
<Fragment>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.deletePhase.waitForSnapshotLabel"
|
||||
defaultMessage="Snapshot policy name"
|
||||
/>
|
||||
<OptionalLabel />
|
||||
</Fragment>
|
||||
}
|
||||
>
|
||||
<SnapshotPolicies
|
||||
value={phaseData.waitForSnapshotPolicy}
|
||||
onChange={(value) => setPhaseData(phaseProperty('waitForSnapshotPolicy'), value)}
|
||||
getUrlForApp={getUrlForApp}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const maxSizeStoredUnits = [
|
||||
{
|
||||
value: 'gb',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.gigabytesLabel', {
|
||||
defaultMessage: 'gigabytes',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'mb',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.megabytesLabel', {
|
||||
defaultMessage: 'megabytes',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'b',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.bytesLabel', {
|
||||
defaultMessage: 'bytes',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'kb',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.kilobytesLabel', {
|
||||
defaultMessage: 'kilobytes',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'tb',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.terabytesLabel', {
|
||||
defaultMessage: 'terabytes',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'pb',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.petabytesLabel', {
|
||||
defaultMessage: 'petabytes',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
export const maxAgeUnits = [
|
||||
{
|
||||
value: 'd',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.daysLabel', {
|
||||
defaultMessage: 'days',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'h',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.hoursLabel', {
|
||||
defaultMessage: 'hours',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'm',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.minutesLabel', {
|
||||
defaultMessage: 'minutes',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 's',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.secondsLabel', {
|
||||
defaultMessage: 'seconds',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'ms',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.millisecondsLabel', {
|
||||
defaultMessage: 'milliseconds',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'micros',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.microsecondsLabel', {
|
||||
defaultMessage: 'microseconds',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'nanos',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.nanosecondsLabel', {
|
||||
defaultMessage: 'nanoseconds',
|
||||
}),
|
||||
},
|
||||
];
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* 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 React, { Fragment, FunctionComponent, useEffect, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiDescribedFormGroup,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { Phases } from '../../../../../../../common/types';
|
||||
|
||||
import {
|
||||
useFormContext,
|
||||
useFormData,
|
||||
UseField,
|
||||
SelectField,
|
||||
ToggleField,
|
||||
NumericField,
|
||||
} from '../../../../../../shared_imports';
|
||||
|
||||
import { ROLLOVER_EMPTY_VALIDATION } from '../../../form_validations';
|
||||
|
||||
import { ROLLOVER_FORM_PATHS } from '../../../constants';
|
||||
|
||||
import { LearnMoreLink, ActiveBadge, PhaseErrorMessage } from '../../';
|
||||
|
||||
import { Forcemerge, SetPriorityInput } from '../shared';
|
||||
|
||||
import { maxSizeStoredUnits, maxAgeUnits } from './constants';
|
||||
|
||||
import { i18nTexts } from './i18n_texts';
|
||||
|
||||
import { useRolloverPath } from '../shared';
|
||||
|
||||
const hotProperty: keyof Phases = 'hot';
|
||||
|
||||
export const HotPhase: FunctionComponent<{ setWarmPhaseOnRollover: (v: boolean) => void }> = ({
|
||||
setWarmPhaseOnRollover,
|
||||
}) => {
|
||||
const [{ [useRolloverPath]: isRolloverEnabled }] = useFormData({ watch: [useRolloverPath] });
|
||||
const form = useFormContext();
|
||||
|
||||
const isShowingErrors = form.isValid === false;
|
||||
const [showEmptyRolloverFieldsError, setShowEmptyRolloverFieldsError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setWarmPhaseOnRollover(isRolloverEnabled ?? false);
|
||||
}, [setWarmPhaseOnRollover, isRolloverEnabled]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<div>
|
||||
<h2 className="eui-displayInlineBlock eui-alignMiddle">
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.hotPhase.hotPhaseLabel"
|
||||
defaultMessage="Hot phase"
|
||||
/>
|
||||
</h2>{' '}
|
||||
{isShowingErrors ? null : <ActiveBadge />}
|
||||
<PhaseErrorMessage isShowingErrors={isShowingErrors} />
|
||||
</div>
|
||||
}
|
||||
titleSize="s"
|
||||
description={
|
||||
<Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.hotPhase.hotPhaseDescriptionMessage"
|
||||
defaultMessage="This phase is required. You are actively querying and
|
||||
writing to your index. For faster updates, you can roll over the index when it gets too big or too old."
|
||||
/>
|
||||
</p>
|
||||
</Fragment>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<UseField<boolean>
|
||||
key="_meta.hot.useRollover"
|
||||
path="_meta.hot.useRollover"
|
||||
component={ToggleField}
|
||||
componentProps={{
|
||||
hasEmptyLabelSpace: true,
|
||||
fullWidth: false,
|
||||
helpText: (
|
||||
<>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.hotPhase.rolloverDescriptionMessage"
|
||||
defaultMessage="The new index created by rollover is added
|
||||
to the index alias and designated as the write index."
|
||||
/>
|
||||
</p>
|
||||
<LearnMoreLink
|
||||
text={
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.hotPhase.learnAboutRolloverLinkText"
|
||||
defaultMessage="Learn about rollover"
|
||||
/>
|
||||
}
|
||||
docPath="indices-rollover-index.html"
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
),
|
||||
euiFieldProps: {
|
||||
'data-test-subj': 'rolloverSwitch',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{isRolloverEnabled && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
{showEmptyRolloverFieldsError && (
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={i18nTexts.rollOverConfigurationCallout.title}
|
||||
data-test-subj="rolloverSettingsRequired"
|
||||
color="danger"
|
||||
>
|
||||
<div>{i18nTexts.rollOverConfigurationCallout.body}</div>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
)}
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem style={{ maxWidth: 188 }}>
|
||||
<UseField path={ROLLOVER_FORM_PATHS.maxSize}>
|
||||
{(field) => {
|
||||
const showErrorCallout = field.errors.some(
|
||||
(e) => e.validationType === ROLLOVER_EMPTY_VALIDATION
|
||||
);
|
||||
if (showErrorCallout !== showEmptyRolloverFieldsError) {
|
||||
setShowEmptyRolloverFieldsError(showErrorCallout);
|
||||
}
|
||||
return (
|
||||
<NumericField
|
||||
field={field}
|
||||
euiFieldProps={{
|
||||
'data-test-subj': `${hotProperty}-selectedMaxSizeStored`,
|
||||
min: 1,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</UseField>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ maxWidth: 188 }}>
|
||||
<UseField
|
||||
key="_meta.hot.maxStorageSizeUnit"
|
||||
path="_meta.hot.maxStorageSizeUnit"
|
||||
component={SelectField}
|
||||
componentProps={{
|
||||
'data-test-subj': `${hotProperty}-selectedMaxSizeStoredUnits`,
|
||||
hasEmptyLabelSpace: true,
|
||||
euiFieldProps: {
|
||||
options: maxSizeStoredUnits,
|
||||
'aria-label': i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.hotPhase.maximumIndexSizeUnitsAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Maximum index size units',
|
||||
}
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem style={{ maxWidth: 188 }}>
|
||||
<UseField
|
||||
path={ROLLOVER_FORM_PATHS.maxDocs}
|
||||
component={NumericField}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
'data-test-subj': `${hotProperty}-selectedMaxDocuments`,
|
||||
min: 1,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem style={{ maxWidth: 188 }}>
|
||||
<UseField
|
||||
path={ROLLOVER_FORM_PATHS.maxAge}
|
||||
component={NumericField}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
'data-test-subj': `${hotProperty}-selectedMaxAge`,
|
||||
min: 1,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ maxWidth: 188 }}>
|
||||
<UseField
|
||||
key="_meta.hot.maxAgeUnit"
|
||||
path="_meta.hot.maxAgeUnit"
|
||||
component={SelectField}
|
||||
componentProps={{
|
||||
'data-test-subj': `${hotProperty}-selectedMaxAgeUnits`,
|
||||
hasEmptyLabelSpace: true,
|
||||
euiFieldProps: {
|
||||
'aria-label': i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.hotPhase.maximumAgeUnitsAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Maximum age units',
|
||||
}
|
||||
),
|
||||
options: maxAgeUnits,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
</EuiDescribedFormGroup>
|
||||
{isRolloverEnabled && <Forcemerge phase="hot" />}
|
||||
<SetPriorityInput phase={hotProperty} />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const i18nTexts = {
|
||||
maximumAgeRequiredMessage: i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.editPolicy.maximumAgeMissingError',
|
||||
{
|
||||
defaultMessage: 'A maximum age is required.',
|
||||
}
|
||||
),
|
||||
maximumSizeRequiredMessage: i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.editPolicy.maximumIndexSizeMissingError',
|
||||
{
|
||||
defaultMessage: 'A maximum index size is required.',
|
||||
}
|
||||
),
|
||||
maximumDocumentsRequiredMessage: i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.editPolicy.maximumDocumentsMissingError',
|
||||
{
|
||||
defaultMessage: 'Maximum documents is required.',
|
||||
}
|
||||
),
|
||||
rollOverConfigurationCallout: {
|
||||
title: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.rolloverConfigurationError.title', {
|
||||
defaultMessage: 'Invalid rollover configuration',
|
||||
}),
|
||||
body: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.rolloverConfigurationError.body', {
|
||||
defaultMessage:
|
||||
'A value for one of maximum size, maximum documents, or maximum age is required.',
|
||||
}),
|
||||
},
|
||||
};
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { DataTierAllocationField } from './data_tier_allocation_field';
|
||||
export { HotPhase } from './hot_phase';
|
|
@ -5,6 +5,9 @@
|
|||
*/
|
||||
|
||||
export { HotPhase } from './hot_phase';
|
||||
|
||||
export { WarmPhase } from './warm_phase';
|
||||
|
||||
export { ColdPhase } from './cold_phase';
|
||||
|
||||
export { DeletePhase } from './delete_phase';
|
|
@ -8,11 +8,11 @@ import React, { FunctionComponent } from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiDescribedFormGroup, EuiFormRow, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { useKibana } from '../../../../../shared_imports';
|
||||
import { PhaseWithAllocationAction, PhaseWithAllocation } from '../../../../../../common/types';
|
||||
import { PhaseValidationErrors } from '../../../../services/policies/policy_validation';
|
||||
import { getAvailableNodeRoleForPhase } from '../../../../lib/data_tiers';
|
||||
import { isNodeRoleFirstPreference } from '../../../../lib/data_tiers/is_node_role_first_preference';
|
||||
import { useKibana } from '../../../../../../shared_imports';
|
||||
import { PhaseWithAllocationAction, PhaseWithAllocation } from '../../../../../../../common/types';
|
||||
import { PhaseValidationErrors } from '../../../../../services/policies/policy_validation';
|
||||
import { getAvailableNodeRoleForPhase } from '../../../../../lib/data_tiers';
|
||||
import { isNodeRoleFirstPreference } from '../../../../../lib/data_tiers/is_node_role_first_preference';
|
||||
|
||||
import {
|
||||
DataTierAllocation,
|
||||
|
@ -20,7 +20,7 @@ import {
|
|||
NoNodeAttributesWarning,
|
||||
NodesDataProvider,
|
||||
CloudDataTierCallout,
|
||||
} from '../../components/data_tier_allocation';
|
||||
} from '../../data_tier_allocation';
|
||||
|
||||
const i18nTexts = {
|
||||
title: i18n.translate('xpack.indexLifecycleMgmt.common.dataTier.title', {
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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 { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiDescribedFormGroup, EuiSpacer, EuiTextColor } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
import { Phases } from '../../../../../../../common/types';
|
||||
|
||||
import { UseField, ToggleField, NumericField, useFormData } from '../../../../../../shared_imports';
|
||||
|
||||
import { i18nTexts } from '../../../i18n_texts';
|
||||
|
||||
import { LearnMoreLink } from '../../';
|
||||
|
||||
interface Props {
|
||||
phase: keyof Phases & string;
|
||||
}
|
||||
|
||||
const forceMergeEnabledPath = '_meta.hot.forceMergeEnabled';
|
||||
|
||||
export const Forcemerge: React.FunctionComponent<Props> = ({ phase }) => {
|
||||
const [{ [forceMergeEnabledPath]: forceMergeEnabled }] = useFormData({
|
||||
watch: [forceMergeEnabledPath],
|
||||
});
|
||||
return (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.forceMerge.enableText"
|
||||
defaultMessage="Force merge"
|
||||
/>
|
||||
</h3>
|
||||
}
|
||||
description={
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.forceMerge.enableExplanationText"
|
||||
defaultMessage="Reduce the number of segments in your shard by merging smaller files and clearing deleted ones."
|
||||
/>{' '}
|
||||
<LearnMoreLink docPath="indices-forcemerge.html" />
|
||||
</EuiTextColor>
|
||||
}
|
||||
titleSize="xs"
|
||||
fullWidth
|
||||
>
|
||||
<UseField
|
||||
key={forceMergeEnabledPath}
|
||||
path={forceMergeEnabledPath}
|
||||
component={ToggleField}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
'data-test-subj': `${phase}-forceMergeSwitch`,
|
||||
'aria-label': i18nTexts.editPolicy.forceMergeEnabledFieldLabel,
|
||||
'aria-controls': 'forcemergeContent',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<div id="forcemergeContent" aria-live="polite" role="region">
|
||||
{forceMergeEnabled && (
|
||||
<>
|
||||
<UseField
|
||||
key={`phases.${phase}.actions.forcemerge.max_num_segments`}
|
||||
path={`phases.${phase}.actions.forcemerge.max_num_segments`}
|
||||
component={NumericField}
|
||||
componentProps={{
|
||||
fullWidth: false,
|
||||
euiFieldProps: {
|
||||
'data-test-subj': `${phase}-selectedForceMergeSegments`,
|
||||
min: 1,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<UseField
|
||||
key="_meta.hot.bestCompression"
|
||||
path="_meta.hot.bestCompression"
|
||||
component={ToggleField}
|
||||
componentProps={{
|
||||
hasEmptyLabelSpace: true,
|
||||
euiFieldProps: {
|
||||
'data-test-subj': `${phase}-bestCompression`,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { useRolloverPath } from '../../../constants';
|
||||
|
||||
export { DataTierAllocationField } from './data_tier_allocation_field';
|
||||
|
||||
export { Forcemerge } from './forcemerge_field';
|
||||
|
||||
export { SetPriorityInput } from './set_priority_input';
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 React, { FunctionComponent } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiTextColor, EuiDescribedFormGroup } from '@elastic/eui';
|
||||
|
||||
import { PhaseWithIndexPriority, Phases } from '../../../../../../../common/types';
|
||||
|
||||
import { UseField, NumericField } from '../../../../../../shared_imports';
|
||||
|
||||
import { propertyof } from '../../../../../services/policies/policy_validation';
|
||||
|
||||
import { LearnMoreLink } from '../../';
|
||||
|
||||
interface Props {
|
||||
phase: keyof Phases & string;
|
||||
}
|
||||
|
||||
export const SetPriorityInput: FunctionComponent<Props> = ({ phase }) => {
|
||||
const phaseIndexPriorityProperty = propertyof<PhaseWithIndexPriority>('phaseIndexPriority');
|
||||
return (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.indexPriorityText"
|
||||
defaultMessage="Index priority"
|
||||
/>
|
||||
</h3>
|
||||
}
|
||||
description={
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.warmPhase.indexPriorityExplanationText"
|
||||
defaultMessage="Set the priority for recovering your indices after a node restart.
|
||||
Indices with higher priorities are recovered before indices with lower priorities."
|
||||
/>{' '}
|
||||
<LearnMoreLink docPath="recovery-prioritization.html" />
|
||||
</EuiTextColor>
|
||||
}
|
||||
titleSize="xs"
|
||||
fullWidth
|
||||
>
|
||||
<UseField
|
||||
key={`phases.${phase}.actions.set_priority.priority`}
|
||||
path={`phases.${phase}.actions.set_priority.priority`}
|
||||
component={NumericField}
|
||||
componentProps={{
|
||||
fullWidth: false,
|
||||
euiFieldProps: {
|
||||
'data-test-subj': `${phase}-${phaseIndexPriorityProperty}`,
|
||||
min: 1,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
};
|
|
@ -18,8 +18,12 @@ import {
|
|||
EuiDescribedFormGroup,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { Phases, WarmPhase as WarmPhaseInterface } from '../../../../../common/types';
|
||||
import { PhaseValidationErrors } from '../../../services/policies/policy_validation';
|
||||
import { useFormData } from '../../../../../shared_imports';
|
||||
import { Phases, WarmPhase as WarmPhaseInterface } from '../../../../../../common/types';
|
||||
import { PhaseValidationErrors } from '../../../../services/policies/policy_validation';
|
||||
|
||||
import { useRolloverPath } from './shared';
|
||||
|
||||
import {
|
||||
LearnMoreLink,
|
||||
ActiveBadge,
|
||||
|
@ -30,7 +34,8 @@ import {
|
|||
MinAgeInput,
|
||||
DescribedFormField,
|
||||
Forcemerge,
|
||||
} from '../components';
|
||||
} from '../';
|
||||
|
||||
import { DataTierAllocationField } from './shared';
|
||||
|
||||
const i18nTexts = {
|
||||
|
@ -61,15 +66,16 @@ interface Props {
|
|||
phaseData: WarmPhaseInterface;
|
||||
isShowingErrors: boolean;
|
||||
errors?: PhaseValidationErrors<WarmPhaseInterface>;
|
||||
hotPhaseRolloverEnabled: boolean;
|
||||
}
|
||||
export const WarmPhase: FunctionComponent<Props> = ({
|
||||
setPhaseData,
|
||||
phaseData,
|
||||
errors,
|
||||
isShowingErrors,
|
||||
hotPhaseRolloverEnabled,
|
||||
}) => {
|
||||
const [{ [useRolloverPath]: hotPhaseRolloverEnabled }] = useFormData({
|
||||
watch: [useRolloverPath],
|
||||
});
|
||||
return (
|
||||
<div id="warmPhaseContent" aria-live="polite" role="region" aria-relevant="additions">
|
||||
<>
|
||||
|
@ -132,7 +138,7 @@ export const WarmPhase: FunctionComponent<Props> = ({
|
|||
/>
|
||||
</EuiFormRow>
|
||||
) : null}
|
||||
{!phaseData.warmPhaseOnRollover ? (
|
||||
{!phaseData.warmPhaseOnRollover || !hotPhaseRolloverEnabled ? (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m" />
|
||||
<MinAgeInput<WarmPhaseInterface>
|
|
@ -4,7 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import {
|
||||
|
@ -17,36 +18,108 @@ import {
|
|||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
EuiCallOut,
|
||||
EuiLoadingSpinner,
|
||||
} from '@elastic/eui';
|
||||
import { Policy, PolicyFromES } from '../../../../../common/types';
|
||||
import { serializePolicy } from '../../../services/policies/policy_serialization';
|
||||
|
||||
import { SerializedPolicy } from '../../../../../common/types';
|
||||
|
||||
import { useFormContext, useFormData } from '../../../../shared_imports';
|
||||
|
||||
interface Props {
|
||||
legacyPolicy: SerializedPolicy;
|
||||
close: () => void;
|
||||
policy: Policy;
|
||||
existingPolicy?: PolicyFromES;
|
||||
policyName: string;
|
||||
}
|
||||
|
||||
export const PolicyJsonFlyout: React.FunctionComponent<Props> = ({
|
||||
close,
|
||||
policy,
|
||||
policyName,
|
||||
existingPolicy,
|
||||
close,
|
||||
legacyPolicy,
|
||||
}) => {
|
||||
const { phases } = serializePolicy(policy, existingPolicy?.policy);
|
||||
const json = JSON.stringify(
|
||||
{
|
||||
policy: {
|
||||
phases,
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
/**
|
||||
* policy === undefined: we are checking validity
|
||||
* policy === null: we have determined the policy is invalid
|
||||
* policy === {@link SerializedPolicy} we have determined the policy is valid
|
||||
*/
|
||||
const [policy, setPolicy] = useState<undefined | null | SerializedPolicy>(undefined);
|
||||
|
||||
const endpoint = `PUT _ilm/policy/${policyName || '<policyName>'}`;
|
||||
const request = `${endpoint}\n${json}`;
|
||||
const form = useFormContext();
|
||||
const [formData, getFormData] = useFormData();
|
||||
|
||||
useEffect(() => {
|
||||
(async function checkPolicy() {
|
||||
setPolicy(undefined);
|
||||
if (await form.validate()) {
|
||||
const p = getFormData() as SerializedPolicy;
|
||||
setPolicy({
|
||||
...legacyPolicy,
|
||||
phases: {
|
||||
...legacyPolicy.phases,
|
||||
hot: p.phases.hot,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
setPolicy(null);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [form, legacyPolicy, formData]);
|
||||
|
||||
let content: React.ReactNode;
|
||||
switch (policy) {
|
||||
case undefined:
|
||||
content = <EuiLoadingSpinner />;
|
||||
break;
|
||||
case null:
|
||||
content = (
|
||||
<EuiCallOut
|
||||
iconType="alert"
|
||||
color="danger"
|
||||
title={i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.policyJsonFlyout.validationErrorCallout.title',
|
||||
{ defaultMessage: 'Invalid policy' }
|
||||
)}
|
||||
>
|
||||
{i18n.translate('xpack.indexLifecycleMgmt.policyJsonFlyout.validationErrorCallout.body', {
|
||||
defaultMessage: 'To view the JSON for this policy address all validation errors.',
|
||||
})}
|
||||
</EuiCallOut>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
const { phases } = policy;
|
||||
|
||||
const json = JSON.stringify(
|
||||
{
|
||||
policy: {
|
||||
phases,
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
|
||||
const endpoint = `PUT _ilm/policy/${policyName || '<policyName>'}`;
|
||||
const request = `${endpoint}\n${json}`;
|
||||
content = (
|
||||
<>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.policyJsonFlyout.descriptionText"
|
||||
defaultMessage="This Elasticsearch request will create or update this index lifecycle policy."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
<EuiCodeBlock language="json" isCopyable>
|
||||
{request}
|
||||
</EuiCodeBlock>
|
||||
</>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlyout maxWidth={480} onClose={close}>
|
||||
|
@ -69,22 +142,7 @@ export const PolicyJsonFlyout: React.FunctionComponent<Props> = ({
|
|||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
|
||||
<EuiFlyoutBody>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.policyJsonFlyout.descriptionText"
|
||||
defaultMessage="This Elasticsearch request will create or update this index lifecycle policy."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiCodeBlock language="json" isCopyable>
|
||||
{request}
|
||||
</EuiCodeBlock>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutBody>{content}</EuiFlyoutBody>
|
||||
|
||||
<EuiFlyoutFooter>
|
||||
<EuiButtonEmpty iconType="cross" onClick={close} flush="left">
|
||||
|
|
|
@ -3,6 +3,12 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* PLEASE NOTE: This component is currently duplicated. A version of this component wired up with
|
||||
* the form lib lives in ./phases/shared
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiFieldNumber, EuiTextColor, EuiDescribedFormGroup } from '@elastic/eui';
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const useRolloverPath = '_meta.hot.useRollover';
|
||||
|
||||
/**
|
||||
* These strings describe the path to their respective values in the serialized
|
||||
* ILM form.
|
||||
*/
|
||||
export const ROLLOVER_FORM_PATHS = {
|
||||
maxDocs: 'phases.hot.actions.rollover.max_docs',
|
||||
maxAge: 'phases.hot.actions.rollover.max_age',
|
||||
maxSize: 'phases.hot.actions.rollover.max_size',
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { produce } from 'immer';
|
||||
|
||||
import { SerializedPolicy } from '../../../../common/types';
|
||||
|
||||
import { splitSizeAndUnits } from '../../services/policies/policy_serialization';
|
||||
|
||||
import { FormInternal } from './types';
|
||||
|
||||
export const deserializer = (policy: SerializedPolicy): FormInternal =>
|
||||
produce<FormInternal>(
|
||||
{
|
||||
...policy,
|
||||
_meta: {
|
||||
hot: {
|
||||
useRollover: Boolean(policy.phases.hot?.actions?.rollover),
|
||||
forceMergeEnabled: Boolean(policy.phases.hot?.actions?.forcemerge),
|
||||
bestCompression:
|
||||
policy.phases.hot?.actions?.forcemerge?.index_codec === 'best_compression',
|
||||
},
|
||||
},
|
||||
},
|
||||
(draft) => {
|
||||
if (draft.phases.hot?.actions?.rollover) {
|
||||
if (draft.phases.hot.actions.rollover.max_size) {
|
||||
const maxSize = splitSizeAndUnits(draft.phases.hot.actions.rollover.max_size);
|
||||
draft.phases.hot.actions.rollover.max_size = maxSize.size;
|
||||
draft._meta.hot.maxStorageSizeUnit = maxSize.units;
|
||||
}
|
||||
|
||||
if (draft.phases.hot.actions.rollover.max_age) {
|
||||
const maxAge = splitSizeAndUnits(draft.phases.hot.actions.rollover.max_age);
|
||||
draft.phases.hot.actions.rollover.max_age = maxAge.size;
|
||||
draft._meta.hot.maxAgeUnit = maxAge.units;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
|
@ -4,8 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment, useEffect, useState, useCallback } from 'react';
|
||||
import React, { Fragment, useEffect, useState, useCallback, useMemo } from 'react';
|
||||
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -27,23 +29,43 @@ import {
|
|||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { useForm, Form } from '../../../shared_imports';
|
||||
|
||||
import { toasts } from '../../services/notification';
|
||||
|
||||
import { Phases, Policy, PolicyFromES } from '../../../../common/types';
|
||||
import { LegacyPolicy, PolicyFromES, SerializedPolicy } from '../../../../common/types';
|
||||
|
||||
import { defaultPolicy } from '../../constants';
|
||||
|
||||
import {
|
||||
validatePolicy,
|
||||
ValidationErrors,
|
||||
findFirstError,
|
||||
} from '../../services/policies/policy_validation';
|
||||
|
||||
import { savePolicy } from '../../services/policies/policy_save';
|
||||
|
||||
import {
|
||||
deserializePolicy,
|
||||
getPolicyByName,
|
||||
initializeNewPolicy,
|
||||
legacySerializePolicy,
|
||||
} from '../../services/policies/policy_serialization';
|
||||
|
||||
import { ErrableFormRow, LearnMoreLink, PolicyJsonFlyout } from './components';
|
||||
import { ColdPhase, DeletePhase, HotPhase, WarmPhase } from './phases';
|
||||
import {
|
||||
ErrableFormRow,
|
||||
LearnMoreLink,
|
||||
PolicyJsonFlyout,
|
||||
ColdPhase,
|
||||
DeletePhase,
|
||||
HotPhase,
|
||||
WarmPhase,
|
||||
} from './components';
|
||||
|
||||
import { schema } from './form_schema';
|
||||
import { deserializer } from './deserializer';
|
||||
import { createSerializer } from './serializer';
|
||||
|
||||
export interface Props {
|
||||
policies: PolicyFromES[];
|
||||
|
@ -57,6 +79,20 @@ export interface Props {
|
|||
) => string;
|
||||
history: RouteComponentProps['history'];
|
||||
}
|
||||
|
||||
const mergeAllSerializedPolicies = (
|
||||
serializedPolicy: SerializedPolicy,
|
||||
legacySerializedPolicy: SerializedPolicy
|
||||
): SerializedPolicy => {
|
||||
return {
|
||||
...legacySerializedPolicy,
|
||||
phases: {
|
||||
...legacySerializedPolicy.phases,
|
||||
hot: serializedPolicy.phases.hot,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const EditPolicy: React.FunctionComponent<Props> = ({
|
||||
policies,
|
||||
policyName,
|
||||
|
@ -73,7 +109,18 @@ export const EditPolicy: React.FunctionComponent<Props> = ({
|
|||
|
||||
const existingPolicy = getPolicyByName(policies, policyName);
|
||||
|
||||
const [policy, setPolicy] = useState<Policy>(
|
||||
const serializer = useMemo(() => {
|
||||
return createSerializer(existingPolicy?.policy);
|
||||
}, [existingPolicy?.policy]);
|
||||
|
||||
const { form } = useForm({
|
||||
schema,
|
||||
defaultValue: existingPolicy?.policy ?? defaultPolicy,
|
||||
deserializer,
|
||||
serializer,
|
||||
});
|
||||
|
||||
const [policy, setPolicy] = useState<LegacyPolicy>(() =>
|
||||
existingPolicy ? deserializePolicy(existingPolicy) : initializeNewPolicy(policyName)
|
||||
);
|
||||
|
||||
|
@ -85,9 +132,26 @@ export const EditPolicy: React.FunctionComponent<Props> = ({
|
|||
history.push('/policies');
|
||||
};
|
||||
|
||||
const setWarmPhaseOnRollover = useCallback(
|
||||
(value: boolean) => {
|
||||
setPolicy((p) => ({
|
||||
...p,
|
||||
phases: {
|
||||
...p.phases,
|
||||
warm: {
|
||||
...p.phases.warm,
|
||||
warmPhaseOnRollover: value,
|
||||
},
|
||||
},
|
||||
}));
|
||||
},
|
||||
[setPolicy]
|
||||
);
|
||||
|
||||
const submit = async () => {
|
||||
setIsShowingErrors(true);
|
||||
const [isValid, validationErrors] = validatePolicy(
|
||||
const { data: formLibPolicy, isValid: newIsValid } = await form.submit();
|
||||
const [legacyIsValid, validationErrors] = validatePolicy(
|
||||
saveAsNew,
|
||||
policy,
|
||||
policies,
|
||||
|
@ -95,20 +159,30 @@ export const EditPolicy: React.FunctionComponent<Props> = ({
|
|||
);
|
||||
setErrors(validationErrors);
|
||||
|
||||
const isValid = legacyIsValid && newIsValid;
|
||||
|
||||
if (!isValid) {
|
||||
toasts.addDanger(
|
||||
i18n.translate('xpack.indexLifecycleMgmt.editPolicy.formErrorsMessage', {
|
||||
defaultMessage: 'Please fix the errors on this page.',
|
||||
})
|
||||
);
|
||||
const firstError = findFirstError(validationErrors);
|
||||
const errorRowId = `${firstError ? firstError.replace('.', '-') : ''}-row`;
|
||||
const element = document.getElementById(errorRowId);
|
||||
if (element) {
|
||||
element.scrollIntoView({ block: 'center', inline: 'nearest' });
|
||||
// This functionality will not be required for once form lib is fully adopted for this form
|
||||
// because errors are reported as fields are edited.
|
||||
if (!legacyIsValid) {
|
||||
const firstError = findFirstError(validationErrors);
|
||||
const errorRowId = `${firstError ? firstError.replace('.', '-') : ''}-row`;
|
||||
const element = document.getElementById(errorRowId);
|
||||
if (element) {
|
||||
element.scrollIntoView({ block: 'center', inline: 'nearest' });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const success = await savePolicy(policy, isNewPolicy || saveAsNew, existingPolicy);
|
||||
const readSerializedPolicy = () => {
|
||||
const legacySerializedPolicy = legacySerializePolicy(policy, existingPolicy?.policy);
|
||||
return mergeAllSerializedPolicies(formLibPolicy, legacySerializedPolicy);
|
||||
};
|
||||
const success = await savePolicy(readSerializedPolicy, isNewPolicy || saveAsNew);
|
||||
if (success) {
|
||||
backToPolicyList();
|
||||
}
|
||||
|
@ -120,7 +194,7 @@ export const EditPolicy: React.FunctionComponent<Props> = ({
|
|||
};
|
||||
|
||||
const setPhaseData = useCallback(
|
||||
(phase: keyof Phases, key: string, value: any) => {
|
||||
(phase: keyof LegacyPolicy['phases'], key: string, value: any) => {
|
||||
setPolicy((nextPolicy) => ({
|
||||
...nextPolicy,
|
||||
phases: {
|
||||
|
@ -132,10 +206,6 @@ export const EditPolicy: React.FunctionComponent<Props> = ({
|
|||
[setPolicy]
|
||||
);
|
||||
|
||||
const setHotPhaseData = useCallback(
|
||||
(key: string, value: any) => setPhaseData('hot', key, value),
|
||||
[setPhaseData]
|
||||
);
|
||||
const setWarmPhaseData = useCallback(
|
||||
(key: string, value: any) => setPhaseData('warm', key, value),
|
||||
[setPhaseData]
|
||||
|
@ -149,23 +219,6 @@ export const EditPolicy: React.FunctionComponent<Props> = ({
|
|||
[setPhaseData]
|
||||
);
|
||||
|
||||
const setWarmPhaseOnRollover = (value: boolean) => {
|
||||
setPolicy({
|
||||
...policy,
|
||||
phases: {
|
||||
...policy.phases,
|
||||
hot: {
|
||||
...policy.phases.hot,
|
||||
rolloverEnabled: value,
|
||||
},
|
||||
warm: {
|
||||
...policy.phases.warm,
|
||||
warmPhaseOnRollover: value,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiPage>
|
||||
<EuiPageBody>
|
||||
|
@ -188,215 +241,210 @@ export const EditPolicy: React.FunctionComponent<Props> = ({
|
|||
</EuiTitle>
|
||||
|
||||
<div className="euiAnimateContentLoad">
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiText color="subdued">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.lifecyclePolicyDescriptionText"
|
||||
defaultMessage="Use an index policy to automate the four phases of the index lifecycle,
|
||||
<Form form={form}>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiText color="subdued">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.lifecyclePolicyDescriptionText"
|
||||
defaultMessage="Use an index policy to automate the four phases of the index lifecycle,
|
||||
from actively writing to the index to deleting it."
|
||||
/>{' '}
|
||||
<LearnMoreLink
|
||||
docPath="index-lifecycle-management.html"
|
||||
text={
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.learnAboutIndexLifecycleManagementLinkText"
|
||||
defaultMessage="Learn about the index lifecycle."
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
{isNewPolicy ? null : (
|
||||
<Fragment>
|
||||
<EuiText>
|
||||
<p>
|
||||
<strong>
|
||||
/>{' '}
|
||||
<LearnMoreLink
|
||||
docPath="index-lifecycle-management.html"
|
||||
text={
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.editingExistingPolicyMessage"
|
||||
defaultMessage="You are editing an existing policy"
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.learnAboutIndexLifecycleManagementLinkText"
|
||||
defaultMessage="Learn about the index lifecycle."
|
||||
/>
|
||||
</strong>
|
||||
.{' '}
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.editingExistingPolicyExplanationMessage"
|
||||
defaultMessage="Any changes you make will affect the indices that are
|
||||
attached to this policy. Alternatively, you can save these changes in
|
||||
a new policy."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
data-test-subj="saveAsNewSwitch"
|
||||
style={{ maxWidth: '100%' }}
|
||||
checked={saveAsNew}
|
||||
onChange={(e) => {
|
||||
setSaveAsNew(e.target.checked);
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.saveAsNewPolicyMessage"
|
||||
defaultMessage="Save as new policy"
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</Fragment>
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
{saveAsNew || isNewPolicy ? (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<div>
|
||||
<span className="eui-displayInlineBlock eui-alignMiddle">
|
||||
<EuiSpacer />
|
||||
|
||||
{isNewPolicy ? null : (
|
||||
<Fragment>
|
||||
<EuiText>
|
||||
<p>
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.editingExistingPolicyMessage"
|
||||
defaultMessage="You are editing an existing policy"
|
||||
/>
|
||||
</strong>
|
||||
.{' '}
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.nameLabel"
|
||||
defaultMessage="Name"
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.editingExistingPolicyExplanationMessage"
|
||||
defaultMessage="Any changes you make will affect the indices that are
|
||||
attached to this policy. Alternatively, you can save these changes in
|
||||
a new policy."
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
titleSize="s"
|
||||
fullWidth
|
||||
>
|
||||
<ErrableFormRow
|
||||
id={'policyName'}
|
||||
label={i18n.translate('xpack.indexLifecycleMgmt.editPolicy.policyNameLabel', {
|
||||
defaultMessage: 'Policy name',
|
||||
})}
|
||||
isShowingErrors={isShowingErrors}
|
||||
errors={errors?.policyName}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.validPolicyNameMessage"
|
||||
defaultMessage="A policy name cannot start with an underscore and cannot contain a question mark or a space."
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
data-test-subj="saveAsNewSwitch"
|
||||
style={{ maxWidth: '100%' }}
|
||||
checked={saveAsNew}
|
||||
onChange={(e) => {
|
||||
setSaveAsNew(e.target.checked);
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.saveAsNewPolicyMessage"
|
||||
defaultMessage="Save as new policy"
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
{saveAsNew || isNewPolicy ? (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<div>
|
||||
<span className="eui-displayInlineBlock eui-alignMiddle">
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.nameLabel"
|
||||
defaultMessage="Name"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
titleSize="s"
|
||||
fullWidth
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj="policyNameField"
|
||||
value={policy.name}
|
||||
onChange={(e) => {
|
||||
setPolicy({ ...policy, name: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</ErrableFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
) : null}
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<HotPhase
|
||||
errors={errors?.hot}
|
||||
isShowingErrors={isShowingErrors && !!errors && Object.keys(errors.hot).length > 0}
|
||||
setPhaseData={setHotPhaseData}
|
||||
phaseData={policy.phases.hot}
|
||||
setWarmPhaseOnRollover={setWarmPhaseOnRollover}
|
||||
/>
|
||||
|
||||
<EuiHorizontalRule />
|
||||
|
||||
<WarmPhase
|
||||
errors={errors?.warm}
|
||||
isShowingErrors={isShowingErrors && !!errors && Object.keys(errors.warm).length > 0}
|
||||
setPhaseData={setWarmPhaseData}
|
||||
phaseData={policy.phases.warm}
|
||||
hotPhaseRolloverEnabled={policy.phases.hot.rolloverEnabled}
|
||||
/>
|
||||
|
||||
<EuiHorizontalRule />
|
||||
|
||||
<ColdPhase
|
||||
errors={errors?.cold}
|
||||
isShowingErrors={isShowingErrors && !!errors && Object.keys(errors.cold).length > 0}
|
||||
setPhaseData={setColdPhaseData}
|
||||
phaseData={policy.phases.cold}
|
||||
hotPhaseRolloverEnabled={policy.phases.hot.rolloverEnabled}
|
||||
/>
|
||||
|
||||
<EuiHorizontalRule />
|
||||
|
||||
<DeletePhase
|
||||
errors={errors?.delete}
|
||||
isShowingErrors={isShowingErrors && !!errors && Object.keys(errors.delete).length > 0}
|
||||
getUrlForApp={getUrlForApp}
|
||||
setPhaseData={setDeletePhaseData}
|
||||
phaseData={policy.phases.delete}
|
||||
hotPhaseRolloverEnabled={policy.phases.hot.rolloverEnabled}
|
||||
/>
|
||||
|
||||
<EuiHorizontalRule />
|
||||
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="savePolicyButton"
|
||||
fill
|
||||
iconType="check"
|
||||
iconSide="left"
|
||||
onClick={submit}
|
||||
color="secondary"
|
||||
>
|
||||
{saveAsNew ? (
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.saveAsNewButton"
|
||||
defaultMessage="Save as new policy"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.saveButton"
|
||||
defaultMessage="Save policy"
|
||||
/>
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty data-test-subj="cancelTestPolicy" onClick={backToPolicyList}>
|
||||
<ErrableFormRow
|
||||
id={'policyName'}
|
||||
label={i18n.translate('xpack.indexLifecycleMgmt.editPolicy.policyNameLabel', {
|
||||
defaultMessage: 'Policy name',
|
||||
})}
|
||||
isShowingErrors={isShowingErrors}
|
||||
errors={errors?.policyName}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.cancelButton"
|
||||
defaultMessage="Cancel"
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.validPolicyNameMessage"
|
||||
defaultMessage="A policy name cannot start with an underscore and cannot contain a question mark or a space."
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty onClick={togglePolicyJsonFlyout} data-test-subj="requestButton">
|
||||
{isShowingPolicyJsonFlyout ? (
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.hidePolicyJsonButto"
|
||||
defaultMessage="Hide request"
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj="policyNameField"
|
||||
value={policy.name}
|
||||
onChange={(e) => {
|
||||
setPolicy({ ...policy, name: e.target.value });
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.showPolicyJsonButto"
|
||||
defaultMessage="Show request"
|
||||
/>
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</ErrableFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
) : null}
|
||||
|
||||
{isShowingPolicyJsonFlyout ? (
|
||||
<PolicyJsonFlyout
|
||||
policyName={policy.name || ''}
|
||||
existingPolicy={existingPolicy}
|
||||
policy={policy}
|
||||
close={() => setIsShowingPolicyJsonFlyout(false)}
|
||||
<EuiSpacer />
|
||||
|
||||
<HotPhase setWarmPhaseOnRollover={setWarmPhaseOnRollover} />
|
||||
|
||||
<EuiHorizontalRule />
|
||||
|
||||
<WarmPhase
|
||||
errors={errors?.warm}
|
||||
isShowingErrors={isShowingErrors && !!errors && Object.keys(errors.warm).length > 0}
|
||||
setPhaseData={setWarmPhaseData}
|
||||
phaseData={policy.phases.warm}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<EuiHorizontalRule />
|
||||
|
||||
<ColdPhase
|
||||
errors={errors?.cold}
|
||||
isShowingErrors={isShowingErrors && !!errors && Object.keys(errors.cold).length > 0}
|
||||
setPhaseData={setColdPhaseData}
|
||||
phaseData={policy.phases.cold}
|
||||
/>
|
||||
|
||||
<EuiHorizontalRule />
|
||||
|
||||
<DeletePhase
|
||||
errors={errors?.delete}
|
||||
isShowingErrors={
|
||||
isShowingErrors && !!errors && Object.keys(errors.delete).length > 0
|
||||
}
|
||||
getUrlForApp={getUrlForApp}
|
||||
setPhaseData={setDeletePhaseData}
|
||||
phaseData={policy.phases.delete}
|
||||
/>
|
||||
|
||||
<EuiHorizontalRule />
|
||||
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="savePolicyButton"
|
||||
fill
|
||||
iconType="check"
|
||||
iconSide="left"
|
||||
disabled={form.isValid === false || form.isSubmitting}
|
||||
onClick={submit}
|
||||
color="secondary"
|
||||
>
|
||||
{saveAsNew ? (
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.saveAsNewButton"
|
||||
defaultMessage="Save as new policy"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.saveButton"
|
||||
defaultMessage="Save policy"
|
||||
/>
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty data-test-subj="cancelTestPolicy" onClick={backToPolicyList}>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.cancelButton"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty onClick={togglePolicyJsonFlyout} data-test-subj="requestButton">
|
||||
{isShowingPolicyJsonFlyout ? (
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.hidePolicyJsonButto"
|
||||
defaultMessage="Hide request"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.showPolicyJsonButto"
|
||||
defaultMessage="Show request"
|
||||
/>
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
{isShowingPolicyJsonFlyout ? (
|
||||
<PolicyJsonFlyout
|
||||
policyName={policy.name || ''}
|
||||
legacyPolicy={legacySerializePolicy(policy, existingPolicy?.policy)}
|
||||
close={() => setIsShowingPolicyJsonFlyout(false)}
|
||||
/>
|
||||
) : null}
|
||||
</Form>
|
||||
</div>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
import { FormSchema, fieldValidators } from '../../../shared_imports';
|
||||
import { defaultSetPriority } from '../../constants';
|
||||
|
||||
import { FormInternal } from './types';
|
||||
import { ifExistsNumberGreaterThanZero, rolloverThresholdsValidator } from './form_validations';
|
||||
import { i18nTexts } from './i18n_texts';
|
||||
|
||||
const { emptyField } = fieldValidators;
|
||||
|
||||
export const schema: FormSchema<FormInternal> = {
|
||||
_meta: {
|
||||
hot: {
|
||||
useRollover: {
|
||||
defaultValue: true,
|
||||
label: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.enableRolloverLabel', {
|
||||
defaultMessage: 'Enable rollover',
|
||||
}),
|
||||
},
|
||||
maxStorageSizeUnit: {
|
||||
defaultValue: 'gb',
|
||||
},
|
||||
maxAgeUnit: {
|
||||
defaultValue: 'd',
|
||||
},
|
||||
forceMergeEnabled: {
|
||||
label: i18nTexts.editPolicy.forceMergeEnabledFieldLabel,
|
||||
},
|
||||
bestCompression: {
|
||||
label: i18n.translate('xpack.indexLifecycleMgmt.forcemerge.bestCompressionLabel', {
|
||||
defaultMessage: 'Compress stored fields',
|
||||
}),
|
||||
helpText: i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.editPolicy.forceMerge.bestCompressionText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Use higher compression for stored fields at the cost of slower performance.',
|
||||
}
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
phases: {
|
||||
hot: {
|
||||
actions: {
|
||||
rollover: {
|
||||
max_age: {
|
||||
label: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.maximumAgeLabel', {
|
||||
defaultMessage: 'Maximum age',
|
||||
}),
|
||||
validations: [
|
||||
{
|
||||
validator: rolloverThresholdsValidator,
|
||||
},
|
||||
{
|
||||
validator: ifExistsNumberGreaterThanZero,
|
||||
},
|
||||
],
|
||||
},
|
||||
max_docs: {
|
||||
label: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.maximumDocumentsLabel', {
|
||||
defaultMessage: 'Maximum documents',
|
||||
}),
|
||||
validations: [
|
||||
{
|
||||
validator: rolloverThresholdsValidator,
|
||||
},
|
||||
{
|
||||
validator: ifExistsNumberGreaterThanZero,
|
||||
},
|
||||
],
|
||||
serializer: (v: string): any => (v ? parseInt(v, 10) : undefined),
|
||||
},
|
||||
max_size: {
|
||||
label: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.maximumIndexSizeLabel', {
|
||||
defaultMessage: 'Maximum index size',
|
||||
}),
|
||||
validations: [
|
||||
{
|
||||
validator: rolloverThresholdsValidator,
|
||||
},
|
||||
{
|
||||
validator: ifExistsNumberGreaterThanZero,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
forcemerge: {
|
||||
max_num_segments: {
|
||||
label: i18n.translate('xpack.indexLifecycleMgmt.forceMerge.numberOfSegmentsLabel', {
|
||||
defaultMessage: 'Number of segments',
|
||||
}),
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(
|
||||
i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.editPolicy.forcemerge.numberOfSegmentsRequiredError',
|
||||
{ defaultMessage: 'A value for number of segments is required.' }
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
validator: ifExistsNumberGreaterThanZero,
|
||||
},
|
||||
],
|
||||
serializer: (v: string): any => (v ? parseInt(v, 10) : undefined),
|
||||
},
|
||||
},
|
||||
set_priority: {
|
||||
priority: {
|
||||
defaultValue: defaultSetPriority as any,
|
||||
label: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.indexPriorityLabel', {
|
||||
defaultMessage: 'Index priority (optional)',
|
||||
}),
|
||||
validations: [{ validator: ifExistsNumberGreaterThanZero }],
|
||||
serializer: (v: string): any => (v ? parseInt(v, 10) : undefined),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { fieldValidators, ValidationFunc } from '../../../shared_imports';
|
||||
|
||||
import { i18nTexts } from './components/phases/hot_phase/i18n_texts';
|
||||
|
||||
import { ROLLOVER_FORM_PATHS } from './constants';
|
||||
|
||||
const { numberGreaterThanField } = fieldValidators;
|
||||
|
||||
export const positiveNumberRequiredMessage = i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.editPolicy.numberAboveZeroRequiredError',
|
||||
{
|
||||
defaultMessage: 'Only numbers above 0 are allowed.',
|
||||
}
|
||||
);
|
||||
|
||||
export const ifExistsNumberGreaterThanZero: ValidationFunc<any, any, any> = (arg) => {
|
||||
if (arg.value) {
|
||||
return numberGreaterThanField({
|
||||
than: 0,
|
||||
message: positiveNumberRequiredMessage,
|
||||
})({
|
||||
...arg,
|
||||
value: parseInt(arg.value, 10),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A special validation type used to keep track of validation errors for
|
||||
* the rollover threshold values not being set (e.g., age and doc count)
|
||||
*/
|
||||
export const ROLLOVER_EMPTY_VALIDATION = 'EMPTY';
|
||||
|
||||
/**
|
||||
* An ILM policy requires that for rollover a value must be set for one of the threshold values.
|
||||
*
|
||||
* This validator checks that and updates form values by setting errors states imperatively to
|
||||
* indicate this error state.
|
||||
*/
|
||||
export const rolloverThresholdsValidator: ValidationFunc = ({ form }) => {
|
||||
const fields = form.getFields();
|
||||
if (
|
||||
!(
|
||||
fields[ROLLOVER_FORM_PATHS.maxAge].value ||
|
||||
fields[ROLLOVER_FORM_PATHS.maxDocs].value ||
|
||||
fields[ROLLOVER_FORM_PATHS.maxSize].value
|
||||
)
|
||||
) {
|
||||
fields[ROLLOVER_FORM_PATHS.maxAge].setErrors([
|
||||
{ validationType: ROLLOVER_EMPTY_VALIDATION, message: i18nTexts.maximumAgeRequiredMessage },
|
||||
]);
|
||||
fields[ROLLOVER_FORM_PATHS.maxDocs].setErrors([
|
||||
{
|
||||
validationType: ROLLOVER_EMPTY_VALIDATION,
|
||||
message: i18nTexts.maximumDocumentsRequiredMessage,
|
||||
},
|
||||
]);
|
||||
fields[ROLLOVER_FORM_PATHS.maxSize].setErrors([
|
||||
{ validationType: ROLLOVER_EMPTY_VALIDATION, message: i18nTexts.maximumSizeRequiredMessage },
|
||||
]);
|
||||
} else {
|
||||
fields[ROLLOVER_FORM_PATHS.maxAge].clearErrors(ROLLOVER_EMPTY_VALIDATION);
|
||||
fields[ROLLOVER_FORM_PATHS.maxDocs].clearErrors(ROLLOVER_EMPTY_VALIDATION);
|
||||
fields[ROLLOVER_FORM_PATHS.maxSize].clearErrors(ROLLOVER_EMPTY_VALIDATION);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const i18nTexts = {
|
||||
editPolicy: {
|
||||
forceMergeEnabledFieldLabel: i18n.translate('xpack.indexLifecycleMgmt.forcemerge.enableLabel', {
|
||||
defaultMessage: 'Force merge data',
|
||||
}),
|
||||
},
|
||||
};
|
|
@ -1,153 +0,0 @@
|
|||
/*
|
||||
* 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 React, { PureComponent, Fragment } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiDescribedFormGroup, EuiSwitch, EuiTextColor, EuiFormRow } from '@elastic/eui';
|
||||
|
||||
import { DeletePhase as DeletePhaseInterface, Phases } from '../../../../../common/types';
|
||||
import { PhaseValidationErrors } from '../../../services/policies/policy_validation';
|
||||
|
||||
import {
|
||||
ActiveBadge,
|
||||
LearnMoreLink,
|
||||
OptionalLabel,
|
||||
PhaseErrorMessage,
|
||||
MinAgeInput,
|
||||
SnapshotPolicies,
|
||||
} from '../components';
|
||||
|
||||
const deleteProperty: keyof Phases = 'delete';
|
||||
const phaseProperty = (propertyName: keyof DeletePhaseInterface) => propertyName;
|
||||
|
||||
interface Props {
|
||||
setPhaseData: (key: keyof DeletePhaseInterface & string, value: string | boolean) => void;
|
||||
phaseData: DeletePhaseInterface;
|
||||
isShowingErrors: boolean;
|
||||
errors?: PhaseValidationErrors<DeletePhaseInterface>;
|
||||
hotPhaseRolloverEnabled: boolean;
|
||||
getUrlForApp: (
|
||||
appId: string,
|
||||
options?: {
|
||||
path?: string;
|
||||
absolute?: boolean;
|
||||
}
|
||||
) => string;
|
||||
}
|
||||
|
||||
export class DeletePhase extends PureComponent<Props> {
|
||||
render() {
|
||||
const {
|
||||
setPhaseData,
|
||||
phaseData,
|
||||
errors,
|
||||
isShowingErrors,
|
||||
hotPhaseRolloverEnabled,
|
||||
getUrlForApp,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div id="deletePhaseContent" aria-live="polite" role="region">
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<div>
|
||||
<h2 className="eui-displayInlineBlock eui-alignMiddle">
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.deletePhase.deletePhaseLabel"
|
||||
defaultMessage="Delete phase"
|
||||
/>
|
||||
</h2>{' '}
|
||||
{phaseData.phaseEnabled && !isShowingErrors ? <ActiveBadge /> : null}
|
||||
<PhaseErrorMessage isShowingErrors={isShowingErrors} />
|
||||
</div>
|
||||
}
|
||||
titleSize="s"
|
||||
description={
|
||||
<Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.deletePhase.deletePhaseDescriptionText"
|
||||
defaultMessage="You no longer need your index. You can define when it is safe to delete it."
|
||||
/>
|
||||
</p>
|
||||
<EuiSwitch
|
||||
data-test-subj="enablePhaseSwitch-delete"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.deletePhase.activateWarmPhaseSwitchLabel"
|
||||
defaultMessage="Activate delete phase"
|
||||
/>
|
||||
}
|
||||
id={`${deleteProperty}-${phaseProperty('phaseEnabled')}`}
|
||||
checked={phaseData.phaseEnabled}
|
||||
onChange={(e) => {
|
||||
setPhaseData(phaseProperty('phaseEnabled'), e.target.checked);
|
||||
}}
|
||||
aria-controls="deletePhaseContent"
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
{phaseData.phaseEnabled ? (
|
||||
<MinAgeInput<DeletePhaseInterface>
|
||||
errors={errors}
|
||||
phaseData={phaseData}
|
||||
phase={deleteProperty}
|
||||
isShowingErrors={isShowingErrors}
|
||||
setPhaseData={setPhaseData}
|
||||
rolloverEnabled={hotPhaseRolloverEnabled}
|
||||
/>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</EuiDescribedFormGroup>
|
||||
{phaseData.phaseEnabled ? (
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.deletePhase.waitForSnapshotTitle"
|
||||
defaultMessage="Wait for snapshot policy"
|
||||
/>
|
||||
</h3>
|
||||
}
|
||||
description={
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.deletePhase.waitForSnapshotDescription"
|
||||
defaultMessage="Specify a snapshot policy to be executed before the deletion of the index. This ensures that a snapshot of the deleted index is available."
|
||||
/>{' '}
|
||||
<LearnMoreLink docPath="ilm-wait-for-snapshot.html" />
|
||||
</EuiTextColor>
|
||||
}
|
||||
titleSize="xs"
|
||||
fullWidth
|
||||
>
|
||||
<EuiFormRow
|
||||
id="deletePhaseWaitForSnapshot"
|
||||
label={
|
||||
<Fragment>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.deletePhase.waitForSnapshotLabel"
|
||||
defaultMessage="Snapshot policy name"
|
||||
/>
|
||||
<OptionalLabel />
|
||||
</Fragment>
|
||||
}
|
||||
>
|
||||
<SnapshotPolicies
|
||||
value={phaseData.waitForSnapshotPolicy}
|
||||
onChange={(value) => setPhaseData(phaseProperty('waitForSnapshotPolicy'), value)}
|
||||
getUrlForApp={getUrlForApp}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,336 +0,0 @@
|
|||
/*
|
||||
* 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 React, { Fragment, PureComponent } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiFieldNumber,
|
||||
EuiSelect,
|
||||
EuiSwitch,
|
||||
EuiFormRow,
|
||||
EuiDescribedFormGroup,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { HotPhase as HotPhaseInterface, Phases } from '../../../../../common/types';
|
||||
import { PhaseValidationErrors } from '../../../services/policies/policy_validation';
|
||||
|
||||
import {
|
||||
LearnMoreLink,
|
||||
ActiveBadge,
|
||||
PhaseErrorMessage,
|
||||
ErrableFormRow,
|
||||
SetPriorityInput,
|
||||
Forcemerge,
|
||||
} from '../components';
|
||||
|
||||
const maxSizeStoredUnits = [
|
||||
{
|
||||
value: 'gb',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.gigabytesLabel', {
|
||||
defaultMessage: 'gigabytes',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'mb',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.megabytesLabel', {
|
||||
defaultMessage: 'megabytes',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'b',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.bytesLabel', {
|
||||
defaultMessage: 'bytes',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'kb',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.kilobytesLabel', {
|
||||
defaultMessage: 'kilobytes',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'tb',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.terabytesLabel', {
|
||||
defaultMessage: 'terabytes',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'pb',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.petabytesLabel', {
|
||||
defaultMessage: 'petabytes',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const maxAgeUnits = [
|
||||
{
|
||||
value: 'd',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.daysLabel', {
|
||||
defaultMessage: 'days',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'h',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.hoursLabel', {
|
||||
defaultMessage: 'hours',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'm',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.minutesLabel', {
|
||||
defaultMessage: 'minutes',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 's',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.secondsLabel', {
|
||||
defaultMessage: 'seconds',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'ms',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.millisecondsLabel', {
|
||||
defaultMessage: 'milliseconds',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'micros',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.microsecondsLabel', {
|
||||
defaultMessage: 'microseconds',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'nanos',
|
||||
text: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.nanosecondsLabel', {
|
||||
defaultMessage: 'nanoseconds',
|
||||
}),
|
||||
},
|
||||
];
|
||||
const hotProperty: keyof Phases = 'hot';
|
||||
const phaseProperty = (propertyName: keyof HotPhaseInterface) => propertyName;
|
||||
|
||||
interface Props {
|
||||
errors?: PhaseValidationErrors<HotPhaseInterface>;
|
||||
isShowingErrors: boolean;
|
||||
phaseData: HotPhaseInterface;
|
||||
setPhaseData: (key: keyof HotPhaseInterface & string, value: string | boolean) => void;
|
||||
setWarmPhaseOnRollover: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export class HotPhase extends PureComponent<Props> {
|
||||
render() {
|
||||
const { setPhaseData, phaseData, isShowingErrors, errors, setWarmPhaseOnRollover } = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<div>
|
||||
<h2 className="eui-displayInlineBlock eui-alignMiddle">
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.hotPhase.hotPhaseLabel"
|
||||
defaultMessage="Hot phase"
|
||||
/>
|
||||
</h2>{' '}
|
||||
{isShowingErrors ? null : <ActiveBadge />}
|
||||
<PhaseErrorMessage isShowingErrors={isShowingErrors} />
|
||||
</div>
|
||||
}
|
||||
titleSize="s"
|
||||
description={
|
||||
<Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.hotPhase.hotPhaseDescriptionMessage"
|
||||
defaultMessage="This phase is required. You are actively querying and
|
||||
writing to your index. For faster updates, you can roll over the index when it gets too big or too old."
|
||||
/>
|
||||
</p>
|
||||
</Fragment>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFormRow
|
||||
id="rolloverFormRow"
|
||||
hasEmptyLabelSpace
|
||||
helpText={
|
||||
<Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.hotPhase.rolloverDescriptionMessage"
|
||||
defaultMessage="The new index created by rollover is added
|
||||
to the index alias and designated as the write index."
|
||||
/>
|
||||
</p>
|
||||
<LearnMoreLink
|
||||
text={
|
||||
<FormattedMessage
|
||||
id="xpack.indexLifecycleMgmt.editPolicy.hotPhase.learnAboutRolloverLinkText"
|
||||
defaultMessage="Learn about rollover"
|
||||
/>
|
||||
}
|
||||
docPath="indices-rollover-index.html"
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
</Fragment>
|
||||
}
|
||||
>
|
||||
<EuiSwitch
|
||||
data-test-subj="rolloverSwitch"
|
||||
checked={phaseData.rolloverEnabled}
|
||||
onChange={async (e) => {
|
||||
setWarmPhaseOnRollover(e.target.checked);
|
||||
}}
|
||||
label={i18n.translate('xpack.indexLifecycleMgmt.hotPhase.enableRolloverLabel', {
|
||||
defaultMessage: 'Enable rollover',
|
||||
})}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
{phaseData.rolloverEnabled ? (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem style={{ maxWidth: 188 }}>
|
||||
<ErrableFormRow
|
||||
id={`${hotProperty}-${phaseProperty('selectedMaxSizeStored')}`}
|
||||
label={i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.hotPhase.maximumIndexSizeLabel',
|
||||
{
|
||||
defaultMessage: 'Maximum index size',
|
||||
}
|
||||
)}
|
||||
isShowingErrors={isShowingErrors}
|
||||
errors={errors?.selectedMaxSizeStored}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
id={`${hotProperty}-${phaseProperty('selectedMaxSizeStored')}`}
|
||||
value={phaseData.selectedMaxSizeStored}
|
||||
onChange={(e) => {
|
||||
setPhaseData(phaseProperty('selectedMaxSizeStored'), e.target.value);
|
||||
}}
|
||||
min={1}
|
||||
/>
|
||||
</ErrableFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ maxWidth: 188 }}>
|
||||
<ErrableFormRow
|
||||
id={`${hotProperty}-${phaseProperty('selectedMaxSizeStoredUnits')}`}
|
||||
hasEmptyLabelSpace
|
||||
isShowingErrors={isShowingErrors}
|
||||
errors={errors?.selectedMaxSizeStoredUnits}
|
||||
>
|
||||
<EuiSelect
|
||||
aria-label={i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.hotPhase.maximumIndexSizeUnitsAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Maximum index size units',
|
||||
}
|
||||
)}
|
||||
value={phaseData.selectedMaxSizeStoredUnits}
|
||||
onChange={(e) => {
|
||||
setPhaseData(phaseProperty('selectedMaxSizeStoredUnits'), e.target.value);
|
||||
}}
|
||||
options={maxSizeStoredUnits}
|
||||
/>
|
||||
</ErrableFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem style={{ maxWidth: 188 }}>
|
||||
<ErrableFormRow
|
||||
id={`${hotProperty}-${phaseProperty('selectedMaxDocuments')}`}
|
||||
label={i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.hotPhase.maximumDocumentsLabel',
|
||||
{
|
||||
defaultMessage: 'Maximum documents',
|
||||
}
|
||||
)}
|
||||
isShowingErrors={isShowingErrors}
|
||||
errors={errors?.selectedMaxDocuments}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
id={`${hotProperty}-${phaseProperty('selectedMaxDocuments')}`}
|
||||
value={phaseData.selectedMaxDocuments}
|
||||
onChange={(e) => {
|
||||
setPhaseData(phaseProperty('selectedMaxDocuments'), e.target.value);
|
||||
}}
|
||||
min={1}
|
||||
/>
|
||||
</ErrableFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem style={{ maxWidth: 188 }}>
|
||||
<ErrableFormRow
|
||||
id={`${hotProperty}-${phaseProperty('selectedMaxAge')}`}
|
||||
label={i18n.translate('xpack.indexLifecycleMgmt.hotPhase.maximumAgeLabel', {
|
||||
defaultMessage: 'Maximum age',
|
||||
})}
|
||||
isShowingErrors={isShowingErrors}
|
||||
errors={errors?.selectedMaxAge}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
id={`${hotProperty}-${phaseProperty('selectedMaxAge')}`}
|
||||
value={phaseData.selectedMaxAge}
|
||||
onChange={(e) => {
|
||||
setPhaseData(phaseProperty('selectedMaxAge'), e.target.value);
|
||||
}}
|
||||
min={1}
|
||||
/>
|
||||
</ErrableFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ maxWidth: 188 }}>
|
||||
<ErrableFormRow
|
||||
id={`${hotProperty}-${phaseProperty('selectedMaxAgeUnits')}`}
|
||||
hasEmptyLabelSpace
|
||||
isShowingErrors={isShowingErrors}
|
||||
errors={errors?.selectedMaxAgeUnits}
|
||||
>
|
||||
<EuiSelect
|
||||
aria-label={i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.hotPhase.maximumAgeUnitsAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Maximum age units',
|
||||
}
|
||||
)}
|
||||
value={phaseData.selectedMaxAgeUnits}
|
||||
onChange={(e) => {
|
||||
setPhaseData(phaseProperty('selectedMaxAgeUnits'), e.target.value);
|
||||
}}
|
||||
options={maxAgeUnits}
|
||||
/>
|
||||
</ErrableFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Fragment>
|
||||
) : null}
|
||||
</EuiDescribedFormGroup>
|
||||
{phaseData.rolloverEnabled ? (
|
||||
<Forcemerge
|
||||
phase={'hot'}
|
||||
phaseData={phaseData}
|
||||
setPhaseData={setPhaseData}
|
||||
isShowingErrors={isShowingErrors}
|
||||
errors={errors}
|
||||
/>
|
||||
) : null}
|
||||
<SetPriorityInput<HotPhaseInterface>
|
||||
errors={errors}
|
||||
phaseData={phaseData}
|
||||
phase={hotProperty}
|
||||
isShowingErrors={isShowingErrors}
|
||||
setPhaseData={setPhaseData}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { SerializedPolicy } from '../../../../common/types';
|
||||
|
||||
import { FormInternal } from './types';
|
||||
|
||||
export const createSerializer = (originalPolicy?: SerializedPolicy) => (
|
||||
data: FormInternal
|
||||
): SerializedPolicy => {
|
||||
const { _meta, ...rest } = data;
|
||||
|
||||
if (!rest.phases || !rest.phases.hot) {
|
||||
rest.phases = { hot: { actions: {} } };
|
||||
}
|
||||
|
||||
if (rest.phases.hot) {
|
||||
rest.phases.hot.min_age = originalPolicy?.phases.hot?.min_age ?? '0ms';
|
||||
}
|
||||
|
||||
if (rest.phases.hot?.actions) {
|
||||
if (rest.phases.hot.actions?.rollover && _meta.hot.useRollover) {
|
||||
if (rest.phases.hot.actions.rollover.max_age) {
|
||||
rest.phases.hot.actions.rollover.max_age = `${rest.phases.hot.actions.rollover.max_age}${_meta.hot.maxAgeUnit}`;
|
||||
}
|
||||
|
||||
if (rest.phases.hot.actions.rollover.max_size) {
|
||||
rest.phases.hot.actions.rollover.max_size = `${rest.phases.hot.actions.rollover.max_size}${_meta.hot.maxStorageSizeUnit}`;
|
||||
}
|
||||
|
||||
if (_meta.hot.bestCompression && rest.phases.hot.actions?.forcemerge) {
|
||||
rest.phases.hot.actions.forcemerge.index_codec = 'best_compression';
|
||||
}
|
||||
} else {
|
||||
delete rest.phases.hot.actions?.rollover;
|
||||
}
|
||||
}
|
||||
|
||||
return rest;
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { SerializedPolicy } from '../../../../common/types';
|
||||
|
||||
/**
|
||||
* Describes the shape of data after deserialization.
|
||||
*/
|
||||
export interface FormInternal extends SerializedPolicy {
|
||||
/**
|
||||
* This is a special internal-only field that is used to display or hide
|
||||
* certain form fields which affects what is ultimately serialized.
|
||||
*/
|
||||
_meta: {
|
||||
hot: {
|
||||
useRollover: boolean;
|
||||
forceMergeEnabled: boolean;
|
||||
bestCompression: boolean;
|
||||
maxStorageSizeUnit?: string;
|
||||
maxAgeUnit?: string;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,190 +0,0 @@
|
|||
/*
|
||||
* 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 { HotPhase, SerializedHotPhase } from '../../../../common/types';
|
||||
import { serializedPhaseInitialization } from '../../constants';
|
||||
import { isNumber, splitSizeAndUnits } from './policy_serialization';
|
||||
import {
|
||||
maximumAgeRequiredMessage,
|
||||
maximumDocumentsRequiredMessage,
|
||||
maximumSizeRequiredMessage,
|
||||
numberRequiredMessage,
|
||||
PhaseValidationErrors,
|
||||
positiveNumberRequiredMessage,
|
||||
positiveNumbersAboveZeroErrorMessage,
|
||||
} from './policy_validation';
|
||||
|
||||
const hotPhaseInitialization: HotPhase = {
|
||||
phaseEnabled: false,
|
||||
rolloverEnabled: false,
|
||||
selectedMaxAge: '',
|
||||
selectedMaxAgeUnits: 'd',
|
||||
selectedMaxSizeStored: '',
|
||||
selectedMaxSizeStoredUnits: 'gb',
|
||||
forceMergeEnabled: false,
|
||||
selectedForceMergeSegments: '',
|
||||
bestCompressionEnabled: false,
|
||||
phaseIndexPriority: '',
|
||||
selectedMaxDocuments: '',
|
||||
};
|
||||
|
||||
export const hotPhaseFromES = (phaseSerialized?: SerializedHotPhase): HotPhase => {
|
||||
const phase: HotPhase = { ...hotPhaseInitialization };
|
||||
|
||||
if (phaseSerialized === undefined || phaseSerialized === null) {
|
||||
return phase;
|
||||
}
|
||||
|
||||
phase.phaseEnabled = true;
|
||||
|
||||
if (phaseSerialized.actions) {
|
||||
const actions = phaseSerialized.actions;
|
||||
|
||||
if (actions.rollover) {
|
||||
const rollover = actions.rollover;
|
||||
phase.rolloverEnabled = true;
|
||||
if (rollover.max_age) {
|
||||
const { size: maxAge, units: maxAgeUnits } = splitSizeAndUnits(rollover.max_age);
|
||||
phase.selectedMaxAge = maxAge;
|
||||
phase.selectedMaxAgeUnits = maxAgeUnits;
|
||||
}
|
||||
if (rollover.max_size) {
|
||||
const { size: maxSize, units: maxSizeUnits } = splitSizeAndUnits(rollover.max_size);
|
||||
phase.selectedMaxSizeStored = maxSize;
|
||||
phase.selectedMaxSizeStoredUnits = maxSizeUnits;
|
||||
}
|
||||
if (rollover.max_docs) {
|
||||
phase.selectedMaxDocuments = rollover.max_docs.toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (actions.forcemerge) {
|
||||
const forcemerge = actions.forcemerge;
|
||||
phase.forceMergeEnabled = true;
|
||||
phase.selectedForceMergeSegments = forcemerge.max_num_segments.toString();
|
||||
// only accepted value for index_codec
|
||||
phase.bestCompressionEnabled = forcemerge.index_codec === 'best_compression';
|
||||
}
|
||||
|
||||
if (actions.set_priority) {
|
||||
phase.phaseIndexPriority = actions.set_priority.priority
|
||||
? actions.set_priority.priority.toString()
|
||||
: '';
|
||||
}
|
||||
}
|
||||
|
||||
return phase;
|
||||
};
|
||||
|
||||
export const hotPhaseToES = (
|
||||
phase: HotPhase,
|
||||
originalPhase?: SerializedHotPhase
|
||||
): SerializedHotPhase => {
|
||||
if (!originalPhase) {
|
||||
originalPhase = { ...serializedPhaseInitialization };
|
||||
}
|
||||
|
||||
const esPhase = { ...originalPhase };
|
||||
|
||||
esPhase.actions = esPhase.actions ? { ...esPhase.actions } : {};
|
||||
|
||||
if (phase.rolloverEnabled) {
|
||||
if (!esPhase.actions.rollover) {
|
||||
esPhase.actions.rollover = {};
|
||||
}
|
||||
if (isNumber(phase.selectedMaxAge)) {
|
||||
esPhase.actions.rollover.max_age = `${phase.selectedMaxAge}${phase.selectedMaxAgeUnits}`;
|
||||
}
|
||||
if (isNumber(phase.selectedMaxSizeStored)) {
|
||||
esPhase.actions.rollover.max_size = `${phase.selectedMaxSizeStored}${phase.selectedMaxSizeStoredUnits}`;
|
||||
}
|
||||
if (isNumber(phase.selectedMaxDocuments)) {
|
||||
esPhase.actions.rollover.max_docs = parseInt(phase.selectedMaxDocuments, 10);
|
||||
}
|
||||
if (phase.forceMergeEnabled && isNumber(phase.selectedForceMergeSegments)) {
|
||||
esPhase.actions.forcemerge = {
|
||||
max_num_segments: parseInt(phase.selectedForceMergeSegments, 10),
|
||||
};
|
||||
if (phase.bestCompressionEnabled) {
|
||||
// only accepted value for index_codec
|
||||
esPhase.actions.forcemerge.index_codec = 'best_compression';
|
||||
}
|
||||
} else {
|
||||
delete esPhase.actions.forcemerge;
|
||||
}
|
||||
} else {
|
||||
delete esPhase.actions.rollover;
|
||||
// forcemerge is only allowed if rollover is enabled
|
||||
if (esPhase.actions.forcemerge) {
|
||||
delete esPhase.actions.forcemerge;
|
||||
}
|
||||
}
|
||||
|
||||
if (isNumber(phase.phaseIndexPriority)) {
|
||||
esPhase.actions.set_priority = {
|
||||
priority: parseInt(phase.phaseIndexPriority, 10),
|
||||
};
|
||||
} else {
|
||||
delete esPhase.actions.set_priority;
|
||||
}
|
||||
|
||||
return esPhase;
|
||||
};
|
||||
|
||||
export const validateHotPhase = (phase: HotPhase): PhaseValidationErrors<HotPhase> => {
|
||||
if (!phase.phaseEnabled) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const phaseErrors = {} as PhaseValidationErrors<HotPhase>;
|
||||
|
||||
// index priority is optional, but if it's set, it needs to be a positive number
|
||||
if (phase.phaseIndexPriority) {
|
||||
if (!isNumber(phase.phaseIndexPriority)) {
|
||||
phaseErrors.phaseIndexPriority = [numberRequiredMessage];
|
||||
} else if (parseInt(phase.phaseIndexPriority, 10) < 0) {
|
||||
phaseErrors.phaseIndexPriority = [positiveNumberRequiredMessage];
|
||||
}
|
||||
}
|
||||
|
||||
// if rollover is enabled
|
||||
if (phase.rolloverEnabled) {
|
||||
// either max_age, max_size or max_documents need to be set
|
||||
if (
|
||||
!isNumber(phase.selectedMaxAge) &&
|
||||
!isNumber(phase.selectedMaxSizeStored) &&
|
||||
!isNumber(phase.selectedMaxDocuments)
|
||||
) {
|
||||
phaseErrors.selectedMaxAge = [maximumAgeRequiredMessage];
|
||||
phaseErrors.selectedMaxSizeStored = [maximumSizeRequiredMessage];
|
||||
phaseErrors.selectedMaxDocuments = [maximumDocumentsRequiredMessage];
|
||||
}
|
||||
|
||||
// max age, max size and max docs need to be above zero if set
|
||||
if (isNumber(phase.selectedMaxAge) && parseInt(phase.selectedMaxAge, 10) < 1) {
|
||||
phaseErrors.selectedMaxAge = [positiveNumbersAboveZeroErrorMessage];
|
||||
}
|
||||
if (isNumber(phase.selectedMaxSizeStored) && parseInt(phase.selectedMaxSizeStored, 10) < 1) {
|
||||
phaseErrors.selectedMaxSizeStored = [positiveNumbersAboveZeroErrorMessage];
|
||||
}
|
||||
if (isNumber(phase.selectedMaxDocuments) && parseInt(phase.selectedMaxDocuments, 10) < 1) {
|
||||
phaseErrors.selectedMaxDocuments = [positiveNumbersAboveZeroErrorMessage];
|
||||
}
|
||||
|
||||
// if forcemerge is enabled, force merge segments needs to be a number above zero
|
||||
if (phase.forceMergeEnabled) {
|
||||
if (!isNumber(phase.selectedForceMergeSegments)) {
|
||||
phaseErrors.selectedForceMergeSegments = [numberRequiredMessage];
|
||||
} else if (parseInt(phase.selectedForceMergeSegments, 10) < 1) {
|
||||
phaseErrors.selectedForceMergeSegments = [positiveNumbersAboveZeroErrorMessage];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...phaseErrors,
|
||||
};
|
||||
};
|
|
@ -7,26 +7,25 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
|
||||
import { Policy, PolicyFromES } from '../../../../common/types';
|
||||
import { SerializedPolicy } from '../../../../common/types';
|
||||
import { savePolicy as savePolicyApi } from '../api';
|
||||
import { showApiError } from '../api_errors';
|
||||
import { getUiMetricsForPhases, trackUiMetric } from '../ui_metric';
|
||||
import { UIM_POLICY_CREATE, UIM_POLICY_UPDATE } from '../../constants';
|
||||
import { toasts } from '../notification';
|
||||
import { serializePolicy } from './policy_serialization';
|
||||
|
||||
export const savePolicy = async (
|
||||
policy: Policy,
|
||||
isNew: boolean,
|
||||
originalEsPolicy?: PolicyFromES
|
||||
readSerializedPolicy: () => SerializedPolicy,
|
||||
isNew: boolean
|
||||
): Promise<boolean> => {
|
||||
const serializedPolicy = serializePolicy(policy, originalEsPolicy?.policy);
|
||||
const serializedPolicy = readSerializedPolicy();
|
||||
|
||||
try {
|
||||
await savePolicyApi(serializedPolicy);
|
||||
} catch (err) {
|
||||
const title = i18n.translate('xpack.indexLifecycleMgmt.editPolicy.saveErrorMessage', {
|
||||
defaultMessage: 'Error saving lifecycle policy {lifecycleName}',
|
||||
values: { lifecycleName: policy.name },
|
||||
values: { lifecycleName: serializedPolicy.name },
|
||||
});
|
||||
showApiError(err, title);
|
||||
return false;
|
||||
|
@ -46,7 +45,7 @@ export const savePolicy = async (
|
|||
: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.updatedMessage', {
|
||||
defaultMessage: 'Updated',
|
||||
}),
|
||||
lifecycleName: policy.name,
|
||||
lifecycleName: serializedPolicy.name,
|
||||
},
|
||||
});
|
||||
toasts.addSuccess(message);
|
||||
|
|
|
@ -6,24 +6,18 @@
|
|||
// Prefer importing entire lodash library, e.g. import { get } from "lodash"
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { deserializePolicy, serializePolicy } from './policy_serialization';
|
||||
import {
|
||||
defaultNewColdPhase,
|
||||
defaultNewDeletePhase,
|
||||
defaultNewHotPhase,
|
||||
defaultNewWarmPhase,
|
||||
} from '../../constants';
|
||||
import { deserializePolicy, legacySerializePolicy } from './policy_serialization';
|
||||
import { defaultNewColdPhase, defaultNewDeletePhase, defaultNewWarmPhase } from '../../constants';
|
||||
import { DataTierAllocationType } from '../../../../common/types';
|
||||
import { coldPhaseInitialization } from './cold_phase';
|
||||
|
||||
describe('Policy serialization', () => {
|
||||
test('serialize a policy using "default" data allocation', () => {
|
||||
expect(
|
||||
serializePolicy(
|
||||
legacySerializePolicy(
|
||||
{
|
||||
name: 'test',
|
||||
phases: {
|
||||
hot: { ...defaultNewHotPhase },
|
||||
warm: {
|
||||
...defaultNewWarmPhase,
|
||||
dataTierAllocationType: 'default',
|
||||
|
@ -56,17 +50,6 @@ describe('Policy serialization', () => {
|
|||
).toEqual({
|
||||
name: 'test',
|
||||
phases: {
|
||||
hot: {
|
||||
actions: {
|
||||
rollover: {
|
||||
max_age: '30d',
|
||||
max_size: '50gb',
|
||||
},
|
||||
set_priority: {
|
||||
priority: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
warm: {
|
||||
actions: {
|
||||
set_priority: {
|
||||
|
@ -88,11 +71,10 @@ describe('Policy serialization', () => {
|
|||
|
||||
test('serialize a policy using "custom" data allocation', () => {
|
||||
expect(
|
||||
serializePolicy(
|
||||
legacySerializePolicy(
|
||||
{
|
||||
name: 'test',
|
||||
phases: {
|
||||
hot: { ...defaultNewHotPhase },
|
||||
warm: {
|
||||
...defaultNewWarmPhase,
|
||||
dataTierAllocationType: 'custom',
|
||||
|
@ -136,17 +118,6 @@ describe('Policy serialization', () => {
|
|||
).toEqual({
|
||||
name: 'test',
|
||||
phases: {
|
||||
hot: {
|
||||
actions: {
|
||||
rollover: {
|
||||
max_age: '30d',
|
||||
max_size: '50gb',
|
||||
},
|
||||
set_priority: {
|
||||
priority: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
warm: {
|
||||
actions: {
|
||||
allocate: {
|
||||
|
@ -182,11 +153,10 @@ describe('Policy serialization', () => {
|
|||
|
||||
test('serialize a policy using "custom" data allocation with no node attributes', () => {
|
||||
expect(
|
||||
serializePolicy(
|
||||
legacySerializePolicy(
|
||||
{
|
||||
name: 'test',
|
||||
phases: {
|
||||
hot: { ...defaultNewHotPhase },
|
||||
warm: {
|
||||
...defaultNewWarmPhase,
|
||||
dataTierAllocationType: 'custom',
|
||||
|
@ -219,17 +189,6 @@ describe('Policy serialization', () => {
|
|||
// There should be no allocation action in any phases...
|
||||
name: 'test',
|
||||
phases: {
|
||||
hot: {
|
||||
actions: {
|
||||
rollover: {
|
||||
max_age: '30d',
|
||||
max_size: '50gb',
|
||||
},
|
||||
set_priority: {
|
||||
priority: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
warm: {
|
||||
actions: {
|
||||
allocate: { include: {}, exclude: {}, require: { something: 'here' } },
|
||||
|
@ -253,11 +212,10 @@ describe('Policy serialization', () => {
|
|||
|
||||
test('serialize a policy using "none" data allocation with no node attributes', () => {
|
||||
expect(
|
||||
serializePolicy(
|
||||
legacySerializePolicy(
|
||||
{
|
||||
name: 'test',
|
||||
phases: {
|
||||
hot: { ...defaultNewHotPhase },
|
||||
warm: {
|
||||
...defaultNewWarmPhase,
|
||||
dataTierAllocationType: 'none',
|
||||
|
@ -290,17 +248,6 @@ describe('Policy serialization', () => {
|
|||
// There should be no allocation action in any phases...
|
||||
name: 'test',
|
||||
phases: {
|
||||
hot: {
|
||||
actions: {
|
||||
rollover: {
|
||||
max_age: '30d',
|
||||
max_size: '50gb',
|
||||
},
|
||||
set_priority: {
|
||||
priority: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
warm: {
|
||||
actions: {
|
||||
migrate: {
|
||||
|
@ -330,7 +277,6 @@ describe('Policy serialization', () => {
|
|||
const originalPolicy = {
|
||||
name: 'test',
|
||||
phases: {
|
||||
hot: { actions: {} },
|
||||
warm: {
|
||||
actions: { allocate: { include: {}, exclude: {}, require: { something: 'here' } } },
|
||||
},
|
||||
|
@ -345,7 +291,6 @@ describe('Policy serialization', () => {
|
|||
const deserializedPolicy = {
|
||||
name: 'test',
|
||||
phases: {
|
||||
hot: { ...defaultNewHotPhase },
|
||||
warm: {
|
||||
...defaultNewWarmPhase,
|
||||
dataTierAllocationType: 'none' as DataTierAllocationType,
|
||||
|
@ -363,26 +308,20 @@ describe('Policy serialization', () => {
|
|||
},
|
||||
};
|
||||
|
||||
serializePolicy(deserializedPolicy, originalPolicy);
|
||||
legacySerializePolicy(deserializedPolicy, originalPolicy);
|
||||
deserializedPolicy.phases.warm.dataTierAllocationType = 'custom';
|
||||
serializePolicy(deserializedPolicy, originalPolicy);
|
||||
legacySerializePolicy(deserializedPolicy, originalPolicy);
|
||||
deserializedPolicy.phases.warm.dataTierAllocationType = 'default';
|
||||
serializePolicy(deserializedPolicy, originalPolicy);
|
||||
legacySerializePolicy(deserializedPolicy, originalPolicy);
|
||||
expect(originalPolicy).toEqual(originalClone);
|
||||
});
|
||||
|
||||
test('serialize a policy using "best_compression" codec for forcemerge', () => {
|
||||
expect(
|
||||
serializePolicy(
|
||||
legacySerializePolicy(
|
||||
{
|
||||
name: 'test',
|
||||
phases: {
|
||||
hot: {
|
||||
...defaultNewHotPhase,
|
||||
forceMergeEnabled: true,
|
||||
selectedForceMergeSegments: '1',
|
||||
bestCompressionEnabled: true,
|
||||
},
|
||||
warm: {
|
||||
...defaultNewWarmPhase,
|
||||
phaseEnabled: true,
|
||||
|
@ -406,21 +345,6 @@ describe('Policy serialization', () => {
|
|||
).toEqual({
|
||||
name: 'test',
|
||||
phases: {
|
||||
hot: {
|
||||
actions: {
|
||||
rollover: {
|
||||
max_age: '30d',
|
||||
max_size: '50gb',
|
||||
},
|
||||
forcemerge: {
|
||||
max_num_segments: 1,
|
||||
index_codec: 'best_compression',
|
||||
},
|
||||
set_priority: {
|
||||
priority: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
warm: {
|
||||
actions: {
|
||||
forcemerge: {
|
||||
|
@ -477,12 +401,6 @@ describe('Policy serialization', () => {
|
|||
).toEqual({
|
||||
name: 'test',
|
||||
phases: {
|
||||
hot: {
|
||||
...defaultNewHotPhase,
|
||||
forceMergeEnabled: true,
|
||||
selectedForceMergeSegments: '1',
|
||||
bestCompressionEnabled: true,
|
||||
},
|
||||
warm: {
|
||||
...defaultNewWarmPhase,
|
||||
warmPhaseOnRollover: false,
|
||||
|
@ -501,16 +419,10 @@ describe('Policy serialization', () => {
|
|||
|
||||
test('delete "best_compression" codec for forcemerge if disabled in UI', () => {
|
||||
expect(
|
||||
serializePolicy(
|
||||
legacySerializePolicy(
|
||||
{
|
||||
name: 'test',
|
||||
phases: {
|
||||
hot: {
|
||||
...defaultNewHotPhase,
|
||||
forceMergeEnabled: true,
|
||||
selectedForceMergeSegments: '1',
|
||||
bestCompressionEnabled: false,
|
||||
},
|
||||
warm: {
|
||||
...defaultNewWarmPhase,
|
||||
phaseEnabled: true,
|
||||
|
@ -527,7 +439,6 @@ describe('Policy serialization', () => {
|
|||
{
|
||||
name: 'test',
|
||||
phases: {
|
||||
hot: { actions: {} },
|
||||
warm: {
|
||||
actions: {
|
||||
forcemerge: {
|
||||
|
@ -542,20 +453,6 @@ describe('Policy serialization', () => {
|
|||
).toEqual({
|
||||
name: 'test',
|
||||
phases: {
|
||||
hot: {
|
||||
actions: {
|
||||
rollover: {
|
||||
max_age: '30d',
|
||||
max_size: '50gb',
|
||||
},
|
||||
forcemerge: {
|
||||
max_num_segments: 1,
|
||||
},
|
||||
set_priority: {
|
||||
priority: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
warm: {
|
||||
actions: {
|
||||
forcemerge: {
|
||||
|
|
|
@ -4,17 +4,15 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Policy, PolicyFromES, SerializedPolicy } from '../../../../common/types';
|
||||
import { LegacyPolicy, PolicyFromES, SerializedPolicy } from '../../../../common/types';
|
||||
|
||||
import {
|
||||
defaultNewColdPhase,
|
||||
defaultNewDeletePhase,
|
||||
defaultNewHotPhase,
|
||||
defaultNewWarmPhase,
|
||||
serializedPhaseInitialization,
|
||||
} from '../../constants';
|
||||
|
||||
import { hotPhaseFromES, hotPhaseToES } from './hot_phase';
|
||||
import { warmPhaseFromES, warmPhaseToES } from './warm_phase';
|
||||
import { coldPhaseFromES, coldPhaseToES } from './cold_phase';
|
||||
import { deletePhaseFromES, deletePhaseToES } from './delete_phase';
|
||||
|
@ -46,11 +44,10 @@ export const getPolicyByName = (
|
|||
}
|
||||
};
|
||||
|
||||
export const initializeNewPolicy = (newPolicyName: string = ''): Policy => {
|
||||
export const initializeNewPolicy = (newPolicyName: string = ''): LegacyPolicy => {
|
||||
return {
|
||||
name: newPolicyName,
|
||||
phases: {
|
||||
hot: { ...defaultNewHotPhase },
|
||||
warm: { ...defaultNewWarmPhase },
|
||||
cold: { ...defaultNewColdPhase },
|
||||
delete: { ...defaultNewDeletePhase },
|
||||
|
@ -58,7 +55,7 @@ export const initializeNewPolicy = (newPolicyName: string = ''): Policy => {
|
|||
};
|
||||
};
|
||||
|
||||
export const deserializePolicy = (policy: PolicyFromES): Policy => {
|
||||
export const deserializePolicy = (policy: PolicyFromES): LegacyPolicy => {
|
||||
const {
|
||||
name,
|
||||
policy: { phases },
|
||||
|
@ -67,7 +64,6 @@ export const deserializePolicy = (policy: PolicyFromES): Policy => {
|
|||
return {
|
||||
name,
|
||||
phases: {
|
||||
hot: hotPhaseFromES(phases.hot),
|
||||
warm: warmPhaseFromES(phases.warm),
|
||||
cold: coldPhaseFromES(phases.cold),
|
||||
delete: deletePhaseFromES(phases.delete),
|
||||
|
@ -75,8 +71,8 @@ export const deserializePolicy = (policy: PolicyFromES): Policy => {
|
|||
};
|
||||
};
|
||||
|
||||
export const serializePolicy = (
|
||||
policy: Policy,
|
||||
export const legacySerializePolicy = (
|
||||
policy: LegacyPolicy,
|
||||
originalEsPolicy: SerializedPolicy = {
|
||||
name: policy.name,
|
||||
phases: { hot: { ...serializedPhaseInitialization } },
|
||||
|
@ -84,7 +80,7 @@ export const serializePolicy = (
|
|||
): SerializedPolicy => {
|
||||
const serializedPolicy = {
|
||||
name: policy.name,
|
||||
phases: { hot: hotPhaseToES(policy.phases.hot, originalEsPolicy.phases.hot) },
|
||||
phases: {},
|
||||
} as SerializedPolicy;
|
||||
if (policy.phases.warm.phaseEnabled) {
|
||||
serializedPolicy.phases.warm = warmPhaseToES(policy.phases.warm, originalEsPolicy.phases.warm);
|
||||
|
|
|
@ -8,12 +8,10 @@ import { i18n } from '@kbn/i18n';
|
|||
import {
|
||||
ColdPhase,
|
||||
DeletePhase,
|
||||
HotPhase,
|
||||
Policy,
|
||||
LegacyPolicy,
|
||||
PolicyFromES,
|
||||
WarmPhase,
|
||||
} from '../../../../common/types';
|
||||
import { validateHotPhase } from './hot_phase';
|
||||
import { validateWarmPhase } from './warm_phase';
|
||||
import { validateColdPhase } from './cold_phase';
|
||||
import { validateDeletePhase } from './delete_phase';
|
||||
|
@ -35,27 +33,6 @@ export const positiveNumberRequiredMessage = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const maximumAgeRequiredMessage = i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.editPolicy.maximumAgeMissingError',
|
||||
{
|
||||
defaultMessage: 'A maximum age is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const maximumSizeRequiredMessage = i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.editPolicy.maximumIndexSizeMissingError',
|
||||
{
|
||||
defaultMessage: 'A maximum index size is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const maximumDocumentsRequiredMessage = i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.editPolicy.maximumDocumentsMissingError',
|
||||
{
|
||||
defaultMessage: 'Maximum documents is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const positiveNumbersAboveZeroErrorMessage = i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.editPolicy.positiveNumberAboveZeroRequiredError',
|
||||
{
|
||||
|
@ -112,7 +89,6 @@ export type PhaseValidationErrors<T> = {
|
|||
};
|
||||
|
||||
export interface ValidationErrors {
|
||||
hot: PhaseValidationErrors<HotPhase>;
|
||||
warm: PhaseValidationErrors<WarmPhase>;
|
||||
cold: PhaseValidationErrors<ColdPhase>;
|
||||
delete: PhaseValidationErrors<DeletePhase>;
|
||||
|
@ -121,7 +97,7 @@ export interface ValidationErrors {
|
|||
|
||||
export const validatePolicy = (
|
||||
saveAsNew: boolean,
|
||||
policy: Policy,
|
||||
policy: LegacyPolicy,
|
||||
policies: PolicyFromES[],
|
||||
originalPolicyName: string
|
||||
): [boolean, ValidationErrors] => {
|
||||
|
@ -152,13 +128,11 @@ export const validatePolicy = (
|
|||
}
|
||||
}
|
||||
|
||||
const hotPhaseErrors = validateHotPhase(policy.phases.hot);
|
||||
const warmPhaseErrors = validateWarmPhase(policy.phases.warm);
|
||||
const coldPhaseErrors = validateColdPhase(policy.phases.cold);
|
||||
const deletePhaseErrors = validateDeletePhase(policy.phases.delete);
|
||||
const isValid =
|
||||
policyNameErrors.length === 0 &&
|
||||
Object.keys(hotPhaseErrors).length === 0 &&
|
||||
Object.keys(warmPhaseErrors).length === 0 &&
|
||||
Object.keys(coldPhaseErrors).length === 0 &&
|
||||
Object.keys(deletePhaseErrors).length === 0;
|
||||
|
@ -166,7 +140,6 @@ export const validatePolicy = (
|
|||
isValid,
|
||||
{
|
||||
policyName: [...policyNameErrors],
|
||||
hot: hotPhaseErrors,
|
||||
warm: warmPhaseErrors,
|
||||
cold: coldPhaseErrors,
|
||||
delete: deletePhaseErrors,
|
||||
|
@ -183,9 +156,6 @@ export const findFirstError = (errors?: ValidationErrors): string | undefined =>
|
|||
return propertyof<ValidationErrors>('policyName');
|
||||
}
|
||||
|
||||
if (Object.keys(errors.hot).length > 0) {
|
||||
return `${propertyof<ValidationErrors>('hot')}.${Object.keys(errors.hot)[0]}`;
|
||||
}
|
||||
if (Object.keys(errors.warm).length > 0) {
|
||||
return `${propertyof<ValidationErrors>('warm')}.${Object.keys(errors.warm)[0]}`;
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ import {
|
|||
UIM_CONFIG_SET_PRIORITY,
|
||||
UIM_CONFIG_WARM_PHASE,
|
||||
defaultNewColdPhase,
|
||||
defaultNewHotPhase,
|
||||
defaultNewWarmPhase,
|
||||
defaultSetPriority,
|
||||
} from '../constants';
|
||||
|
||||
import { Phases } from '../../../common/types';
|
||||
|
@ -45,8 +45,7 @@ export function getUiMetricsForPhases(phases: Phases): string[] {
|
|||
const isHotPhasePriorityChanged =
|
||||
phases.hot &&
|
||||
phases.hot.actions.set_priority &&
|
||||
phases.hot.actions.set_priority.priority !==
|
||||
parseInt(defaultNewHotPhase.phaseIndexPriority, 10);
|
||||
phases.hot.actions.set_priority.priority !== parseInt(defaultSetPriority, 10);
|
||||
|
||||
const isWarmPhasePriorityChanged =
|
||||
phases.warm &&
|
||||
|
|
|
@ -143,7 +143,7 @@ export class IndexLifecycleSummary extends Component<Props, State> {
|
|||
);
|
||||
return (
|
||||
<Fragment key="phaseDefinition">
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle key="phaseDefinition_title">
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
defaultMessage="Phase definition"
|
||||
|
@ -204,14 +204,14 @@ export class IndexLifecycleSummary extends Component<Props, State> {
|
|||
}
|
||||
content = content || '-';
|
||||
const cell = (
|
||||
<>
|
||||
<Fragment key={String(arrayIndex)}>
|
||||
<EuiDescriptionListTitle key={fieldName}>
|
||||
<strong>{label}</strong>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription key={fieldName + '_desc'}>
|
||||
{content}
|
||||
</EuiDescriptionListDescription>
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
if (arrayIndex % 2 === 0) {
|
||||
rows.left.push(cell);
|
||||
|
|
|
@ -3,8 +3,31 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { AppServicesContext } from './types';
|
||||
import { useKibana as _useKibana } from '../../../../src/plugins/kibana_react/public';
|
||||
|
||||
export {
|
||||
useForm,
|
||||
useFormData,
|
||||
Form,
|
||||
UseField,
|
||||
FieldConfig,
|
||||
OnFormUpdateArg,
|
||||
ValidationFunc,
|
||||
getFieldValidityAndErrorMessage,
|
||||
useFormContext,
|
||||
FormSchema,
|
||||
} from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
|
||||
|
||||
export { fieldValidators } from '../../../../src/plugins/es_ui_shared/static/forms/helpers';
|
||||
|
||||
export {
|
||||
ToggleField,
|
||||
NumericField,
|
||||
SelectField,
|
||||
} from '../../../../src/plugins/es_ui_shared/static/forms/components';
|
||||
|
||||
export { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public';
|
||||
|
||||
export const useKibana = () => _useKibana<AppServicesContext>();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue