/* * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ import React, { forwardRef, useContext, useEffect, useImperativeHandle, useRef, Ref, useState } from "react"; import { EuiButton, EuiButtonEmpty, EuiCodeBlock, EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiTab, EuiTabs, EuiTitle, } from "@elastic/eui"; import queryString from "query-string"; import { TemplateItem, TemplateItemRemote } from "../../../../../models/interfaces"; import useField, { FieldInstance } from "../../../../lib/field"; import CustomFormRow from "../../../../components/CustomFormRow"; import { ServicesContext } from "../../../../services"; import { BrowserServices } from "../../../../models/interfaces"; import { CoreServicesContext } from "../../../../components/core_services"; import { CoreStart } from "opensearch-dashboards/public"; import { submitTemplate, getTemplate, simulateTemplate, formatTemplate, formatRemoteTemplateToEditTemplate } from "../../hooks"; import { Modal } from "../../../../components/Modal"; import { RouteComponentProps } from "react-router-dom"; import { ROUTES } from "../../../../utils/constants"; import DeleteTemplateModal from "../../../Templates/containers/DeleteTemplatesModal"; import DefineTemplate, { OverviewTemplate } from "../../components/DefineTemplate"; import IndexSettings from "../../components/IndexSettings"; import IndexAlias from "../IndexAlias"; import TemplateMappings from "../TemplateMappings"; import { merge } from "lodash"; import ComposableTemplate from "../ComposableTemplate"; import PreviewTemplate from "../PreviewTemplate"; import { ContentPanel } from "../../../../components/ContentPanel"; import { FLOW_ENUM, TemplateItemEdit } from "../../interface"; import BottomBar from "../../../../components/BottomBar"; import { diffJson } from "../../../../utils/helpers"; import UnsavedChangesBottomBar from "../../../../components/UnsavedChangesBottomBar"; import { IndexForm } from "../../../../containers/IndexForm"; import { TABS_ENUM, tabs } from "../../constant"; export interface TemplateDetailProps { templateName?: string; onCancel?: () => void; onSubmitSuccess?: (templateName: string) => void; history: RouteComponentProps["history"]; location: RouteComponentProps["location"]; } const TemplateDetail = (props: TemplateDetailProps, ref: Ref) => { const { templateName, onCancel, onSubmitSuccess, history } = props; const isEdit = !!templateName; const services = useContext(ServicesContext) as BrowserServices; const coreServices = useContext(CoreServicesContext) as CoreStart; const [selectedTabId, setSelectedTabId] = useState(TABS_ENUM.SUMMARY); const [visible, setVisible] = useState(false); const [previewFlyoutVisible, setPreviewFlyoutVisible] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const oldValue = useRef(undefined); const searchObject = queryString.parseUrl(props.location.search); /* istanbul ignore next */ if (searchObject.query.values) { try { searchObject.query.values = JSON.parse((searchObject.query.values || "") as string); } catch (e) { // do nothing } } const defaultValues = merge( {}, { priority: 0, template: {}, _meta: { flow: FLOW_ENUM.SIMPLE, }, } as Partial, searchObject.query.values as any ); const field = useField({ values: defaultValues, onChange(name, value) { if (name === "data_stream" && value === undefined) { field.deleteValue(name); } }, }); const simulateField = useField({ values: { priority: 0, template: {}, _meta: { flow: FLOW_ENUM.SIMPLE, }, } as Partial, }); const destroyRef = useRef(false); const onSubmit = async () => { const { errors, values: templateDetail } = (await field.validatePromise()) || {}; if (errors) { return; } setIsSubmitting(true); const result = await submitTemplate({ value: templateDetail, commonService: services.commonService, isEdit, }); /* istanbul ignore next */ if (destroyRef.current) { return; } setIsSubmitting(false); if (result.ok) { return result; } else { return { ...result, error: result?.body?.error?.caused_by?.reason || result.error, }; } }; useImperativeHandle(ref, () => field); const refreshTemplate = () => { getTemplate({ templateName: templateName || "", coreService: coreServices, commonService: services.commonService, }) .then((template) => { oldValue.current = JSON.parse(JSON.stringify(template)); field.resetValues(template); simulateTemplate({ commonService: services.commonService, template: field.getValues(), }).then((simulateResult) => { if (simulateResult.ok) { simulateField.resetValues(simulateResult.response); } else { coreServices.notifications.toasts.addDanger(simulateResult.error); } }); }) .catch(() => { // do nothing props.history.replace(ROUTES.TEMPLATES); }); }; useEffect(() => { if (isEdit) { refreshTemplate(); } return () => { destroyRef.current = true; }; }, []); const values: TemplateItemEdit = field.getValues(); const subCompontentProps = { ...props, isEdit, field, readonly: selectedTabId === TABS_ENUM.SUMMARY && isEdit, }; const PreviewTemplateButton = () => ( { const result = await simulateTemplate({ template: field.getValues(), commonService: services.commonService, }); if (result.ok) { simulateField.resetValues(result.response); setPreviewFlyoutVisible(true); } else { coreServices.notifications.toasts.addDanger(result.error); } }} data-test-subj="CreateIndexTemplatePreviewButton" color="ghost" > Preview template ); return ( <> {isEdit ?

{templateName}

:

{isEdit ? "Edit" : "Create"} template

}
{isEdit ? null : ( Index templates let you initialize new indexes with predefined mappings and settings.{" "} Learn more } > <> )}
{isEdit ? ( { const showValue: TemplateItemRemote = { ...values, template: IndexForm.transformIndexDetailToRemote(values.template), }; Modal.show({ locale: { ok: "Close", }, style: { width: 800, }, "data-test-subj": "templateJSONDetailModal", title: values.name, content: ( {JSON.stringify(showValue, null, 2)} ), }); }} > View JSON setVisible(true)}> Delete { setVisible(false); }} onDelete={() => { setVisible(false); history.replace(ROUTES.TEMPLATES); }} /> ) : null}
{isEdit ? ( <> ) : null} {isEdit ? ( <> {tabs.map((item) => ( { setSelectedTabId(item.id); }} isSelected={selectedTabId === item.id} key={item.id} data-test-subj={`TemplateDetailTab-${item.id}`} > {item.name} ))} ) : null} {subCompontentProps.readonly ? null : ( <> )} {values._meta?.flow === FLOW_ENUM.COMPONENTS && !subCompontentProps.readonly ? ( <> ) : null} {previewFlyoutVisible && simulateField.getValues() ? ( setPreviewFlyoutVisible(false)} title="Preview template" content={} /> ) : null} {isEdit ? null : ( <> Cancel { const result = await onSubmit(); if (result) { if (result.ok) { coreServices.notifications.toasts.addSuccess(`${values.name} has been successfully created.`); onSubmitSuccess && onSubmitSuccess(values.name); } else { coreServices.notifications.toasts.addDanger(result.error); } } }} isLoading={isSubmitting} data-test-subj="CreateIndexTemplateCreateButton" > Create template )} {isEdit && selectedTabId === TABS_ENUM.CONFIG && diffJson(formatTemplate(oldValue.current), formatTemplate(values)) ? ( { field.resetValues( formatRemoteTemplateToEditTemplate({ templateDetail: { ...oldValue.current, template: IndexForm.transformIndexDetailToRemote(oldValue.current?.template), }, }) ); }} onClickSubmit={async () => { const result = await onSubmit(); if (result) { if (result.ok) { coreServices.notifications.toasts.addSuccess(`${values.name} has been successfully updated.`); refreshTemplate(); } else { coreServices.notifications.toasts.addDanger(result.error); } } }} renderProps={({ renderCancel, renderConfirm, renderUnsavedText }) => { return ( <> {renderUnsavedText()} {renderCancel()} {renderConfirm()} ); }} /> ) : null} ); }; // @ts-ignore export default forwardRef(TemplateDetail);