fix(ui): rewrite AlertManagerInput with hooks

This commit is contained in:
Łukasz Mierzwa
2020-05-10 15:44:01 +01:00
committed by Łukasz Mierzwa
parent 60bdf96be1
commit 65e7f7e33d
13 changed files with 145 additions and 158 deletions

View File

@@ -4,12 +4,12 @@ exports[`<AlertGroupCollapseConfiguration /> matches snapshot with default value
"
<div class=\\"form-group mb-0\\">
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-yk16xz-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-g1d714-ValueContainer\\">
<div class=\\"react-select__single-value css-1uccc91-singleValue\\">
<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\\">
Collapse on mobile
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"css-ps6ina-Input\\">
<div class=\\"react-select__input\\"
style=\\"display: inline-block;\\"
>
@@ -29,7 +29,7 @@ exports[`<AlertGroupCollapseConfiguration /> matches snapshot with default value
</div>
</div>
</div>
<div class=\\"react-select__indicators css-1hb7zxy-IndicatorsContainer\\">
<div class=\\"react-select__indicators css-vcwr3k-IndicatorsContainer\\">
<span class=\\"react-select__indicator-separator css-1okebmr-indicatorSeparator\\">
</span>
<div aria-hidden=\\"true\\"

View File

@@ -6,12 +6,12 @@ exports[`<AlertGroupSortConfiguration /> matches snapshot with default values 1`
<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\\">
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-yk16xz-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-g1d714-ValueContainer\\">
<div class=\\"react-select__single-value css-1uccc91-singleValue\\">
<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\\">
Use defaults from karma config file
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"css-ps6ina-Input\\">
<div class=\\"react-select__input\\"
style=\\"display: inline-block;\\"
>
@@ -31,7 +31,7 @@ exports[`<AlertGroupSortConfiguration /> matches snapshot with default values 1`
</div>
</div>
</div>
<div class=\\"react-select__indicators css-1hb7zxy-IndicatorsContainer\\">
<div class=\\"react-select__indicators css-vcwr3k-IndicatorsContainer\\">
<span class=\\"react-select__indicator-separator css-1okebmr-indicatorSeparator\\">
</span>
<div aria-hidden=\\"true\\"

View File

@@ -6,12 +6,12 @@ exports[`<MultiGridConfiguration /> matches snapshot with default values 1`] = `
<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-yk16xz-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-g1d714-ValueContainer\\">
<div class=\\"react-select__single-value css-1uccc91-singleValue\\">
<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=\\"css-ps6ina-Input\\">
<div class=\\"react-select__input\\"
style=\\"display: inline-block;\\"
>
@@ -31,7 +31,7 @@ exports[`<MultiGridConfiguration /> matches snapshot with default values 1`] = `
</div>
</div>
</div>
<div class=\\"react-select__indicators css-1hb7zxy-IndicatorsContainer\\">
<div class=\\"react-select__indicators css-vcwr3k-IndicatorsContainer\\">
<span class=\\"react-select__indicator-separator css-1okebmr-indicatorSeparator\\">
</span>
<div aria-hidden=\\"true\\"

View File

@@ -4,12 +4,12 @@ exports[`<ThemeConfiguration /> matches snapshot with default values 1`] = `
"
<div class=\\"form-group mb-2\\">
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-yk16xz-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-g1d714-ValueContainer\\">
<div class=\\"react-select__single-value css-1uccc91-singleValue\\">
<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\\">
Automatic theme, follow browser preference
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"css-ps6ina-Input\\">
<div class=\\"react-select__input\\"
style=\\"display: inline-block;\\"
>
@@ -29,7 +29,7 @@ exports[`<ThemeConfiguration /> matches snapshot with default values 1`] = `
</div>
</div>
</div>
<div class=\\"react-select__indicators css-1hb7zxy-IndicatorsContainer\\">
<div class=\\"react-select__indicators css-vcwr3k-IndicatorsContainer\\">
<span class=\\"react-select__indicator-separator css-1okebmr-indicatorSeparator\\">
</span>
<div aria-hidden=\\"true\\"

View File

@@ -1,8 +1,8 @@
import React from "react";
import React, { useEffect } from "react";
import PropTypes from "prop-types";
import { action } from "mobx";
import { observer } from "mobx-react";
import { autorun } from "mobx";
import { useObserver } from "mobx-react";
import Select from "react-select";
@@ -12,86 +12,71 @@ import {
AlertmanagerClustersToOption,
} from "Stores/SilenceFormStore";
import { ThemeContext } from "Components/Theme";
import { MultiSelect } from "Components/MultiSelect";
import { ValidationError } from "Components/MultiSelect/ValidationError";
const AlertManagerInput = observer(
class AlertManagerInput extends MultiSelect {
static propTypes = {
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
};
static contextType = ThemeContext;
const AlertManagerInput = ({ alertStore, silenceFormStore }) => {
useEffect(() => {
if (silenceFormStore.data.alertmanagers.length === 0) {
silenceFormStore.data.setAlertmanagers(
AlertmanagerClustersToOption(alertStore.data.clustersWithoutReadOnly)
);
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
constructor(props) {
super(props);
const { alertStore, silenceFormStore } = props;
if (silenceFormStore.data.alertmanagers.length === 0) {
silenceFormStore.data.alertmanagers = AlertmanagerClustersToOption(
useEffect(
// https://mobx-react.netlify.app/recipes-effects
() =>
autorun(() => {
// get the list of last known alertmanagers
const currentAlertmanagers = AlertmanagerClustersToOption(
alertStore.data.clustersWithoutReadOnly
);
}
}
onChange = action((newValue, actionMeta) => {
const { silenceFormStore } = this.props;
silenceFormStore.data.alertmanagers = newValue || [];
});
componentDidUpdate() {
const { alertStore, silenceFormStore } = this.props;
// get the list of last known alertmanagers
const currentAlertmanagers = AlertmanagerClustersToOption(
alertStore.data.clustersWithoutReadOnly
);
// now iterate what's set as silence form values and reset it if any
// mismatch is detected
for (const silenceAM of silenceFormStore.data.alertmanagers) {
if (
!currentAlertmanagers.map((am) => am.label).includes(silenceAM.label)
) {
silenceFormStore.data.alertmanagers = currentAlertmanagers;
}
}
}
render() {
const { alertStore, silenceFormStore } = this.props;
const extraProps = {};
if (silenceFormStore.data.silenceID !== null) {
extraProps.isDisabled = true;
}
return (
<Select
styles={this.context.reactSelectStyles}
classNamePrefix="react-select"
instanceId="silence-input-alertmanagers"
defaultValue={silenceFormStore.data.alertmanagers}
options={AlertmanagerClustersToOption(
alertStore.data.clustersWithoutReadOnly
)}
getOptionValue={JSON.stringify}
placeholder={
silenceFormStore.data.wasValidated ? (
<ValidationError />
) : (
"Alertmanager"
)
// now iterate what's set as silence form values and reset it if any
// mismatch is detected
for (const silenceAM of silenceFormStore.data.alertmanagers) {
if (
!currentAlertmanagers
.map((am) => am.label)
.includes(silenceAM.label)
) {
silenceFormStore.data.alertmanagers = currentAlertmanagers;
}
isMulti
onChange={this.onChange}
{...extraProps}
/>
);
}
}
);
}
}),
[] // eslint-disable-line react-hooks/exhaustive-deps
);
const context = React.useContext(ThemeContext);
return useObserver(() => (
<Select
styles={context.reactSelectStyles}
classNamePrefix="react-select"
instanceId="silence-input-alertmanagers"
value={silenceFormStore.data.alertmanagers}
options={AlertmanagerClustersToOption(
alertStore.data.clustersWithoutReadOnly
)}
getOptionValue={JSON.stringify}
placeholder={
silenceFormStore.data.wasValidated ? (
<ValidationError />
) : (
"Alertmanager"
)
}
isMulti
onChange={(newValue) => {
silenceFormStore.data.setAlertmanagers(newValue || []);
}}
isDisabled={silenceFormStore.data.silenceID !== null}
/>
));
};
AlertManagerInput.propTypes = {
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
};
export { AlertManagerInput };

View File

@@ -4,19 +4,17 @@ import { mount } from "enzyme";
import toDiffableHtml from "diffable-html";
import { MockThemeContext } from "__mocks__/Theme";
import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { ThemeContext } from "Components/Theme";
import {
ReactSelectColors,
ReactSelectStyles,
} from "Components/Theme/ReactSelect";
import { AlertManagerInput } from ".";
let alertStore;
let silenceFormStore;
beforeEach(() => {
jest.spyOn(React, "useContext").mockImplementation(() => MockThemeContext);
alertStore = new AlertStore([]);
alertStore.data.upstreams.clusters = {
ha: ["am1", "am2"],
@@ -65,16 +63,10 @@ beforeEach(() => {
const MountedAlertManagerInput = () => {
return mount(
<ThemeContext.Provider
value={{
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light),
}}
>
<AlertManagerInput
alertStore={alertStore}
silenceFormStore={silenceFormStore}
/>
</ThemeContext.Provider>
<AlertManagerInput
alertStore={alertStore}
silenceFormStore={silenceFormStore}
/>
);
};
@@ -162,12 +154,10 @@ describe("<AlertManagerInput />", () => {
});
it("silenceFormStore.data.alertmanagers gets updated from alertStore.data.upstreams.instances on mismatch", () => {
const tree = MountedAlertManagerInput();
MountedAlertManagerInput();
alertStore.data.upstreams.clusters = {
amNew: ["amNew"],
};
// force update since this is where the mismatch check lives
tree.instance().componentDidUpdate();
expect(silenceFormStore.data.alertmanagers).toContainEqual({
label: "amNew",
value: ["amNew"],

View File

@@ -2,6 +2,7 @@ import React from "react";
import { mount } from "enzyme";
import { MockThemeContext } from "__mocks__/Theme";
import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import {
@@ -9,41 +10,46 @@ import {
SilenceFormStage,
NewEmptyMatcher,
} from "Stores/SilenceFormStore";
import { ThemeContext } from "Components/Theme";
import {
ReactSelectColors,
ReactSelectStyles,
} from "Components/Theme/ReactSelect";
import { SilenceForm } from "./SilenceForm";
let alertStore;
let settingsStore;
let silenceFormStore;
beforeAll(() => {
fetch.mockResponse(JSON.stringify([]));
});
beforeEach(() => {
jest.spyOn(React, "useContext").mockImplementation(() => MockThemeContext);
alertStore = new AlertStore([]);
settingsStore = new Settings();
silenceFormStore = new SilenceFormStore();
alertStore.data.upstreams.clusters = {
am1: ["am1"],
};
alertStore.data.upstreams.instances = [
{
name: "am1",
uri: "http://am1.example.com",
publicURI: "http://am1.example.com",
readonly: false,
headers: {},
corsCredentials: "include",
error: "",
version: "0.17.0",
cluster: "am1",
clusterMembers: ["am1"],
},
];
});
const MountedSilenceForm = () => {
return mount(
<ThemeContext.Provider
value={{
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light),
}}
>
<SilenceForm
alertStore={alertStore}
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
previewOpen={false}
/>
</ThemeContext.Provider>
<SilenceForm
alertStore={alertStore}
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
previewOpen={false}
/>
);
};
@@ -176,7 +182,7 @@ describe("<SilenceForm />", () => {
matcher.name = "job";
matcher.values = [{ label: "node_exporter", value: "node_exporter" }];
silenceFormStore.data.matchers = [matcher];
silenceFormStore.data.alertmanagers = [{ label: "am1", value: ["am1"] }];
silenceFormStore.data.setAlertmanagers([{ label: "am1", value: ["am1"] }]);
silenceFormStore.data.author = "me@example.com";
silenceFormStore.data.comment = "fake silence";
const tree = MountedSilenceForm();

View File

@@ -3,12 +3,12 @@
exports[`<LabelNameInput /> matches snapshot 1`] = `
"
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-yk16xz-control\\">
<div class=\\"react-select__value-container react-select__value-container--has-value css-g1d714-ValueContainer\\">
<div class=\\"react-select__single-value css-1uccc91-singleValue\\">
<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\\">
cluster
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"css-ps6ina-Input\\">
<div class=\\"react-select__input\\"
style=\\"display: inline-block;\\"
>
@@ -28,7 +28,7 @@ exports[`<LabelNameInput /> matches snapshot 1`] = `
</div>
</div>
</div>
<div class=\\"react-select__indicators css-1hb7zxy-IndicatorsContainer\\">
<div class=\\"react-select__indicators css-vcwr3k-IndicatorsContainer\\">
<span class=\\"react-select__indicator-separator css-1okebmr-indicatorSeparator\\">
</span>
<div aria-hidden=\\"true\\"

View File

@@ -3,14 +3,14 @@
exports[`<LabelValueInput /> fetches suggestions on mount 1`] = `
"
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-yk16xz-control\\">
<div class=\\"react-select__value-container react-select__value-container--is-multi css-g1d714-ValueContainer\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--is-multi css-1pfcrl6-ValueContainer\\">
<div>
<div class=\\"react-select__placeholder css-1wa3eu0-placeholder\\">
Label value
</div>
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"css-ps6ina-Input\\">
<div class=\\"react-select__input\\"
style=\\"display: inline-block;\\"
>
@@ -30,7 +30,7 @@ exports[`<LabelValueInput /> fetches suggestions on mount 1`] = `
</div>
</div>
</div>
<div class=\\"react-select__indicators css-1hb7zxy-IndicatorsContainer\\">
<div class=\\"react-select__indicators css-vcwr3k-IndicatorsContainer\\">
<span class=\\"react-select__indicator-separator css-1okebmr-indicatorSeparator\\">
</span>
<div aria-hidden=\\"true\\"
@@ -56,14 +56,14 @@ exports[`<LabelValueInput /> fetches suggestions on mount 1`] = `
exports[`<LabelValueInput /> matches snapshot 1`] = `
"
<div class=\\" css-2b097c-container\\">
<div class=\\"react-select__control css-yk16xz-control\\">
<div class=\\"react-select__value-container react-select__value-container--is-multi css-g1d714-ValueContainer\\">
<div class=\\"react-select__control css-r5n82u-control\\">
<div class=\\"react-select__value-container react-select__value-container--is-multi css-1pfcrl6-ValueContainer\\">
<div>
<div class=\\"react-select__placeholder css-1wa3eu0-placeholder\\">
Label value
</div>
</div>
<div class=\\"css-b8ldur-Input\\">
<div class=\\"css-ps6ina-Input\\">
<div class=\\"react-select__input\\"
style=\\"display: inline-block;\\"
>
@@ -83,7 +83,7 @@ exports[`<LabelValueInput /> matches snapshot 1`] = `
</div>
</div>
</div>
<div class=\\"react-select__indicators css-1hb7zxy-IndicatorsContainer\\">
<div class=\\"react-select__indicators css-vcwr3k-IndicatorsContainer\\">
<span class=\\"react-select__indicator-separator css-1okebmr-indicatorSeparator\\">
</span>
<div aria-hidden=\\"true\\"

View File

@@ -43,10 +43,7 @@ const useFetchDelete = (uri, options, deps = []) => {
return () => {
isCancelled = true;
};
// eslint doesn't like ...deps
// eslint-disable-next-line
}, [uri, options, ...deps]);
}, [uri, options, ...deps]); // eslint-disable-line react-hooks/exhaustive-deps
return { response, error, isDeleting };
};

View File

@@ -65,10 +65,7 @@ const useFetchGet = (uri, { autorun = true, deps = [] } = {}) => {
return () => {
isCanceled.current = true;
};
// eslint doesn't like ...deps
// eslint-disable-next-line
}, [uri, get, autorun, ...deps]);
}, [uri, get, autorun, ...deps]); // eslint-disable-line react-hooks/exhaustive-deps
return { response, error, isLoading, isRetrying, retryCount, get };
};

View File

@@ -213,6 +213,10 @@ class SilenceFormStore {
this.silenceID = null;
},
setAlertmanagers(val) {
this.alertmanagers = val;
},
setStageSubmit() {
this.currentStage = SilenceFormStage.Submit;
},
@@ -313,6 +317,7 @@ class SilenceFormStore {
resetStartEnd: action.bound,
resetProgress: action.bound,
resetSilenceID: action.bound,
setAlertmanagers: action.bound,
setStageSubmit: action.bound,
addEmptyMatcher: action.bound,
deleteMatcher: action.bound,

View File

@@ -1,8 +1,15 @@
import {
ReactSelectStyles,
ReactSelectColors,
} from "Components/Theme/ReactSelect";
const MockThemeContext = {
animations: {
in: undefined,
duration: 500,
},
isDark: false,
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light),
};
export { MockThemeContext };