Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more test case for reindex #404

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions cypress/integration/reindex_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { PLUGIN_NAME } from "../support/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(() => {
Expand Down Expand Up @@ -144,4 +145,83 @@ describe("Reindex", () => {
});
});
});

describe("Reindex successfully for newly created index", () => {
before(() => {
cy.deleteAllIndices();
// Load logs data
cy.request({
method: "POST",
url: `${Cypress.env("opensearch_dashboards")}/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="More Action"]').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="Please enter the name for your 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(/Reindex .* success .* taskId .*/);

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);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
import { EuiSpacer, EuiFormRow, EuiLink, EuiOverlayMask, EuiLoadingSpinner, EuiContextMenu, EuiButton, EuiToast } from "@elastic/eui";
import { EuiSpacer, EuiFormRow, EuiLink, EuiOverlayMask, EuiLoadingSpinner, EuiContextMenu, EuiButton } from "@elastic/eui";
import { set, merge, omit } from "lodash";
import { ContentPanel } from "../../../../components/ContentPanel";
import AliasSelect, { AliasSelectProps } from "../AliasSelect";
Expand Down Expand Up @@ -373,7 +373,7 @@ const IndexDetail = (
data-test-subj="More Action"
panelPaddingSize="none"
button={
<EuiButton iconType="arrowDown" iconSide="right">
<EuiButton iconType="arrowDown" iconSide="right" data-test-subj="importSettingMappingBtn">
Import settings and mappings
</EuiButton>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
import { render } from "@testing-library/react";
import React from "react";
import CreateIndexFlyout from "./CreateIndexFlyout";
import { coreServicesMock, browserServicesMock } from "../../../../../test/mocks";
import { coreServicesMock, browserServicesMock, apiCallerMock } from "../../../../../test/mocks";
import { CoreServicesContext } from "../../../../components/core_services";

apiCallerMock(browserServicesMock);

describe("<CreateIndexFlyout /> spec", () => {
it("renders the component", async () => {
const component = render(
<CoreServicesContext.Provider value={coreServicesMock}>
<CreateIndexFlyout sourceIndices={[]} commonService={browserServicesMock.commonService} />,
<CreateIndexFlyout sourceIndices={[]} commonService={browserServicesMock.commonService} onCloseFlyout={() => {}} />,
</CoreServicesContext.Provider>
);
expect(component).toMatchSnapshot();
Expand Down
124 changes: 118 additions & 6 deletions public/pages/Reindex/container/Reindex/Reindex.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@ const indices = [
"store.size": "100KB",
uuid: "some_uuid",
},
{
"docs.count": 5,
"docs.deleted": 2,
health: "green",
index: "index-source-2",
pri: "1",
"pri.store.size": "100KB",
rep: "0",
status: "open",
"store.size": "100KB",
uuid: "some_uuid",
},
];

const dataStreams = [
Expand All @@ -98,6 +110,30 @@ const aliases = [
alias: "alias-1",
index: "index-source",
filter: "-",
is_write_index: "false",
"routing.index": "-",
"routing.search": "-",
},
{
alias: "alias-1",
index: "index-source-2",
filter: "-",
is_write_index: "true",
"routing.index": "-",
"routing.search": "-",
},
{
alias: "alias-2",
index: "index-test-1",
filter: "-",
is_write_index: "-",
"routing.index": "-",
"routing.search": "-",
},
{
alias: "alias-2",
index: "index-test-2",
filter: "-",
is_write_index: "-",
"routing.index": "-",
"routing.search": "-",
Expand All @@ -110,14 +146,18 @@ const mockApi = () => {
response: { indices: args.search.length > 0 ? indices.filter((index) => index.index.startsWith(args.search)) : indices },
}));

browserServicesMock.indexService.getDataStreams = jest.fn().mockResolvedValue({
browserServicesMock.indexService.getDataStreams = jest.fn().mockImplementation((args) => ({
ok: true,
response: { dataStreams: dataStreams, totalDataStreams: 1 },
});
browserServicesMock.indexService.getAliases = jest.fn().mockResolvedValue({
response: {
dataStreams: args.search.length > 0 ? dataStreams.filter((ds) => ds.name.startsWith(args.search)) : dataStreams,
},
}));
browserServicesMock.indexService.getAliases = jest.fn().mockImplementation((args) => ({
ok: true,
response: { aliases: aliases, totalAliases: 1 },
});
response: {
aliases: args.search.length > 0 ? aliases.filter((alias) => alias.alias.startsWith(args.search)) : aliases,
},
}));

browserServicesMock.commonService.apiCaller = jest.fn().mockImplementation((args) => {
if (args.endpoint === "transport.request") {
Expand Down Expand Up @@ -455,6 +495,78 @@ describe("<Reindex /> spec", () => {
});
});

it("source and destination must be different", async () => {
mockApi();
const { getByText, getAllByTestId, getByTestId } = renderWithRouter();

await waitFor(() => {
getByText("Configure source index");
});
userEvent.type(getAllByTestId("comboBoxSearchInput")[0], "index-source");
await waitFor(() => {});
fireEvent.keyDown(getAllByTestId("comboBoxSearchInput")[0], { key: "ArrowDown", code: "ArrowDown" });
fireEvent.keyDown(getAllByTestId("comboBoxSearchInput")[0], { key: "Enter", code: "Enter" });
await waitFor(() => {});

userEvent.type(getAllByTestId("comboBoxSearchInput")[1], "index-source");
await waitFor(() => {});
fireEvent.keyDown(getAllByTestId("comboBoxSearchInput")[1], { key: "ArrowDown", code: "ArrowDown" });
fireEvent.keyDown(getAllByTestId("comboBoxSearchInput")[1], { key: "Enter", code: "Enter" });
await waitFor(() => {});

userEvent.click(getByTestId("reindexConfirmButton"));

await waitFor(() => {
expect(getByText("Index [index-source] both exists in source and destination")).toBeInTheDocument();
});

userEvent.type(getAllByTestId("comboBoxSearchInput")[0], "{Backspace}index-source-2");
await waitFor(() => {});
fireEvent.keyDown(getAllByTestId("comboBoxSearchInput")[0], { key: "ArrowDown", code: "ArrowDown" });
fireEvent.keyDown(getAllByTestId("comboBoxSearchInput")[0], { key: "Enter", code: "Enter" });
await waitFor(() => {});

// change to alias
userEvent.type(getAllByTestId("comboBoxSearchInput")[1], "alias-1");
await waitFor(() => {});
fireEvent.keyDown(getAllByTestId("comboBoxSearchInput")[1], { key: "ArrowDown", code: "ArrowDown" });
fireEvent.keyDown(getAllByTestId("comboBoxSearchInput")[1], { key: "Enter", code: "Enter" });
await waitFor(() => {});

userEvent.click(getByTestId("reindexConfirmButton"));

await waitFor(() => {
expect(getByText("Index [index-source-2] both exists in source and destination")).toBeInTheDocument();
});
});

it("destination alias must have writing index behind", async () => {
mockApi();
const { getByText, getAllByTestId, getByTestId } = renderWithRouter();

await waitFor(() => {
getByText("Configure source index");
});
userEvent.type(getAllByTestId("comboBoxSearchInput")[0], "index-source");
await waitFor(() => {});
fireEvent.keyDown(getAllByTestId("comboBoxSearchInput")[0], { key: "ArrowDown", code: "ArrowDown" });
fireEvent.keyDown(getAllByTestId("comboBoxSearchInput")[0], { key: "Enter", code: "Enter" });
await waitFor(() => {});

// change to alias
userEvent.type(getAllByTestId("comboBoxSearchInput")[1], "alias-2");
await waitFor(() => {});
fireEvent.keyDown(getAllByTestId("comboBoxSearchInput")[1], { key: "ArrowDown", code: "ArrowDown" });
fireEvent.keyDown(getAllByTestId("comboBoxSearchInput")[1], { key: "Enter", code: "Enter" });
await waitFor(() => {});

userEvent.click(getByTestId("reindexConfirmButton"));

await waitFor(() => {
expect(getByText("Alias [alias-2] don't have writing index behind it")).toBeInTheDocument();
});
});

it("slices format validation", async () => {
mockApi();
const { getByText, getAllByTestId, getByTestId } = renderWithRouter();
Expand Down
41 changes: 31 additions & 10 deletions public/pages/Reindex/container/Reindex/Reindex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,24 @@ export default class Reindex extends Component<ReindexProps, ReindexState> {
const aliases = _.uniq(aliasResponse.response.aliases.map((alias) => alias.alias))
// TODO system alias
.filter((alias) => !alias.startsWith("."))
.map((name) => ({
label: name,
value: {
isAlias: true,
indices: aliasResponse.response.aliases.filter((alias) => alias.alias === name).map((alias) => alias.index),
},
}));
.map((name) => {
const indexBelongsToAlias = aliasResponse.response.aliases.filter((alias) => alias.alias === name).map((alias) => alias.index);
let writingIndex = aliasResponse.response.aliases
.filter((alias) => alias.alias === name && alias.is_write_index === "true")
.map((alias) => alias.index);
if (writingIndex.length === 0 && indexBelongsToAlias.length === 1) {
// set writing index when there is only 1 index for alias
writingIndex = indexBelongsToAlias;
}
return {
label: name,
value: {
isAlias: true,
indices: indexBelongsToAlias,
writingIndex: writingIndex,
},
};
});
options.push({ label: "aliases", options: aliases });
} else {
this.context.notifications.toasts.addDanger(aliasResponse.error);
Expand Down Expand Up @@ -285,6 +296,14 @@ export default class Reindex extends Component<ReindexProps, ReindexState> {
return false;
}

// if destination is alias, then it must have a writing index behind it
if (dest.value?.isAlias) {
if (!dest.value?.writingIndex || dest.value.writingIndex.length !== 1) {
this.setState({ destError: `Alias [${dest.label}] don't have writing index behind it` });
return false;
}
}

let expandedSource: string[] = [],
expandedDestination: string[] = [];
sources.forEach((item) => {
Expand All @@ -294,12 +313,12 @@ export default class Reindex extends Component<ReindexProps, ReindexState> {

selectedOptions.forEach((item) => {
expandedDestination.push(item.label);
item.value?.isAlias && item.value.indices && expandedDestination.push(...item.value.indices);
item.value?.isAlias && item.value.writingIndex && expandedDestination.push(...item.value.writingIndex);
});

const duplication = _.intersection(expandedSource, expandedDestination);
if (duplication.length > 0) {
this.setState({ destError: `index [${duplication.join(",")}] both exists in source and destination` });
this.setState({ destError: `Index [${duplication.join(",")}] both exists in source and destination` });
return false;
}
return true;
Expand Down Expand Up @@ -544,7 +563,9 @@ export default class Reindex extends Component<ReindexProps, ReindexState> {
</CustomFormRow>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton onClick={() => this.setState({ showCreateIndexFlyout: true })}>Create Index</EuiButton>
<EuiButton data-test-subj="createIndexButton" onClick={() => this.setState({ showCreateIndexFlyout: true })}>
Create Index
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</ContentPanel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ exports[`<Reindex /> spec renders the component 1`] = `
>
<button
class="euiButton euiButton--primary"
data-test-subj="createIndexButton"
type="button"
>
<span
Expand Down
1 change: 1 addition & 0 deletions public/pages/Reindex/models/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ export interface IndexSelectItem {
isDataStream?: boolean;
isAlias?: boolean;
indices?: string[];
writingIndex?: string[];
}
8 changes: 8 additions & 0 deletions public/services/IndexService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,12 @@ describe("IndexService spec", () => {

expect(httpClientMock.get).toHaveBeenCalledTimes(1);
});

it("calls search nodejs route when calling getAlias", async () => {
httpClientMock.post = jest.fn().mockResolvedValue({ data: {} });
const queryObject = {};
await indexService.getAliases(queryObject);

expect(httpClientMock.get).toHaveBeenCalledTimes(1);
});
});
2 changes: 1 addition & 1 deletion server/services/AliasServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default class AliasServices {
ok: true,
response: {
aliases: aliases,
total: aliases.length,
totalAliases: aliases.length,
},
},
});
Expand Down