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

Feature: Alias management #405

Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
76d1c0f
feat: add aliases page
SuZhou-Joe Nov 21, 2022
bb06fa7
feat: update snapshot
SuZhou-Joe Nov 21, 2022
4ebf3ab
feat: enable search
SuZhou-Joe Nov 22, 2022
e8e8afa
feat: add some test
SuZhou-Joe Nov 22, 2022
8be07ec
feat: update
SuZhou-Joe Nov 22, 2022
98fefc0
feat: update
SuZhou-Joe Nov 22, 2022
e3afa67
feat: update
SuZhou-Joe Nov 22, 2022
a6aef29
feat: extract remote select component
SuZhou-Joe Nov 22, 2022
796d79b
feat: enable alias features
SuZhou-Joe Nov 22, 2022
269e6da
feat: update navigation position
SuZhou-Joe Nov 23, 2022
dd2171b
refractor: delete modal
SuZhou-Joe Nov 24, 2022
29dc387
feat: security enhancement for apiCaller
SuZhou-Joe Nov 24, 2022
4884dfd
feat: add edit function
SuZhou-Joe Nov 25, 2022
28da289
feat: update alias
SuZhou-Joe Nov 25, 2022
2b9b853
feat: add status
SuZhou-Joe Nov 25, 2022
03c7236
feat: update status select to combobox
SuZhou-Joe Nov 25, 2022
3fe78d0
feat: update function test
SuZhou-Joe Nov 25, 2022
1d7c74e
feat: update unit test
SuZhou-Joe Nov 25, 2022
7bb749b
feat: update unit test
SuZhou-Joe Nov 25, 2022
c8b8115
feat: remove async flag
SuZhou-Joe Nov 25, 2022
ad0d602
feat: update
SuZhou-Joe Nov 25, 2022
9d60711
feat: update control unit test
SuZhou-Joe Nov 25, 2022
cd6646a
feat: update
SuZhou-Joe Nov 25, 2022
2ec5d17
feat: update
SuZhou-Joe Nov 25, 2022
9cd0979
feat: update
SuZhou-Joe Nov 25, 2022
965f8f3
faet: update
SuZhou-Joe Nov 25, 2022
52eeed0
feat: add unit test
SuZhou-Joe Nov 26, 2022
7ea99bb
feat: add unit test
SuZhou-Joe Nov 26, 2022
08cca86
feat: add unit test
SuZhou-Joe Nov 26, 2022
5673375
feat: update
SuZhou-Joe Nov 26, 2022
f37abd9
feat: update
SuZhou-Joe Nov 27, 2022
3fecb21
feat: add system alias / index warning
SuZhou-Joe Nov 27, 2022
dd821d8
feat: add more mapping types
SuZhou-Joe Nov 27, 2022
38d9f1f
feat: update
SuZhou-Joe Nov 27, 2022
83ab7cf
feat: update
SuZhou-Joe Nov 27, 2022
592ec72
feat: make status column sortable
SuZhou-Joe Nov 27, 2022
c81213e
feat: remove type with no handler
SuZhou-Joe Nov 28, 2022
21ed0c5
feat: remove type with no handler
SuZhou-Joe Nov 28, 2022
45fbc15
feat: filter system index/alias from select
SuZhou-Joe Nov 28, 2022
bb91d4d
feat: add callout when matching templates
SuZhou-Joe Nov 28, 2022
6f2ea21
refractor: use field to set values in indexMapping
SuZhou-Joe Nov 28, 2022
87fdf06
feat: update index mappings
SuZhou-Joe Nov 28, 2022
0d20cae
feat: merge conflict
SuZhou-Joe Nov 28, 2022
0b105d2
feat: update
SuZhou-Joe Nov 28, 2022
247bd25
feat: update
SuZhou-Joe Nov 28, 2022
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
116 changes: 116 additions & 0 deletions cypress/integration/aliases.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
import { PLUGIN_NAME } from "../support/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.addAlias(`${SAMPLE_ALIAS_PREFIX}-${i}`, `${SAMPLE_INDEX_PREFIX}-${i % 11}`);
}
cy.removeAlias(`${SAMPLE_ALIAS_PREFIX}-0`);
cy.addAlias(`${SAMPLE_ALIAS_PREFIX}-0`, `${SAMPLE_INDEX_PREFIX}-*`);
});

