feat(ui): add configuration UI for enabling multi-grid

This commit is contained in:
Łukasz Mierzwa
2020-03-30 17:58:25 +01:00
parent a178a6bed8
commit 2cd96abef7
10 changed files with 533 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
import React from "react";
import PropTypes from "prop-types";
import { action, observable } from "mobx";
import { observer } from "mobx-react";
import Creatable from "react-select/creatable";
import { FetchGet } from "Common/Fetch";
import { FormatBackendURI } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { ThemeContext } from "Components/Theme";
const disabledLabel = "Disable multi-grid";
const emptyValue = { label: disabledLabel, value: "" };
const valueToOption = (v) => ({ label: v ? v : disabledLabel, value: v });
const GridLabelName = observer(
class GridLabelName extends Creatable {
static propTypes = {
settingsStore: PropTypes.instanceOf(Settings).isRequired,
};
static contextType = ThemeContext;
suggestions = observable({
names: [],
});
populateNameSuggestions = action(() => {
this.nameSuggestionsFetch = FetchGet(
FormatBackendURI(`labelNames.json`),
{}
)
.then(
(result) => result.json(),
(err) => {
return [];
}
)
.then((result) => {
this.suggestions.names = [
...[emptyValue],
...result.map((value) => ({
label: value,
value: value,
})),
];
})
.catch((err) => {
console.error(err.message);
this.suggestions.names = [emptyValue];
});
});
onChange = action((newValue, actionMeta) => {
const { settingsStore } = this.props;
settingsStore.multiGridConfig.config.gridLabel = newValue.value;
});
componentDidMount() {
this.populateNameSuggestions();
}
render() {
const { settingsStore } = this.props;
return (
<Creatable
styles={this.context.reactSelectStyles}
classNamePrefix="react-select"
instanceId="configuration-grid-label"
defaultValue={valueToOption(
settingsStore.multiGridConfig.config.gridLabel
)}
options={this.suggestions.names}
onChange={this.onChange}
/>
);
}
}
);
export { GridLabelName };

View File

@@ -0,0 +1,62 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { action } from "mobx";
import { observer } from "mobx-react";
import { Settings } from "Stores/Settings";
import { ThemeContext } from "Components/Theme";
import { GridLabelName } from "./GridLabelName";
const MultiGridConfiguration = observer(
class MultiGridConfiguration extends Component {
static propTypes = {
settingsStore: PropTypes.instanceOf(Settings).isRequired,
};
static contextType = ThemeContext;
onSortReverseChange = action((event) => {
const { settingsStore } = this.props;
settingsStore.multiGridConfig.config.gridSortReverse =
event.target.checked;
});
render() {
const { settingsStore } = this.props;
return (
<div className="form-group mb-0">
<div className="d-flex flex-fill flex-lg-row flex-column justify-content-between">
<div className="flex-shrink-0 flex-grow-1 flex-basis-auto mx-0 mx-lg-1 mt-1 mt-lg-0">
<GridLabelName settingsStore={settingsStore} />
</div>
<div className="flex-shrink-1 flex-grow-0 form-check form-check-inline flex-basis-auto mt-1 mt-lg-0 ml-0 ml-lg-1 mr-0">
<span className="custom-control custom-switch">
<input
id="configuration-multigrid-sort-reverse"
className="custom-control-input"
type="checkbox"
value=""
checked={
settingsStore.multiGridConfig.config.gridSortReverse ||
false
}
onChange={this.onSortReverseChange}
/>
<label
className="custom-control-label cursor-pointer mr-3"
htmlFor="configuration-multigrid-sort-reverse"
>
Reverse order
</label>
</span>
</div>
</div>
</div>
);
}
}
);
export { MultiGridConfiguration };

View File

