diff --git a/.github/workflows/cypress-workflow.yml b/.github/workflows/cypress-workflow.yml index 80e8f35c6..1dfaf92fa 100644 --- a/.github/workflows/cypress-workflow.yml +++ b/.github/workflows/cypress-workflow.yml @@ -7,8 +7,8 @@ on: branches: - "*" env: - OPENSEARCH_DASHBOARDS_VERSION: '2.5' - OPENSEARCH_VERSION: '2.5.0-SNAPSHOT' + OPENSEARCH_DASHBOARDS_VERSION: '2.x' + OPENSEARCH_VERSION: '2.6.0-SNAPSHOT' jobs: tests: name: Run Cypress E2E tests @@ -29,7 +29,7 @@ jobs: with: path: index-management repository: opensearch-project/index-management - ref: '2.5' + ref: '2.6' - name: Run opensearch with plugin run: | cd index-management diff --git a/.github/workflows/unit-tests-workflow.yml b/.github/workflows/unit-tests-workflow.yml index 1bc935419..15a4ac604 100644 --- a/.github/workflows/unit-tests-workflow.yml +++ b/.github/workflows/unit-tests-workflow.yml @@ -7,7 +7,7 @@ on: branches: - "*" env: - OPENSEARCH_DASHBOARDS_VERSION: '2.5' + OPENSEARCH_DASHBOARDS_VERSION: '2.x' jobs: tests: name: Run unit tests diff --git a/cypress/integration/rollover.js b/cypress/integration/rollover.js new file mode 100644 index 000000000..9132220dc --- /dev/null +++ b/cypress/integration/rollover.js @@ -0,0 +1,100 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import { PLUGIN_NAME } from "../support/constants"; + +const rolloverValidAlias = "rollover-valid-alias"; +const rolloverAliasNeedTargetIndex = "rollover-alias-need-target-index"; +const rolloverDataStream = "data-stream-rollover"; +const validIndex = "index-000001"; +const invalidIndex = "index-test-rollover"; + +describe("Rollover", () => { + before(() => { + // Set welcome screen tracking to false + localStorage.setItem("home:welcome:show", "false"); + cy.deleteTemplate("index-common-template"); + cy.deleteAllIndices(); + cy.request({ + url: `${Cypress.env("opensearch")}/_data_stream/*`, + method: "DELETE", + failOnStatusCode: false, + }); + cy.createIndex(validIndex); + cy.createIndex(invalidIndex); + cy.addAlias(rolloverValidAlias, validIndex); + cy.addAlias(rolloverAliasNeedTargetIndex, invalidIndex); + cy.createIndexTemplate("index-common-template", { + index_patterns: ["data-stream-*"], + data_stream: {}, + template: { + aliases: { + alias_for_common_1: {}, + alias_for_common_2: {}, + }, + settings: { + number_of_shards: 2, + number_of_replicas: 1, + }, + }, + }); + cy.request({ + url: `${Cypress.env("opensearch")}/_data_stream/${rolloverDataStream}`, + method: "PUT", + failOnStatusCode: false, + }); + }); + + describe("rollover", () => { + it("rollover data stream successfully", () => { + // Visit ISM OSD + cy.visit(`${Cypress.env("opensearch_dashboards")}/app/${PLUGIN_NAME}#/rollover/${rolloverDataStream}`); + cy.contains("Configure source", { timeout: 60000 }); + + // click create + cy.get('[data-test-subj="rolloverSubmitButton"]').click({ force: true }); + + cy.contains(/has been successfully rollover./); + }); + + it("rollover valid alias successfully", () => { + // Visit ISM OSD + cy.visit(`${Cypress.env("opensearch_dashboards")}/app/${PLUGIN_NAME}#/rollover/${rolloverValidAlias}`); + cy.contains("Configure new rollover index", { timeout: 60000 }); + + // click create + cy.get('[data-test-subj="rolloverSubmitButton"]').click({ force: true }); + + cy.contains(/has been successfully rollover./); + }); + + it("rollover invalid alias successfully", () => { + // Visit ISM OSD + cy.visit(`${Cypress.env("opensearch_dashboards")}/app/${PLUGIN_NAME}#/rollover/${rolloverAliasNeedTargetIndex}`); + cy.contains("Configure new rollover index", { timeout: 60000 }); + + // click create + cy.get('[data-test-subj="rolloverSubmitButton"]').click({ force: true }); + + cy.contains("Invalid index name."); + + cy.get('[data-test-subj="form-name-index"] input').type("index-test-rollover-target"); + + // click create + cy.get('[data-test-subj="rolloverSubmitButton"]').click({ force: true }); + + cy.contains(/has been successfully rollover./); + }); + }); + + after(() => { + cy.deleteTemplate("index-common-template"); + cy.deleteAllIndices(); + cy.request({ + url: `${Cypress.env("opensearch")}/_data_stream/*`, + method: "DELETE", + failOnStatusCode: false, + }); + }); +}); diff --git a/models/interfaces.ts b/models/interfaces.ts index 5ab513986..52c025fa1 100644 --- a/models/interfaces.ts +++ b/models/interfaces.ts @@ -622,6 +622,7 @@ export interface IAPICaller { endpoint: string; method?: string; data?: any; + hideLog?: boolean; } export interface IRecoveryItem { diff --git a/opensearch_dashboards.json b/opensearch_dashboards.json index ba809f725..3a0de2e95 100644 --- a/opensearch_dashboards.json +++ b/opensearch_dashboards.json @@ -1,7 +1,7 @@ { "id": "indexManagementDashboards", - "version": "2.5.0.0", - "opensearchDashboardsVersion": "2.5.1", + "version": "2.6.0.0", + "opensearchDashboardsVersion": "2.6.0", "configPath": ["opensearch_index_management"], "requiredPlugins": ["navigation", "opensearchDashboardsReact"], "server": true, diff --git a/package.json b/package.json index 46c1fdba8..6bb5f8025 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opensearch_index_management_dashboards", - "version": "2.5.0.0", + "version": "2.6.0.0", "description": "Opensearch Dashboards plugin for Index Management", "main": "index.js", "license": "Apache-2.0", diff --git a/public/pages/CreateIndex/components/AliasSelect/AliasSelect.test.tsx b/public/components/AliasSelect/AliasSelect.test.tsx similarity index 100% rename from public/pages/CreateIndex/components/AliasSelect/AliasSelect.test.tsx rename to public/components/AliasSelect/AliasSelect.test.tsx diff --git a/public/pages/CreateIndex/components/AliasSelect/__snapshots__/AliasSelect.test.tsx.snap b/public/components/AliasSelect/__snapshots__/AliasSelect.test.tsx.snap similarity index 100% rename from public/pages/CreateIndex/components/AliasSelect/__snapshots__/AliasSelect.test.tsx.snap rename to public/components/AliasSelect/__snapshots__/AliasSelect.test.tsx.snap diff --git a/public/pages/CreateIndex/components/AliasSelect/index.tsx b/public/components/AliasSelect/index.tsx similarity index 89% rename from public/pages/CreateIndex/components/AliasSelect/index.tsx rename to public/components/AliasSelect/index.tsx index d321499fb..8b8087787 100644 --- a/public/pages/CreateIndex/components/AliasSelect/index.tsx +++ b/public/components/AliasSelect/index.tsx @@ -5,10 +5,10 @@ import React, { forwardRef, useRef } from "react"; import { EuiComboBoxProps } from "@elastic/eui"; -import RemoteSelect, { RemoteSelectProps } from "../../../../components/RemoteSelect"; -import { ServerResponse } from "../../../../../server/models/types"; -import { filterByMinimatch } from "../../../../../utils/helper"; -import { SYSTEM_ALIAS } from "../../../../../utils/constants"; +import RemoteSelect, { RemoteSelectProps } from "../RemoteSelect"; +import { ServerResponse } from "../../../server/models/types"; +import { filterByMinimatch } from "../../../utils/helper"; +import { SYSTEM_ALIAS } from "../../../utils/constants"; export interface AliasSelectProps extends Omit, "value" | "onChange"> { value?: Record; diff --git a/public/components/FormGenerator/built_in_components/index.tsx b/public/components/FormGenerator/built_in_components/index.tsx index de2782731..c725d7da0 100644 --- a/public/components/FormGenerator/built_in_components/index.tsx +++ b/public/components/FormGenerator/built_in_components/index.tsx @@ -60,7 +60,14 @@ const componentMap: Record { - const findItem = options.find((item: { label: string }) => item.label === searchValue); + const allOptions = (options as { label: string; options?: { label: string }[] }[]).reduce((total, current) => { + if (current.options) { + return [...total, ...current.options]; + } else { + return [...total, current]; + } + }, [] as { label: string }[]); + const findItem = allOptions.find((item: { label: string }) => item.label === searchValue); if (findItem) { onChange(searchValue); } diff --git a/public/components/FormGenerator/index.tsx b/public/components/FormGenerator/index.tsx index 29b2278bf..9ca7d7241 100644 --- a/public/components/FormGenerator/index.tsx +++ b/public/components/FormGenerator/index.tsx @@ -2,7 +2,7 @@ import React, { forwardRef, useRef, useImperativeHandle, useEffect, useMemo } fr import { EuiForm, EuiFormProps, EuiSpacer } from "@elastic/eui"; import { isEqual, omit, pick } from "lodash"; import AllBuiltInComponents, { IFieldComponentProps } from "./built_in_components"; -import useField, { InitOption, FieldOption, Rule, FieldInstance, FieldName } from "../../lib/field"; +import useField, { InitOption, FieldOption, Rule, FieldInstance, FieldName, transformNameToString } from "../../lib/field"; import AdvancedSettings, { IAdvancedSettingsProps, IAdvancedSettingsRef } from "../AdvancedSettings"; import CustomFormRow, { CustomFormRowProps } from "../CustomFormRow"; @@ -23,7 +23,7 @@ interface IFormGeneratorAdvancedSettings extends IAdvancedSettingsProps { export interface IField { rowProps: Pick; - name: string; + name: FieldName; type?: keyof typeof AllBuiltInComponents; component?: React.ComponentType; options?: Omit; @@ -129,10 +129,10 @@ function FormGenerator(props: IFormGeneratorProps, ref: React.Ref void; + docVersion: string; + refreshOptions: AliasSelectProps["refreshOptions"]; value?: Partial; oldValue?: Partial; - onChange: (value: IndexDetailProps["value"]) => void; isEdit?: boolean; readonly?: boolean; - refreshOptions: AliasSelectProps["refreshOptions"]; mode?: IndicesUpdateMode; onSimulateIndexTemplate?: (indexName: string) => Promise>; onGetIndexDetail?: (indexName: string) => Promise; sourceIndices?: string[]; onSubmit?: () => Promise<{ ok: boolean }>; refreshIndex?: () => void; - docVersion: string; + withoutPanel?: boolean; } export interface IIndexDetailRef { validate: () => Promise; hasUnsavedChanges: (mode: IndicesUpdateMode) => number; getMappingsJSONEditorValue: () => string; + simulateFromTemplate: () => Promise; + importSettings: (args: { index: string }) => Promise; } const TemplateInfoCallout = (props: { visible: boolean }) => { @@ -110,6 +113,7 @@ const IndexDetail = ( onSubmit, refreshIndex, docVersion, + withoutPanel, }: IndexDetailProps, ref: Ref ) => { @@ -132,18 +136,6 @@ const IndexDetail = ( const aliasesRef = useRef(null); const settingsRef = useRef(null); const mappingsRef = useRef(null); - useImperativeHandle(ref, () => ({ - validate: async () => { - const result = await Promise.all([ - aliasesRef.current?.validatePromise().then((result) => result.errors), - mappingsRef.current?.validate(), - settingsRef.current?.validatePromise().then((result) => result.errors), - ]); - return result.every((item) => !item); - }, - hasUnsavedChanges: (mode: IndicesUpdateMode) => diffJson(oldValue?.[mode], finalValue[mode]), - getMappingsJSONEditorValue: () => mappingsRef.current?.getJSONEditorValue() || "", - })); const onIndexInputBlur = useCallback(async () => { await new Promise((resolve) => setTimeout(resolve, 200)); if (destroyRef.current) { @@ -229,6 +221,7 @@ const IndexDetail = ( // omit alias ...omit(indexDetail, ["aliases", "data_stream"]), mappings: { + ...indexDetail?.mappings, properties: transformObjectToArray(indexDetail?.mappings?.properties || {}), }, // pick some metadata in index @@ -238,6 +231,20 @@ const IndexDetail = ( hasEdit.current = false; } }; + useImperativeHandle(ref, () => ({ + validate: async () => { + const result = await Promise.all([ + aliasesRef.current?.validatePromise().then((result) => result.errors), + mappingsRef.current?.validate(), + settingsRef.current?.validatePromise().then((result) => result.errors), + ]); + return result.every((item) => !item); + }, + hasUnsavedChanges: (mode: IndicesUpdateMode) => diffJson(oldValue?.[mode], finalValue[mode]), + getMappingsJSONEditorValue: () => mappingsRef.current?.getJSONEditorValue() || "", + simulateFromTemplate: onIndexInputBlur, + importSettings: onImportSettings, + })); const formFields: IField[] = useMemo(() => { return [ { @@ -406,9 +413,25 @@ const IndexDetail = ( if (mode && mode === IndicesUpdateMode.alias) { return content; } + + const title = "Define index"; + + if (withoutPanel) { + return ( + <> + + {title} + + + {content} + + + ); + } + return ( <> - + {content} @@ -508,6 +531,21 @@ const IndexDetail = ( return content; } + const title = "Index settings"; + + if (withoutPanel) { + return ( + <> + +

{title}

+
+ + {content} + + + ); + } + return ( <> @@ -538,6 +576,19 @@ const IndexDetail = ( return content; } + if (withoutPanel) { + return ( + <> + +
Index mapping
+
+ + {content} + + + ); + } + return ( spec render mappings with object type 1`] = ` spec render mappings with object type 1`] = ` spec render mappings with object type 1`] = ` spec render component 1`] = ` + + + +
+
+ Must be in lowercase letters. Cannot begin with underscores or hyphens. Spaces, commas, and characters :, ", *, +, /, , |, ?, #, > are not allowed. +
+
+ + +
+
+
+
+
+ +
+
+
+ Allow this index to be referenced by existing aliases or specify a new alias. +
+ + +
+
+
+ +
+
+
+
+

+ Index settings + + + + +

+
+
+
+
+
+
+
+ +
+
+
+
+ Specify the number of primary shards for the index. Default is 1. +
+
+ The number of primary shards cannot be changed after the index is created. +
+
+ +
+
+ +
+
+
+
+
+
+
+ +
+
+
+ Specify the number of replicas each primary shard should have. Default is 1. +
+ +
+
+ +
+
+
+
+
+
+
+ +
+
+
+ Specify how often the index should refresh, which publishes the most recent changes and make them available for search. Default is 1 second. +
+ +
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+

+ Specify a comma-delimited list of settings. + + + View index settings. + EuiIconMock + + (opens in a new tab or window) + + +

+

+ All the settings will be handled in flat structure. + + + Learn more. + EuiIconMock + + (opens in a new tab or window) + + +

+
+ +
+ +
+