Merge pull request #447 from prymitive/grid-sort

feat(ui): allow sorting alert grid
This commit is contained in:
Łukasz Mierzwa
2019-02-15 18:09:21 +00:00
committed by GitHub
14 changed files with 680 additions and 15 deletions

View File

@@ -4,6 +4,8 @@ import PropTypes from "prop-types";
import { observable, action } from "mobx";
import { observer } from "mobx-react";
import moment from "moment";
import MasonryInfiniteScroller from "react-masonry-infinite";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -67,6 +69,42 @@ const AlertGrid = observer(
);
});
compare = (a, b) => {
const { settingsStore } = this.props;
// don't sort if sorting is disabled
if (
settingsStore.gridConfig.config.sortOrder ===
settingsStore.gridConfig.options.disabled.value
)
return 0;
const getLabelValue = g => {
// if timestamp sort is enabled use latest alert for sorting
if (
settingsStore.gridConfig.config.sortOrder ===
settingsStore.gridConfig.options.startsAt.value
) {
return moment.max(g.alerts.map(a => moment(a.startsAt)));
}
const label = settingsStore.gridConfig.config.sortLabel;
return g.labels[label] || g.alerts[0].labels[label] || "";
};
const av = getLabelValue(a);
const bv = getLabelValue(b);
const val = settingsStore.gridConfig.config.reverseSort ? -1 : 1;
if (av > bv) {
return val;
} else if (av < bv) {
return val * -1;
} else {
return 0;
}
};
componentDidUpdate() {
// whenever grid component re-renders we need to ensure that grid elements
// are packed correctly
@@ -94,13 +132,13 @@ const AlertGrid = observer(
</div>
}
>
{Object.keys(alertStore.data.groups)
.sort()
{Object.values(alertStore.data.groups)
.sort(this.compare)
.slice(0, this.groupsToRender.value)
.map(id => (
.map(group => (
<AlertGroup
key={id}
group={alertStore.data.groups[id]}
key={group.id}
group={group}
showAlertmanagers={
Object.keys(alertStore.data.upstreams.clusters).length > 1
}

View File

@@ -46,12 +46,12 @@ const MockGroup = (groupName, alertCount) => {
return group;
};
const MockGroupList = count => {
const MockGroupList = (count, alertPerGroup) => {
let groups = {};
for (let i = 1; i <= count; i++) {
let id = `id${i}`;
let hash = `hash${i}`;
let group = MockGroup(`group${i}`, count);
let group = MockGroup(`group${i}`, alertPerGroup);
group.id = id;
group.hash = hash;
groups[id] = group;
@@ -66,14 +66,14 @@ const MockGroupList = count => {
describe("<AlertGrid />", () => {
it("renders only first 50 alert groups", () => {
MockGroupList(60);
MockGroupList(60, 5);
const tree = ShallowAlertGrid();
const alertGroups = tree.find("AlertGroup");
expect(alertGroups).toHaveLength(50);
});
it("appends 30 groups after loadMore() call", () => {
MockGroupList(100);
MockGroupList(100, 5);
const tree = ShallowAlertGrid();
// call it directly, it should happen on scroll to the bottom of the page
tree.instance().loadMore();
@@ -107,6 +107,127 @@ describe("<AlertGrid />", () => {
const tree = ShallowAlertGrid();
const instance = tree.instance();
instance.storeMasonryRef("foo");
expect(instance.masonryComponentReference.ref).toBe("foo");
expect(instance.masonryComponentReference.ref).toEqual("foo");
});
it("doesn't sort groups when sorting is set to 'disabled'", () => {
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.options.disabled.value;
settingsStore.gridConfig.config.reverseSort = false;
MockGroupList(3, 1);
const tree = ShallowAlertGrid();
const alertGroups = tree.find("AlertGroup");
expect(alertGroups.map(g => g.props().group.id)).toEqual([
"id1",
"id2",
"id3"
]);
});
it("doesn't sort groups when sorting is set to 'disabled' and 'reverse' is on", () => {
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.options.disabled.value;
settingsStore.gridConfig.config.reverseSort = true;
MockGroupList(3, 1);
const tree = ShallowAlertGrid();
const alertGroups = tree.find("AlertGroup");
expect(alertGroups.map(g => g.props().group.id)).toEqual([
"id1",
"id2",
"id3"
]);
});
it("groups are sorted by timestamp when sorting is set to 'startsAt'", () => {
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.options.startsAt.value;
settingsStore.gridConfig.config.reverseSort = false;
MockGroupList(3, 1);
alertStore.data.groups.id1.alerts[0].startsAt = "2001-01-01T00:00:00Z";
alertStore.data.groups.id2.alerts[0].startsAt = "2002-01-01T00:00:00Z";
alertStore.data.groups.id3.alerts[0].startsAt = "2000-01-01T00:00:00Z";
const tree = ShallowAlertGrid();
const alertGroups = tree.find("AlertGroup");
expect(alertGroups.map(g => g.props().group.id)).toEqual([
"id3",
"id1",
"id2"
]);
});
it("groups are sorted by reversed timestamp when sorting is set to 'startsAt' and 'reverse' is on", () => {
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.options.startsAt.value;
settingsStore.gridConfig.config.reverseSort = true;
MockGroupList(3, 1);
alertStore.data.groups.id1.alerts[0].startsAt = "2001-01-01T00:00:00Z";
alertStore.data.groups.id2.alerts[0].startsAt = "2002-01-01T00:00:00Z";
alertStore.data.groups.id3.alerts[0].startsAt = "2000-01-01T00:00:00Z";
const tree = ShallowAlertGrid();
const alertGroups = tree.find("AlertGroup");
expect(alertGroups.map(g => g.props().group.id)).toEqual([
"id2",
"id1",
"id3"
]);
});
it("groups are sorted by label when sorting is set to 'label'", () => {
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.options.label.value;
settingsStore.gridConfig.config.sortLabel = "instance";
settingsStore.gridConfig.config.reverseSort = false;
MockGroupList(3, 1);
alertStore.data.groups.id1.alerts[0].labels.instance = "abc1";
alertStore.data.groups.id2.alerts[0].labels.instance = "abc3";
alertStore.data.groups.id3.alerts[0].labels.instance = "abc2";
const tree = ShallowAlertGrid();
const alertGroups = tree.find("AlertGroup");
expect(alertGroups.map(g => g.props().group.id)).toEqual([
"id1",
"id3",
"id2"
]);
});
it("groups are sorted by reverse label when sorting is set to 'label' and 'reverse' is on", () => {
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.options.label.value;
settingsStore.gridConfig.config.sortLabel = "instance";
settingsStore.gridConfig.config.reverseSort = true;
MockGroupList(3, 1);
alertStore.data.groups.id1.alerts[0].labels.instance = "abc1";
alertStore.data.groups.id2.alerts[0].labels.instance = "abc3";
alertStore.data.groups.id3.alerts[0].labels.instance = "abc2";
const tree = ShallowAlertGrid();
const alertGroups = tree.find("AlertGroup");
expect(alertGroups.map(g => g.props().group.id)).toEqual([
"id2",
"id3",
"id1"
]);
});
it("sorting is no-op when when sorting is set to 'label' and alerts lack that label", () => {
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.options.label.value;
settingsStore.gridConfig.config.sortLabel = "foo";
settingsStore.gridConfig.config.reverseSort = false;
MockGroupList(3, 1);
const tree = ShallowAlertGrid();
const alertGroups = tree.find("AlertGroup");
expect(alertGroups.map(g => g.props().group.id)).toEqual([
"id1",
"id2",
"id3"
]);
});
});

View File

@@ -16,7 +16,7 @@ const FakeConfiguration = () => {
return mount(<AlertGroupConfiguration settingsStore={settingsStore} />);
};
describe("<AlertGroupConfiguration /> className", () => {
describe("<AlertGroupConfiguration />", () => {
it("matches snapshot with default values", () => {
const tree = FakeConfiguration();
expect(toDiffableHtml(tree.html())).toMatchSnapshot();

View File

@@ -0,0 +1,109 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { action } from "mobx";
import { observer } from "mobx-react";
import ReactSelect from "react-select";
import { Settings } from "Stores/Settings";
import { ReactSelectStyles } from "Components/MultiSelect";
import { SortLabelName } from "./SortLabelName";
const AlertGroupSortConfiguration = observer(
class AlertGroupSortConfiguration extends Component {
static propTypes = {
settingsStore: PropTypes.instanceOf(Settings).isRequired
};
constructor(props) {
super(props);
this.validateConfig();
}
onSortOrderChange = action((newValue, actionMeta) => {
const { settingsStore } = this.props;
settingsStore.gridConfig.config.sortOrder = newValue.value;
});
onSortReverseChange = action(event => {
const { settingsStore } = this.props;
settingsStore.gridConfig.config.reverseSort = event.target.checked;
});
valueToOption = val => {
const { settingsStore } = this.props;
return { label: settingsStore.gridConfig.options[val].label, value: val };
};
validateConfig = action(() => {
const { settingsStore } = this.props;
if (
!Object.values(settingsStore.gridConfig.options)
.map(o => o.value)
.includes(settingsStore.gridConfig.config.sortOrder)
) {
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.defaults.sortOrder;
}
});
render() {
const { settingsStore } = this.props;
return (
<div className="form-group">
<div className="text-center">
<label className="mb-4">Grid sort order</label>
</div>
<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">
<ReactSelect
styles={ReactSelectStyles}
classNamePrefix="react-select"
instanceId="configuration-sort-order"
defaultValue={this.valueToOption(
settingsStore.gridConfig.config.sortOrder
)}
options={Object.values(settingsStore.gridConfig.options)}
onChange={this.onSortOrderChange}
hideSelectedOptions
/>
</div>
{settingsStore.gridConfig.config.sortOrder ===
settingsStore.gridConfig.options.label.value ? (
<div className="flex-shrink-0 flex-grow-1 flex-basis-auto mx-0 mx-lg-1 mt-1 mt-lg-0">
<SortLabelName settingsStore={settingsStore} />
</div>
) : null}
<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-sort-reverse"
className="custom-control-input"
type="checkbox"
value=""
checked={settingsStore.gridConfig.config.reverseSort}
onChange={this.onSortReverseChange}
/>
<label
className="custom-control-label cursor-pointer mr-3"
htmlFor="configuration-sort-reverse"
>
Reverse
</label>
</span>
</div>
</div>
</div>
);
}
}
);
export { AlertGroupSortConfiguration };

View File

@@ -0,0 +1,141 @@
import React from "react";
import { mount } from "enzyme";
import toDiffableHtml from "diffable-html";
import { Settings } from "Stores/Settings";
import { AlertGroupSortConfiguration } from "./AlertGroupSortConfiguration";
let settingsStore;
beforeEach(() => {
fetch.mockResponse(JSON.stringify([]));
settingsStore = new Settings();
});
afterEach(() => {
jest.restoreAllMocks();
});
const FakeConfiguration = () => {
return mount(<AlertGroupSortConfiguration settingsStore={settingsStore} />);
};
const ExpandSortLabelSuggestions = async () => {
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.options.label.value;
const tree = FakeConfiguration();
const labelSelect = tree.find("SortLabelName");
await expect(
labelSelect.instance().nameSuggestionsFetch
).resolves.toBeUndefined();
tree
.find("input#react-select-configuration-sort-label-input")
.simulate("change", { target: { value: "a" } });
fetch.resetMocks();
return tree;
};
describe("<AlertGroupSortConfiguration />", () => {
it("matches snapshot with default values", () => {
const tree = FakeConfiguration();
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
it("invalid sortOrder value is reset on mount", done => {
settingsStore.gridConfig.config.sortOrder = "badValue";
FakeConfiguration();
setTimeout(() => {
expect(settingsStore.gridConfig.config.sortOrder).toBe(
settingsStore.gridConfig.defaults.sortOrder
);
done();
}, 200);
});
it("changing sort order value update settingsStore", async done => {
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.options.label.value;
expect(settingsStore.gridConfig.config.sortOrder).toBe(
settingsStore.gridConfig.options.label.value
);
const tree = FakeConfiguration();
tree.instance().onSortOrderChange({
label: settingsStore.gridConfig.options.startsAt.label,
value: settingsStore.gridConfig.options.startsAt.value
});
setTimeout(() => {
expect(settingsStore.gridConfig.config.sortOrder).toBe(
settingsStore.gridConfig.options.startsAt.value
);
done();
}, 200);
});
it("label select is not rendered when sort order is != 'label'", () => {
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.options.disabled.value;
const tree = FakeConfiguration();
const labelSelect = tree.find("SortLabelName");
expect(labelSelect).toHaveLength(0);
});
it("label select is rendered when sort order is == 'label'", () => {
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.options.label.value;
const tree = FakeConfiguration();
const labelSelect = tree.find("SortLabelName");
expect(labelSelect).toHaveLength(1);
});
it("label select renders suggestions on click", async () => {
fetch.mockResponse(JSON.stringify(["alertname", "cluster", "fakeLabel"]));
const tree = await ExpandSortLabelSuggestions();
const options = tree.find(".react-select__option");
expect(options).toHaveLength(3);
expect(options.at(0).text()).toBe("alertname");
expect(options.at(1).text()).toBe("cluster");
expect(options.at(2).text()).toBe("fakeLabel");
});
it("label select handles fetch errors", async () => {
fetch.mockReject("error");
const tree = await ExpandSortLabelSuggestions();
const options = tree.find(".react-select__option");
expect(options).toHaveLength(0);
});
it("label select handles invalid JSON", async () => {
jest.spyOn(console, "error").mockImplementation(() => {});
fetch.mockResponse("invalid JSON");
const tree = await ExpandSortLabelSuggestions();
const options = tree.find(".react-select__option");
expect(options).toHaveLength(0);
});
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(".react-select__option");
options.at(1).simulate("click");
setTimeout(() => {
expect(settingsStore.gridConfig.config.sortLabel).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-sort-reverse");
expect(settingsStore.gridConfig.config.reverseSort).toBe(false);
checkbox.simulate("change", { target: { checked: true } });
setTimeout(() => {
expect(settingsStore.gridConfig.config.reverseSort).toBe(true);
done();
}, 200);
});
});

View File

@@ -16,7 +16,7 @@ const FakeConfiguration = () => {
return mount(<FetchConfiguration settingsStore={settingsStore} />);
};
describe("<FetchConfiguration /> className", () => {
describe("<FetchConfiguration />", () => {
it("matches snapshot with default values", () => {
const tree = FakeConfiguration();
expect(toDiffableHtml(tree.html())).toMatchSnapshot();

View File

@@ -0,0 +1,76 @@
import React from "react";
import PropTypes from "prop-types";
import { action, observable } from "mobx";
import { observer } from "mobx-react";
import CreatableSelect from "react-select/lib/Creatable";
import { FormatBackendURI } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { ReactSelectStyles } from "Components/MultiSelect";
const valueToOption = v => ({ label: v, value: v });
const SortLabelName = observer(
class SortLabelName extends CreatableSelect {
static propTypes = {
settingsStore: PropTypes.instanceOf(Settings).isRequired
};
suggestions = observable({
names: []
});
populateNameSuggestions = action(() => {
this.nameSuggestionsFetch = fetch(FormatBackendURI(`labelNames.json`), {
credentials: "include"
})
.then(
result => result.json(),
err => {
return [];
}
)
.then(result => {
this.suggestions.names = result.map(value => ({
label: value,
value: value
}));
})
.catch(err => {
console.error(err.message);
this.suggestions.names = [];
});
});
onChange = action((newValue, actionMeta) => {
const { settingsStore } = this.props;
settingsStore.gridConfig.config.sortLabel = newValue.value;
});
componentDidMount() {
this.populateNameSuggestions();
}
render() {
const { settingsStore } = this.props;
return (
<CreatableSelect
styles={ReactSelectStyles}
classNamePrefix="react-select"
instanceId="configuration-sort-label"
defaultValue={valueToOption(
settingsStore.gridConfig.config.sortLabel
)}
options={this.suggestions.names}
onChange={this.onChange}
/>
);
}
}
);
export { SortLabelName };

View File

@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<AlertGroupConfiguration /> className matches snapshot with default values 1`] = `
exports[`<AlertGroupConfiguration /> matches snapshot with default values 1`] = `
"
<div class=\\"form-group text-center\\">
<label class=\\"mb-4\\">

View File

@@ -0,0 +1,78 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<AlertGroupSortConfiguration /> matches snapshot with default values 1`] = `
"
<div class=\\"form-group\\">
<div class=\\"text-center\\">
<label class=\\"mb-4\\">
Grid sort order
</label>
</div>
<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-10nd86i\\">
<div class=\\"css-7jxtyj react-select__control\\">
<div class=\\"css-pb81dw react-select__value-container react-select__value-container--has-value\\">
<div class=\\"css-xp4uvy react-select__single-value\\">
Sort by alert timestamp
</div>
<div class=\\"css-1g6gooi\\">
<div class=\\"react-select__input\\"
style=\\"display: inline-block;\\"
>
<input autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-configuration-sort-order-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=\\"css-mik995 react-select__indicators\\">
<span class=\\"css-d8oujb react-select__indicator-separator\\">
</span>
<div aria-hidden=\\"true\\"
class=\\"css-1ep9fjw react-select__indicator react-select__dropdown-indicator\\"
>
<svg height=\\"20\\"
width=\\"20\\"
viewbox=\\"0 0 20 20\\"
aria-hidden=\\"true\\"
focusable=\\"false\\"
class=\\"css-19bqh2r\\"
>
<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-sort-reverse\\"
class=\\"custom-control-input\\"
type=\\"checkbox\\"
value
checked
>
<label class=\\"custom-control-label cursor-pointer mr-3\\"
for=\\"configuration-sort-reverse\\"
>
Reverse
</label>
</span>
</div>
</div>
</div>
"
`;

View File

@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<FetchConfiguration /> className matches snapshot with default values 1`] = `
exports[`<FetchConfiguration /> matches snapshot with default values 1`] = `
"
<div class=\\"form-group text-center\\">
<label class=\\"mb-4\\">

View File

@@ -4,12 +4,15 @@ import PropTypes from "prop-types";
import { Settings } from "Stores/Settings";
import { FetchConfiguration } from "./FetchConfiguration";
import { AlertGroupConfiguration } from "./AlertGroupConfiguration";
import { AlertGroupSortConfiguration } from "./AlertGroupSortConfiguration";
const Configuration = ({ settingsStore }) => (
<form className="px-3">
<FetchConfiguration settingsStore={settingsStore} />
<div className="mt-5" />
<AlertGroupConfiguration settingsStore={settingsStore} />
<div className="mt-5" />
<AlertGroupSortConfiguration settingsStore={settingsStore} />
</form>
);
Configuration.propTypes = {

View File

@@ -10,7 +10,7 @@ describe("<Configuration />", () => {
const settingsStore = new Settings();
const tree = shallow(<Configuration settingsStore={settingsStore} />);
expect(tree.text()).toBe(
"<FetchConfiguration /><AlertGroupConfiguration />"
"<FetchConfiguration /><AlertGroupConfiguration /><AlertGroupSortConfiguration />"
);
});
});

View File

@@ -110,6 +110,80 @@ exports[`<MainModalContent /> matches snapshot 1`] = `
</span>
</div>
</div>
<div class=\\"mt-5\\">
</div>
<div class=\\"form-group\\">
<div class=\\"text-center\\">
<label class=\\"mb-4\\">
Grid sort order
</label>
</div>
<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-10nd86i\\">
<div class=\\"css-7jxtyj react-select__control\\">
<div class=\\"css-pb81dw react-select__value-container react-select__value-container--has-value\\">
<div class=\\"css-xp4uvy react-select__single-value\\">
Sort by alert timestamp
</div>
<div class=\\"css-1g6gooi\\">
<div class=\\"react-select__input\\"
style=\\"display: inline-block;\\"
>
<input autocapitalize=\\"none\\"
autocomplete=\\"off\\"
autocorrect=\\"off\\"
id=\\"react-select-configuration-sort-order-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=\\"css-mik995 react-select__indicators\\">
<span class=\\"css-d8oujb react-select__indicator-separator\\">
</span>
<div aria-hidden=\\"true\\"
class=\\"css-1ep9fjw react-select__indicator react-select__dropdown-indicator\\"
>
<svg height=\\"20\\"
width=\\"20\\"
viewbox=\\"0 0 20 20\\"
aria-hidden=\\"true\\"
focusable=\\"false\\"
class=\\"css-19bqh2r\\"
>
<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-sort-reverse\\"
class=\\"custom-control-input\\"
type=\\"checkbox\\"
value
checked
>
<label class=\\"custom-control-label cursor-pointer mr-3\\"
for=\\"configuration-sort-reverse\\"
>
Reverse
</label>
</span>
</div>
</div>
</div>
</form>
</div>
<div class=\\"modal-footer\\">

View File

@@ -1,6 +1,8 @@
import { action } from "mobx";
import { localStored } from "mobx-stored";
import { StaticLabels } from "Common/Query";
class SavedFilters {
config = localStored(
"savedFilters",
@@ -54,11 +56,34 @@ class SilenceFormConfig {
});
}
class GridConfig {
options = Object.freeze({
disabled: { label: "No sorting", value: "disabled" },
startsAt: { label: "Sort by alert timestamp", value: "startsAt" },
label: { label: "Sort by alert label", value: "label" }
});
defaults = {
sortOrder: this.options.startsAt.value,
reverseSort: true,
sortLabel: StaticLabels.AlertName
};
config = localStored(
"gridConfig",
{
sortOrder: this.defaults.sortOrder,
reverseSort: this.defaults.reverseSort,
sortLabel: this.defaults.sortLabel
},
{ delay: 100 }
);
}
class Settings {
constructor() {
this.savedFilters = new SavedFilters();
this.fetchConfig = new FetchConfig();
this.alertGroupConfig = new AlertGroupConfig();
this.gridConfig = new GridConfig();
this.silenceFormConfig = new SilenceFormConfig();
}
}