diff --git a/cypress/integration/create_index.js b/cypress/integration/create_index.js
index 68fed27a5..354340f98 100644
--- a/cypress/integration/create_index.js
+++ b/cypress/integration/create_index.js
@@ -93,12 +93,7 @@ describe("Create Index", () => {
});
it("Update alias successfully", () => {
- cy.get(`[data-test-subj="view-index-detail-button-${SAMPLE_INDEX}"]`)
- .click()
- .get("#index-detail-modal-alias")
- .click()
- .get('[data-test-subj="detail-modal-edit"]')
- .click();
+ cy.get(`[data-test-subj="view-index-detail-button-${SAMPLE_INDEX}"]`).click().get("#index-detail-modal-alias").click();
// add a alias and remove the exist alias
cy.get('[data-test-subj="comboBoxSearchInput"]')
@@ -109,17 +104,10 @@ describe("Create Index", () => {
.end()
.get('[data-test-subj="createIndexCreateButton"]')
.click({ force: true })
- .end()
- .get('[data-test-subj="change_diff_confirm-confirm"]')
- .click();
+ .end();
// check the index
- cy.get(`[data-test-subj="view-index-detail-button-${SAMPLE_INDEX}"]`)
- .click()
- .get("#index-detail-modal-alias")
- .click()
- .get('[data-test-subj="detail-modal-edit"]')
- .click();
+ cy.get(`[data-test-subj="view-index-detail-button-${SAMPLE_INDEX}"]`).click().get("#index-detail-modal-alias").click();
cy.get(`[value="${SAMPLE_INDEX}"]`)
.should("exist")
@@ -133,12 +121,7 @@ describe("Create Index", () => {
});
it("Update settings successfully", () => {
- cy.get(`[data-test-subj="view-index-detail-button-${SAMPLE_INDEX}"]`)
- .click()
- .get("#index-detail-modal-settings")
- .click()
- .get('[data-test-subj="detail-modal-edit"]')
- .click();
+ cy.get(`[data-test-subj="view-index-detail-button-${SAMPLE_INDEX}"]`).click().get("#index-detail-modal-settings").click();
cy.get('[aria-controls="accordion_for_create_index_settings"]')
.click()
@@ -172,23 +155,13 @@ describe("Create Index", () => {
.get('[data-test-subj="change_diff_confirm-confirm"]')
.click();
- cy.get(`[data-test-subj="view-index-detail-button-${SAMPLE_INDEX}"]`)
- .click()
- .get("#index-detail-modal-settings")
- .click()
- .get('[data-test-subj="detail-modal-edit"]')
- .click();
+ cy.get(`[data-test-subj="view-index-detail-button-${SAMPLE_INDEX}"]`).click().get("#index-detail-modal-settings").click();
cy.get('[placeholder="The number of replica shards each primary shard should have."]').should("have.value", 12);
});
it("Update mappings successfully", () => {
- cy.get(`[data-test-subj="view-index-detail-button-${SAMPLE_INDEX}"]`)
- .click()
- .get("#index-detail-modal-mappings")
- .click()
- .get('[data-test-subj="detail-modal-edit"]')
- .click();
+ cy.get(`[data-test-subj="view-index-detail-button-${SAMPLE_INDEX}"]`).click().get("#index-detail-modal-mappings").click();
cy.get('[data-test-subj="create index add field button"]')
.click()
@@ -201,12 +174,7 @@ describe("Create Index", () => {
.get('[data-test-subj="change_diff_confirm-confirm"]')
.click();
- cy.get(`[data-test-subj="view-index-detail-button-${SAMPLE_INDEX}"]`)
- .click()
- .get("#index-detail-modal-mappings")
- .click()
- .get('[data-test-subj="detail-modal-edit"]')
- .click();
+ cy.get(`[data-test-subj="view-index-detail-button-${SAMPLE_INDEX}"]`).click().get("#index-detail-modal-mappings").click();
cy.get('[data-test-subj="mapping-visual-editor-2-field-type"]').should("have.value", "text").end();
});
diff --git a/cypress/integration/split_index.js b/cypress/integration/split_index.js
index 8e16d3ee8..2b0bdd6d5 100644
--- a/cypress/integration/split_index.js
+++ b/cypress/integration/split_index.js
@@ -36,23 +36,14 @@ describe("Split Index", () => {
// The index should exist
cy.get(`#_selection_column_${SAMPLE_INDEX}-checkbox`).should("have.exist").end();
- cy.get(`[data-test-subj="view-index-detail-button-${SAMPLE_INDEX}"]`)
- .click()
- .end()
- .get("#index-detail-modal-settings")
- .click()
- .end()
- .get('[data-test-subj="detail-modal-edit"]')
- .click()
- .end();
+ cy.get(`[data-test-subj="view-index-detail-button-${SAMPLE_INDEX}"]`).click().end().get("#index-detail-modal-settings").click().end();
cy.get('[placeholder="The number of primary shards in the index. Default is 1."]').then(($shardNumber) => {
split_number = $shardNumber.val() * 2;
});
// Update Index status to blocks write otherwise we can't apply split operation on it
- cy.updateIndexSettings(SAMPLE_INDEX, {"index.blocks.write":"true"})
- .end()
+ cy.updateIndexSettings(SAMPLE_INDEX, { "index.blocks.write": "true" }).end();
});
it("Split successfully", () => {
@@ -89,9 +80,6 @@ describe("Split Index", () => {
.end()
.get("#index-detail-modal-settings")
.click()
- .end()
- .get('[data-test-subj="detail-modal-edit"]')
- .click()
.end();
cy.get('[placeholder="The number of primary shards in the index. Default is 1."]').should("have.value", `${split_number}`).end();
@@ -110,6 +98,6 @@ describe("Split Index", () => {
.get('[data-test-subj="flyout-footer-action-button"]')
.click()
.end();
- })
+ });
});
});
diff --git a/public/components/EuiToolTipWrapper/EuiToolTipWrapper.test.tsx b/public/components/EuiToolTipWrapper/EuiToolTipWrapper.test.tsx
new file mode 100644
index 000000000..2abc9fcb7
--- /dev/null
+++ b/public/components/EuiToolTipWrapper/EuiToolTipWrapper.test.tsx
@@ -0,0 +1,32 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from "react";
+import { render, waitFor, act } from "@testing-library/react";
+import { fireEvent } from "@testing-library/dom";
+import EuiToolTipWrapper from "./index";
+
+const WrappedInput = EuiToolTipWrapper((props: any) => );
+
+describe(" spec", () => {
+ it("render the component", async () => {
+ render();
+ await waitFor(() => {});
+ expect(document.body.children).toMatchSnapshot();
+ });
+
+ it("render the error", async () => {
+ const { queryByText, container } = render();
+ const anchorDOM = container.querySelector(".euiToolTipAnchor") as Element;
+ await act(async () => {
+ await fireEvent.mouseOver(anchorDOM, {
+ bubbles: true,
+ });
+ });
+ await waitFor(() => {
+ expect(queryByText("test error")).not.toBeNull();
+ });
+ });
+});
diff --git a/public/components/EuiToolTipWrapper/__snapshots__/EuiToolTipWrapper.test.tsx.snap b/public/components/EuiToolTipWrapper/__snapshots__/EuiToolTipWrapper.test.tsx.snap
new file mode 100644
index 000000000..abfaf69d1
--- /dev/null
+++ b/public/components/EuiToolTipWrapper/__snapshots__/EuiToolTipWrapper.test.tsx.snap
@@ -0,0 +1,13 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` spec render the component 1`] = `
+HTMLCollection [
+
+
+
+
+
,
+]
+`;
diff --git a/public/components/EuiToolTipWrapper/index.tsx b/public/components/EuiToolTipWrapper/index.tsx
new file mode 100644
index 000000000..cce0f2934
--- /dev/null
+++ b/public/components/EuiToolTipWrapper/index.tsx
@@ -0,0 +1,79 @@
+import React, { forwardRef } from "react";
+import { EuiToolTip } from "@elastic/eui";
+
+interface IEuiToolTipWrapperOptions {
+ disabledKey?: string;
+}
+
+export interface IEuiToolTipWrapperProps {
+ disabledReason?:
+ | string
+ | {
+ visible: boolean;
+ message: string;
+ }[];
+}
+
+export default function EuiToolTipWrapper(
+ Component: React.ComponentType,
+ options?: IEuiToolTipWrapperOptions
+): React.ComponentType {
+ return forwardRef(({ disabledReason, children, ...others }, ref) => {
+ const finalOptions: Required = {
+ ...{
+ disabledKey: "disabled",
+ },
+ ...options,
+ };
+ let formattedReason: IEuiToolTipWrapperProps["disabledReason"];
+ if (typeof disabledReason === "string") {
+ formattedReason = [
+ {
+ visible: true,
+ message: disabledReason,
+ },
+ ];
+ } else {
+ formattedReason = disabledReason;
+ }
+ formattedReason = formattedReason?.filter((item) => item.visible);
+ const propsDisabled = (others as Record)[finalOptions.disabledKey];
+ const disabled = propsDisabled === undefined ? !!formattedReason?.length : propsDisabled;
+ const finalProps: IEuiToolTipWrapperProps = {
+ ...others,
+ [finalOptions.disabledKey]: disabled,
+ };
+ return (
+
+ This field is disabled because:
+
+ {formattedReason?.map((item, index) => (
+ -
+
+ {index + 1}.
+
+ {item.message}
+
+ ))}
+
+ >
+ ) : undefined
+ }
+ display="block"
+ position="right"
+ >
+ <>
+
+ >
+
+ );
+ }) as React.ComponentType;
+}
diff --git a/public/components/FormGenerator/__snapshots__/FormGenerator.test.tsx.snap b/public/components/FormGenerator/__snapshots__/FormGenerator.test.tsx.snap
index 255e2e2f5..a53ffb20b 100644
--- a/public/components/FormGenerator/__snapshots__/FormGenerator.test.tsx.snap
+++ b/public/components/FormGenerator/__snapshots__/FormGenerator.test.tsx.snap
@@ -25,21 +25,25 @@ HTMLCollection [
diff --git a/public/components/FormGenerator/built_in_components/index.tsx b/public/components/FormGenerator/built_in_components/index.tsx
index dc3adcdc5..81b83955e 100644
--- a/public/components/FormGenerator/built_in_components/index.tsx
+++ b/public/components/FormGenerator/built_in_components/index.tsx
@@ -1,20 +1,33 @@
import React, { forwardRef } from "react";
import { EuiFieldNumber, EuiFieldText, EuiSwitch } from "@elastic/eui";
+import EuiToolTipWrapper, { IEuiToolTipWrapperProps } from "../../EuiToolTipWrapper";
type ComponentMapEnum = "Input" | "Number" | "Switch";
-const componentMap: Record void; value?: any }>> = {
- Input: forwardRef(({ onChange, value, ...others }, ref: React.Ref) => (
- onChange(e.target.value)} {...others} />
- )),
- Number: forwardRef(({ onChange, value, ...others }, ref: React.Ref) => (
- onChange(e.target.value)} value={value || ""} {...others} />
- )),
- Switch: forwardRef(({ value, onChange, ...others }, ref: React.Ref) => (
-
- onChange(e.target.checked)} {...others} />
-
- )),
+export interface IFieldComponentProps extends IEuiToolTipWrapperProps {
+ onChange: (val: IFieldComponentProps["value"]) => void;
+ value?: any;
+ [key: string]: any;
+}
+
+const componentMap: Record> = {
+ Input: EuiToolTipWrapper(
+ forwardRef(({ onChange, value, disabledReason, disabled, ...others }, ref: React.Ref) => (
+ onChange(e.target.value)} disabled={disabled} {...others} />
+ )) as React.ComponentType
+ ),
+ Number: EuiToolTipWrapper(
+ forwardRef(({ onChange, value, ...others }, ref: React.Ref) => (
+ onChange(e.target.value)} value={value || ""} {...others} />
+ )) as React.ComponentType
+ ),
+ Switch: EuiToolTipWrapper(
+ forwardRef(({ value, onChange, ...others }, ref: React.Ref) => (
+
+ onChange(e.target.checked)} {...others} />
+
+ )) as React.ComponentType
+ ),
};
export default componentMap;
diff --git a/public/containers/IndexDetail/index.tsx b/public/containers/IndexDetail/index.tsx
index 727a22ec3..db70c1b8c 100644
--- a/public/containers/IndexDetail/index.tsx
+++ b/public/containers/IndexDetail/index.tsx
@@ -36,7 +36,7 @@ export default function IndexDetail(props: IIndexDetailProps) {
props.onGetIndicesDetail && props.onGetIndicesDetail(finalResponse);
setLoading(false);
})();
- }, [props.indices, setLoading, setItems, coreServices]);
+ }, [props.indices.join(","), setLoading, setItems, coreServices]);
return (
;
@@ -137,9 +145,17 @@ const IndexDetail = (
},
],
props: {
- disabled:
- (isEdit && !INDEX_DYNAMIC_SETTINGS.includes("index.number_of_shards")) || templateSimulateLoading || !finalValue.index,
placeholder: "The number of primary shards in the index. Default is 1.",
+ disabledReason: [
+ {
+ visible: !finalValue.index,
+ message: indexNameEmptyTips,
+ },
+ {
+ visible: isEdit && !INDEX_DYNAMIC_SETTINGS.includes("index.number_of_shards"),
+ message: staticSettingsTips,
+ },
+ ],
},
},
},
@@ -157,9 +173,17 @@ const IndexDetail = (
},
],
props: {
- disabled:
- (isEdit && !INDEX_DYNAMIC_SETTINGS.includes("index.number_of_replicas")) || templateSimulateLoading || !finalValue.index,
placeholder: "The number of replica shards each primary shard should have.",
+ disabledReason: [
+ {
+ visible: !finalValue.index,
+ message: indexNameEmptyTips,
+ },
+ {
+ visible: isEdit && !INDEX_DYNAMIC_SETTINGS.includes("index.number_of_replicas"),
+ message: staticSettingsTips,
+ },
+ ],
},
},
},
@@ -172,9 +196,17 @@ const IndexDetail = (
type: "Input",
options: {
props: {
- disabled:
- (isEdit && !INDEX_DYNAMIC_SETTINGS.includes("index.refresh_interval")) || templateSimulateLoading || !finalValue.index,
placeholder: "Can be set to -1 to disable refreshing.",
+ disabledReason: [
+ {
+ visible: !finalValue.index,
+ message: indexNameEmptyTips,
+ },
+ {
+ visible: isEdit && !INDEX_DYNAMIC_SETTINGS.includes("index.refresh_interval"),
+ message: staticSettingsTips,
+ },
+ ],
},
},
},
@@ -204,6 +236,7 @@ const IndexDetail = (
onBlur: onIndexInputBlur,
isLoading: templateSimulateLoading,
disabled: isEdit || templateSimulateLoading,
+ disabledReason: "Index name can not be modified",
},
rules: [
{
@@ -223,9 +256,10 @@ const IndexDetail = (
props: {
refreshOptions: refreshOptions,
isDisabled: !finalValue.index,
+ disabledReason: indexNameEmptyTips,
},
},
- component: AliasSelect as any,
+ component: WrappedAliasSelect as React.ComponentType,
},
]}
value={{
diff --git a/public/pages/CreateIndex/components/IndexDetail/__snapshots__/IndexDetail.test.tsx.snap b/public/pages/CreateIndex/components/IndexDetail/__snapshots__/IndexDetail.test.tsx.snap
index be8ca8444..6ddfc3922 100644
--- a/public/pages/CreateIndex/components/IndexDetail/__snapshots__/IndexDetail.test.tsx.snap
+++ b/public/pages/CreateIndex/components/IndexDetail/__snapshots__/IndexDetail.test.tsx.snap
@@ -56,22 +56,26 @@ exports[` spec renders the component 1`] = `
>
Please enter the name before moving to other fields
-
+
spec renders the component 1`] = `
>
Select existing aliases or specify a new alias
-
-
+
diff --git a/public/pages/CreateIndex/components/IndexMapping/IndexMapping.tsx b/public/pages/CreateIndex/components/IndexMapping/IndexMapping.tsx
index 3787374e1..c63024050 100644
--- a/public/pages/CreateIndex/components/IndexMapping/IndexMapping.tsx
+++ b/public/pages/CreateIndex/components/IndexMapping/IndexMapping.tsx
@@ -24,6 +24,7 @@ import { Modal } from "../../../../components/Modal";
import { MappingsProperties, MappingsPropertiesObject } from "../../../../../models/interfaces";
import { INDEX_MAPPING_TYPES, INDEX_MAPPING_TYPES_WITH_CHILDREN } from "../../../../utils/constants";
import "./IndexMapping.scss";
+import EuiToolTipWrapper from "../../../../components/EuiToolTipWrapper";
export interface IndexMappingProps {
value?: MappingsProperties;
@@ -52,6 +53,11 @@ interface IMappingLabel {
id: string;
}
+const OLD_VALUE_DISABLED_REASON = "Old mappings can not be modified";
+
+const EuiFieldTextWrapped = EuiToolTipWrapper(EuiFieldText);
+const EuiSelectWrapped = EuiToolTipWrapper(EuiSelect);
+
const MappingLabel = forwardRef(
(
{ value, onChange, onFieldNameCheck, disabled, onAddSubField, onDeleteField, id }: IMappingLabel,
@@ -99,28 +105,28 @@ const MappingLabel = forwardRef(
e.stopPropagation()}>
- <>
- setFieldNameState(e.target.value)}
- onBlur={async (e) => {
- const error = await onValidate();
- if (!error) {
- onFieldChange("fieldName", fieldNameState);
- }
- }}
- />
- >
+ setFieldNameState(e.target.value)}
+ onBlur={async (e) => {
+ const error = await onValidate();
+ if (!error) {
+ onFieldChange("fieldName", fieldNameState);
+ }
+ }}
+ />
- spec render mappings with object type 1`] = `
@@ -167,74 +173,79 @@ exports[` spec render mappings with object type 1`] = `
@@ -344,20 +355,26 @@ exports[` spec render mappings with object type 1`] = `
@@ -381,74 +398,79 @@ exports[` spec render mappings with object type 1`] = `
diff --git a/public/pages/CreateIndex/containers/CreateIndex/CreateIndex.test.tsx b/public/pages/CreateIndex/containers/CreateIndex/CreateIndex.test.tsx
index 250543bc3..6f685d4cc 100644
--- a/public/pages/CreateIndex/containers/CreateIndex/CreateIndex.test.tsx
+++ b/public/pages/CreateIndex/containers/CreateIndex/CreateIndex.test.tsx
@@ -10,100 +10,13 @@ import userEvent from "@testing-library/user-event";
import { CoreStart } from "opensearch-dashboards/public";
import CreateIndex from "./CreateIndex";
import { ServicesConsumer, ServicesContext } from "../../../../services";
-import { browserServicesMock, coreServicesMock } from "../../../../../test/mocks";
+import { browserServicesMock, coreServicesMock, apiCallerMock } from "../../../../../test/mocks";
import { BrowserServices } from "../../../../models/interfaces";
import { ModalProvider, ModalRoot } from "../../../../components/Modal";
-import { IndicesUpdateMode, ROUTES } from "../../../../utils/constants";
+import { ROUTES } from "../../../../utils/constants";
import { CoreServicesConsumer, CoreServicesContext } from "../../../../components/core_services";
-browserServicesMock.commonService.apiCaller = jest.fn(
- async (payload): Promise => {
- switch (payload.endpoint) {
- case "transport.request": {
- if (payload.data?.path?.startsWith("/_index_template/_simulate_index/")) {
- return {
- ok: true,
- response: {
- template: {
- settings: {
- index: {
- number_of_replicas: "10",
- },
- },
- },
- },
- };
- }
- }
- case "indices.create":
- if (payload.data?.index === "bad_index") {
- return {
- ok: false,
- error: "bad_index",
- };
- }
-
- return {
- ok: true,
- response: {},
- };
- break;
- case "cat.aliases":
- return {
- ok: true,
- response: [
- {
- alias: ".kibana",
- index: ".kibana_1",
- filter: "-",
- is_write_index: "-",
- },
- {
- alias: "2",
- index: "1234",
- filter: "-",
- is_write_index: "-",
- },
- ],
- };
- case "indices.get":
- const payloadIndex = payload.data?.index;
- if (payloadIndex === "bad_index") {
- return {
- ok: false,
- error: "bad_error",
- response: {},
- };
- }
-
- return {
- ok: true,
- response: {
- [payload.data?.index]: {
- aliases: {
- update_test_1: {},
- },
- mappings: {
- properties: {
- test_mapping_1: {
- type: "text",
- },
- },
- },
- settings: {
- "index.number_of_shards": "1",
- "index.number_of_replicas": "1",
- },
- },
- },
- };
- }
- return {
- ok: true,
- response: {},
- };
- }
-);
+apiCallerMock(browserServicesMock);
function renderCreateIndexWithRouter(initialEntries = [ROUTES.CREATE_INDEX] as string[]) {
return {
@@ -152,258 +65,28 @@ function renderCreateIndexWithRouter(initialEntries = [ROUTES.CREATE_INDEX] as s
}
describe(" spec", () => {
- it("show a toast if getIndices gracefully fails", async () => {
- const { getByText } = renderCreateIndexWithRouter([`${ROUTES.CREATE_INDEX}/bad_index`]);
-
- await waitFor(() => {
- getByText("Update");
- });
- expect(coreServicesMock.notifications.toasts.addDanger).toHaveBeenCalledTimes(1);
- expect(coreServicesMock.notifications.toasts.addDanger).toHaveBeenCalledWith("bad_error");
- });
-
- it("shows error for index name input when clicking create", async () => {
- const { queryByText, getByText } = renderCreateIndexWithRouter();
-
- await waitFor(() => getByText("Define index"));
-
- expect(queryByText("Index name can not be null.")).toBeNull();
-
- userEvent.click(getByText("Create"));
+ it("it goes to indices page when click cancel", async () => {
+ const { getByText } = renderCreateIndexWithRouter([`${ROUTES.CREATE_INDEX}/good_index`]);
+ await waitFor(() => {});
+ userEvent.click(getByText("Cancel"));
await waitFor(() => {
- expect(queryByText("Index name can not be null.")).not.toBeNull();
+ expect(getByText(`location is: ${ROUTES.INDEX_POLICIES}`)).toBeInTheDocument();
});
});
- it("routes you back to indices and shows a success toast when successfully creating a index", async () => {
- const { getByText, getByPlaceholderText, getByTestId } = renderCreateIndexWithRouter();
+ it("it goes to indices page when click create successfully", async () => {
+ const { getByText, getByPlaceholderText } = renderCreateIndexWithRouter([`${ROUTES.CREATE_INDEX}`]);
await waitFor(() => {
getByText("Define index");
});
- userEvent.type(getByPlaceholderText("Please enter the name for your index"), `some_index`);
- userEvent.click(document.body);
- await waitFor(() => {
- expect(getByTestId("form-name-index.number_of_replicas").querySelector("input")).toHaveAttribute("value", "10");
- });
- userEvent.click(getByText("Create"));
- await waitFor(() => {
- expect(coreServicesMock.notifications.toasts.addSuccess).toHaveBeenCalledWith("some_index has been successfully created.");
- });
- });
-
- it("shows a danger toast when getting graceful error from create index", async () => {
- const { getByText, getByPlaceholderText } = renderCreateIndexWithRouter();
-
- await waitFor(() => getByText("Define index"));
+ const indexNameInput = getByPlaceholderText("Please enter the name for your index");
- userEvent.type(getByPlaceholderText("Please enter the name for your index"), `bad_index`);
+ userEvent.type(indexNameInput, `good_index`);
+ userEvent.click(document.body);
userEvent.click(getByText("Create"));
- await waitFor(() => {
- expect(coreServicesMock.notifications.toasts.addDanger).toHaveBeenCalledWith("bad_index");
- });
- });
-
- it("it shows detail and does not call any api when nothing modified", async () => {
- const { getByText, getByTestId } = renderCreateIndexWithRouter([`${ROUTES.CREATE_INDEX}/good_index`]);
- await waitFor(() => getByText("Define index"));
- userEvent.click(getByText("Update"));
- await waitFor(() => {});
- userEvent.click(getByTestId("change_diff_confirm-confirm"));
-
- await waitFor(() => {
- // it shows detail and does not call any api when nothing modified
- expect(browserServicesMock.commonService.apiCaller).toBeCalledTimes(2);
- });
- });
-
- it("shows detail info and update others", async () => {
- const { getByText, getByTestId, getByTitle } = renderCreateIndexWithRouter([`${ROUTES.CREATE_INDEX}/good_index`]);
-
- await waitFor(() => getByText("Define index"));
-
- userEvent.click(getByTitle("update_test_1").querySelector("button") as Element);
- userEvent.type(getByTestId("comboBoxSearchInput"), "test_1{enter}");
- userEvent.type(getByTestId("form-name-index.number_of_replicas").querySelector("input") as Element, "2");
- userEvent.click(getByTestId("create index add field button"));
- await waitFor(() => {});
- await userEvent.clear(getByTestId("mapping-visual-editor-1-field-name"));
- await userEvent.type(getByTestId("mapping-visual-editor-1-field-name"), "test_mapping_2");
- await userEvent.click(document.body);
- userEvent.click(getByText("Update"));
- await waitFor(() => {});
- userEvent.click(getByTestId("change_diff_confirm-confirm"));
-
- await waitFor(() => {
- expect(coreServicesMock.notifications.toasts.addSuccess).toBeCalledTimes(1);
- expect(browserServicesMock.commonService.apiCaller).toBeCalledWith({
- endpoint: "indices.updateAliases",
- method: "PUT",
- data: {
- body: {
- actions: [
- {
- remove: {
- index: "good_index",
- alias: "update_test_1",
- },
- },
- {
- add: {
- index: "good_index",
- alias: "test_1",
- },
- },
- ],
- },
- },
- });
-
- expect(browserServicesMock.commonService.apiCaller).toBeCalledWith({
- endpoint: "indices.putSettings",
- method: "PUT",
- data: {
- index: "good_index",
- flat_settings: true,
- body: {
- "index.number_of_replicas": "12",
- },
- },
- });
-
- expect(browserServicesMock.commonService.apiCaller).toBeCalledWith({
- endpoint: "indices.putMapping",
- method: "PUT",
- data: {
- index: "good_index",
- body: {
- properties: {
- test_mapping_2: {
- type: "text",
- },
- },
- },
- },
- });
- });
- });
-
- it("shows detail alias and update alias only", async () => {
- const { getByText, getByTestId, getByTitle } = renderCreateIndexWithRouter([
- `${ROUTES.CREATE_INDEX}/good_index/${IndicesUpdateMode.alias}`,
- ]);
-
- await waitFor(() => {});
-
- userEvent.click(getByTitle("update_test_1").querySelector("button") as Element);
- userEvent.type(getByTestId("comboBoxSearchInput"), "test_1{enter}");
- await waitFor(() => {});
- userEvent.click(getByText("Update"));
- await waitFor(() => {});
- userEvent.click(getByTestId("change_diff_confirm-cancel"));
- await waitFor(() => {});
- userEvent.click(getByText("Update"));
- await waitFor(() => {});
- userEvent.click(getByTestId("change_diff_confirm-confirm"));
-
- await waitFor(() => {
- // shows detail alias and update alias only
- expect(coreServicesMock.notifications.toasts.addSuccess).toBeCalledTimes(1);
- expect(browserServicesMock.commonService.apiCaller).toBeCalledWith({
- endpoint: "indices.updateAliases",
- method: "PUT",
- data: {
- body: {
- actions: [
- {
- remove: {
- index: "good_index",
- alias: "update_test_1",
- },
- },
- {
- add: {
- index: "good_index",
- alias: "test_1",
- },
- },
- ],
- },
- },
- });
- });
- });
-
- it("shows detail settings and update settings only", async () => {
- const { getByText, getByTestId } = renderCreateIndexWithRouter([`${ROUTES.CREATE_INDEX}/good_index/${IndicesUpdateMode.mappings}`]);
-
- await waitFor(() => {});
-
- userEvent.click(getByTestId("create index add field button"));
- await waitFor(() => {});
- await userEvent.clear(getByTestId("mapping-visual-editor-1-field-name"));
- await userEvent.type(getByTestId("mapping-visual-editor-1-field-name"), "test_mapping_2");
- await userEvent.click(document.body);
- await waitFor(() => {});
- userEvent.click(getByText("Update"));
- await waitFor(() => {});
- userEvent.click(getByTestId("change_diff_confirm-confirm"));
-
- await waitFor(() => {
- // shows detail settings and update settings only
- expect(coreServicesMock.notifications.toasts.addSuccess).toBeCalledTimes(1);
-
- expect(browserServicesMock.commonService.apiCaller).toBeCalledWith({
- endpoint: "indices.putMapping",
- method: "PUT",
- data: {
- index: "good_index",
- body: {
- properties: {
- test_mapping_2: {
- type: "text",
- },
- },
- },
- },
- });
- });
- });
-
- it("shows detail mappings and update mappings only", async () => {
- const { getByText, getByTestId } = renderCreateIndexWithRouter([`${ROUTES.CREATE_INDEX}/good_index/${IndicesUpdateMode.settings}`]);
-
- await waitFor(() => {});
-
- userEvent.type(getByTestId("form-name-index.number_of_replicas").querySelector("input") as Element, "2");
- userEvent.click(getByText("Update"));
- await waitFor(() => {});
- userEvent.click(getByTestId("change_diff_confirm-confirm"));
-
- await waitFor(() => {
- // shows detail mappings and update mappings only
- expect(coreServicesMock.notifications.toasts.addSuccess).toBeCalledTimes(1);
-
- expect(browserServicesMock.commonService.apiCaller).toBeCalledWith({
- endpoint: "indices.putSettings",
- method: "PUT",
- data: {
- index: "good_index",
- flat_settings: true,
- body: {
- "index.number_of_replicas": "12",
- },
- },
- });
- });
- });
-
- it("it goes to indices page when click cancel", async () => {
- const { getByText } = renderCreateIndexWithRouter([`${ROUTES.CREATE_INDEX}/good_index`]);
- await waitFor(() => {});
- userEvent.click(getByText("Cancel"));
await waitFor(() => {
expect(getByText(`location is: ${ROUTES.INDEX_POLICIES}`)).toBeInTheDocument();
});
diff --git a/public/pages/CreateIndex/containers/CreateIndex/CreateIndex.tsx b/public/pages/CreateIndex/containers/CreateIndex/CreateIndex.tsx
index 22ee867d1..46c10fb01 100644
--- a/public/pages/CreateIndex/containers/CreateIndex/CreateIndex.tsx
+++ b/public/pages/CreateIndex/containers/CreateIndex/CreateIndex.tsx
@@ -4,53 +4,27 @@
*/
import React, { Component } from "react";
-import { EuiSpacer, EuiTitle, EuiFlexGroup, EuiFlexItem, EuiButton, EuiButtonEmpty } from "@elastic/eui";
+import { EuiSpacer, EuiTitle } from "@elastic/eui";
import { RouteComponentProps } from "react-router-dom";
-import { get, set, differenceWith, isEqual } from "lodash";
-import { diffArrays } from "diff";
-import flattern from "flat";
-// eui depends on react-ace, so we can import react-ace here
-import { MonacoEditorDiffReact } from "../../../../components/MonacoEditor";
-import IndexDetail from "../../components/IndexDetail";
-import { IAliasAction, IndexItem, IndexItemRemote, MappingsProperties } from "../../../../../models/interfaces";
+import IndexForm from "../IndexForm";
import { BREADCRUMBS, IndicesUpdateMode, ROUTES } from "../../../../utils/constants";
import { CoreServicesContext } from "../../../../components/core_services";
-import { IIndexDetailRef, IndexDetailProps } from "../../components/IndexDetail/IndexDetail";
-import { transformArrayToObject, transformObjectToArray } from "../../components/IndexMapping/IndexMapping";
import { CommonService } from "../../../../services/index";
-import { ServerResponse } from "../../../../../server/models/types";
-import { Modal } from "../../../../components/Modal";
interface CreateIndexProps extends RouteComponentProps<{ index?: string; mode?: IndicesUpdateMode }> {
isEdit?: boolean;
commonService: CommonService;
}
-interface CreateIndexState {
- indexDetail: IndexItem;
- oldIndexDetail?: IndexItem;
- isSubmitting: boolean;
-}
-
-export default class CreateIndex extends Component {
+export default class CreateIndex extends Component {
static contextType = CoreServicesContext;
- state: CreateIndexState = {
- isSubmitting: false,
- indexDetail: {
- index: "",
- settings: {
- "index.number_of_shards": 1,
- "index.number_of_replicas": 1,
- "index.refresh_interval": "1s",
- },
- mappings: {},
- },
- oldIndexDetail: undefined,
- };
- indexDetailRef: IIndexDetailRef | null = null;
+
+ get commonService() {
+ return this.props.commonService;
+ }
get index() {
- return this.props.match.params.index || "";
+ return this.props.match.params.index;
}
get isEdit() {
@@ -59,29 +33,6 @@ export default class CreateIndex extends Component => {
const isEdit = this.isEdit;
- if (isEdit) {
- const response: ServerResponse> = await this.props.commonService.apiCaller({
- endpoint: "indices.get",
- data: {
- index: this.index,
- flat_settings: true,
- },
- });
- if (response.ok) {
- const payload = {
- ...response.response[this.index || ""],
- index: this.index,
- };
- set(payload, "mappings.properties", transformObjectToArray(get(payload, "mappings.properties", {})));
-
- this.setState({
- indexDetail: payload,
- oldIndexDetail: JSON.parse(JSON.stringify(payload)),
- });
- } else {
- this.context.notifications.toasts.addDanger(response.error);
- }
- }
this.context.chrome.setBreadcrumbs([
BREADCRUMBS.INDEX_MANAGEMENT,
BREADCRUMBS.INDICES,
@@ -93,294 +44,8 @@ export default class CreateIndex extends Component {
- this.setState({
- indexDetail: {
- ...this.state.indexDetail,
- ...value,
- },
- });
- };
-
- updateAlias = async (): Promise> => {
- const { indexDetail, oldIndexDetail } = this.state;
- const { index } = indexDetail;
- // handle the alias here
- const diffedAliasArrayes = diffArrays(Object.keys(oldIndexDetail?.aliases || {}), Object.keys(indexDetail.aliases || {}));
- const aliasActions: IAliasAction[] = diffedAliasArrayes.reduce((total: IAliasAction[], current) => {
- if (current.added) {
- return [
- ...total,
- ...current.value.map((item) => ({
- add: {
- index,
- alias: item,
- },
- })),
- ];
- } else if (current.removed) {
- return [
- ...total,
- ...current.value.map((item) => ({
- remove: {
- index,
- alias: item,
- },
- })),
- ];
- }
-
- return total;
- }, [] as IAliasAction[]);
-
- // alias may have many unexpected errors, do that before update index settings.
- if (aliasActions.length) {
- return await this.props.commonService.apiCaller({
- endpoint: "indices.updateAliases",
- method: "PUT",
- data: {
- body: {
- actions: aliasActions,
- },
- },
- });
- }
-
- return Promise.resolve({
- ok: true,
- response: {},
- });
- };
- updateSettings = async (): Promise> => {
- const { indexDetail, oldIndexDetail } = this.state;
- const { index } = indexDetail;
-
- const newSettings = (indexDetail?.settings || {}) as Required["settings"];
- const oldSettings = (oldIndexDetail?.settings || {}) as Required["settings"];
- const differences = differenceWith(Object.entries(newSettings), Object.entries(oldSettings), isEqual);
- if (!differences.length) {
- return {
- ok: true,
- response: {},
- };
- }
-
- const finalSettings = differences.reduce((total, current) => {
- if (newSettings[current[0]] !== undefined) {
- return {
- ...total,
- [current[0]]: newSettings[current[0]],
- };
- }
-
- return total;
- }, {});
-
- return await this.props.commonService.apiCaller({
- endpoint: "indices.putSettings",
- method: "PUT",
- data: {
- index,
- flat_settings: true,
- // In edit mode, only dynamic settings can be modified
- body: finalSettings,
- },
- });
- };
- updateMappings = async (): Promise> => {
- const { indexDetail, oldIndexDetail } = this.state;
- const { index } = indexDetail;
- // handle the mappings here
- const newMappingProperties = indexDetail?.mappings?.properties || [];
- const diffedMappingArrayes = diffArrays(
- (oldIndexDetail?.mappings?.properties || []).map((item) => item.fieldName),
- newMappingProperties.map((item) => item.fieldName)
- );
- const newMappingFields: MappingsProperties = diffedMappingArrayes
- .filter((item) => item.added)
- .reduce((total, current) => [...total, ...current.value], [] as string[])
- .map((current) => newMappingProperties.find((item) => item.fieldName === current) as MappingsProperties[number])
- .filter((item) => item);
-
- const newMappingSettings = transformArrayToObject(newMappingFields);
-
- if (newMappingFields.length) {
- return await this.props.commonService.apiCaller({
- endpoint: "indices.putMapping",
- method: "PUT",
- data: {
- index,
- body: {
- properties: newMappingSettings,
- },
- },
- });
- }
-
- return Promise.resolve({
- ok: true,
- response: {},
- });
- };
-
- chainPromise = async (promises: Promise>[]): Promise> => {
- const newPromises = [...promises];
- while (newPromises.length) {
- const result = (await newPromises.shift()) as ServerResponse;
- if (!result?.ok) {
- return result;
- }
- }
-
- return {
- ok: true,
- response: {},
- };
- };
-
- showDiff = async (): Promise> => {
- return new Promise((resolve, reject) => {
- Modal.show({
- title: "Please confirm the change.",
- "data-test-subj": "change_diff_confirm",
- type: "confirm",
- content: (
- <>
- The following changes will be done once you click the confirm button, Please make sure you want to do all the changes.
-
-
- >
- ),
- onOk: () =>
- resolve({
- ok: true,
- response: {},
- }),
- onCancel: () => {
- resolve({
- ok: false,
- error: "",
- });
- },
- });
- });
- };
-
- onSubmit = async (): Promise => {
- const { mode } = this.props.match.params;
- const { indexDetail } = this.state;
- const { index, mappings, ...others } = indexDetail;
- if (!(await this.indexDetailRef?.validate())) {
- return;
- }
- this.setState({ isSubmitting: true });
- let result: ServerResponse;
- if (this.isEdit) {
- const diffConfirm = await this.showDiff();
- if (!diffConfirm.ok) {
- this.setState({ isSubmitting: false });
- return;
- }
- let chainedPromises: Promise>[] = [];
- if (!mode) {
- chainedPromises.push(...[this.updateMappings(), this.updateAlias(), this.updateSettings()]);
- } else {
- switch (mode) {
- case IndicesUpdateMode.alias:
- chainedPromises.push(this.updateAlias());
- break;
- case IndicesUpdateMode.settings:
- chainedPromises.push(this.updateSettings());
- break;
- case IndicesUpdateMode.mappings:
- chainedPromises.push(this.updateMappings());
- break;
- }
- }
- result = await this.chainPromise(chainedPromises);
- } else {
- result = await this.props.commonService.apiCaller({
- endpoint: "indices.create",
- method: "PUT",
- data: {
- index,
- body: {
- ...others,
- mappings: {
- properties: transformArrayToObject(mappings?.properties || []),
- },
- },
- },
- });
- }
- this.setState({ isSubmitting: false });
-
- // handle all the response here
- if (result && result.ok) {
- this.context.notifications.toasts.addSuccess(`${indexDetail.index} has been successfully ${this.isEdit ? "updated" : "created"}.`);
- this.props.history.push(ROUTES.INDICES);
- } else {
- this.context.notifications.toasts.addDanger(result.error);
- }
- };
-
- onSimulateIndexTemplate = (indexName: string): Promise> => {
- return this.props.commonService
- .apiCaller<{ template: IndexItemRemote }>({
- endpoint: "transport.request",
- data: {
- path: `/_index_template/_simulate_index/${indexName}`,
- method: "POST",
- },
- })
- .then((res) => {
- if (res.ok && res.response && res.response.template) {
- return {
- ...res,
- response: {
- ...res.response.template,
- settings: flattern(res.response.template?.settings || {}),
- },
- };
- }
-
- return {
- ok: false,
- error: "",
- } as ServerResponse;
- });
- };
-
render() {
const isEdit = this.isEdit;
- const { indexDetail, isSubmitting, oldIndexDetail } = this.state;
return (
@@ -388,40 +53,13 @@ export default class CreateIndex extends Component{isEdit ? "Edit" : "Create"} index
- (this.indexDetailRef = ref)}
- isEdit={this.isEdit}
- value={indexDetail}
- oldValue={oldIndexDetail}
- onChange={this.onDetailChange}
- onSimulateIndexTemplate={this.onSimulateIndexTemplate}
- refreshOptions={(aliasName) =>
- this.props.commonService.apiCaller({
- endpoint: "cat.aliases",
- method: "GET",
- data: {
- format: "json",
- name: aliasName,
- expand_wildcards: "open",
- },
- })
- }
+ onCancel={this.onCancel}
+ onSubmitSuccess={() => this.props.history.push(ROUTES.INDICES)}
/>
-
-
-
-
-
- Cancel
-
-
-
-
- {isEdit ? "Update" : "Create"}
-
-
-
);
}
diff --git a/public/pages/CreateIndex/containers/IndexForm/IndexForm.test.tsx b/public/pages/CreateIndex/containers/IndexForm/IndexForm.test.tsx
new file mode 100644
index 000000000..ff7615f35
--- /dev/null
+++ b/public/pages/CreateIndex/containers/IndexForm/IndexForm.test.tsx
@@ -0,0 +1,310 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from "react";
+import { render, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import IndexForm, { IndexFormProps } from "./index";
+import { ServicesConsumer, ServicesContext } from "../../../../services";
+import { browserServicesMock, coreServicesMock, apiCallerMock } from "../../../../../test/mocks";
+import { BrowserServices } from "../../../../models/interfaces";
+import { IndicesUpdateMode } from "../../../../utils/constants";
+import { CoreServicesContext } from "../../../../components/core_services";
+
+apiCallerMock(browserServicesMock);
+
+function renderCreateIndexWithRouter(props: Omit) {
+ return {
+ ...render(
+
+
+
+ {(services: BrowserServices | null) => services && }
+
+
+
+ ),
+ };
+}
+
+describe(" spec", () => {
+ it("show a toast if getIndices gracefully fails", async () => {
+ const { getByText } = renderCreateIndexWithRouter({
+ index: "bad_index",
+ });
+
+ await waitFor(() => {
+ getByText("Update");
+ });
+ expect(coreServicesMock.notifications.toasts.addDanger).toHaveBeenCalledTimes(1);
+ expect(coreServicesMock.notifications.toasts.addDanger).toHaveBeenCalledWith("bad_error");
+ });
+
+ it("shows error for index name input when clicking create", async () => {
+ const { queryByText, getByText } = renderCreateIndexWithRouter({});
+
+ await waitFor(() => getByText("Define index"));
+
+ expect(queryByText("Index name can not be null.")).toBeNull();
+
+ userEvent.click(getByText("Create"));
+ await waitFor(() => {
+ expect(queryByText("Index name can not be null.")).not.toBeNull();
+ });
+ });
+
+ it("routes you back to indices and shows a success toast when successfully creating a index", async () => {
+ const { getByText, getByPlaceholderText, getByTestId } = renderCreateIndexWithRouter({});
+
+ await waitFor(() => {
+ getByText("Define index");
+ });
+
+ const indexNameInput = getByPlaceholderText("Please enter the name for your index");
+
+ userEvent.type(indexNameInput, `bad_index`);
+ userEvent.click(document.body);
+ await waitFor(() => {});
+ userEvent.clear(indexNameInput);
+ userEvent.type(indexNameInput, `good_index`);
+ userEvent.click(document.body);
+ await waitFor(() => {
+ expect(getByTestId("form-name-index.number_of_replicas").querySelector("input")).toHaveAttribute("value", "10");
+ });
+ userEvent.click(getByText("Create"));
+ await waitFor(() => {
+ expect(coreServicesMock.notifications.toasts.addSuccess).toHaveBeenCalledWith("[good_index] has been successfully created.");
+ });
+ });
+
+ it("shows a danger toast when getting graceful error from create index", async () => {
+ const { getByText, getByPlaceholderText } = renderCreateIndexWithRouter({});
+
+ await waitFor(() => getByText("Define index"));
+
+ userEvent.type(getByPlaceholderText("Please enter the name for your index"), `bad_index`);
+ userEvent.click(getByText("Create"));
+
+ await waitFor(() => {
+ expect(coreServicesMock.notifications.toasts.addDanger).toHaveBeenCalledWith("bad_index");
+ });
+ });
+
+ it("it shows detail and does not call any api when nothing modified", async () => {
+ const { getByText, getByTestId } = renderCreateIndexWithRouter({
+ index: "good_index",
+ });
+ await waitFor(() => getByText("Define index"));
+ userEvent.click(getByText("Update"));
+ await waitFor(() => {});
+ userEvent.click(getByTestId("change_diff_confirm-confirm"));
+
+ await waitFor(() => {
+ // it shows detail and does not call any api when nothing modified
+ expect(browserServicesMock.commonService.apiCaller).toBeCalledTimes(2);
+ });
+ });
+
+ it("shows detail info and update others", async () => {
+ const { getByText, getByTestId, getByTitle } = renderCreateIndexWithRouter({
+ index: "good_index",
+ });
+
+ await waitFor(() => getByText("Define index"));
+
+ userEvent.click(getByTitle("update_test_1").querySelector("button") as Element);
+ userEvent.type(getByTestId("comboBoxSearchInput"), "test_1{enter}");
+ userEvent.type(getByTestId("form-name-index.number_of_replicas").querySelector("input") as Element, "2");
+ userEvent.click(getByTestId("create index add field button"));
+ await waitFor(() => {});
+ await userEvent.clear(getByTestId("mapping-visual-editor-1-field-name"));
+ await userEvent.type(getByTestId("mapping-visual-editor-1-field-name"), "test_mapping_2");
+ await userEvent.click(document.body);
+ userEvent.click(getByText("Update"));
+ await waitFor(() => {});
+ userEvent.click(getByTestId("change_diff_confirm-confirm"));
+
+ await waitFor(() => {
+ expect(coreServicesMock.notifications.toasts.addSuccess).toBeCalledTimes(1);
+ expect(browserServicesMock.commonService.apiCaller).toBeCalledWith({
+ endpoint: "indices.updateAliases",
+ method: "PUT",
+ data: {
+ body: {
+ actions: [
+ {
+ remove: {
+ index: "good_index",
+ alias: "update_test_1",
+ },
+ },
+ {
+ add: {
+ index: "good_index",
+ alias: "test_1",
+ },
+ },
+ ],
+ },
+ },
+ });
+
+ expect(browserServicesMock.commonService.apiCaller).toBeCalledWith({
+ endpoint: "indices.putSettings",
+ method: "PUT",
+ data: {
+ index: "good_index",
+ flat_settings: true,
+ body: {
+ "index.number_of_replicas": "12",
+ },
+ },
+ });
+
+ expect(browserServicesMock.commonService.apiCaller).toBeCalledWith({
+ endpoint: "indices.putMapping",
+ method: "PUT",
+ data: {
+ index: "good_index",
+ body: {
+ properties: {
+ test_mapping_2: {
+ type: "text",
+ },
+ },
+ },
+ },
+ });
+ });
+ });
+
+ it("shows detail alias and update alias only", async () => {
+ const { getByText, getByTestId, getByTitle } = renderCreateIndexWithRouter({
+ index: "good_index",
+ mode: IndicesUpdateMode.alias,
+ });
+
+ await waitFor(() => {});
+
+ userEvent.click(getByTitle("update_test_1").querySelector("button") as Element);
+ userEvent.type(getByTestId("comboBoxSearchInput"), "test_1{enter}");
+ await waitFor(() => {});
+ userEvent.click(getByText("Update"));
+
+ await waitFor(() => {
+ // shows detail alias and update alias only
+ expect(coreServicesMock.notifications.toasts.addSuccess).toBeCalledTimes(1);
+ expect(browserServicesMock.commonService.apiCaller).toBeCalledWith({
+ endpoint: "indices.updateAliases",
+ method: "PUT",
+ data: {
+ body: {
+ actions: [
+ {
+ remove: {
+ index: "good_index",
+ alias: "update_test_1",
+ },
+ },
+ {
+ add: {
+ index: "good_index",
+ alias: "test_1",
+ },
+ },
+ ],
+ },
+ },
+ });
+ });
+ });
+
+ it("shows detail mappings and update mappings only", async () => {
+ const { getByText, getByTestId } = renderCreateIndexWithRouter({
+ index: "good_index",
+ mode: IndicesUpdateMode.mappings,
+ });
+
+ await waitFor(() => {});
+
+ userEvent.click(getByTestId("create index add field button"));
+ await waitFor(() => {});
+ await userEvent.clear(getByTestId("mapping-visual-editor-1-field-name"));
+ await userEvent.type(getByTestId("mapping-visual-editor-1-field-name"), "test_mapping_2");
+ await userEvent.click(document.body);
+ await waitFor(() => {});
+ userEvent.click(getByText("Update"));
+ await waitFor(() => {});
+ userEvent.click(getByTestId("change_diff_confirm-cancel"));
+ await waitFor(() => {});
+ userEvent.click(getByText("Update"));
+ await waitFor(() => {});
+ userEvent.click(getByTestId("change_diff_confirm-confirm"));
+
+ await waitFor(() => {
+ // shows detail settings and update settings only
+ expect(coreServicesMock.notifications.toasts.addSuccess).toBeCalledTimes(1);
+
+ expect(browserServicesMock.commonService.apiCaller).toBeCalledWith({
+ endpoint: "indices.putMapping",
+ method: "PUT",
+ data: {
+ index: "good_index",
+ body: {
+ properties: {
+ test_mapping_2: {
+ type: "text",
+ },
+ },
+ },
+ },
+ });
+ });
+ });
+
+ it("shows detail settings and update settings only", async () => {
+ const { getByText, getByTestId } = renderCreateIndexWithRouter({
+ index: "good_index",
+ mode: IndicesUpdateMode.settings,
+ });
+
+ await waitFor(() => {});
+
+ userEvent.type(getByTestId("form-name-index.number_of_replicas").querySelector("input") as Element, "2");
+ userEvent.click(getByText("Update"));
+ await waitFor(() => {});
+ userEvent.click(getByTestId("change_diff_confirm-confirm"));
+
+ await waitFor(() => {
+ expect(coreServicesMock.notifications.toasts.addSuccess).toBeCalledTimes(1);
+
+ expect(browserServicesMock.commonService.apiCaller).toBeCalledWith({
+ endpoint: "indices.putSettings",
+ method: "PUT",
+ data: {
+ index: "good_index",
+ flat_settings: true,
+ body: {
+ "index.number_of_replicas": "12",
+ },
+ },
+ });
+ });
+ });
+
+ it("it triggers onCancel when click cancel", async () => {
+ const onCancelMock = jest.fn();
+ const { getByText } = renderCreateIndexWithRouter({
+ index: "good_index",
+ onCancel: onCancelMock,
+ });
+ await waitFor(() => {});
+ userEvent.click(getByText("Cancel"));
+ await (() => {
+ expect(onCancelMock).toBeCalledTimes(1);
+ expect(onCancelMock).toBeCalledWith(undefined);
+ });
+ });
+});
diff --git a/public/pages/CreateIndex/containers/IndexForm/index.tsx b/public/pages/CreateIndex/containers/IndexForm/index.tsx
new file mode 100644
index 000000000..f05c480bd
--- /dev/null
+++ b/public/pages/CreateIndex/containers/IndexForm/index.tsx
@@ -0,0 +1,454 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { Component } from "react";
+import { EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiButton, EuiButtonEmpty } from "@elastic/eui";
+import { get, set, differenceWith, isEqual } from "lodash";
+import { diffArrays } from "diff";
+import flattern from "flat";
+// eui depends on react-ace, so we can import react-ace here
+import { MonacoEditorDiffReact } from "../../../../components/MonacoEditor";
+import IndexDetail from "../../components/IndexDetail";
+import { IAliasAction, IndexItem, IndexItemRemote, MappingsProperties } from "../../../../../models/interfaces";
+import { BREADCRUMBS, IndicesUpdateMode } from "../../../../utils/constants";
+import { CoreServicesContext } from "../../../../components/core_services";
+import { IIndexDetailRef, IndexDetailProps } from "../../components/IndexDetail/IndexDetail";
+import { transformArrayToObject, transformObjectToArray } from "../../components/IndexMapping/IndexMapping";
+import { CommonService } from "../../../../services/index";
+import { ServerResponse } from "../../../../../server/models/types";
+import { Modal } from "../../../../components/Modal";
+
+export interface IndexFormProps {
+ index?: string;
+ mode?: IndicesUpdateMode;
+ commonService: CommonService;
+ onCancel?: () => void;
+ onSubmitSuccess?: () => void;
+}
+
+interface CreateIndexState {
+ indexDetail: IndexItem;
+ oldIndexDetail?: IndexItem;
+ isSubmitting: boolean;
+}
+
+export default class CreateIndex extends Component {
+ static contextType = CoreServicesContext;
+ state: CreateIndexState = {
+ isSubmitting: false,
+ indexDetail: {
+ index: "",
+ settings: {
+ "index.number_of_shards": 1,
+ "index.number_of_replicas": 1,
+ "index.refresh_interval": "1s",
+ },
+ mappings: {},
+ },
+ oldIndexDetail: undefined,
+ };
+
+ indexDetailRef: IIndexDetailRef | null = null;
+
+ get commonService() {
+ return this.props.commonService;
+ }
+
+ get index() {
+ return this.props.index;
+ }
+
+ get isEdit() {
+ return this.index !== undefined;
+ }
+
+ get mode() {
+ return this.props.mode;
+ }
+
+ componentDidMount = async (): Promise => {
+ const isEdit = this.isEdit;
+ if (isEdit) {
+ const response: ServerResponse> = await this.commonService.apiCaller({
+ endpoint: "indices.get",
+ data: {
+ index: this.index,
+ flat_settings: true,
+ },
+ });
+ if (response.ok) {
+ const payload = {
+ ...response.response[this.index || ""],
+ index: this.index,
+ };
+ set(payload, "mappings.properties", transformObjectToArray(get(payload, "mappings.properties", {})));
+
+ this.setState({
+ indexDetail: payload as IndexItem,
+ oldIndexDetail: JSON.parse(JSON.stringify(payload)),
+ });
+ } else {
+ this.context.notifications.toasts.addDanger(response.error);
+ }
+ }
+ this.context.chrome.setBreadcrumbs([
+ BREADCRUMBS.INDEX_MANAGEMENT,
+ BREADCRUMBS.INDICES,
+ isEdit ? BREADCRUMBS.EDIT_INDEX : BREADCRUMBS.CREATE_INDEX,
+ ]);
+ };
+
+ onCancel = () => {
+ this.props.onCancel && this.props.onCancel();
+ };
+
+ onDetailChange: IndexDetailProps["onChange"] = (value) => {
+ this.setState({
+ indexDetail: {
+ ...this.state.indexDetail,
+ ...value,
+ },
+ });
+ };
+
+ updateAlias = async (): Promise> => {
+ const { indexDetail, oldIndexDetail } = this.state;
+ const { index } = indexDetail;
+ // handle the alias here
+ const diffedAliasArrayes = diffArrays(Object.keys(oldIndexDetail?.aliases || {}), Object.keys(indexDetail.aliases || {}));
+ const aliasActions: IAliasAction[] = diffedAliasArrayes.reduce((total: IAliasAction[], current) => {
+ if (current.added) {
+ return [
+ ...total,
+ ...current.value.map((item) => ({
+ add: {
+ index,
+ alias: item,
+ },
+ })),
+ ];
+ } else if (current.removed) {
+ return [
+ ...total,
+ ...current.value.map((item) => ({
+ remove: {
+ index,
+ alias: item,
+ },
+ })),
+ ];
+ }
+
+ return total;
+ }, [] as IAliasAction[]);
+
+ // alias may have many unexpected errors, do that before update index settings.
+ if (aliasActions.length) {
+ return await this.commonService.apiCaller({
+ endpoint: "indices.updateAliases",
+ method: "PUT",
+ data: {
+ body: {
+ actions: aliasActions,
+ },
+ },
+ });
+ }
+
+ return Promise.resolve({
+ ok: true,
+ response: {},
+ });
+ };
+ updateSettings = async (): Promise> => {
+ const { indexDetail, oldIndexDetail } = this.state;
+ const { index } = indexDetail;
+
+ const newSettings = (indexDetail?.settings || {}) as Required["settings"];
+ const oldSettings = (oldIndexDetail?.settings || {}) as Required["settings"];
+ const differences = differenceWith(Object.entries(newSettings), Object.entries(oldSettings), isEqual);
+ if (!differences.length) {
+ return {
+ ok: true,
+ response: {},
+ };
+ }
+
+ const finalSettings = differences.reduce((total, current) => {
+ if (newSettings[current[0]] !== undefined) {
+ return {
+ ...total,
+ [current[0]]: newSettings[current[0]],
+ };
+ }
+
+ return total;
+ }, {});
+
+ return await this.commonService.apiCaller({
+ endpoint: "indices.putSettings",
+ method: "PUT",
+ data: {
+ index,
+ flat_settings: true,
+ // In edit mode, only dynamic settings can be modified
+ body: finalSettings,
+ },
+ });
+ };
+ updateMappings = async (): Promise> => {
+ const { indexDetail, oldIndexDetail } = this.state;
+ const { index } = indexDetail;
+ // handle the mappings here
+ const newMappingProperties = indexDetail?.mappings?.properties || [];
+ const diffedMappingArrayes = diffArrays(
+ (oldIndexDetail?.mappings?.properties || []).map((item) => item.fieldName),
+ newMappingProperties.map((item) => item.fieldName)
+ );
+ const newMappingFields: MappingsProperties = diffedMappingArrayes
+ .filter((item) => item.added)
+ .reduce((total, current) => [...total, ...current.value], [] as string[])
+ .map((current) => newMappingProperties.find((item) => item.fieldName === current) as MappingsProperties[number])
+ .filter((item) => item);
+
+ const newMappingSettings = transformArrayToObject(newMappingFields);
+
+ if (newMappingFields.length) {
+ return await this.commonService.apiCaller({
+ endpoint: "indices.putMapping",
+ method: "PUT",
+ data: {
+ index,
+ body: {
+ properties: newMappingSettings,
+ },
+ },
+ });
+ }
+
+ return Promise.resolve({
+ ok: true,
+ response: {},
+ });
+ };
+
+ chainPromise = async (promises: Promise>[]): Promise> => {
+ const newPromises = [...promises];
+ while (newPromises.length) {
+ const result = (await newPromises.shift()) as ServerResponse;
+ if (!result?.ok) {
+ return result;
+ }
+ }
+
+ return {
+ ok: true,
+ response: {},
+ };
+ };
+
+ getOrderedJson = (json: Record) => {
+ const entries = Object.entries(json);
+ entries.sort((a, b) => (a[0] < b[0] ? -1 : 1));
+ return entries.reduce((total, [key, value]) => ({ ...total, [key]: value }), {});
+ };
+
+ showDiff = async (): Promise> => {
+ return new Promise((resolve, reject) => {
+ if (this.mode === IndicesUpdateMode.alias) {
+ resolve({
+ ok: true,
+ response: {},
+ });
+ return;
+ }
+ Modal.show({
+ title: "Please confirm the change.",
+ "data-test-subj": "change_diff_confirm",
+ type: "confirm",
+ maxWidth: "100%",
+ style: {
+ width: "70vw",
+ },
+ content: (
+ <>
+ The following changes will be done once you click the confirm button, Please make sure you want to do all the changes.
+
+
+ >
+ ),
+ onOk: () =>
+ resolve({
+ ok: true,
+ response: {},
+ }),
+ onCancel: () => {
+ resolve({
+ ok: false,
+ error: "",
+ });
+ },
+ });
+ });
+ };
+
+ onSubmit = async (): Promise => {
+ const mode = this.mode;
+ const { indexDetail } = this.state;
+ const { index, mappings, ...others } = indexDetail;
+ if (!(await this.indexDetailRef?.validate())) {
+ return;
+ }
+ this.setState({ isSubmitting: true });
+ let result: ServerResponse;
+ if (this.isEdit) {
+ const diffConfirm = await this.showDiff();
+ if (!diffConfirm.ok) {
+ this.setState({ isSubmitting: false });
+ return;
+ }
+ let chainedPromises: Promise>[] = [];
+ if (!mode) {
+ chainedPromises.push(...[this.updateMappings(), this.updateAlias(), this.updateSettings()]);
+ } else {
+ switch (mode) {
+ case IndicesUpdateMode.alias:
+ chainedPromises.push(this.updateAlias());
+ break;
+ case IndicesUpdateMode.settings:
+ chainedPromises.push(this.updateSettings());
+ break;
+ case IndicesUpdateMode.mappings:
+ chainedPromises.push(this.updateMappings());
+ break;
+ }
+ }
+ result = await this.chainPromise(chainedPromises);
+ } else {
+ result = await this.commonService.apiCaller({
+ endpoint: "indices.create",
+ method: "PUT",
+ data: {
+ index,
+ body: {
+ ...others,
+ mappings: {
+ properties: transformArrayToObject(mappings?.properties || []),
+ },
+ },
+ },
+ });
+ }
+ this.setState({ isSubmitting: false });
+
+ // handle all the response here
+ if (result && result.ok) {
+ this.context.notifications.toasts.addSuccess(`[${indexDetail.index}] has been successfully ${this.isEdit ? "updated" : "created"}.`);
+ this.props.onSubmitSuccess && this.props.onSubmitSuccess();
+ } else {
+ this.context.notifications.toasts.addDanger(result.error);
+ }
+ };
+
+ onSimulateIndexTemplate = (indexName: string): Promise> => {
+ return this.commonService
+ .apiCaller<{ template: IndexItemRemote }>({
+ endpoint: "transport.request",
+ data: {
+ path: `/_index_template/_simulate_index/${indexName}`,
+ method: "POST",
+ },
+ })
+ .then((res) => {
+ if (res.ok && res.response && res.response.template) {
+ return {
+ ...res,
+ response: {
+ ...res.response.template,
+ settings: flattern(res.response.template?.settings || {}),
+ },
+ };
+ }
+
+ return {
+ ok: false,
+ error: "",
+ } as ServerResponse;
+ });
+ };
+
+ render() {
+ const isEdit = this.isEdit;
+ const { indexDetail, isSubmitting, oldIndexDetail } = this.state;
+
+ return (
+ <>
+ (this.indexDetailRef = ref)}
+ isEdit={this.isEdit}
+ value={indexDetail}
+ oldValue={oldIndexDetail}
+ onChange={this.onDetailChange}
+ onSimulateIndexTemplate={this.onSimulateIndexTemplate}
+ refreshOptions={(aliasName) =>
+ this.commonService.apiCaller({
+ endpoint: "cat.aliases",
+ method: "GET",
+ data: {
+ format: "json",
+ name: aliasName,
+ expand_wildcards: "open",
+ },
+ })
+ }
+ />
+
+
+
+
+
+ Cancel
+
+
+
+
+ {isEdit ? "Update" : "Create"}
+
+
+
+ >
+ );
+ }
+}
diff --git a/public/pages/Indices/components/ShrinkIndexFlyout/__snapshots__/ShrinkIndexFlyout.test.tsx.snap b/public/pages/Indices/components/ShrinkIndexFlyout/__snapshots__/ShrinkIndexFlyout.test.tsx.snap
index 8275aeba4..8bfc7bbd9 100644
--- a/public/pages/Indices/components/ShrinkIndexFlyout/__snapshots__/ShrinkIndexFlyout.test.tsx.snap
+++ b/public/pages/Indices/components/ShrinkIndexFlyout/__snapshots__/ShrinkIndexFlyout.test.tsx.snap
@@ -233,24 +233,28 @@ HTMLCollection [
>
The number of primary shards in the new shrunken index.
-
+
Must be a multi of undefined
-
+
spec", () => {
+ const onUpdateSuccessMock = jest.fn();
it("render the component", async () => {
- browserServicesMock.commonService.apiCaller = jest.fn().mockResolvedValue({
- ok: true,
- response: {
- test_index: {
- aliases: {},
- mappings: {},
- settings: {
- index: {
- number_of_shards: "1",
- number_of_replicas: "1",
- provided_name: "test_index",
- },
- },
- },
- },
- });
- const { container, getByTestId } = renderWithRouter({
+ const { container, getByTestId, getByDisplayValue } = renderWithRouter({
index: "test_index",
record: {
"docs.count": "5",
@@ -64,6 +50,10 @@ describe("container spec", () => {
data_stream: "",
},
onDelete: () => null,
+ onUpdateIndex: onUpdateSuccessMock,
+ onClose: () => null,
+ onOpen: () => null,
+ onShrink: () => null,
});
await waitFor(() => {
@@ -82,5 +72,14 @@ describe("container spec", () => {
},
});
});
+
+ userEvent.click(document.getElementById("index-detail-modal-alias") as Element);
+ await waitFor(() => {
+ expect(getByDisplayValue("test_index")).not.toBeNull();
+ });
+ userEvent.click(getByTestId("createIndexCreateButton"));
+ await waitFor(() => {
+ expect(onUpdateSuccessMock).toBeCalledTimes(1);
+ });
});
});
diff --git a/public/pages/Indices/containers/IndexDetail/index.tsx b/public/pages/Indices/containers/IndexDetail/index.tsx
index d2d7cd050..e0e88feb5 100644
--- a/public/pages/Indices/containers/IndexDetail/index.tsx
+++ b/public/pages/Indices/containers/IndexDetail/index.tsx
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import React, { useContext, useEffect, useMemo, useState } from "react";
+import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
import {
EuiButtonEmpty,
EuiCopy,
@@ -11,17 +11,11 @@ import {
EuiDescriptionList,
EuiDescriptionListTitle,
EuiDescriptionListDescription,
- EuiFlexGrid,
- EuiFlexItem,
EuiSpacer,
- EuiFlexGroup,
- EuiButton,
- EuiBasicTable,
EuiFlyout,
EuiFlyoutHeader,
EuiTitle,
EuiFlyoutBody,
- EuiCodeBlock,
} from "@elastic/eui";
import { get } from "lodash";
import { Link } from "react-router-dom";
@@ -31,10 +25,12 @@ import { ManagedCatIndex } from "../../../../../server/models/interfaces";
import { IndicesUpdateMode, ROUTES } from "../../../../utils/constants";
import { ServicesContext } from "../../../../services";
import { BrowserServices } from "../../../../models/interfaces";
+import IndexForm from "../../../CreateIndex/containers/IndexForm";
-export interface IndexDetailModalProps extends Pick {
+export interface IndexDetailModalProps extends Omit {
index: string;
record: ManagedCatIndex;
+ onUpdateIndex: () => void;
}
interface IFinalDetail extends ManagedCatIndex, IndexItem {}
@@ -57,7 +53,7 @@ const OVERVIEW_DISPLAY_INFO: {
},
{
label: "Creation date",
- value: ({ detail }) => {new Date(parseInt(detail.settings?.index.creation_date || "0")).toLocaleString()},
+ value: ({ detail }) => {new Date(parseInt(detail.settings?.index?.creation_date || "0")).toLocaleString()},
},
{
label: "Total size",
@@ -94,7 +90,7 @@ const OVERVIEW_DISPLAY_INFO: {
return (
{blocks.map(([key, value]) => (
- - {key}
+ - {key}
))}
);
@@ -111,7 +107,7 @@ const OVERVIEW_DISPLAY_INFO: {
];
export default function IndexDetail(props: IndexDetailModalProps) {
- const { index, record, onDelete } = props;
+ const { index, record, onUpdateIndex, ...others } = props;
const [visible, setVisible] = useState(false);
const [detail, setDetail] = useState({} as IndexItem);
const finalDetail: IFinalDetail = useMemo(
@@ -149,6 +145,21 @@ export default function IndexDetail(props: IndexDetailModalProps) {
});
}
}, [visible]);
+
+ const onCloseFlyout = useCallback(() => {
+ setVisible(false);
+ }, [setVisible]);
+
+ const indexFormCommonProps = {
+ index: props.index,
+ commonService: services.commonService,
+ onCancel: onCloseFlyout,
+ onSubmitSuccess: () => {
+ onCloseFlyout();
+ onUpdateIndex();
+ },
+ };
+
return (
<>
@@ -158,14 +169,13 @@ export default function IndexDetail(props: IndexDetailModalProps) {
{index}
{visible ? (
- setVisible(false)} hideCloseButton>
+
{index}
- {/* {index} */}
-
+
@@ -209,22 +219,7 @@ export default function IndexDetail(props: IndexDetailModalProps) {
content: (
<>
-
-
- Advanced index settings
-
-
-
-
- Edit
-
-
-
-
-
-
- {JSON.stringify(finalDetail.settings || {}, null, 2)}
-
+
>
),
},
@@ -234,22 +229,7 @@ export default function IndexDetail(props: IndexDetailModalProps) {
content: (
<>
-
-
- Index mappings
-
-
-
-
- Edit
-
-
-
-
-
-
- {JSON.stringify(finalDetail.mappings || {}, null, 2)}
-
+
>
),
},
@@ -259,31 +239,7 @@ export default function IndexDetail(props: IndexDetailModalProps) {
content: (
<>
-
-
- Index alias
-
-
-
-
- Edit
-
-
-
-
-
- ({ alias: item }))}
- columns={[
- {
- field: "alias",
- name: "Alias name",
- render: (val: string, record: { alias: string }) => {val},
- },
- ]}
- />
+
>
),
},
diff --git a/public/pages/Indices/containers/Indices/Indices.tsx b/public/pages/Indices/containers/Indices/Indices.tsx
index 0571dd8f5..7be690553 100644
--- a/public/pages/Indices/containers/Indices/Indices.tsx
+++ b/public/pages/Indices/containers/Indices/Indices.tsx
@@ -113,7 +113,14 @@ export default class Indices extends Component {
if (getIndicesResponse.ok) {
const { indices, totalIndices } = getIndicesResponse.response;
- this.setState({ indices, totalIndices });
+ const payload = {
+ indices,
+ totalIndices,
+ selectedItems: this.state.selectedItems
+ .map((item) => indices.find((remoteItem) => remoteItem.index === item.index))
+ .filter((item) => item),
+ } as IndicesState;
+ this.setState(payload);
} else {
this.context.notifications.toasts.addDanger(getIndicesResponse.error);
}
@@ -260,6 +267,7 @@ export default class Indices extends Component {
onClose: this.getIndices,
onShrink: this.getIndices,
onReindex: this.getIndices,
+ onUpdateIndex: this.getIndices,
})}
isSelectable={true}
itemId="index"
diff --git a/public/pages/Indices/containers/IndicesActions/IndicesActions.test.tsx b/public/pages/Indices/containers/IndicesActions/IndicesActions.test.tsx
index cfe97a7d3..baa80c3a7 100644
--- a/public/pages/Indices/containers/IndicesActions/IndicesActions.test.tsx
+++ b/public/pages/Indices/containers/IndicesActions/IndicesActions.test.tsx
@@ -274,7 +274,7 @@ describe(" spec", () => {
await waitFor(() => {
expect(browserServicesMock.commonService.apiCaller).toHaveBeenCalledTimes(2);
expect(coreServicesMock.notifications.toasts.addSuccess).toHaveBeenCalledTimes(1);
- expect(coreServicesMock.notifications.toasts.addSuccess).toHaveBeenCalledWith("Delete successfully");
+ expect(coreServicesMock.notifications.toasts.addSuccess).toHaveBeenCalledWith("Delete [test_index] successfully");
expect(onDelete).toHaveBeenCalledTimes(1);
});
});
diff --git a/public/pages/Indices/containers/IndicesActions/index.tsx b/public/pages/Indices/containers/IndicesActions/index.tsx
index 452a35ee8..abdf524a3 100644
--- a/public/pages/Indices/containers/IndicesActions/index.tsx
+++ b/public/pages/Indices/containers/IndicesActions/index.tsx
@@ -22,8 +22,8 @@ import ShrinkIndexFlyout from "../../components/ShrinkIndexFlyout";
import { getErrorMessage } from "../../../../utils/helpers";
import ReindexFlyout from "../../components/ReindexFlyout";
import SplitIndexFlyout from "../../components/SplitIndexFlyout";
-import {IndexItem} from "../../../../../models/interfaces";
-import {ServerResponse} from "../../../../../server/models/types";
+import { IndexItem } from "../../../../../models/interfaces";
+import { ServerResponse } from "../../../../../server/models/types";
export interface IndicesActionsProps {
selectedItems: ManagedCatIndex[];
@@ -50,14 +50,15 @@ export default function IndicesActions(props: IndicesActionsProps) {
};
const onDeleteIndexModalConfirm = useCallback(async () => {
+ const indexPayload = selectedItems.map((item) => item.index).join(",");
const result = await services.commonService.apiCaller({
endpoint: "indices.delete",
data: {
- index: selectedItems.map((item) => item.index).join(","),
+ index: indexPayload,
},
});
if (result && result.ok) {
- coreServices.notifications.toasts.addSuccess("Delete successfully");
+ coreServices.notifications.toasts.addSuccess(`Delete [${indexPayload}] successfully`);
onDeleteIndexModalClose();
onDelete();
} else {
@@ -78,7 +79,7 @@ export default function IndicesActions(props: IndicesActionsProps) {
target: targetIndex,
body: {
settings: {
- ...settingsPayload
+ ...settingsPayload,
},
},
},
@@ -88,8 +89,9 @@ export default function IndicesActions(props: IndicesActionsProps) {
onDelete();
onCloseFlyout();
} else {
- coreServices.notifications.toasts.addDanger(result?.error ||
- "There was a problem submit split index request, please check with admin");
+ coreServices.notifications.toasts.addDanger(
+ result?.error || "There was a problem submit split index request, please check with admin"
+ );
}
};
@@ -175,7 +177,7 @@ export default function IndicesActions(props: IndicesActionsProps) {
);
const getIndexSettings = async (indexName: string, flat: boolean): Promise> => {
- const result : ServerResponse> = await services.commonService.apiCaller({
+ const result: ServerResponse> = await services.commonService.apiCaller({
endpoint: "indices.getSettings",
data: {
index: indexName,
@@ -282,8 +284,7 @@ export default function IndicesActions(props: IndicesActionsProps) {
{
name: "Split",
"data-test-subj": "Split Action",
- disabled: !selectedItems.length
- || selectedItems.length > 1,
+ disabled: !selectedItems.length || selectedItems.length > 1,
onClick: () => setSplitIndexFlyoutVisible(true),
},
{
@@ -338,14 +339,15 @@ export default function IndicesActions(props: IndicesActionsProps) {
/>
)}
- {splitIndexFlyoutVisible &&
+ {splitIndexFlyoutVisible && (
}
+ />
+ )}
>
);
}
diff --git a/public/pages/Indices/utils/constants.tsx b/public/pages/Indices/utils/constants.tsx
index 77eeac269..f21577b73 100644
--- a/public/pages/Indices/utils/constants.tsx
+++ b/public/pages/Indices/utils/constants.tsx
@@ -5,7 +5,7 @@
import React from "react";
import { EuiHealth, EuiTableFieldDataColumnType } from "@elastic/eui";
-import IndexDetail from "../containers/IndexDetail";
+import IndexDetail, { IndexDetailModalProps } from "../containers/IndexDetail";
import { IndicesActionsProps } from "../containers/IndicesActions";
import { ManagedCatIndex } from "../../../../server/models/interfaces";
import { SortDirection } from "../../../utils/constants";
@@ -32,7 +32,7 @@ const HEALTH_TO_COLOR: {
red: "danger",
};
-interface IColumnOptions extends Omit {}
+interface IColumnOptions extends Omit, Pick {}
const getColumns = (props: IColumnOptions): EuiTableFieldDataColumnType[] => {
return [
diff --git a/test/mocks/index.ts b/test/mocks/index.ts
index df4405a77..47bf18162 100644
--- a/test/mocks/index.ts
+++ b/test/mocks/index.ts
@@ -9,4 +9,100 @@ import httpClientMock from "./httpClientMock";
import styleMock from "./styleMock";
import coreServicesMock from "./coreServicesMock";
-export { browserServicesMock, historyMock, httpClientMock, styleMock, coreServicesMock };
+const apiCallerMock = (browserServicesMockObject: typeof browserServicesMock) => {
+ browserServicesMockObject.commonService.apiCaller = jest.fn(
+ async (payload): Promise => {
+ switch (payload.endpoint) {
+ case "transport.request": {
+ if (payload.data?.path?.startsWith("/_index_template/_simulate_index/bad_index")) {
+ return {
+ ok: true,
+ response: {},
+ };
+ } else {
+ return {
+ ok: true,
+ response: {
+ template: {
+ settings: {
+ index: {
+ number_of_replicas: "10",
+ },
+ },
+ },
+ },
+ };
+ }
+ }
+ case "indices.create":
+ if (payload.data?.index === "bad_index") {
+ return {
+ ok: false,
+ error: "bad_index",
+ };
+ }
+
+ return {
+ ok: true,
+ response: {},
+ };
+ break;
+ case "cat.aliases":
+ return {
+ ok: true,
+ response: [
+ {
+ alias: ".kibana",
+ index: ".kibana_1",
+ filter: "-",
+ is_write_index: "-",
+ },
+ {
+ alias: "2",
+ index: "1234",
+ filter: "-",
+ is_write_index: "-",
+ },
+ ],
+ };
+ case "indices.get":
+ const payloadIndex = payload.data?.index;
+ if (payloadIndex === "bad_index") {
+ return {
+ ok: false,
+ error: "bad_error",
+ response: {},
+ };
+ }
+
+ return {
+ ok: true,
+ response: {
+ [payload.data?.index]: {
+ aliases: {
+ update_test_1: {},
+ },
+ mappings: {
+ properties: {
+ test_mapping_1: {
+ type: "text",
+ },
+ },
+ },
+ settings: {
+ "index.number_of_shards": "1",
+ "index.number_of_replicas": "1",
+ },
+ },
+ },
+ };
+ }
+ return {
+ ok: true,
+ response: {},
+ };
+ }
+ );
+};
+
+export { browserServicesMock, historyMock, httpClientMock, styleMock, coreServicesMock, apiCallerMock };