/* * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ import React, { Component, Fragment } from "react"; import _ from "lodash"; import { EuiButton, EuiButtonEmpty, EuiModal, EuiModalBody, EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, EuiOverlayMask, EuiComboBox, EuiFormRow, EuiFieldText, EuiCallOut, EuiText, EuiSpacer, EuiCodeBlock, EuiLink, EuiIcon, } from "@elastic/eui"; import { BrowserServices } from "../../../../models/interfaces"; import { PolicyOption } from "../../models/interfaces"; import { DocumentPolicy, State } from "../../../../../models/interfaces"; import { getErrorMessage } from "../../../../utils/helpers"; import { DOCUMENTATION_URL } from "../../../../utils/constants"; import { CoreServicesContext } from "../../../../components/core_services"; interface ApplyPolicyModalProps { onClose: () => void; services: BrowserServices; indices: string[]; } interface ApplyPolicyModalState { isLoading: boolean; selectedPolicy: PolicyOption | null; selectedPolicyError: string; hasRolloverAction: boolean; policyOptions: PolicyOption[]; rolloverAlias: string; rolloverAliasError: string; hasSubmitted: boolean; } export default class ApplyPolicyModal extends Component { static contextType = CoreServicesContext; state: ApplyPolicyModalState = { isLoading: false, selectedPolicy: null, selectedPolicyError: "", hasRolloverAction: false, policyOptions: [], rolloverAlias: "", rolloverAliasError: "", hasSubmitted: false, }; async componentDidMount(): Promise { await this.onPolicySearchChange(""); } onApplyPolicy = async (selectedPolicy: PolicyOption, hasRolloverAction: boolean, rolloverAlias: string): Promise => { try { const { onClose, indices, services: { indexService }, } = this.props; const policyId = selectedPolicy.label; const applyPolicyResponse = await indexService.applyPolicy(indices, policyId); if (applyPolicyResponse.ok) { const { updatedIndices, failedIndices, failures } = applyPolicyResponse.response; if (updatedIndices) { this.context.notifications.toasts.addSuccess(`Applied policy to ${updatedIndices} indices`); if (hasRolloverAction && rolloverAlias && indices.length === 1) { await this.onEditRolloverAlias(indices[0], rolloverAlias); } } if (failures) { this.context.notifications.toasts.addDanger( `Failed to apply policy to ${failedIndices .map((failedIndex) => `[${failedIndex.indexName}, ${failedIndex.reason}]`) .join(", ")}` ); } onClose(); } else { this.context.notifications.toasts.addDanger(applyPolicyResponse.error); } } catch (err) { this.context.notifications.toasts.addDanger(getErrorMessage(err, "There was a problem adding policy to indices")); } }; onEditRolloverAlias = async (index: string, rolloverAlias: string): Promise => { const { services: { indexService }, } = this.props; try { const response = await indexService.editRolloverAlias(index, rolloverAlias); if (response.ok) { if (response.response.acknowledged) { this.context.notifications.toasts.addSuccess(`Edited rollover alias on ${index}`); } else { this.context.notifications.toasts.addDanger(`Failed to edit rollover alias on ${index}`); } } else { this.context.notifications.toasts.addDanger(response.error); } } catch (err) { this.context.notifications.toasts.addDanger(getErrorMessage(err, `There was a problem editing rollover alias on ${index}`)); } }; onPolicySearchChange = async (searchValue: string): Promise => { const { services: { indexService }, } = this.props; this.setState({ isLoading: true, policyOptions: [] }); try { const searchPoliciesResponse = await indexService.searchPolicies(searchValue, true); if (searchPoliciesResponse.ok) { const policies = searchPoliciesResponse.response.policies.map((p: DocumentPolicy) => ({ label: p.id, policy: p.policy, })); this.setState({ policyOptions: policies }); } else { if (searchPoliciesResponse.error.startsWith("[index_not_found_exception]")) { this.context.notifications.toasts.addDanger("You have not created a policy yet"); } else { this.context.notifications.toasts.addDanger(searchPoliciesResponse.error); } } } catch (err) { if (this.context != null) this.context.notifications.toasts.addDanger(err.message); } this.setState({ isLoading: false }); }; onChangeSelectedPolicy = (selectedPolicies: PolicyOption[]): void => { const selectedPolicy = selectedPolicies.length ? selectedPolicies[0] : null; const hasRolloverAction = this.hasRolloverAction(selectedPolicy); this.setState({ selectedPolicy, hasRolloverAction, selectedPolicyError: this.getSelectedPolicyError(selectedPolicy), }); }; onChangeRolloverAlias = (e: React.ChangeEvent): void => { const rolloverAlias = e.target.value; this.setState({ rolloverAlias, rolloverAliasError: this.getRolloverAliasError(rolloverAlias), }); }; onSubmit = async (): Promise => { const { selectedPolicy, rolloverAlias } = this.state; const selectedPolicyError = this.getSelectedPolicyError(selectedPolicy); const rolloverAliasError = this.getRolloverAliasError(rolloverAlias); const hasSubmitError = !!selectedPolicyError || !!rolloverAliasError; if (hasSubmitError) { this.setState({ selectedPolicyError, rolloverAliasError, hasSubmitted: true }); } else { // @ts-ignore await this.onApplyPolicy(selectedPolicy, this.hasRolloverAction(selectedPolicy), rolloverAlias); } }; getRolloverAliasError = (rolloverAlias: string): string => { const { hasRolloverAction } = this.state; const { indices } = this.props; const isDataStream = indices[0].includes(".ds"); const hasSingleIndexSelected = indices.length === 1; const requiresAlias = hasRolloverAction && hasSingleIndexSelected && !isDataStream; const hasAliasError = requiresAlias && !rolloverAlias; return hasAliasError ? "Required" : ""; }; getSelectedPolicyError = (selectedPolicy: PolicyOption | null): string => (selectedPolicy ? "" : "You must select a policy"); hasRolloverAction = (selectedPolicy: PolicyOption | null): boolean => _.get(selectedPolicy, "policy.states", []).some((state: State) => state.actions.some((action) => action.hasOwnProperty("rollover"))); renderRollover = (): React.ReactNode | null => { const { rolloverAlias, hasRolloverAction, rolloverAliasError, hasSubmitted } = this.state; const { indices } = this.props; const hasSingleIndexSelected = indices.length === 1; if (!hasRolloverAction) return null; if (hasSingleIndexSelected) { return (

This policy includes a rollover action. Specify a rollover alias.{" "} Learn more

} isInvalid={hasSubmitted && !!rolloverAliasError} error={rolloverAliasError} fullWidth >
); } return ( You are applying a policy with rollover to multiple indices. You will need to add a unique rollover_alias setting to each index.

} iconType="alert" size="s" color="warning" /> ); }; renderPreview = (): React.ReactNode | null => { const { selectedPolicy } = this.state; if (!selectedPolicy) return null; let policyString = ""; try { policyString = JSON.stringify({ policy: selectedPolicy.policy }, null, 4); } catch (err) { console.error(err); } if (!policyString) { return null; } return (

Preview

{policyString}
); }; render() { const { policyOptions, selectedPolicy, selectedPolicyError, isLoading, hasSubmitted } = this.state; const { onClose } = this.props; const selectedOptions = selectedPolicy ? [selectedPolicy] : []; return ( {/* // @ts-ignore */} Apply policy

Choose the policy you want to use for the selected indices. A copy of the policy will be created and applied to the indices.

{this.renderPreview()} {this.renderRollover()}
Cancel Apply
); } }