[ILM] Use global field to set the snapshot repository (#94602)
This commit is contained in:
parent
c9d1dbf599
commit
310194193a
|
@ -67,6 +67,46 @@ describe('<EditPolicy /> searchable snapshots', () => {
|
|||
expect(actions.hot.searchableSnapshotsExists()).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should set the repository from previously defined repository', async () => {
|
||||
const { actions } = testBed;
|
||||
|
||||
const repository = 'myRepo';
|
||||
await actions.hot.setSearchableSnapshot(repository);
|
||||
await actions.cold.enable(true);
|
||||
await actions.cold.toggleSearchableSnapshot(true);
|
||||
await actions.frozen.enable(true);
|
||||
|
||||
await actions.savePolicy();
|
||||
const latestRequest = server.requests[server.requests.length - 1];
|
||||
expect(latestRequest.method).toBe('POST');
|
||||
expect(latestRequest.url).toBe('/api/index_lifecycle_management/policies');
|
||||
const reqBody = JSON.parse(JSON.parse(latestRequest.requestBody).body);
|
||||
|
||||
expect(reqBody.phases.hot.actions.searchable_snapshot.snapshot_repository).toBe(repository);
|
||||
expect(reqBody.phases.cold.actions.searchable_snapshot.snapshot_repository).toBe(repository);
|
||||
expect(reqBody.phases.frozen.actions.searchable_snapshot.snapshot_repository).toBe(repository);
|
||||
});
|
||||
|
||||
test('should update the repository in all searchable snapshot actions', async () => {
|
||||
const { actions } = testBed;
|
||||
|
||||
await actions.hot.setSearchableSnapshot('myRepo');
|
||||
await actions.cold.enable(true);
|
||||
await actions.cold.toggleSearchableSnapshot(true);
|
||||
await actions.frozen.enable(true);
|
||||
|
||||
// We update the repository in one phase
|
||||
await actions.frozen.setSearchableSnapshot('changed');
|
||||
await actions.savePolicy();
|
||||
const latestRequest = server.requests[server.requests.length - 1];
|
||||
const reqBody = JSON.parse(JSON.parse(latestRequest.requestBody).body);
|
||||
|
||||
// And all phases should be updated
|
||||
expect(reqBody.phases.hot.actions.searchable_snapshot.snapshot_repository).toBe('changed');
|
||||
expect(reqBody.phases.cold.actions.searchable_snapshot.snapshot_repository).toBe('changed');
|
||||
expect(reqBody.phases.frozen.actions.searchable_snapshot.snapshot_repository).toBe('changed');
|
||||
});
|
||||
|
||||
describe('on cloud', () => {
|
||||
describe('new policy', () => {
|
||||
beforeEach(async () => {
|
||||
|
@ -86,6 +126,7 @@ describe('<EditPolicy /> searchable snapshots', () => {
|
|||
const { component } = testBed;
|
||||
component.update();
|
||||
});
|
||||
|
||||
test('defaults searchable snapshot to true on cloud', async () => {
|
||||
const { find, actions } = testBed;
|
||||
await actions.cold.enable(true);
|
||||
|
@ -112,14 +153,17 @@ describe('<EditPolicy /> searchable snapshots', () => {
|
|||
const { component } = testBed;
|
||||
component.update();
|
||||
});
|
||||
|
||||
test('correctly sets snapshot repository default to "found-snapshots"', async () => {
|
||||
const { actions } = testBed;
|
||||
await actions.cold.enable(true);
|
||||
await actions.cold.toggleSearchableSnapshot(true);
|
||||
await actions.savePolicy();
|
||||
const latestRequest = server.requests[server.requests.length - 1];
|
||||
const request = JSON.parse(JSON.parse(latestRequest.requestBody).body);
|
||||
expect(request.phases.cold.actions.searchable_snapshot.snapshot_repository).toEqual(
|
||||
expect(latestRequest.method).toBe('POST');
|
||||
expect(latestRequest.url).toBe('/api/index_lifecycle_management/policies');
|
||||
const reqBody = JSON.parse(JSON.parse(latestRequest.requestBody).body);
|
||||
expect(reqBody.phases.cold.actions.searchable_snapshot.snapshot_repository).toEqual(
|
||||
'found-snapshots'
|
||||
);
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React, { FunctionComponent } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useConfigurationIssues } from '../../../form';
|
||||
import { useConfiguration } from '../../../form';
|
||||
import {
|
||||
DataTierAllocationField,
|
||||
SearchableSnapshotField,
|
||||
|
@ -29,7 +29,7 @@ const i18nTexts = {
|
|||
};
|
||||
|
||||
export const ColdPhase: FunctionComponent = () => {
|
||||
const { isUsingSearchableSnapshotInHotPhase } = useConfigurationIssues();
|
||||
const { isUsingSearchableSnapshotInHotPhase } = useConfiguration();
|
||||
|
||||
return (
|
||||
<Phase phase="cold" topLevelSettings={<SearchableSnapshotField phase="cold" />}>
|
||||
|
|
|
@ -20,18 +20,16 @@ import {
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { useFormData } from '../../../../../../shared_imports';
|
||||
|
||||
import { i18nTexts } from '../../../i18n_texts';
|
||||
|
||||
import { usePhaseTimings } from '../../../form';
|
||||
|
||||
import { MinAgeField, SnapshotPoliciesField } from '../shared_fields';
|
||||
import './delete_phase.scss';
|
||||
import { usePhaseTimings, globalFields } from '../../../form';
|
||||
import { PhaseIcon } from '../../phase_icon';
|
||||
import { MinAgeField, SnapshotPoliciesField } from '../shared_fields';
|
||||
import { PhaseErrorIndicator } from '../phase/phase_error_indicator';
|
||||
|
||||
import './delete_phase.scss';
|
||||
|
||||
const formFieldPaths = {
|
||||
enabled: '_meta.delete.enabled',
|
||||
enabled: globalFields.deleteEnabled.path,
|
||||
};
|
||||
|
||||
export const DeletePhase: FunctionComponent = () => {
|
||||
|
|
|
@ -23,7 +23,7 @@ import { useFormData, SelectField, NumericField } from '../../../../../../shared
|
|||
|
||||
import { i18nTexts } from '../../../i18n_texts';
|
||||
|
||||
import { ROLLOVER_EMPTY_VALIDATION, useConfigurationIssues, UseField } from '../../../form';
|
||||
import { ROLLOVER_EMPTY_VALIDATION, useConfiguration, UseField } from '../../../form';
|
||||
|
||||
import { useEditPolicyContext } from '../../../edit_policy_context';
|
||||
|
||||
|
@ -47,7 +47,7 @@ export const HotPhase: FunctionComponent = () => {
|
|||
const [formData] = useFormData({
|
||||
watch: isUsingDefaultRolloverPath,
|
||||
});
|
||||
const { isUsingRollover } = useConfigurationIssues();
|
||||
const { isUsingRollover } = useConfiguration();
|
||||
const isUsingDefaultRollover: boolean = get(formData, isUsingDefaultRolloverPath);
|
||||
const [showEmptyRolloverFieldsError, setShowEmptyRolloverFieldsError] = useState(false);
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
|
||||
import { getFieldValidityAndErrorMessage } from '../../../../../../../shared_imports';
|
||||
|
||||
import { UseField, useConfigurationIssues } from '../../../../form';
|
||||
import { UseField, useConfiguration } from '../../../../form';
|
||||
|
||||
import { getUnitsAriaLabelForPhase, getTimingLabelForPhase } from './util';
|
||||
|
||||
|
@ -81,7 +81,7 @@ interface Props {
|
|||
}
|
||||
|
||||
export const MinAgeField: FunctionComponent<Props> = ({ phase }): React.ReactElement => {
|
||||
const { isUsingRollover } = useConfigurationIssues();
|
||||
const { isUsingRollover } = useConfiguration();
|
||||
return (
|
||||
<UseField path={`phases.${phase}.min_age`}>
|
||||
{(field) => {
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { EuiComboBoxOptionOption } from '@elastic/eui';
|
||||
|
||||
import { ComboBoxField, FieldHook } from '../../../../../../../shared_imports';
|
||||
import { useGlobalFields } from '../../../../form';
|
||||
|
||||
interface PropsRepositoryCombobox {
|
||||
field: FieldHook;
|
||||
isLoading: boolean;
|
||||
repos: string[];
|
||||
noSuggestions: boolean;
|
||||
globalRepository: string;
|
||||
}
|
||||
|
||||
export const RepositoryComboBoxField = ({
|
||||
field,
|
||||
isLoading,
|
||||
repos,
|
||||
noSuggestions,
|
||||
globalRepository,
|
||||
}: PropsRepositoryCombobox) => {
|
||||
const isMounted = useRef(false);
|
||||
const { setValue } = field;
|
||||
const {
|
||||
searchableSnapshotRepo: { setValue: setSearchableSnapshotRepository },
|
||||
} = useGlobalFields();
|
||||
|
||||
useEffect(() => {
|
||||
// We keep our phase searchable action field in sync
|
||||
// with the default repository field declared globally for the policy
|
||||
if (isMounted.current) {
|
||||
setValue(Boolean(globalRepository.trim()) ? [globalRepository] : []);
|
||||
}
|
||||
isMounted.current = true;
|
||||
}, [setValue, globalRepository]);
|
||||
|
||||
return (
|
||||
<ComboBoxField
|
||||
field={field}
|
||||
fullWidth={false}
|
||||
euiFieldProps={{
|
||||
'data-test-subj': 'searchableSnapshotCombobox',
|
||||
options: repos.map((repo) => ({ label: repo, value: repo })),
|
||||
singleSelection: { asPlainText: true },
|
||||
isLoading,
|
||||
noSuggestions,
|
||||
onCreateOption: (newOption: string) => {
|
||||
setSearchableSnapshotRepository(newOption);
|
||||
},
|
||||
onChange: (options: EuiComboBoxOptionOption[]) => {
|
||||
if (options.length > 0) {
|
||||
setSearchableSnapshotRepository(options[0].label);
|
||||
} else {
|
||||
setSearchableSnapshotRepository('');
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -5,24 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FunctionComponent, useState, useEffect } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { get } from 'lodash';
|
||||
import React, { FunctionComponent, useState, useEffect } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiComboBoxOptionOption,
|
||||
EuiTextColor,
|
||||
EuiSpacer,
|
||||
EuiCallOut,
|
||||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { ComboBoxField, useKibana, useFormData } from '../../../../../../../shared_imports';
|
||||
import { EuiTextColor, EuiSpacer, EuiCallOut, EuiLink } from '@elastic/eui';
|
||||
|
||||
import { useKibana, useFormData } from '../../../../../../../shared_imports';
|
||||
import { useEditPolicyContext } from '../../../../edit_policy_context';
|
||||
import { useConfigurationIssues, UseField, searchableSnapshotFields } from '../../../../form';
|
||||
import { useConfiguration, UseField, globalFields } from '../../../../form';
|
||||
import { FieldLoadingError, DescribedFormRow, LearnMoreLink } from '../../../';
|
||||
import { SearchableSnapshotDataProvider } from './searchable_snapshot_data_provider';
|
||||
import { RepositoryComboBoxField } from './repository_combobox_field';
|
||||
|
||||
import './_searchable_snapshot_field.scss';
|
||||
|
||||
|
@ -31,12 +25,6 @@ export interface Props {
|
|||
canBeDisabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* This repository is provisioned by Elastic Cloud and will always
|
||||
* exist as a "managed" repository.
|
||||
*/
|
||||
const CLOUD_DEFAULT_REPO = 'found-snapshots';
|
||||
|
||||
const geti18nTexts = (phase: Props['phase']) => ({
|
||||
title: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotFieldTitle', {
|
||||
defaultMessage: 'Searchable snapshot',
|
||||
|
@ -71,13 +59,15 @@ export const SearchableSnapshotField: FunctionComponent<Props> = ({
|
|||
services: { cloud },
|
||||
} = useKibana();
|
||||
const { getUrlForApp, policy, license, isNewPolicy } = useEditPolicyContext();
|
||||
const { isUsingSearchableSnapshotInHotPhase } = useConfigurationIssues();
|
||||
const { isUsingSearchableSnapshotInHotPhase } = useConfiguration();
|
||||
|
||||
const searchableSnapshotRepoPath = `phases.${phase}.actions.searchable_snapshot.snapshot_repository`;
|
||||
|
||||
const [formData] = useFormData({ watch: searchableSnapshotRepoPath });
|
||||
const searchableSnapshotRepo = get(formData, searchableSnapshotRepoPath);
|
||||
const [formData] = useFormData({
|
||||
watch: globalFields.searchableSnapshotRepo.path,
|
||||
});
|
||||
|
||||
const searchableSnapshotGlobalRepo = get(formData, globalFields.searchableSnapshotRepo.path);
|
||||
const isColdPhase = phase === 'cold';
|
||||
const isFrozenPhase = phase === 'frozen';
|
||||
const isColdOrFrozenPhase = isColdPhase || isFrozenPhase;
|
||||
|
@ -164,7 +154,10 @@ export const SearchableSnapshotField: FunctionComponent<Props> = ({
|
|||
/>
|
||||
</EuiCallOut>
|
||||
);
|
||||
} else if (searchableSnapshotRepo && !repos.includes(searchableSnapshotRepo)) {
|
||||
} else if (
|
||||
searchableSnapshotGlobalRepo &&
|
||||
!repos.includes(searchableSnapshotGlobalRepo)
|
||||
) {
|
||||
calloutContent = (
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
|
@ -201,49 +194,17 @@ export const SearchableSnapshotField: FunctionComponent<Props> = ({
|
|||
|
||||
return (
|
||||
<div className="ilmSearchableSnapshotField">
|
||||
<UseField<string>
|
||||
config={{
|
||||
...searchableSnapshotFields.snapshot_repository,
|
||||
defaultValue: cloud?.isCloudEnabled ? CLOUD_DEFAULT_REPO : undefined,
|
||||
}}
|
||||
<UseField
|
||||
path={searchableSnapshotRepoPath}
|
||||
>
|
||||
{(field) => {
|
||||
const singleSelectionArray: [selectedSnapshot?: string] = field.value
|
||||
? [field.value]
|
||||
: [];
|
||||
|
||||
return (
|
||||
<ComboBoxField
|
||||
field={
|
||||
{
|
||||
...field,
|
||||
value: singleSelectionArray,
|
||||
} as any
|
||||
}
|
||||
label={field.label}
|
||||
fullWidth={false}
|
||||
euiFieldProps={{
|
||||
'data-test-subj': 'searchableSnapshotCombobox',
|
||||
options: repos.map((repo) => ({ label: repo, value: repo })),
|
||||
singleSelection: { asPlainText: true },
|
||||
isLoading,
|
||||
noSuggestions: !!(error || repos.length === 0),
|
||||
onCreateOption: (newOption: string) => {
|
||||
field.setValue(newOption);
|
||||
},
|
||||
onChange: (options: EuiComboBoxOptionOption[]) => {
|
||||
if (options.length > 0) {
|
||||
field.setValue(options[0].label);
|
||||
} else {
|
||||
field.setValue('');
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
defaultValue={!!searchableSnapshotGlobalRepo ? [searchableSnapshotGlobalRepo] : []}
|
||||
component={RepositoryComboBoxField}
|
||||
componentProps={{
|
||||
globalRepository: searchableSnapshotGlobalRepo,
|
||||
isLoading,
|
||||
repos,
|
||||
noSuggestions: !!(error || repos.length === 0),
|
||||
}}
|
||||
</UseField>
|
||||
/>
|
||||
{calloutContent && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React, { FunctionComponent } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useConfigurationIssues } from '../../../form';
|
||||
import { useConfiguration } from '../../../form';
|
||||
|
||||
import {
|
||||
ForcemergeField,
|
||||
|
@ -30,7 +30,7 @@ const i18nTexts = {
|
|||
};
|
||||
|
||||
export const WarmPhase: FunctionComponent = () => {
|
||||
const { isUsingSearchableSnapshotInHotPhase } = useConfigurationIssues();
|
||||
const { isUsingSearchableSnapshotInHotPhase } = useConfiguration();
|
||||
|
||||
return (
|
||||
<Phase phase="warm">
|
||||
|
|
|
@ -11,7 +11,7 @@ import { useFormData } from '../../../../../shared_imports';
|
|||
|
||||
import { formDataToAbsoluteTimings } from '../../lib';
|
||||
|
||||
import { useConfigurationIssues } from '../../form';
|
||||
import { useConfiguration } from '../../form';
|
||||
|
||||
import { FormInternal } from '../../types';
|
||||
|
||||
|
@ -20,7 +20,7 @@ import { Timeline as ViewComponent } from './timeline';
|
|||
export const Timeline: FunctionComponent = () => {
|
||||
const [formData] = useFormData<FormInternal>();
|
||||
const timings = formDataToAbsoluteTimings(formData);
|
||||
const { isUsingRollover } = useConfigurationIssues();
|
||||
const { isUsingRollover } = useConfiguration();
|
||||
return (
|
||||
<ViewComponent
|
||||
hotPhaseMinAge={timings.hot.min_age}
|
||||
|
|
|
@ -18,3 +18,9 @@ export const ROLLOVER_FORM_PATHS = {
|
|||
maxAge: 'phases.hot.actions.rollover.max_age',
|
||||
maxSize: 'phases.hot.actions.rollover.max_size',
|
||||
};
|
||||
|
||||
/**
|
||||
* This repository is provisioned by Elastic Cloud and will always
|
||||
* exist as a "managed" repository.
|
||||
*/
|
||||
export const CLOUD_DEFAULT_REPO = 'found-snapshots';
|
||||
|
|
|
@ -30,15 +30,11 @@ import {
|
|||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { TextField, useForm, useFormData } from '../../../shared_imports';
|
||||
|
||||
import { TextField, useForm, useFormData, useKibana } from '../../../shared_imports';
|
||||
import { toasts } from '../../services/notification';
|
||||
import { createDocLink } from '../../services/documentation';
|
||||
|
||||
import { UseField } from './form';
|
||||
|
||||
import { savePolicy } from './save_policy';
|
||||
|
||||
import {
|
||||
ColdPhase,
|
||||
DeletePhase,
|
||||
|
@ -49,11 +45,14 @@ import {
|
|||
Timeline,
|
||||
FormErrorsCallout,
|
||||
} from './components';
|
||||
|
||||
import { createPolicyNameValidations, createSerializer, deserializer, Form, schema } from './form';
|
||||
|
||||
import {
|
||||
createPolicyNameValidations,
|
||||
createSerializer,
|
||||
createDeserializer,
|
||||
Form,
|
||||
getSchema,
|
||||
} from './form';
|
||||
import { useEditPolicyContext } from './edit_policy_context';
|
||||
|
||||
import { FormInternal } from './types';
|
||||
|
||||
export interface Props {
|
||||
|
@ -76,20 +75,38 @@ export const EditPolicy: React.FunctionComponent<Props> = ({ history }) => {
|
|||
license,
|
||||
} = useEditPolicyContext();
|
||||
|
||||
const serializer = useMemo(() => {
|
||||
return createSerializer(isNewPolicy ? undefined : currentPolicy);
|
||||
}, [isNewPolicy, currentPolicy]);
|
||||
const {
|
||||
services: { cloud },
|
||||
} = useKibana();
|
||||
|
||||
const [saveAsNew, setSaveAsNew] = useState(false);
|
||||
const originalPolicyName: string = isNewPolicy ? '' : policyName!;
|
||||
const isAllowedByLicense = license.canUseSearchableSnapshot();
|
||||
const isCloudEnabled = Boolean(cloud?.isCloudEnabled);
|
||||
|
||||
const serializer = useMemo(() => {
|
||||
return createSerializer(isNewPolicy ? undefined : currentPolicy);
|
||||
}, [isNewPolicy, currentPolicy]);
|
||||
|
||||
const deserializer = useMemo(() => {
|
||||
return createDeserializer(isCloudEnabled);
|
||||
}, [isCloudEnabled]);
|
||||
|
||||
const defaultValue = useMemo(
|
||||
() => ({
|
||||
...currentPolicy,
|
||||
name: originalPolicyName,
|
||||
}),
|
||||
[currentPolicy, originalPolicyName]
|
||||
);
|
||||
|
||||
const schema = useMemo(() => {
|
||||
return getSchema(isCloudEnabled);
|
||||
}, [isCloudEnabled]);
|
||||
|
||||
const { form } = useForm({
|
||||
schema,
|
||||
defaultValue: {
|
||||
...currentPolicy,
|
||||
name: originalPolicyName,
|
||||
},
|
||||
defaultValue,
|
||||
deserializer,
|
||||
serializer,
|
||||
});
|
||||
|
|
|
@ -9,20 +9,25 @@ import React, { FunctionComponent } from 'react';
|
|||
|
||||
import { Form as LibForm, FormHook } from '../../../../../shared_imports';
|
||||
|
||||
import { ConfigurationIssuesProvider } from '../configuration_issues_context';
|
||||
import { ConfigurationProvider } from '../configuration_context';
|
||||
import { FormErrorsProvider } from '../form_errors_context';
|
||||
import { PhaseTimingsProvider } from '../phase_timings_context';
|
||||
import { GlobalFieldsProvider } from '../global_fields_context';
|
||||
|
||||
interface Props {
|
||||
form: FormHook;
|
||||
}
|
||||
|
||||
export const Form: FunctionComponent<Props> = ({ form, children }) => (
|
||||
<LibForm form={form}>
|
||||
<ConfigurationIssuesProvider>
|
||||
<FormErrorsProvider>
|
||||
<PhaseTimingsProvider>{children}</PhaseTimingsProvider>
|
||||
</FormErrorsProvider>
|
||||
</ConfigurationIssuesProvider>
|
||||
</LibForm>
|
||||
);
|
||||
export const Form: FunctionComponent<Props> = ({ form, children }) => {
|
||||
return (
|
||||
<LibForm form={form}>
|
||||
<ConfigurationProvider>
|
||||
<FormErrorsProvider>
|
||||
<GlobalFieldsProvider>
|
||||
<PhaseTimingsProvider>{children}</PhaseTimingsProvider>
|
||||
</GlobalFieldsProvider>
|
||||
</FormErrorsProvider>
|
||||
</ConfigurationProvider>
|
||||
</LibForm>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -12,7 +12,7 @@ import { useFormData } from '../../../../shared_imports';
|
|||
|
||||
import { isUsingDefaultRolloverPath, isUsingCustomRolloverPath } from '../constants';
|
||||
|
||||
export interface ConfigurationIssues {
|
||||
export interface Configuration {
|
||||
/**
|
||||
* Whether the serialized policy will use rollover. This blocks certain actions in
|
||||
* the form such as hot phase (forcemerge, shrink) and cold phase (searchable snapshot).
|
||||
|
@ -28,7 +28,7 @@ export interface ConfigurationIssues {
|
|||
isUsingSearchableSnapshotInColdPhase: boolean;
|
||||
}
|
||||
|
||||
const ConfigurationIssuesContext = createContext<ConfigurationIssues>(null as any);
|
||||
const ConfigurationContext = createContext<Configuration>(null as any);
|
||||
|
||||
const pathToHotPhaseSearchableSnapshot =
|
||||
'phases.hot.actions.searchable_snapshot.snapshot_repository';
|
||||
|
@ -36,7 +36,7 @@ const pathToHotPhaseSearchableSnapshot =
|
|||
const pathToColdPhaseSearchableSnapshot =
|
||||
'phases.cold.actions.searchable_snapshot.snapshot_repository';
|
||||
|
||||
export const ConfigurationIssuesProvider: FunctionComponent = ({ children }) => {
|
||||
export const ConfigurationProvider: FunctionComponent = ({ children }) => {
|
||||
const [formData] = useFormData({
|
||||
watch: [
|
||||
pathToHotPhaseSearchableSnapshot,
|
||||
|
@ -49,25 +49,18 @@ export const ConfigurationIssuesProvider: FunctionComponent = ({ children }) =>
|
|||
// Provide default value, as path may become undefined if removed from the DOM
|
||||
const isUsingCustomRollover = get(formData, isUsingCustomRolloverPath, true);
|
||||
|
||||
return (
|
||||
<ConfigurationIssuesContext.Provider
|
||||
value={{
|
||||
isUsingRollover: isUsingDefaultRollover === false ? isUsingCustomRollover : true,
|
||||
isUsingSearchableSnapshotInHotPhase:
|
||||
get(formData, pathToHotPhaseSearchableSnapshot) != null,
|
||||
isUsingSearchableSnapshotInColdPhase:
|
||||
get(formData, pathToColdPhaseSearchableSnapshot) != null,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ConfigurationIssuesContext.Provider>
|
||||
);
|
||||
const context: Configuration = {
|
||||
isUsingRollover: isUsingDefaultRollover === false ? isUsingCustomRollover : true,
|
||||
isUsingSearchableSnapshotInHotPhase: get(formData, pathToHotPhaseSearchableSnapshot) != null,
|
||||
isUsingSearchableSnapshotInColdPhase: get(formData, pathToColdPhaseSearchableSnapshot) != null,
|
||||
};
|
||||
|
||||
return <ConfigurationContext.Provider value={context}>{children}</ConfigurationContext.Provider>;
|
||||
};
|
||||
|
||||
export const useConfigurationIssues = () => {
|
||||
const ctx = useContext(ConfigurationIssuesContext);
|
||||
if (!ctx)
|
||||
throw new Error('Cannot use configuration issues outside of configuration issues context');
|
||||
export const useConfiguration = () => {
|
||||
const ctx = useContext(ConfigurationContext);
|
||||
if (!ctx) throw new Error('Cannot use configuration outside of configuration context');
|
||||
|
||||
return ctx;
|
||||
};
|
|
@ -8,18 +8,29 @@
|
|||
import { produce } from 'immer';
|
||||
|
||||
import { SerializedPolicy } from '../../../../../common/types';
|
||||
|
||||
import { splitSizeAndUnits } from '../../../lib/policies';
|
||||
|
||||
import { determineDataTierAllocationType, isUsingDefaultRollover } from '../../../lib';
|
||||
|
||||
import { getDefaultRepository } from '../lib';
|
||||
import { FormInternal } from '../types';
|
||||
import { CLOUD_DEFAULT_REPO } from '../constants';
|
||||
|
||||
export const deserializer = (policy: SerializedPolicy): FormInternal => {
|
||||
export const createDeserializer = (isCloudEnabled: boolean) => (
|
||||
policy: SerializedPolicy
|
||||
): FormInternal => {
|
||||
const {
|
||||
phases: { hot, warm, cold, frozen, delete: deletePhase },
|
||||
} = policy;
|
||||
|
||||
let defaultRepository = getDefaultRepository([
|
||||
hot?.actions.searchable_snapshot,
|
||||
cold?.actions.searchable_snapshot,
|
||||
frozen?.actions.searchable_snapshot,
|
||||
]);
|
||||
|
||||
if (!defaultRepository && isCloudEnabled) {
|
||||
defaultRepository = CLOUD_DEFAULT_REPO;
|
||||
}
|
||||
|
||||
const _meta: FormInternal['_meta'] = {
|
||||
hot: {
|
||||
isUsingDefaultRollover: isUsingDefaultRollover(policy),
|
||||
|
@ -49,6 +60,9 @@ export const deserializer = (policy: SerializedPolicy): FormInternal => {
|
|||
delete: {
|
||||
enabled: Boolean(deletePhase),
|
||||
},
|
||||
searchableSnapshot: {
|
||||
repository: defaultRepository,
|
||||
},
|
||||
};
|
||||
|
||||
return produce<FormInternal>(
|
||||
|
|
|
@ -9,7 +9,7 @@ import { setAutoFreeze } from 'immer';
|
|||
import { cloneDeep } from 'lodash';
|
||||
import { SerializedPolicy } from '../../../../../common/types';
|
||||
import { defaultRolloverAction } from '../../../constants';
|
||||
import { deserializer } from './deserializer';
|
||||
import { createDeserializer } from './deserializer';
|
||||
import { createSerializer } from './serializer';
|
||||
import { FormInternal } from '../types';
|
||||
|
||||
|
@ -18,6 +18,8 @@ const isObject = (v: unknown): v is { [key: string]: any } =>
|
|||
|
||||
const unknownValue = { some: 'value' };
|
||||
|
||||
const deserializer = createDeserializer(false);
|
||||
|
||||
const populateWithUnknownEntries = (v: unknown) => {
|
||||
if (isObject(v)) {
|
||||
for (const key of Object.keys(v)) {
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { createContext, FunctionComponent, useContext } from 'react';
|
||||
import { UseMultiFields, FieldHook, FieldConfig } from '../../../../shared_imports';
|
||||
|
||||
/**
|
||||
* Those are the fields that we always want present in our form.
|
||||
*/
|
||||
interface GlobalFieldsTypes {
|
||||
deleteEnabled: boolean;
|
||||
searchableSnapshotRepo: string;
|
||||
}
|
||||
|
||||
type GlobalFields = {
|
||||
[K in keyof GlobalFieldsTypes]: FieldHook<GlobalFieldsTypes[K]>;
|
||||
};
|
||||
|
||||
const GlobalFieldsContext = createContext<GlobalFields | null>(null);
|
||||
|
||||
export const globalFields: Record<
|
||||
keyof GlobalFields,
|
||||
{ path: string; config?: FieldConfig<any> }
|
||||
> = {
|
||||
deleteEnabled: {
|
||||
path: '_meta.delete.enabled',
|
||||
},
|
||||
searchableSnapshotRepo: {
|
||||
path: '_meta.searchableSnapshot.repository',
|
||||
},
|
||||
};
|
||||
|
||||
export const GlobalFieldsProvider: FunctionComponent = ({ children }) => {
|
||||
return (
|
||||
<UseMultiFields<GlobalFieldsTypes> fields={globalFields}>
|
||||
{(fields) => {
|
||||
return (
|
||||
<GlobalFieldsContext.Provider value={fields}>{children}</GlobalFieldsContext.Provider>
|
||||
);
|
||||
}}
|
||||
</UseMultiFields>
|
||||
);
|
||||
};
|
||||
|
||||
export const useGlobalFields = () => {
|
||||
const ctx = useContext(GlobalFieldsContext);
|
||||
if (!ctx) throw new Error('Cannot use global fields outside of global fields context');
|
||||
|
||||
return ctx;
|
||||
};
|
|
@ -5,20 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { deserializer } from './deserializer';
|
||||
export { createDeserializer } from './deserializer';
|
||||
|
||||
export { createSerializer } from './serializer';
|
||||
|
||||
export { schema, searchableSnapshotFields } from './schema';
|
||||
export { getSchema } from './schema';
|
||||
|
||||
export * from './validations';
|
||||
|
||||
export { Form, EnhancedUseField as UseField } from './components';
|
||||
|
||||
export {
|
||||
ConfigurationIssuesProvider,
|
||||
useConfigurationIssues,
|
||||
} from './configuration_issues_context';
|
||||
export { ConfigurationProvider, useConfiguration } from './configuration_context';
|
||||
|
||||
export { FormErrorsProvider, useFormErrorsContext } from './form_errors_context';
|
||||
|
||||
|
@ -27,3 +24,5 @@ export {
|
|||
usePhaseTimings,
|
||||
PhaseTimingConfiguration,
|
||||
} from './phase_timings_context';
|
||||
|
||||
export { useGlobalFields, globalFields } from './global_fields_context';
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React, { createContext, FunctionComponent, useContext } from 'react';
|
||||
import { useFormData } from '../../../../shared_imports';
|
||||
import { FormInternal } from '../types';
|
||||
import { UseField } from './index';
|
||||
import { useGlobalFields } from './index';
|
||||
|
||||
export interface PhaseTimingConfiguration {
|
||||
/**
|
||||
|
@ -48,6 +48,7 @@ export interface PhaseTimings {
|
|||
const PhaseTimingsContext = createContext<PhaseTimings>(null as any);
|
||||
|
||||
export const PhaseTimingsProvider: FunctionComponent = ({ children }) => {
|
||||
const { deleteEnabled } = useGlobalFields();
|
||||
const [formData] = useFormData<FormInternal>({
|
||||
watch: [
|
||||
'_meta.warm.enabled',
|
||||
|
@ -58,21 +59,15 @@ export const PhaseTimingsProvider: FunctionComponent = ({ children }) => {
|
|||
});
|
||||
|
||||
return (
|
||||
<UseField path="_meta.delete.enabled">
|
||||
{(field) => {
|
||||
return (
|
||||
<PhaseTimingsContext.Provider
|
||||
value={{
|
||||
...getPhaseTimingConfiguration(formData),
|
||||
isDeletePhaseEnabled: formData?._meta?.delete?.enabled,
|
||||
setDeletePhaseEnabled: field.setValue,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</PhaseTimingsContext.Provider>
|
||||
);
|
||||
<PhaseTimingsContext.Provider
|
||||
value={{
|
||||
...getPhaseTimingConfiguration(formData),
|
||||
isDeletePhaseEnabled: deleteEnabled.value,
|
||||
setDeletePhaseEnabled: deleteEnabled.setValue,
|
||||
}}
|
||||
</UseField>
|
||||
>
|
||||
{children}
|
||||
</PhaseTimingsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -9,12 +9,8 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { FormSchema, fieldValidators } from '../../../../shared_imports';
|
||||
import { defaultIndexPriority } from '../../../constants';
|
||||
import { ROLLOVER_FORM_PATHS } from '../constants';
|
||||
|
||||
import { FormInternal } from '../types';
|
||||
|
||||
const rolloverFormPaths = Object.values(ROLLOVER_FORM_PATHS);
|
||||
|
||||
import { ROLLOVER_FORM_PATHS, CLOUD_DEFAULT_REPO } from '../constants';
|
||||
import { i18nTexts } from '../i18n_texts';
|
||||
import {
|
||||
ifExistsNumberGreaterThanZero,
|
||||
ifExistsNumberNonNegative,
|
||||
|
@ -22,7 +18,7 @@ import {
|
|||
minAgeValidator,
|
||||
} from './validations';
|
||||
|
||||
import { i18nTexts } from '../i18n_texts';
|
||||
const rolloverFormPaths = Object.values(ROLLOVER_FORM_PATHS);
|
||||
|
||||
const { emptyField, numberGreaterThanField } = fieldValidators;
|
||||
|
||||
|
@ -54,6 +50,13 @@ export const searchableSnapshotFields = {
|
|||
validations: [
|
||||
{ validator: emptyField(i18nTexts.editPolicy.errors.searchableSnapshotRepoRequired) },
|
||||
],
|
||||
// TODO: update text copy
|
||||
helpText: i18n.translate(
|
||||
'xpack.indexLifecycleMgmt.editPolicy.searchableSnapshot.repositoryHelpText',
|
||||
{
|
||||
defaultMessage: 'Each phase uses the same snapshot repository.',
|
||||
}
|
||||
),
|
||||
},
|
||||
storage: {
|
||||
label: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.searchableSnapshot.storageLabel', {
|
||||
|
@ -114,7 +117,7 @@ const getPriorityField = (phase: 'hot' | 'warm' | 'cold' | 'frozen') => ({
|
|||
serializer: serializers.stringToNumber,
|
||||
});
|
||||
|
||||
export const schema: FormSchema<FormInternal> = {
|
||||
export const getSchema = (isCloudEnabled: boolean): FormSchema => ({
|
||||
_meta: {
|
||||
hot: {
|
||||
isUsingDefaultRollover: {
|
||||
|
@ -230,6 +233,11 @@ export const schema: FormSchema<FormInternal> = {
|
|||
defaultValue: 'd',
|
||||
},
|
||||
},
|
||||
searchableSnapshot: {
|
||||
repository: {
|
||||
defaultValue: isCloudEnabled ? CLOUD_DEFAULT_REPO : '',
|
||||
},
|
||||
},
|
||||
},
|
||||
phases: {
|
||||
hot: {
|
||||
|
@ -288,6 +296,7 @@ export const schema: FormSchema<FormInternal> = {
|
|||
set_priority: {
|
||||
priority: getPriorityField('hot'),
|
||||
},
|
||||
searchable_snapshot: searchableSnapshotFields,
|
||||
},
|
||||
},
|
||||
warm: {
|
||||
|
@ -375,4 +384,4 @@ export const schema: FormSchema<FormInternal> = {
|
|||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -124,7 +124,12 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => (
|
|||
/**
|
||||
* HOT PHASE SEARCHABLE SNAPSHOT
|
||||
*/
|
||||
if (!updatedPolicy.phases.hot!.actions?.searchable_snapshot) {
|
||||
if (updatedPolicy.phases.hot!.actions?.searchable_snapshot) {
|
||||
hotPhaseActions.searchable_snapshot = {
|
||||
...hotPhaseActions.searchable_snapshot,
|
||||
snapshot_repository: _meta.searchableSnapshot.repository,
|
||||
};
|
||||
} else {
|
||||
delete hotPhaseActions.searchable_snapshot;
|
||||
}
|
||||
}
|
||||
|
@ -234,7 +239,12 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => (
|
|||
/**
|
||||
* COLD PHASE SEARCHABLE SNAPSHOT
|
||||
*/
|
||||
if (!updatedPolicy.phases.cold?.actions?.searchable_snapshot) {
|
||||
if (updatedPolicy.phases.cold?.actions?.searchable_snapshot) {
|
||||
coldPhase.actions.searchable_snapshot = {
|
||||
...coldPhase.actions.searchable_snapshot,
|
||||
snapshot_repository: _meta.searchableSnapshot.repository,
|
||||
};
|
||||
} else {
|
||||
delete coldPhase.actions.searchable_snapshot;
|
||||
}
|
||||
} else {
|
||||
|
@ -251,7 +261,12 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => (
|
|||
/**
|
||||
* FROZEN PHASE SEARCHABLE SNAPSHOT
|
||||
*/
|
||||
if (!updatedPolicy.phases.frozen?.actions?.searchable_snapshot) {
|
||||
if (updatedPolicy.phases.frozen?.actions?.searchable_snapshot) {
|
||||
frozenPhase.actions.searchable_snapshot = {
|
||||
...frozenPhase.actions.searchable_snapshot,
|
||||
snapshot_repository: _meta.searchableSnapshot.repository,
|
||||
};
|
||||
} else {
|
||||
delete frozenPhase.actions.searchable_snapshot;
|
||||
}
|
||||
} else {
|
||||
|
@ -271,7 +286,7 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => (
|
|||
deletePhase.actions.delete = deletePhase.actions.delete ?? {};
|
||||
|
||||
/**
|
||||
* DELETE PHASE SEARCHABLE SNAPSHOT
|
||||
* DELETE PHASE MIN AGE
|
||||
*/
|
||||
if (updatedPolicy.phases.delete?.min_age) {
|
||||
deletePhase.min_age = `${updatedPolicy.phases.delete!.min_age}${_meta.delete.minAgeUnit}`;
|
||||
|
|
|
@ -6,13 +6,15 @@
|
|||
*/
|
||||
|
||||
import { flow } from 'fp-ts/function';
|
||||
import { deserializer } from '../form';
|
||||
import { createDeserializer } from '../form';
|
||||
|
||||
import {
|
||||
formDataToAbsoluteTimings,
|
||||
calculateRelativeFromAbsoluteMilliseconds,
|
||||
} from './absolute_timing_to_relative_timing';
|
||||
|
||||
const deserializer = createDeserializer(false);
|
||||
|
||||
export const calculateRelativeTimingMs = flow(
|
||||
formDataToAbsoluteTimings,
|
||||
calculateRelativeFromAbsoluteMilliseconds
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SearchableSnapshotAction } from '../../../../../common/types';
|
||||
|
||||
export const getDefaultRepository = (
|
||||
configs: Array<SearchableSnapshotAction | undefined>
|
||||
): string => {
|
||||
if (configs.length === 0) {
|
||||
return '';
|
||||
}
|
||||
if (Boolean(configs[0]?.snapshot_repository)) {
|
||||
return configs[0]!.snapshot_repository;
|
||||
}
|
||||
return getDefaultRepository(configs.slice(1));
|
||||
};
|
|
@ -12,3 +12,5 @@ export {
|
|||
PhaseAgeInMilliseconds,
|
||||
RelativePhaseTimingInMs,
|
||||
} from './absolute_timing_to_relative_timing';
|
||||
|
||||
export { getDefaultRepository } from './get_default_repository';
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SerializedPolicy } from '../../../../common/types';
|
||||
|
||||
export type DataTierAllocationType = 'node_roles' | 'node_attrs' | 'none';
|
||||
|
@ -76,5 +75,8 @@ export interface FormInternal extends SerializedPolicy {
|
|||
cold: ColdPhaseMetaFields;
|
||||
frozen: FrozenPhaseMetaFields;
|
||||
delete: DeletePhaseMetaFields;
|
||||
searchableSnapshot: {
|
||||
repository: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ export {
|
|||
useFormContext,
|
||||
FormSchema,
|
||||
ValidationConfig,
|
||||
UseMultiFields,
|
||||
} from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
|
||||
|
||||
export { fieldValidators } from '../../../../src/plugins/es_ui_shared/static/forms/helpers';
|
||||
|
|
Loading…
Reference in a new issue