@@ -0,0 +1,100 @@
import React from "react";
import { mount } from "enzyme";
import toDiffableHtml from "diffable-html";
import { Settings } from "Stores/Settings";
import { ThemeContext } from "Components/Theme";
import {
ReactSelectColors,
ReactSelectStyles,
} from "Components/Theme/ReactSelect";
import { MultiGridConfiguration } from "./MultiGridConfiguration";
let settingsStore;
beforeEach(() => {
fetch.mockResponse(JSON.stringify([]));
settingsStore = new Settings();
});
afterEach(() => {
jest.restoreAllMocks();
});
const FakeConfiguration = () => {
return mount(
<ThemeContext.Provider
value={{
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light),
}}
>
<MultiGridConfiguration settingsStore={settingsStore} />
</ThemeContext.Provider>
);
};
const ExpandSortLabelSuggestions = async () => {
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.options.label.value;
const tree = FakeConfiguration();
const labelSelect = tree.find("GridLabelName");
await expect(
labelSelect.instance().nameSuggestionsFetch
).resolves.toBeUndefined();
tree
.find("input#react-select-configuration-grid-label-input")
.simulate("change", { target: { value: "a" } });
fetch.resetMocks();
return tree;
};
describe("<MultiGridConfiguration />", () => {
it("matches snapshot with default values", () => {
const tree = FakeConfiguration();
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
it("label select handles fetch errors", async () => {
fetch.mockReject(new Error("Fetch error"));
const tree = await ExpandSortLabelSuggestions();
const options = tree.find("div.react-select__option");
expect(options).toHaveLength(1);
expect(options.text()).toBe("Disable multi-grid");
});
it("label select handles invalid JSON", async () => {
jest.spyOn(console, "error").mockImplementation(() => {});
fetch.mockResponse("invalid JSON");
const tree = await ExpandSortLabelSuggestions();
const options = tree.find("div.react-select__option");
expect(options).toHaveLength(1);
expect(options.text()).toBe("Disable multi-grid");
});
it("clicking on a label option updates settingsStore", async (done) => {
fetch.mockResponse(JSON.stringify(["alertname", "cluster", "fakeLabel"]));
const tree = await ExpandSortLabelSuggestions();
const options = tree.find("div.react-select__option");
options.at(2).simulate("click");
setTimeout(() => {
expect(settingsStore.multiGridConfig.config.gridLabel).toBe("cluster");
done();
}, 200);
});
it("clicking on the 'reverse' checkbox updates settingsStore", (done) => {
settingsStore.gridConfig.config.reverseSort = false;
const tree = FakeConfiguration();
const checkbox = tree.find("#configuration-multigrid-sort-reverse");
expect(settingsStore.gridConfig.config.reverseSort).toBe(false);
checkbox.simulate("change", { target: { checked: true } });
setTimeout(() => {
expect(settingsStore.multiGridConfig.config.gridSortReverse).toBe(true);
done();
}, 200);
});
});

View File

@@ -0,0 +1,72 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<MultiGridConfiguration /> matches snapshot with default values 1`] = `
"
<div class=\\"form-group mb-0\\">
<div class=\\"d-flex flex-fill flex-lg-row flex-column justify-content-between\\">
<div class=\\"flex-shrink-0 flex-grow-1 flex-basis-auto mx-0 mx-lg-1 mt-1 mt-lg-0\\">
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-1ne8613-ValueContainer\\">
<div class=\\"react-select__single-value css-1wh03ml-singleValue\\">
Disable multi-grid
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"react-select__input\\"
style=\\"display: inline-block;\\"
>
<input autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-configuration-grid-label-input\\"
spellcheck=\\"false\\"
tabindex=\\"0\\"
type=\\"text\\"
aria-autocomplete=\\"list\\"
style=\\"box-sizing: content-box; width: 2px; border: 0px; font-size: inherit; opacity: 1; outline: 0; padding: 0px;\\"
value
>
<div style=\\"position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: inherit; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;\\">
</div>
</div>
</div>
</div>
<div class=\\"react-select__indicators css-vcwr3k-IndicatorsContainer\\">
<span class=\\"react-select__indicator-separator css-1okebmr-indicatorSeparator\\">
</span>
<div aria-hidden=\\"true\\"
class=\\"react-select__indicator react-select__dropdown-indicator css-tlfecz-indicatorContainer\\"
>
<svg height=\\"20\\"
width=\\"20\\"
viewbox=\\"0 0 20 20\\"
aria-hidden=\\"true\\"
focusable=\\"false\\"
class=\\"css-6q0nyr-Svg\\"
>
<path d=\\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\\">
</path>
</svg>
</div>
</div>
</div>
</div>
</div>
<div class=\\"flex-shrink-1 flex-grow-0 form-check form-check-inline flex-basis-auto mt-1 mt-lg-0 ml-0 ml-lg-1 mr-0\\">
<span class=\\"custom-control custom-switch\\">
<input id=\\"configuration-multigrid-sort-reverse\\"
class=\\"custom-control-input\\"
type=\\"checkbox\\"
value
>
<label class=\\"custom-control-label cursor-pointer mr-3\\"
for=\\"configuration-multigrid-sort-reverse\\"
>
Reverse order
</label>
</span>
</div>
</div>
</div>
"
`;

View File

@@ -521,6 +521,103 @@ exports[`<Configuration /> matches snapshot 1`] = `
</div>
</div>
</div>
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Multi-grid
</div>
<div>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"
data-icon=\\"chevron-down\\"
class=\\"svg-inline--fa fa-chevron-down fa-w-14 text-muted\\"
role=\\"img\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
viewbox=\\"0 0 448 512\\"
>
<path fill=\\"currentColor\\"
d=\\"M207.029 381.476L12.686 187.132c-9.373-9.373-9.373-24.569 0-33.941l22.667-22.667c9.357-9.357 24.522-9.375 33.901-.04L224 284.505l154.745-154.021c9.379-9.335 24.544-9.317 33.901.04l22.667 22.667c9.373 9.373 9.373 24.569 0 33.941L240.971 381.476c-9.373 9.372-24.569 9.372-33.942 0z\\"
>
</path>
</svg>
</div>
</div>
</div>
<div class=\\"Collapsible__contentOuter collapse show\\"
style=\\"height: auto; transition: none; overflow: visible;\\"
>
<div class=\\"Collapsible__contentInner card-body my-2\\">
<div class=\\"form-group mb-0\\">
<div class=\\"d-flex flex-fill flex-lg-row flex-column justify-content-between\\">
<div class=\\"flex-shrink-0 flex-grow-1 flex-basis-auto mx-0 mx-lg-1 mt-1 mt-lg-0\\">
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-1ne8613-ValueContainer\\">
<div class=\\"react-select__single-value css-1wh03ml-singleValue\\">
Disable multi-grid
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"react-select__input\\"
style=\\"display: inline-block;\\"
>
<input autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-configuration-grid-label-input\\"
spellcheck=\\"false\\"
tabindex=\\"0\\"
type=\\"text\\"
aria-autocomplete=\\"list\\"
style=\\"box-sizing: content-box; width: 2px; border: 0px; font-size: inherit; opacity: 1; outline: 0; padding: 0px;\\"
value
>
<div style=\\"position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: inherit; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;\\">
</div>
</div>
</div>
</div>
<div class=\\"react-select__indicators css-vcwr3k-IndicatorsContainer\\">
<span class=\\"react-select__indicator-separator css-1okebmr-indicatorSeparator\\">
</span>
<div aria-hidden=\\"true\\"
class=\\"react-select__indicator react-select__dropdown-indicator css-tlfecz-indicatorContainer\\"
>
<svg height=\\"20\\"
width=\\"20\\"
viewbox=\\"0 0 20 20\\"
aria-hidden=\\"true\\"
focusable=\\"false\\"
class=\\"css-6q0nyr-Svg\\"
>
<path d=\\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\\">
</path>
</svg>
</div>
</div>
</div>
</div>
</div>
<div class=\\"flex-shrink-1 flex-grow-0 form-check form-check-inline flex-basis-auto mt-1 mt-lg-0 ml-0 ml-lg-1 mr-0\\">
<span class=\\"custom-control custom-switch\\">
<input id=\\"configuration-multigrid-sort-reverse\\"
class=\\"custom-control-input\\"
type=\\"checkbox\\"
value
>
<label class=\\"custom-control-label cursor-pointer mr-3\\"
for=\\"configuration-multigrid-sort-reverse\\"
>
Reverse order
</label>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
"
`;

View File

@@ -11,6 +11,7 @@ import { AlertGroupSortConfiguration } from "./AlertGroupSortConfiguration";
import { AlertGroupCollapseConfiguration } from "./AlertGroupCollapseConfiguration";
import { AlertGroupTitleBarColor } from "./AlertGroupTitleBarColor";
import { ThemeConfiguration } from "./ThemeConfiguration";
import { MultiGridConfiguration } from "./MultiGridConfiguration";
const Configuration = ({ settingsStore, defaultIsOpen }) => (
<form className="px-3 accordion">
@@ -56,6 +57,11 @@ const Configuration = ({ settingsStore, defaultIsOpen }) => (
content={<AlertGroupSortConfiguration settingsStore={settingsStore} />}
extraProps={{ open: defaultIsOpen }}
/>
<Accordion
text="Multi-grid"
content={<MultiGridConfiguration settingsStore={settingsStore} />}
extraProps={{ open: defaultIsOpen }}
/>
</form>
);
Configuration.propTypes = {

View File

@@ -12,6 +12,14 @@ import {
} from "Components/Theme/ReactSelect";
import { Configuration } from ".";
beforeEach(() => {
fetch.mockResponse(JSON.stringify([]));
});
afterEach(() => {
jest.restoreAllMocks();
});
describe("<Configuration />", () => {
it("matches snapshot", () => {
const settingsStore = new Settings();

View File

@@ -21,6 +21,7 @@ beforeEach(() => {
alertStore = new AlertStore([]);
settingsStore = new Settings();
onHide.mockClear();
fetch.mockResponse(JSON.stringify([]));
});
afterEach(() => {

View File

@@ -540,6 +540,103 @@ exports[`<MainModalContent /> matches snapshot 1`] = `
</div>
</div>
</div>
<div class=\\"Collapsible card\\">
<div class=\\"Collapsible__trigger is-open card-header cursor-pointer\\">
<div class=\\"d-flex flex-row justify-content-between\\">
<div>
Multi-grid
</div>
<div>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"
data-icon=\\"chevron-down\\"
class=\\"svg-inline--fa fa-chevron-down fa-w-14 text-muted\\"
role=\\"img\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
viewbox=\\"0 0 448 512\\"
>
<path fill=\\"currentColor\\"
d=\\"M207.029 381.476L12.686 187.132c-9.373-9.373-9.373-24.569 0-33.941l22.667-22.667c9.357-9.357 24.522-9.375 33.901-.04L224 284.505l154.745-154.021c9.379-9.335 24.544-9.317 33.901.04l22.667 22.667c9.373 9.373 9.373 24.569 0 33.941L240.971 381.476c-9.373 9.372-24.569 9.372-33.942 0z\\"
>
</path>
</svg>
</div>
</div>
</div>
<div class=\\"Collapsible__contentOuter collapse show\\"
style=\\"height: auto; transition: none; overflow: visible;\\"
>
<div class=\\"Collapsible__contentInner card-body my-2\\">
<div class=\\"form-group mb-0\\">
<div class=\\"d-flex flex-fill flex-lg-row flex-column justify-content-between\\">
<div class=\\"flex-shrink-0 flex-grow-1 flex-basis-auto mx-0 mx-lg-1 mt-1 mt-lg-0\\">
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-1ne8613-ValueContainer\\">
<div class=\\"react-select__single-value css-1wh03ml-singleValue\\">
Disable multi-grid
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"react-select__input\\"
style=\\"display: inline-block;\\"
>
<input autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-configuration-grid-label-input\\"
spellcheck=\\"false\\"
tabindex=\\"0\\"
type=\\"text\\"
aria-autocomplete=\\"list\\"
style=\\"box-sizing: content-box; width: 2px; border: 0px; font-size: inherit; opacity: 1; outline: 0; padding: 0px;\\"
value
>
<div style=\\"position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: inherit; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;\\">
</div>
</div>
</div>
</div>
<div class=\\"react-select__indicators css-vcwr3k-IndicatorsContainer\\">
<span class=\\"react-select__indicator-separator css-1okebmr-indicatorSeparator\\">
</span>
<div aria-hidden=\\"true\\"
class=\\"react-select__indicator react-select__dropdown-indicator css-tlfecz-indicatorContainer\\"
>
<svg height=\\"20\\"
width=\\"20\\"
viewbox=\\"0 0 20 20\\"
aria-hidden=\\"true\\"
focusable=\\"false\\"
class=\\"css-6q0nyr-Svg\\"
>
<path d=\\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\\">
</path>
</svg>
</div>
</div>
</div>
</div>
</div>
<div class=\\"flex-shrink-1 flex-grow-0 form-check form-check-inline flex-basis-auto mt-1 mt-lg-0 ml-0 ml-lg-1 mr-0\\">
<span class=\\"custom-control custom-switch\\">
<input id=\\"configuration-multigrid-sort-reverse\\"
class=\\"custom-control-input\\"
type=\\"checkbox\\"
value
>
<label class=\\"custom-control-label cursor-pointer mr-3\\"
for=\\"configuration-multigrid-sort-reverse\\"
>
Reverse order
</label>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
<div class=\\"modal-footer\\">

View File

@@ -21,6 +21,12 @@ beforeAll(() => {
beforeEach(() => {
alertStore = new AlertStore([]);
settingsStore = new Settings();
fetch.mockResponse(JSON.stringify([]));
});
afterEach(() => {
jest.restoreAllMocks();
});
const MountedMainModal = () => {