beforeEach(() => {
// Visit ISM OSD
cy.visit(`${Cypress.env("opensearch_dashboards")}/app/${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("You have no aliases.");
});
});

describe("shows more flyout", () => {
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 button").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(".euiFlyoutFooter .euiButton--fill").click().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="More Action"] button')
.click()
.get('[data-test-subj="Edit Action"]')
.should("be.disabled")
.get(`#_selection_column_${SAMPLE_ALIAS_PREFIX}-0-checkbox`)
.click()
.get('[data-test-subj="More Action"] button')
.click()
.get('[data-test-subj="Edit Action"]')
.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(".euiFlyoutFooter .euiButton--fill")
.click()
.end();

cy.get('[data-test-subj="7 more"]').should("exist");

cy.get('[data-test-subj="More Action"] button').click().get('[data-test-subj="Delete Action"]').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_ALIAS_PREFIX}-0-checkbox`).should("not.exist");
});
});

after(() => {
cy.deleteAllIndices();
for (let i = 0; i < 30; i++) {
cy.removeAlias(`${SAMPLE_ALIAS_PREFIX}-${i}`);
}
cy.removeAlias(CREATE_ALIAS);
});
});
2 changes: 1 addition & 1 deletion cypress/integration/indices_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ describe("Indices", () => {
});

cy.request({
method: "PUT",
method: "POST",
url: `${Cypress.env("opensearch_dashboards")}/api/ism/apiCaller`,
headers: {
"osd-xsrf": true,
Expand Down
36 changes: 36 additions & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,39 @@ Cypress.Commands.add("disableJitter", () => {
};
cy.request("PUT", `${Cypress.env("opensearch")}/_cluster/settings`, jitterJson);
});

Cypress.Commands.add("addAlias", (alias, index) => {
cy.request({
url: `${Cypress.env("opensearch")}/_aliases`,
method: "POST",
body: {
actions: [
{
add: {
index,
alias,
},
},
],
},
failOnStatusCode: false,
});
});

Cypress.Commands.add("removeAlias", (alias) => {
cy.request({
url: `${Cypress.env("opensearch")}/_aliases`,
method: "POST",
body: {
actions: [
{
remove: {
index: "*",
alias,
},
},
],
},
failOnStatusCode: false,
});
});
2 changes: 2 additions & 0 deletions cypress/support/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,5 +118,7 @@ declare namespace Cypress {
* cy.createPipeline("pipelineId", {"description": "sample description", "processors": []})
*/
createPipeline(pipelineId: string, pipeline: object);
addAlias(alias: string, index: string);
removeAlias(alias: string);
}
}
18 changes: 10 additions & 8 deletions public/components/FormGenerator/FormGenerator.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,16 @@ describe("<FormGenerator /> spec", () => {
);

userEvent.type(getByTestId("form-name-test_component").querySelector("input") as Element, "1");
expect(onChangeMock).toBeCalledWith(
{
test: "1",
test_component: "1",
},
"test_component",
"1"
);
await waitFor(() => {
expect(onChangeMock).toBeCalledWith(
{
test: "1",
test_component: "1",
},
"test_component",
"1"
);
});
});

it("shows error with custom validation in class component", async () => {
Expand Down
4 changes: 2 additions & 2 deletions public/components/FormGenerator/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { forwardRef, useRef, useImperativeHandle, useEffect, useMemo } from "react";
import { EuiForm, EuiFormProps, EuiFormRowProps } from "@elastic/eui";
import AllBuiltInComponents from "./built_in_components";
import AllBuiltInComponents, { IFieldComponentProps } from "./built_in_components";
// import Field, { InitOption, FieldOption, Rule } from "../../lib/field";
import useField, { InitOption, FieldOption, Rule, FieldInstance } from "../../lib/field";
import AdvancedSettings, { IAdvancedSettingsProps } from "../AdvancedSettings";
Expand All @@ -23,7 +23,7 @@ export interface IField {
rowProps: Pick<EuiFormRowProps, "label" | "helpText">;
name: string;
type?: keyof typeof AllBuiltInComponents;
component?: typeof AllBuiltInComponents["Input"];
component?: React.ComponentType<IFieldComponentProps>;
options?: Omit<IInitOption, "name">;
}

Expand Down
70 changes: 70 additions & 0 deletions public/components/RemoteSelect/RemoteSelect.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState } from "react";
import { render, waitFor } from "@testing-library/react";
import RemoteSelect, { RemoteSelectProps } from "./index";
import userEvent from "@testing-library/user-event";

const onChangeMock = jest.fn();

const AliasSelectWithOnchange = (props: RemoteSelectProps) => {
const [tempValue, setTempValue] = useState<string[]>(props.value || []);
return (
<RemoteSelect
{...props}
value={tempValue}
onChange={(val) => {
onChangeMock(val);
setTempValue(val);
}}
/>
);
};

describe("<AliasSelect /> spec", () => {
it("renders the component", async () => {
const { container } = render(<RemoteSelect refreshOptions={() => Promise.resolve({ ok: true, response: [] })} onChange={() => {}} />);
await waitFor(() => {
expect(container.firstChild).toMatchSnapshot();
});
});

it("renders with error", async () => {
const { container } = render(
<RemoteSelect refreshOptions={() => Promise.resolve({ ok: false, error: "error" })} onChange={() => {}} />
);
await waitFor(() => {
expect(container.firstChild).toMatchSnapshot();
});
});

it("it should choose options or create one", async () => {
const { getByTestId } = render(
<AliasSelectWithOnchange refreshOptions={() => Promise.resolve({ ok: true, response: [{ label: "test" }] })} />
);
await waitFor(() => {
expect(getByTestId("comboBoxInput")).toBeInTheDocument();
});
await userEvent.click(getByTestId("comboBoxInput"));
await waitFor(() => {
expect(document.querySelector('button[title="test"]')).toBeInTheDocument();
});
await userEvent.click(document.querySelector('button[title="test"]') as Element);
await waitFor(() => {
expect(onChangeMock).toBeCalledTimes(1);
expect(onChangeMock).toBeCalledWith(["test"]);
});
await userEvent.type(getByTestId("comboBoxInput"), "test2{enter}");
await waitFor(() => {
expect(onChangeMock).toBeCalledTimes(2);
expect(onChangeMock).toBeCalledWith(["test", "test2"]);
});
await userEvent.type(getByTestId("comboBoxInput"), " {enter}");
await waitFor(() => {
expect(onChangeMock).toBeCalledTimes(2);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<AliasSelect /> spec renders the component 1`] = `
<div
aria-expanded="false"
aria-haspopup="listbox"
class="euiComboBox"
role="combobox"
>
<div
class="euiFormControlLayout"
>
<div
class="euiFormControlLayout__childrenWrapper"
>
<div
class="euiComboBox__inputWrap euiComboBox__inputWrap-isLoading euiComboBox__inputWrap-isClearable"
data-test-subj="comboBoxInput"
tabindex="-1"
>
<div
class="euiComboBox__input"
style="font-size: 14px; display: inline-block;"
>
<input
aria-controls=""
data-test-subj="comboBoxSearchInput"
role="textbox"
style="box-sizing: content-box; width: 2px;"
value=""
/>
<div
style="position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;"
/>
</div>
</div>
<div
class="euiFormControlLayoutIcons euiFormControlLayoutIcons--right"
>
<span
class="euiLoadingSpinner euiLoadingSpinner--medium"
/>
<button
aria-label="Open list of options"
class="euiFormControlLayoutCustomIcon euiFormControlLayoutCustomIcon--clickable"
data-test-subj="comboBoxToggleListButton"
type="button"
>
EuiIconMock
</button>
</div>
</div>
</div>
</div>
`;

exports[`<AliasSelect /> spec renders with error 1`] = `
<div
aria-expanded="false"
aria-haspopup="listbox"
class="euiComboBox"
role="combobox"
>
<div
class="euiFormControlLayout"
>
<div
class="euiFormControlLayout__childrenWrapper"
>
<div
class="euiComboBox__inputWrap euiComboBox__inputWrap-isLoading euiComboBox__inputWrap-isClearable"
data-test-subj="comboBoxInput"
tabindex="-1"
>
<div
class="euiComboBox__input"
style="font-size: 14px; display: inline-block;"
>
<input
aria-controls=""
data-test-subj="comboBoxSearchInput"
role="textbox"
style="box-sizing: content-box; width: 2px;"
value=""
/>
<div
style="position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;"
/>
</div>
</div>
<div
class="euiFormControlLayoutIcons euiFormControlLayoutIcons--right"
>
<span
class="euiLoadingSpinner euiLoadingSpinner--medium"
/>
<button
aria-label="Open list of options"
class="euiFormControlLayoutCustomIcon euiFormControlLayoutCustomIcon--clickable"
data-test-subj="comboBoxToggleListButton"
type="button"
>
EuiIconMock
</button>
</div>
</div>
</div>
</div>
`;
Loading