diff --git a/.github/workflows/release-e2e-workflow-template.yml b/.github/workflows/release-e2e-workflow-template.yml index 6aae73421..dd38ffdce 100644 --- a/.github/workflows/release-e2e-workflow-template.yml +++ b/.github/workflows/release-e2e-workflow-template.yml @@ -69,7 +69,7 @@ jobs: - name: Get node and yarn versions id: versions run: | - echo "::set-output name=node_version::$(node -p "(require('./opensearch-dashboards-${{ env.VERSION }}-linux-x64/package.json').engines.node).match(/[.0-9]+/)[0]")" + echo "::set-output name=node_version::$(node -p "(require('./opensearch-dashboards-${{ env.VERSION }}/package.json').engines.node).match(/[.0-9]+/)[0]")" - name: Setup node uses: actions/setup-node@v1 with: diff --git a/cypress/integration/plugins/index-management-dashboards-plugin/aliases.js b/cypress/integration/plugins/index-management-dashboards-plugin/aliases.js new file mode 100644 index 000000000..2928f2062 --- /dev/null +++ b/cypress/integration/plugins/index-management-dashboards-plugin/aliases.js @@ -0,0 +1,119 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import { IM_PLUGIN_NAME, BASE_PATH } from '../../../utils/constants'; + +const SAMPLE_INDEX_PREFIX = 'index-for-alias-test'; +const SAMPLE_ALIAS_PREFIX = 'alias-for-test'; +const CREATE_ALIAS = 'create-alias'; +const EDIT_INDEX = 'index-edit-index-for-alias-test'; + +describe('Aliases', () => { + before(() => { + // Set welcome screen tracking to false + localStorage.setItem('home:welcome:show', 'false'); + cy.deleteAllIndices(); + for (let i = 0; i < 11; i++) { + cy.createIndex(`${SAMPLE_INDEX_PREFIX}-${i}`, null); + } + cy.createIndex(EDIT_INDEX, null); + for (let i = 0; i < 30; i++) { + cy.addIndexAlias( + `${SAMPLE_ALIAS_PREFIX}-${i}`, + `${SAMPLE_INDEX_PREFIX}-${i % 11}` + ); + } + cy.removeIndexAlias(`${SAMPLE_ALIAS_PREFIX}-0`); + cy.addIndexAlias(`${SAMPLE_ALIAS_PREFIX}-0`, `${SAMPLE_INDEX_PREFIX}-*`); + }); + + beforeEach(() => { + // Visit ISM OSD + cy.visit(`${BASE_PATH}/app/${IM_PLUGIN_NAME}#/aliases`); + + // Common text to wait for to confirm page loaded, give up to 60 seconds for initial load + cy.contains('Rows per page', { timeout: 60000 }); + }); + + describe('can be searched / sorted / paginated', () => { + it('successfully', () => { + cy.get('[data-test-subj="pagination-button-1"]').should('exist'); + cy.get('[placeholder="Search..."]').type('alias-for-test-0{enter}'); + cy.contains('alias-for-test-0'); + cy.get('.euiTableRow').should('have.length', 1); + cy.get('[data-test-subj="comboBoxSearchInput"]').type('closed{enter}'); + + cy.contains( + 'There are no aliases matching your applied filters. Reset your filters to view your aliases.' + ); + }); + }); + + describe('shows more modal', () => { + it('successfully', () => { + cy.get('[placeholder="Search..."]').type('alias-for-test-0{enter}'); + cy.contains('alias-for-test-0'); + cy.get('.euiTableRow').should('have.length', 1); + cy.get('.euiTableRowCell [data-test-subj="8 more"]') + .click() + .get('[data-test-subj="indices-table"] .euiTableRow') + .should('have.length', 10); + }); + }); + + describe('can create a alias with wildcard and specific name', () => { + it('successfully', () => { + cy.get('[data-test-subj="Create AliasButton"]').click(); + cy.get('[data-test-subj="form-name-alias"]').type(CREATE_ALIAS); + cy.get( + '[data-test-subj="form-name-indexArray"] [data-test-subj="comboBoxSearchInput"]' + ).type(`${EDIT_INDEX}{enter}${SAMPLE_INDEX_PREFIX}-*{enter}`); + cy.get('.euiModalFooter .euiButton--fill') + .click({ force: true }) + .get('[data-test-subj="9 more"]') + .should('exist'); + }); + }); + + describe('can edit / delete a alias', () => { + it('successfully', () => { + cy.get('[placeholder="Search..."]').type( + `${SAMPLE_ALIAS_PREFIX}-0{enter}` + ); + cy.contains(`${SAMPLE_ALIAS_PREFIX}-0`); + cy.get('[data-test-subj="moreAction"] button') + .click() + .get('[data-test-subj="editAction"]') + .should('be.disabled') + .get(`#_selection_column_${SAMPLE_ALIAS_PREFIX}-0-checkbox`) + .click() + .get('[data-test-subj="moreAction"] button') + .click() + .get('[data-test-subj="editAction"]') + .click() + .get( + '[data-test-subj="form-name-indexArray"] [data-test-subj="comboBoxInput"]' + ) + .click() + .type(`${EDIT_INDEX}{enter}`) + .get(`[title="${SAMPLE_INDEX_PREFIX}-0"] button`) + .click() + .get(`[title="${SAMPLE_INDEX_PREFIX}-1"] button`) + .click() + .get('.euiModalFooter .euiButton--fill') + .click({ force: true }) + .end(); + + cy.get('[data-test-subj="7 more"]').should('exist'); + }); + }); + + after(() => { + cy.deleteAllIndices(); + for (let i = 0; i < 30; i++) { + cy.removeIndexAlias(`${SAMPLE_ALIAS_PREFIX}-${i}`); + } + cy.removeIndexAlias(CREATE_ALIAS); + }); +}); diff --git a/cypress/integration/plugins/index-management-dashboards-plugin/create_index.js b/cypress/integration/plugins/index-management-dashboards-plugin/create_index.js new file mode 100644 index 000000000..5fac1ed57 --- /dev/null +++ b/cypress/integration/plugins/index-management-dashboards-plugin/create_index.js @@ -0,0 +1,308 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import { IM_PLUGIN_NAME, BASE_PATH } from '../../../utils/constants'; + +const SAMPLE_INDEX = 'index-specific-index'; + +describe('Create Index', () => { + before(() => { + // Set welcome screen tracking to false + localStorage.setItem('home:welcome:show', 'false'); + cy.deleteAllIndices(); + cy.deleteTemplate('index-common-template'); + cy.deleteTemplate('index-specific-template'); + cy.createIndexTemplate('index-common-template', { + index_patterns: ['index-*'], + template: { + aliases: { + alias_for_common_1: {}, + alias_for_common_2: {}, + }, + settings: { + number_of_shards: 2, + number_of_replicas: 1, + }, + }, + }); + cy.createIndexTemplate('index-specific-template', { + index_patterns: ['index-specific-*'], + priority: 1, + template: { + aliases: { + alias_for_specific_1: {}, + }, + settings: { + number_of_shards: 3, + number_of_replicas: 2, + }, + mappings: { + properties: { + text: { + type: 'text', + }, + }, + }, + }, + }); + }); + + describe('can be created and updated', () => { + beforeEach(() => { + // Visit ISM OSD + cy.visit(`${BASE_PATH}/app/${IM_PLUGIN_NAME}#/indices`); + cy.contains('Rows per page', { timeout: 60000 }); + }); + + it('Create a index successfully', () => { + // enter create page + cy.get('[data-test-subj="Create IndexButton"]').click(); + cy.contains('Create index'); + + // type field name + cy.get('[placeholder="Specify a name for the new index."]') + .type(SAMPLE_INDEX) + .blur(); + + cy.wait(1000); + + cy.get('[data-test-subj="comboBoxSearchInput"]') + .get('[title="alias_for_specific_1"]') + .should('exist'); + + cy.get('[data-test-subj="comboBoxSearchInput"]').type( + 'some_test_alias{enter}' + ); + + cy.get('[data-test-subj="editorTypeJsonEditor"]').click().end(); + + cy.get( + '[data-test-subj="mappingsJsonEditorFormRow"] [data-test-subj="jsonEditor-valueDisplay"]' + ).should(($editor) => { + expect(JSON.parse($editor.val())).to.deep.equal({ + properties: { + text: { + type: 'text', + }, + }, + }); + }); + + cy.get('[data-test-subj="mappingsJsonEditorFormRow"] .ace_text-input') + .focus() + .clear({ force: true }) + .type( + JSON.stringify({ + properties: { + text: { + type: 'text', + }, + }, + dynamic: true, + }), + { parseSpecialCharSequences: false, force: true } + ) + .end() + .wait(1000) + .get('[data-test-subj="editorTypeVisualEditor"]') + .click() + .end(); + + // add a field + cy.get('[data-test-subj="createIndexAddFieldButton"]').click().end(); + cy.get('[data-test-subj="mapping-visual-editor-1-field-name"]').type( + 'text_mappings' + ); + + // click create + cy.get('[data-test-subj="createIndexCreateButton"]').click({ + force: true, + }); + + // The index should exist + cy.get(`#_selection_column_${SAMPLE_INDEX}-checkbox`).should( + 'have.exist' + ); + + // check the index detail + cy.visit( + `${BASE_PATH}/app/${IM_PLUGIN_NAME}#/create-index/${SAMPLE_INDEX}` + ); + + // index name and alias should exist + cy.get(`[title="${SAMPLE_INDEX}"]`) + .should('have.exist') + .end() + .get('[title="some_test_alias"]') + .should('have.exist') + .end() + .get('[data-test-subj="mapping-visual-editor-0-field-type"]') + .should('have.attr', 'title', 'text') + .end() + .get('[data-test-subj="mapping-visual-editor-1-field-name"]') + .should('have.attr', 'title', 'text_mappings') + .end() + .get('[data-test-subj="editorTypeJsonEditor"]') + .click() + .end() + .get( + '[data-test-subj="mappingsJsonEditorFormRow"] [data-test-subj="jsonEditor-valueDisplay"]' + ) + .should(($editor) => { + expect(JSON.parse($editor.val())).to.deep.equal({ + dynamic: 'true', + properties: {}, + }); + }); + }); + + it('Update alias successfully', () => { + cy.get(`[data-test-subj="viewIndexDetailButton-${SAMPLE_INDEX}"]`) + .click() + .get('#indexDetailModalAlias') + .click(); + + // add a alias and remove the exist alias + cy.get('[data-test-subj="comboBoxSearchInput"]') + .type('some_new_test_alias{enter}') + .end() + .get('[title="some_test_alias"] .euiBadge__iconButton') + .click() + .end() + .get('[data-test-subj="createIndexCreateButton"]') + .click({ force: true }) + .end(); + + cy.get('[title="some_test_alias"]') + .should('not.exist') + .end() + .get('[title="some_new_test_alias"]') + .should('exist') + .end(); + }); + + it('Update settings successfully', () => { + cy.get(`[data-test-subj="viewIndexDetailButton-${SAMPLE_INDEX}"]`) + .click() + .get('#indexDetailModalSettings') + .click(); + + cy.get('[aria-controls="accordionForCreateIndexSettings"]') + .click() + .end() + .get('.ace_text-input') + .focus() + .clear({ force: true }) + .type( + '{ "index.blocks.write": true, "index.number_of_shards": 2, "index.number_of_replicas": 3 }', + { + parseSpecialCharSequences: false, + force: true, + } + ) + .blur(); + + cy.get('[data-test-subj="createIndexCreateButton"]').click({ + force: true, + }); + + cy.contains(`Can't update non dynamic settings`).should('exist'); + + cy.get('.ace_text-input') + .focus() + .clear({ force: true }) + .type('{ "index.blocks.write": true, "index.number_of_shards": "3" }', { + parseSpecialCharSequences: false, + force: true, + }) + .end() + .wait(1000) + .get('[placeholder="Specify number of replicas."]') + .clear() + .type(2) + .end(); + + cy.get('[data-test-subj="createIndexCreateButton"]').click({ + force: true, + }); + + cy.wait(1000) + .get('[data-test-subj="form-name-index.number_of_replicas"] input') + .should('have.value', '2'); + }); + + it('Update mappings successfully', () => { + cy.get(`[data-test-subj="viewIndexDetailButton-${SAMPLE_INDEX}"]`) + .click() + .get('#indexDetailModalMappings') + .click(); + + cy.get('[data-test-subj="createIndexAddFieldButton"]') + .click() + .end() + .get('[data-test-subj="mapping-visual-editor-2-field-name"]') + .type('text_mappings_2') + .end() + .get('[data-test-subj="createIndexCreateButton"]') + .click({ force: true }); + + cy.get('[data-test-subj="mapping-visual-editor-2-field-type"]') + .should('have.attr', 'title', 'text') + .end(); + + cy.get('[data-test-subj="editorTypeJsonEditor"]') + .click() + .end() + .get('.ace_text-input') + .focus() + .clear({ force: true }) + .type('{ "dynamic": true }', { + parseSpecialCharSequences: false, + force: true, + }) + .blur() + .end() + .wait(1000) + .get('[data-test-subj="createIndexCreateButton"]') + .click({ force: true }); + + cy.wait(1000) + .get('[data-test-subj="editorTypeJsonEditor"]') + .click() + .end() + .get('[data-test-subj="previousMappingsJsonButton"]') + .click() + .end() + .get( + '[data-test-subj="previousMappingsJsonModal"] [data-test-subj="jsonEditor-valueDisplay"]' + ) + .should( + 'have.text', + JSON.stringify( + { + dynamic: 'true', + properties: { + text: { + type: 'text', + }, + text_mappings: { + type: 'text', + }, + text_mappings_2: { + type: 'text', + }, + }, + }, + null, + 2 + ) + ); + }); + }); + + after(() => { + cy.deleteTemplate('index-common-template'); + cy.deleteTemplate('index-specific-template'); + }); +}); diff --git a/cypress/integration/plugins/index-management-dashboards-plugin/indices_spec.js b/cypress/integration/plugins/index-management-dashboards-plugin/indices_spec.js index 3086b686b..3202b998a 100644 --- a/cypress/integration/plugins/index-management-dashboards-plugin/indices_spec.js +++ b/cypress/integration/plugins/index-management-dashboards-plugin/indices_spec.js @@ -3,7 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { BASE_PATH, IM_PLUGIN_NAME } from '../../../utils/constants'; +import { + BASE_PATH, + IM_PLUGIN_NAME, + BACKEND_BASE_PATH, +} from '../../../utils/constants'; import samplePolicy from '../../../fixtures/plugins/index-management-dashboards-plugin/sample_policy'; const POLICY_ID = 'test_policy_id'; @@ -147,14 +151,12 @@ describe('Indices', () => { }); // Click apply policy button + cy.get('[data-test-subj="moreAction"]').click(); cy.get(`[data-test-subj="Apply policyButton"]`).click({ force: true }); cy.get(`input[data-test-subj="comboBoxSearchInput"]`) - .focus() - .type(POLICY_ID, { - parseSpecialCharSequences: false, - delay: 1, - }); + .click() + .type(POLICY_ID); // Click the policy option cy.get(`button[role="option"]`).first().click({ force: true }); @@ -174,6 +176,368 @@ describe('Indices', () => { cy.get(`tbody > tr:contains("${SAMPLE_INDEX}") > td`) .filter(`:nth-child(4)`) .contains('Yes'); + + // Confirm the information shows in detail modal + cy.get( + `[data-test-subj="viewIndexDetailButton-${SAMPLE_INDEX}"]` + ).click(); + cy.get( + `[data-test-subj="indexDetailOverviewItem-Managed by policy"] .euiDescriptionList__description a` + ).contains(POLICY_ID); + }); + }); + + describe('can make indices deleted', () => { + before(() => { + cy.deleteAllIndices(); + cy.deleteIMJobs(); + cy.createIndex(SAMPLE_INDEX); + }); + + it('successfully', () => { + // Confirm we have our initial index + cy.contains(SAMPLE_INDEX); + + // Click actions button + cy.get('[data-test-subj="moreAction"]').click(); + + // Delete btn should be disabled if no items selected + cy.get('[data-test-subj="deleteAction"]').should( + 'have.class', + 'euiContextMenuItem-isDisabled' + ); + + // click any where to hide actions + cy.get('#_selection_column_sample_index-checkbox').click(); + cy.get('[data-test-subj="deleteAction"]').should('not.exist'); + + // Click actions button + cy.get('[data-test-subj="moreAction"]').click(); + // Delete btn should be enabled + cy.get('[data-test-subj="deleteAction"]') + .should('exist') + .should('not.have.class', 'euiContextMenuItem-isDisabled') + .click(); + // The confirm button should be disabled + cy.get('[data-test-subj="Delete Confirm button"]').should( + 'have.class', + 'euiButton-isDisabled' + ); + cy.wait(1000); + // type delete + cy.get('[placeholder="delete"]').type('delete'); + cy.get('[data-test-subj="Delete Confirm button"]').should( + 'not.have.class', + 'euiContextMenuItem-isDisabled' + ); + // click to delete + cy.get('[data-test-subj="Delete Confirm button"]').click(); + // the sample_index should not exist + cy.wait(500); + cy.get('#_selection_column_sample_index-checkbox').should('not.exist'); + }); + }); + + describe('shows detail of a index when click the item', () => { + before(() => { + cy.deleteAllIndices(); + cy.deleteIMJobs(); + cy.createIndex(SAMPLE_INDEX); + }); + + it('successfully', () => { + cy.get( + `[data-test-subj="viewIndexDetailButton-${SAMPLE_INDEX}"]` + ).click(); + cy.get( + `[data-test-subj="indexDetailOverviewItem-Index name"] .euiDescriptionList__description > span` + ).should('have.text', SAMPLE_INDEX); + }); + }); + + describe('can search with reindex & recovery status', () => { + const reindexedIndex = + 'reindex_opensearch_dashboards_sample_data_ecommerce'; + const splittedIndex = 'split_opensearch_dashboards_sample_data_logs'; + before(() => { + cy.deleteAllIndices(); + cy.deleteIMJobs(); + // Visit ISM OSD + cy.visit(`${BASE_PATH}/app/${IM_PLUGIN_NAME}#/indices`); + + // Common text to wait for to confirm page loaded, give up to 60 seconds for initial load + cy.contains('Rows per page', { timeout: 60000 }); + + cy.request({ + method: 'POST', + url: `${BASE_PATH}/api/sample_data/ecommerce`, + headers: { + 'osd-xsrf': true, + }, + }).then((response) => { + expect(response.status).equal(200); + }); + + cy.request({ + method: 'POST', + url: `${BASE_PATH}/api/sample_data/logs`, + headers: { + 'osd-xsrf': true, + }, + }).then((response) => { + expect(response.status).equal(200); + }); + + cy.request({ + method: 'PUT', + url: `${BACKEND_BASE_PATH}/${splittedIndex}/_settings`, + body: { + 'index.blocks.read_only': false, + }, + failOnStatusCode: false, + }); + cy.request({ + method: 'DELETE', + url: `${BACKEND_BASE_PATH}/${reindexedIndex}`, + failOnStatusCode: false, + }); + cy.request({ + method: 'DELETE', + url: `${BACKEND_BASE_PATH}/${splittedIndex}`, + failOnStatusCode: false, + }); + }); + + it('Successfully', () => { + cy.request({ + method: 'PUT', + url: `${BACKEND_BASE_PATH}/${reindexedIndex}`, + body: { + settings: { + index: { + number_of_shards: 1, + number_of_replicas: '0', + }, + }, + }, + }); + // do a simple reindex + cy.request( + 'POST', + `${BACKEND_BASE_PATH}/_reindex?wait_for_completion=false`, + { + source: { + index: 'opensearch_dashboards_sample_data_ecommerce', + }, + dest: { + index: reindexedIndex, + }, + } + ); + + cy.get('[placeholder="Search"]').type('o'); + + // do a simple split + cy.request( + 'PUT', + `${BACKEND_BASE_PATH}/opensearch_dashboards_sample_data_logs/_settings`, + { + 'index.blocks.write': true, + } + ); + + cy.request({ + method: 'POST', + url: `${BASE_PATH}/api/ism/apiCaller`, + headers: { + 'osd-xsrf': true, + }, + body: { + endpoint: 'indices.split', + data: { + index: 'opensearch_dashboards_sample_data_logs', + target: splittedIndex, + body: { + settings: { + index: { + number_of_shards: 2, + }, + }, + }, + }, + }, + }); + + cy.get('[placeholder="Search"]').type('p'); + }); + + after(() => { + cy.request({ + method: 'DELETE', + url: `${BACKEND_BASE_PATH}/${reindexedIndex}`, + failOnStatusCode: false, + }); + cy.request({ + method: 'DELETE', + url: `${BACKEND_BASE_PATH}/${splittedIndex}`, + failOnStatusCode: false, + }); + }); + }); + + describe('can shrink an index', () => { + before(() => { + cy.deleteAllIndices(); + cy.deleteIMJobs(); + cy.createIndex(SAMPLE_INDEX, null, { + settings: { + 'index.blocks.write': true, + 'index.number_of_shards': 2, + 'index.number_of_replicas': 0, + }, + }); + }); + + it('successfully shrink an index', () => { + // Type in SAMPLE_INDEX in search input + cy.get(`input[type="search"]`).focus().type(SAMPLE_INDEX); + + cy.wait(1000).get('.euiTableRow').should('have.length', 1); + // Confirm we have our initial index + cy.contains(SAMPLE_INDEX); + + cy.get('[data-test-subj="moreAction"]').click(); + // Shrink btn should be disabled if no items selected + cy.get('[data-test-subj="Shrink Action"]').should( + 'have.class', + 'euiContextMenuItem-isDisabled' + ); + + // Select an index + cy.get(`[data-test-subj="checkboxSelectRow-${SAMPLE_INDEX}"]`).check({ + force: true, + }); + + cy.get('[data-test-subj="moreAction"]').click(); + // Shrink btn should be enabled + cy.get('[data-test-subj="Shrink Action"]') + .should('exist') + .should('not.have.class', 'euiContextMenuItem-isDisabled') + .click(); + + // Check for Shrink page + cy.contains('Shrink index'); + + // Enter target index name + cy.get(`input[data-test-subj="targetIndexNameInput"]`).type( + `${SAMPLE_INDEX}_shrunken` + ); + + // Click shrink index button + cy.get('button').contains('Shrink').click({ force: true }); + + // Check for success toast + cy.contains( + `Successfully started shrinking ${SAMPLE_INDEX}. The shrunken index will be named ${SAMPLE_INDEX}_shrunken.` + ); + }); + }); + + describe('can close and open an index', () => { + before(() => { + cy.deleteAllIndices(); + cy.deleteIMJobs(); + cy.createIndex(SAMPLE_INDEX); + }); + + it('successfully close an index', () => { + cy.contains(SAMPLE_INDEX); + + cy.get('[data-test-subj="moreAction"]').click(); + // Close btn should be disabled if no items selected + cy.get('[data-test-subj="Close Action"]').should( + 'have.class', + 'euiContextMenuItem-isDisabled' + ); + + // Select an index + cy.get(`[data-test-subj="checkboxSelectRow-${SAMPLE_INDEX}"]`).check({ + force: true, + }); + + cy.get('[data-test-subj="moreAction"]').click(); + // Close btn should be enabled + cy.get('[data-test-subj="Close Action"]') + .should('exist') + .should('not.have.class', 'euiContextMenuItem-isDisabled') + .click(); + + // Check for close index modal + cy.contains('Close indices'); + + // Close confirm button should be disabled + cy.get('[data-test-subj="Close Confirm button"]').should( + 'have.class', + 'euiButton-isDisabled' + ); + // type close + cy.get('[placeholder="close"]').type('close'); + cy.get('[data-test-subj="Close Confirm button"]').should( + 'not.have.class', + 'euiContextMenuItem-isDisabled' + ); + + // Click close confirm button + cy.get('[data-test-subj="Close Confirm button"]').click(); + + // Check for success toast + cy.contains('Close [sample_index] successfully'); + + // Confirm the index is closed + cy.get(`input[type="search"]`).focus().type(SAMPLE_INDEX); + cy.get('tbody > tr').should(($tr) => { + expect($tr, '1 row').to.have.length(1); + expect($tr, 'item').to.contain('close'); + }); + }); + + it('successfully open an index', () => { + // Confirm we have our initial index + cy.contains(SAMPLE_INDEX); + + cy.get('[data-test-subj="moreAction"]').click(); + // Open btn should be disabled if no items selected + cy.get('[data-test-subj="Open Action"]').should( + 'have.class', + 'euiContextMenuItem-isDisabled' + ); + + // Select an index + cy.get(`[data-test-subj="checkboxSelectRow-${SAMPLE_INDEX}"]`).check({ + force: true, + }); + + cy.get('[data-test-subj="moreAction"]').click(); + // Open btn should be enabled + cy.get('[data-test-subj="Open Action"]') + .should('exist') + .should('not.have.class', 'euiContextMenuItem-isDisabled') + .click(); + + // Check for open index modal + cy.contains('Open indices'); + + cy.get('[data-test-subj="Open Confirm button"]').click(); + + // Check for success toast + cy.contains('Open [sample_index] successfully'); + + // Confirm the index is open + cy.get(`input[type="search"]`).focus().type(SAMPLE_INDEX); + cy.get('tbody > tr').should(($tr) => { + expect($tr, '1 row').to.have.length(1); + expect($tr, 'item').to.contain('open'); + }); }); }); }); diff --git a/cypress/integration/plugins/index-management-dashboards-plugin/reindex_spec.js b/cypress/integration/plugins/index-management-dashboards-plugin/reindex_spec.js new file mode 100644 index 000000000..4af29df97 --- /dev/null +++ b/cypress/integration/plugins/index-management-dashboards-plugin/reindex_spec.js @@ -0,0 +1,251 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { IM_PLUGIN_NAME, BASE_PATH } from '../../../utils/constants'; +const REINDEX_DEST = 'test-ecomm-rdx'; +const REINDEX_DEST_NO_SOURCE = 'test-reindex-nosource'; +const REINDEX_NEW_CREATED = 'test-logs-new'; + +describe('Reindex', () => { + beforeEach(() => { + // Set welcome screen tracking to false + localStorage.setItem('home:welcome:show', 'false'); + + // Visit ISM OSD + cy.visit(`${BASE_PATH}/app/${IM_PLUGIN_NAME}#/indices`); + + // Common text to wait for to confirm page loaded, give up to 60 seconds for initial load + cy.contains('Rows per page', { timeout: 60000 }); + }); + + describe('Reindex validation error', () => { + before(() => { + cy.deleteAllIndices(); + // Load ecommerce data + cy.request({ + method: 'POST', + url: `${BASE_PATH}/api/sample_data/ecommerce`, + headers: { + 'osd-xsrf': true, + }, + }).then((response) => { + expect(response.status).equal(200); + }); + + cy.createIndex(REINDEX_DEST_NO_SOURCE, null, { + mappings: { + _source: { + enabled: false, + }, + properties: { + name: { + type: 'keyword', + }, + }, + }, + }); + }); + + it('source validation failed', () => { + // Confirm we have our initial index + cy.contains(REINDEX_DEST_NO_SOURCE); + + cy.get( + `[data-test-subj="checkboxSelectRow-${REINDEX_DEST_NO_SOURCE}"]` + ).check({ force: true }); + + // Click actions button + cy.get('[data-test-subj="moreAction"]').click(); + // Reindex should show as activate + cy.get('[data-test-subj="Reindex Action"]') + .should('exist') + .should('not.have.class', 'euiContextMenuItem-isDisabled') + .click(); + + cy.contains(/_sources is not enabled/); + }); + }); + + describe('Reindex successfully', () => { + before(() => { + cy.deleteAllIndices(); + // Load ecommerce data + cy.request({ + method: 'POST', + url: `${BASE_PATH}/api/sample_data/ecommerce`, + headers: { + 'osd-xsrf': true, + }, + }).then((response) => { + expect(response.status).equal(200); + }); + + cy.createIndex(REINDEX_DEST, null, { + settings: { 'index.number_of_replicas': 0 }, + }); + + cy.createPipeline('bumpOrderId', { + description: 'sample description', + processors: [ + { + set: { + field: 'order_id', + value: '200{{order_id}}', + }, + }, + ], + }); + }); + + it('successfully', () => { + // Confirm we have our initial index + cy.contains('opensearch_dashboards_sample_data_ecommerce'); + + // Click actions button + cy.get('[data-test-subj="moreAction"]').click(); + // Reindex should show as activate + cy.get('[data-test-subj="Reindex Action"]') + .should('exist') + .should('not.have.class', 'euiContextMenuItem-isDisabled') + .click(); + + cy.get(`div[data-test-subj="sourceSelector"]`) + .find(`input[data-test-subj="comboBoxSearchInput"]`) + .type(`opensearch_dashboards_sample_data_ecommerce{downArrow}{enter}`); + + cy.get(`div[data-test-subj="destinationSelector"]`) + .find(`input[data-test-subj="comboBoxSearchInput"]`) + .type(`${REINDEX_DEST}{downArrow}{enter}`); + + // open advance option + cy.get('[data-test-subj="advanceOptionToggle"]').click(); + + // enable subset query + cy.get('[data-test-subj="subsetOption"] #subset').click({ force: true }); + + // input query to reindex subset + cy.get('[data-test-subj="queryJsonEditor"] textarea') + .focus() + .clear() + .type('{"query":{"match":{"category":"Men\'s Clothing"}}}', { + parseSpecialCharSequences: false, + }); + + // set slices to auto + cy.get('[data-test-subj="sliceEnabled"]').click({ force: true }); + + // input pipeline + cy.get(`div[data-test-subj="pipelineCombobox"]`) + .find(`input[data-test-subj="comboBoxSearchInput"]`) + .type('bumpOrderId{enter}'); + + // click to perform reindex + cy.get('[data-test-subj="reindexConfirmButton"]').click(); + cy.wait(10); + cy.contains(/Successfully started reindexing/); + + cy.wait(10000); + // Type in REINDEX_DEST in search input + cy.get(`input[type="search"]`).focus().type(REINDEX_DEST); + + // Confirm we only see REINDEX_DEST in table + cy.get('tbody > tr').should(($tr) => { + expect($tr, '1 row').to.have.length(1); + expect($tr, 'item').to.contain(REINDEX_DEST); + }); + }); + }); + + describe('Reindex successfully for newly created index', () => { + before(() => { + cy.deleteAllIndices(); + // Load logs data + cy.request({ + method: 'POST', + url: `${BASE_PATH}/api/sample_data/logs`, + headers: { + 'osd-xsrf': true, + }, + }).then((response) => { + expect(response.status).equal(200); + }); + }); + + it('successfully', () => { + // search + cy.get(`input[type="search"]`) + .focus() + .type('opensearch_dashboards_sample_data_logs'); + + cy.wait(1000); + + // Confirm we have our initial index + cy.contains('opensearch_dashboards_sample_data_logs'); + + // select logs index + cy.get( + '#_selection_column_opensearch_dashboards_sample_data_logs-checkbox' + ).click(); + + // Click actions button + cy.get('[data-test-subj="moreAction"]').click(); + // Reindex should show as activate + cy.get('[data-test-subj="Reindex Action"]').click(); + + // open advance option + cy.get('[data-test-subj="advanceOptionToggle"]').click(); + + // enable subset query + cy.get('[data-test-subj="subsetOption"] #subset').click({ force: true }); + + // input query to reindex subset + cy.get('[data-test-subj="queryJsonEditor"] textarea') + .focus() + .clear() + .type('{"query":{"match":{"ip":"135.201.60.64"}}}', { + parseSpecialCharSequences: false, + }); + + // create destination + cy.get('[data-test-subj="createIndexButton"]').click(); + cy.contains('Create Index'); + + cy.get('[placeholder="Specify a name for the new index."]') + .type(REINDEX_NEW_CREATED) + .blur(); + cy.wait(1000); + + // import setting and mapping + cy.get('[data-test-subj="importSettingMappingBtn"]').click(); + cy.get( + '[data-test-subj="import-settings-opensearch_dashboards_sample_data_logs"]' + ).click(); + + cy.wait(10); + cy.contains(/have been import successfully/); + + cy.get('[data-test-subj="flyout-footer-action-button"]').click({ + force: true, + }); + + // click to perform reindex + cy.get('[data-test-subj="reindexConfirmButton"]').click(); + cy.wait(10); + cy.contains(/Successfully started reindexing/); + + cy.wait(10000); + // Type in REINDEX_DEST in search input + cy.get(`input[type="search"]`).focus().type(REINDEX_NEW_CREATED); + + // Confirm we only see REINDEX_DEST in table + cy.get('tbody > tr').should(($tr) => { + expect($tr, '1 row').to.have.length(1); + expect($tr, 'item').to.contain(REINDEX_NEW_CREATED); + // subset data number + expect($tr, 'item').to.contain(13); + }); + }); + }); +}); diff --git a/cypress/integration/plugins/index-management-dashboards-plugin/split_index.js b/cypress/integration/plugins/index-management-dashboards-plugin/split_index.js new file mode 100644 index 000000000..b420761f1 --- /dev/null +++ b/cypress/integration/plugins/index-management-dashboards-plugin/split_index.js @@ -0,0 +1,220 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import { IM_PLUGIN_NAME, BASE_PATH } from '../../../utils/constants'; + +const sampleIndex = 'index-split'; +const sampleAlias = 'alias-split'; + +describe('Split Index', () => { + before(() => { + // Set welcome screen tracking to false + localStorage.setItem('home:welcome:show', 'false'); + cy.deleteAllIndices(); + }); + + describe('can be created and updated', () => { + beforeEach(() => { + // Visit ISM OSD + cy.visit(`${BASE_PATH}/app/${IM_PLUGIN_NAME}#/indices`); + cy.contains('Rows per page', { timeout: 60000 }); + }); + + let splitNumber = 2; + let replicaNumber = 1; + it('Create an index successfully', () => { + // enter create page + cy.get('[data-test-subj="Create IndexButton"]').click(); + cy.contains('Create index'); + + // type field name + cy.get('[placeholder="Specify a name for the new index."]') + .type(sampleIndex) + .end(); + + cy.get('[data-test-subj="comboBoxSearchInput"]') + .focus() + .type(`${sampleAlias}`) + .end(); + + // click create + cy.get('[data-test-subj="createIndexCreateButton"]') + .click({ force: true }) + .end(); + + // The index should exist + cy.get(`#_selection_column_${sampleIndex}-checkbox`) + .should('have.exist') + .end(); + + cy.get(`[data-test-subj="viewIndexDetailButton-${sampleIndex}"]`) + .click() + .end(); + cy.get('#indexDetailModalSettings').click().end(); + + cy.get( + '[data-test-subj="form-name-index.number_of_shards"] .euiText' + ).then(($shardNumber) => { + splitNumber = $shardNumber.attr('title') * 2; + }); + + cy.get('#indexDetailModalAlias').click().end(); + cy.get(`[title="${sampleAlias}"]`).should('exist').end(); + + // Update Index status to blocks write otherwise we can't apply split operation on it + cy.updateIndexSettings(sampleIndex, { + 'index.blocks.write': 'true', + }).end(); + }); // create index + + it('Split successfully', () => { + const targetIndex = `${sampleIndex}` + '-target'; + cy.get(`[data-test-subj="checkboxSelectRow-${sampleIndex}"]`) + .click() + .end() + .get('[data-test-subj="moreAction"]') + .click() + .end() + .get('[data-test-subj="Split Action"]') + .click() + .end() + // Target Index Name is required + .get('[data-test-subj="targetIndexNameInput"]') + .type(`${targetIndex}`) + .end() + // Number of shards after split is required + .get('[data-test-subj="numberOfShardsInput"]') + .type(`${splitNumber}{downArrow}{enter}`) + .end() + .get('[data-test-subj="numberOfReplicasInput"]') + .clear() + .type(`${replicaNumber}`) + .end() + .get('[data-test-subj="splitButton"]') + .click() + .end(); + + cy.get(`[data-test-subj="viewIndexDetailButton-${targetIndex}"]`) + .click() + .end(); + cy.get('#indexDetailModalSettings').click().end(); + cy.get('[data-test-subj="form-name-index.number_of_shards"] .euiText') + .should('have.text', `${splitNumber}`) + .end(); + cy.get('[data-test-subj="form-name-index.number_of_replicas"] input') + .should('have.value', `${replicaNumber}`) + .end(); + }); // Split + + it('Split successfully with advanced setting', () => { + const targetIndex = `${sampleIndex}` + '-setting'; + cy.get(`[data-test-subj="checkboxSelectRow-${sampleIndex}"]`) + .click() + .end() + .get('[data-test-subj="moreAction"]') + .click() + .end() + .get('[data-test-subj="Split Action"]') + .click() + .end() + .get('[data-test-subj=targetIndexNameInput]') + .type(`${targetIndex}`) + .end() + // Instead of input shard number at shard field, another option is to populate it in advanced setting + .get('[aria-controls="accordionForCreateIndexSettings"]') + .click() + .end() + .get('[data-test-subj="codeEditorContainer"] textarea') + .focus() + // Need to remove the default {} in advanced setting + .clear() + .type( + `{"index.number_of_shards": "${splitNumber}", "index.number_of_replicas": "${replicaNumber}"}`, + { + parseSpecialCharSequences: false, + } + ) + .end() + .get('[data-test-subj="splitButton"]') + .click() + .end(); + + cy.get(`[data-test-subj="viewIndexDetailButton-${targetIndex}"]`) + .click() + .end(); + cy.get('#indexDetailModalSettings').click().end(); + cy.get('[data-test-subj="form-name-index.number_of_shards"] .euiText') + .should('have.text', `${splitNumber}`) + .end(); + cy.get('[data-test-subj="form-name-index.number_of_replicas"] input') + .should('have.value', `${replicaNumber}`) + .end(); + }); // advanced setting + + it('Split successfully with alias', () => { + const targetIndex = `${sampleIndex}` + '-alias'; + const newAlias = 'alias-new'; + cy.get(`[data-test-subj="checkboxSelectRow-${sampleIndex}"]`) + .click() + .end() + .get('[data-test-subj="moreAction"]') + .click() + .end() + .get('[data-test-subj="Split Action"]') + .click() + .end() + .get('[data-test-subj=targetIndexNameInput]') + .type(`${targetIndex}`) + .end() + .get('[data-test-subj="numberOfShardsInput"]') + .type(`${splitNumber}{downArrow}{enter}`) + .end() + // Assign to an existing alias and a new alias + .get( + '[data-test-subj="form-name-aliases"] [data-test-subj="comboBoxSearchInput"]' + ) + .type(`${sampleAlias}{enter}${newAlias}{enter}`) + .end() + .get('[data-test-subj="splitButton"]') + .click() + .end(); + + cy.get(`[data-test-subj="viewIndexDetailButton-${targetIndex}"]`) + .click() + .end(); + // Verify alias associated with the new index + cy.get('#indexDetailModalAlias').click().end(); + cy.get(`[title="${newAlias}"]`).should('exist').end(); + cy.get(`[title="${sampleAlias}"]`).should('exist').end(); + }); // Create with alias + + it('Update blocks write to true', () => { + // Set index to not blocks write + cy.updateIndexSettings(sampleIndex, { + 'index.blocks.write': 'false', + }).end(); + cy.get(`[data-test-subj="checkboxSelectRow-${sampleIndex}"]`) + .click() + .end() + .get('[data-test-subj="moreAction"]') + .click() + .end() + .get('[data-test-subj="Split Action"]') + .click() + .end() + // Index can't be split if it's blocks write status is not true + .get('[data-test-subj="splitButton"]') + .should('have.class', 'euiButton-isDisabled') + .end() + .wait(1000) + // Set index to blocks write + .get('[data-test-subj="set-indexsetting-button"]') + .click() + .end() + .get('[data-test-subj="splitButton"]') + .click() + .end(); + }); // Blocks write + }); +}); diff --git a/cypress/integration/plugins/index-management-dashboards-plugin/templates.js b/cypress/integration/plugins/index-management-dashboards-plugin/templates.js new file mode 100644 index 000000000..90d506994 --- /dev/null +++ b/cypress/integration/plugins/index-management-dashboards-plugin/templates.js @@ -0,0 +1,106 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import { IM_PLUGIN_NAME, BASE_PATH } from '../../../utils/constants'; + +const SAMPLE_TEMPLATE_PREFIX = 'index-for-alias-test'; +const MAX_TEMPLATE_NUMBER = 30; + +describe('Templates', () => { + before(() => { + // Set welcome screen tracking to false + localStorage.setItem('home:welcome:show', 'false'); + cy.deleteTemplate(`${SAMPLE_TEMPLATE_PREFIX}-${MAX_TEMPLATE_NUMBER}`); + for (let i = 0; i < MAX_TEMPLATE_NUMBER; i++) { + cy.deleteTemplate(`${SAMPLE_TEMPLATE_PREFIX}-${i}`); + cy.createIndexTemplate(`${SAMPLE_TEMPLATE_PREFIX}-${i}`, { + index_patterns: ['template-test-*'], + priority: i, + template: { + aliases: {}, + settings: { + number_of_shards: 2, + number_of_replicas: 1, + }, + }, + }); + } + }); + + beforeEach(() => { + // Visit ISM OSD + cy.visit(`${BASE_PATH}/app/${IM_PLUGIN_NAME}#/templates`); + + // Common text to wait for to confirm page loaded, give up to 60 seconds for initial load + cy.contains('Rows per page', { timeout: 60000 }); + }); + + describe('can be searched / sorted / paginated', () => { + it('successfully', () => { + cy.get('[data-test-subj="pagination-button-1"]').should('exist'); + cy.get('[placeholder="Search..."]').type(`${SAMPLE_TEMPLATE_PREFIX}-0`); + cy.contains(`${SAMPLE_TEMPLATE_PREFIX}-0`); + cy.get('.euiTableRow').should('have.length', 1); + }); + }); + + describe('can create a template', () => { + it('successfully', () => { + cy.get('[data-test-subj="Create templateButton"]').click(); + cy.contains('Define template'); + + cy.get('[data-test-subj="form-row-name"] input').type( + `${SAMPLE_TEMPLATE_PREFIX}-${MAX_TEMPLATE_NUMBER}` + ); + cy.get( + '[data-test-subj="form-row-index_patterns"] [data-test-subj="comboBoxSearchInput"]' + ).type('test{enter}'); + cy.get('[data-test-subj="CreateIndexTemplateCreateButton"]').click(); + + cy.contains( + `${SAMPLE_TEMPLATE_PREFIX}-${MAX_TEMPLATE_NUMBER} has been successfully created.` + ); + + cy.get('[placeholder="Search..."]').type( + `${SAMPLE_TEMPLATE_PREFIX}-${MAX_TEMPLATE_NUMBER}` + ); + cy.contains(`${SAMPLE_TEMPLATE_PREFIX}-${MAX_TEMPLATE_NUMBER}`); + cy.get('.euiTableRow').should('have.length', 1); + }); + }); + + describe('can delete a template', () => { + it('successfully', () => { + cy.get('[placeholder="Search..."]').type(`${SAMPLE_TEMPLATE_PREFIX}-0`); + cy.contains(`${SAMPLE_TEMPLATE_PREFIX}-0`); + cy.get(`#_selection_column_${SAMPLE_TEMPLATE_PREFIX}-0-checkbox`).click(); + + cy.get('[data-test-subj="moreAction"] button') + .click() + .get('[data-test-subj="deleteAction"]') + .click(); + // The confirm button should be disabled + cy.get('[data-test-subj="deleteConfirmButton"]').should('be.disabled'); + // type delete + cy.wait(500).get('[data-test-subj="deleteInput"]').type('delete'); + cy.get('[data-test-subj="deleteConfirmButton"]').should( + 'not.be.disabled' + ); + // click to delete + cy.get('[data-test-subj="deleteConfirmButton"]').click(); + // the alias should not exist + cy.wait(500); + cy.get(`#_selection_column_${SAMPLE_TEMPLATE_PREFIX}-0-checkbox`).should( + 'not.exist' + ); + }); + }); + + after(() => { + cy.deleteTemplate(`${SAMPLE_TEMPLATE_PREFIX}-${MAX_TEMPLATE_NUMBER}`); + for (let i = 0; i < MAX_TEMPLATE_NUMBER; i++) { + cy.deleteTemplate(`${SAMPLE_TEMPLATE_PREFIX}-${i}`); + } + }); +}); diff --git a/cypress/utils/plugins/index-management-dashboards-plugin/commands.js b/cypress/utils/plugins/index-management-dashboards-plugin/commands.js index a50a7ce10..3f4ef9999 100644 --- a/cypress/utils/plugins/index-management-dashboards-plugin/commands.js +++ b/cypress/utils/plugins/index-management-dashboards-plugin/commands.js @@ -82,6 +82,14 @@ Cypress.Commands.add('createRollup', (rollupId, rollupJSON) => { ); }); +Cypress.Commands.add('deleteTemplate', (name) => { + cy.request({ + url: `${Cypress.env('openSearchUrl')}${IM_API.INDEX_TEMPLATE_BASE}/${name}`, + failOnStatusCode: false, + method: 'DELETE', + }); +}); + Cypress.Commands.add('createTransform', (transformId, transformJSON) => { cy.request( 'PUT', @@ -92,6 +100,14 @@ Cypress.Commands.add('createTransform', (transformId, transformJSON) => { ); }); +Cypress.Commands.add('createPipeline', (pipelineId, pipelineJSON) => { + cy.request( + 'PUT', + `${Cypress.env('openSearchUrl')}/_ingest/pipeline/${pipelineId}`, + pipelineJSON + ); +}); + Cypress.Commands.add('disableJitter', () => { // Sets the jitter to 0 in the ISM plugin cluster settings const jitterJson = { @@ -109,3 +125,39 @@ Cypress.Commands.add('disableJitter', () => { jitterJson ); }); + +Cypress.Commands.add('addIndexAlias', (alias, index) => { + cy.request({ + url: `${Cypress.env('openSearchUrl')}/_aliases`, + method: 'POST', + body: { + actions: [ + { + add: { + index, + alias, + }, + }, + ], + }, + failOnStatusCode: false, + }); +}); + +Cypress.Commands.add('removeIndexAlias', (alias) => { + cy.request({ + url: `${Cypress.env('openSearchUrl')}/_aliases`, + method: 'POST', + body: { + actions: [ + { + remove: { + index: '*', + alias, + }, + }, + ], + }, + failOnStatusCode: false, + }); +});