feat(ui): pass sort parameters when fetching alerts

This commit is contained in:
Łukasz Mierzwa
2019-08-07 00:16:08 +01:00
parent b6d6d0844c
commit 548be9e861
4 changed files with 72 additions and 324 deletions

View File

@@ -25,14 +25,59 @@ const Fetcher = observer(
},
markCompleted() {
this.completedAt = moment();
},
forceUpdate() {
this.time = moment(0);
}
},
{
update: action,
markCompleted: action
markCompleted: action,
forceUpdate: action
}
);
getSortSettings = () => {
const { alertStore, settingsStore } = this.props;
let sortSettings = {
useDefaults: false,
sortOrder: "",
sortLabel: "",
sortReverse: ""
};
sortSettings.useDefaults =
settingsStore.gridConfig.config.sortOrder ===
settingsStore.gridConfig.options.default.value;
if (sortSettings.useDefaults === true) {
return sortSettings;
}
sortSettings.sortOrder = alertStore.settings.values.sorting.grid.order;
// don't sort if sorting is disabled
if (
sortSettings.sortOrder ===
settingsStore.gridConfig.options.disabled.value
)
return sortSettings;
sortSettings.sortReverse =
settingsStore.gridConfig.config.reverseSort !== null
? settingsStore.gridConfig.config.reverseSort === true
? "1"
: "0"
: "";
if (settingsStore.gridConfig.config.sortLabel === null) {
sortSettings.sortLabel = settingsStore.gridConfig.config.sortLabel;
}
return sortSettings;
};
fetchIfIdle = () => {
const { alertStore, settingsStore } = this.props;
@@ -59,8 +104,7 @@ const Fetcher = observer(
!alertStore.status.paused
) {
this.lastTick.update();
alertStore.fetchWithThrottle();
this.lastTick.markCompleted();
this.callFetch();
}
};
@@ -68,6 +112,18 @@ const Fetcher = observer(
window.requestAnimationFrame(this.fetchIfIdle);
};
callFetch = () => {
const { alertStore } = this.props;
const sortSettings = this.getSortSettings();
alertStore.fetchWithThrottle(
sortSettings.sortOrder,
sortSettings.sortLabel,
sortSettings.sortReverse
);
this.lastTick.markCompleted();
};
componentDidMount() {
// start first fetch once the browser is done doing busy loading
window.requestAnimationFrame(this.fetchIfIdle);
@@ -79,7 +135,7 @@ const Fetcher = observer(
if (!alertStore.status.paused) {
this.lastTick.update();
alertStore.fetchWithThrottle();
this.callFetch();
}
}
@@ -96,6 +152,9 @@ const Fetcher = observer(
<span
data-filters={alertStore.filters.values.map(f => f.raw).join(" ")}
data-interval={settingsStore.fetchConfig.config.interval}
data-grid-sort-order={settingsStore.gridConfig.config.sortOrder}
data-grid-sort-label={settingsStore.gridConfig.config.sortLabel}
data-grid-sort-reverse={settingsStore.gridConfig.config.reverseSort}
/>
);
}

View File

@@ -53,20 +53,20 @@ const MountedFetcher = () => {
);
};
const FetcherSpan = (label, interval) =>
`<span data-filters="${label}" data-interval="${interval}"></span>`;
const FetcherSpan = (label, interval, sortOrder) =>
`<span data-filters="${label}" data-interval="${interval}" data-grid-sort-order="${sortOrder}"></span>`;
describe("<Fetcher />", () => {
it("renders correctly with 'label=value' filter", () => {
const tree = MountedFetcher();
expect(tree.html()).toBe(FetcherSpan("label=value", 30));
expect(tree.html()).toBe(FetcherSpan("label=value", 30, "default"));
});
it("re-renders on fetch interval change", () => {
const tree = MountedFetcher();
expect(tree.html()).toBe(FetcherSpan("label=value", 30));
expect(tree.html()).toBe(FetcherSpan("label=value", 30, "default"));
settingsStore.fetchConfig.config.interval = 60;
expect(tree.html()).toBe(FetcherSpan("label=value", 60));
expect(tree.html()).toBe(FetcherSpan("label=value", 60, "default"));
});
it("changing interval changes how often fetch is called", () => {
@@ -96,9 +96,9 @@ describe("<Fetcher />", () => {
it("re-renders on filters change", () => {
MockEmptyAPIResponseWithoutFilters();
const tree = MountedFetcher();
expect(tree.html()).toBe(FetcherSpan("label=value", 30));
expect(tree.html()).toBe(FetcherSpan("label=value", 30, "default"));
alertStore.filters.values = [];
expect(tree.html()).toBe(FetcherSpan("", 30));
expect(tree.html()).toBe(FetcherSpan("", 30, "default"));
});
it("calls alertStore.fetchWithThrottle on mount", () => {

View File

@@ -6,8 +6,6 @@ import { observer } from "mobx-react";
import FontFaceObserver from "fontfaceobserver";
import moment from "moment";
import { debounce } from "lodash";
import ReactResizeDetector from "react-resize-detector";
@@ -25,46 +23,6 @@ import { GridSizesConfig, GetGridElementWidth } from "./GridSize";
import "./index.css";
const getGroupStartsAt = g =>
moment.max(g.alerts.map(a => moment(a.startsAt))).valueOf();
const getLabelValue = (alertStore, settingsStore, sortOrder, sortLabel, g) => {
// if timestamp sort is enabled use latest alert for sorting
if (sortOrder === settingsStore.gridConfig.options.startsAt.value) {
return getGroupStartsAt(g);
}
const labelValue =
g.labels[sortLabel] ||
g.shared.labels[sortLabel] ||
g.alerts[0].labels[sortLabel];
let mappedValue;
// check if we have a mapping for label value
if (
labelValue !== undefined &&
alertStore.settings.values.sorting.valueMapping[sortLabel] !== undefined
) {
mappedValue =
alertStore.settings.values.sorting.valueMapping[sortLabel][labelValue];
}
// if we have a mapped value then return it, if not return original value
return mappedValue !== undefined ? mappedValue : labelValue;
};
const compareByTimestamp = (a, b) => {
const ast = getGroupStartsAt(a);
const bst = getGroupStartsAt(b);
if (ast > bst) {
return -1;
} else if (ast < bst) {
return 1;
} else {
return 0;
}
};
const AlertGrid = observer(
class AlertGrid extends Component {
static propTypes = {
@@ -151,68 +109,6 @@ const AlertGrid = observer(
);
});
compare = (a, b) => {
const { alertStore, settingsStore } = this.props;
const useDefaults =
settingsStore.gridConfig.config.sortOrder ===
settingsStore.gridConfig.options.default.value;
const sortOrder = useDefaults
? alertStore.settings.values.sorting.grid.order
: settingsStore.gridConfig.config.sortOrder;
// don't sort if sorting is disabled
if (sortOrder === settingsStore.gridConfig.options.disabled.value)
return 0;
const sortReverse =
useDefaults || settingsStore.gridConfig.config.reverseSort === undefined
? alertStore.settings.values.sorting.grid.reverse
: settingsStore.gridConfig.config.reverseSort;
const sortLabel =
useDefaults || settingsStore.gridConfig.config.sortLabel === undefined
? alertStore.settings.values.sorting.grid.label
: settingsStore.gridConfig.config.sortLabel;
const val = sortReverse ? -1 : 1;
const av = getLabelValue(
alertStore,
settingsStore,
sortOrder,
sortLabel,
a
);
const bv = getLabelValue(
alertStore,
settingsStore,
sortOrder,
sortLabel,
b
);
if (av === undefined && av === undefined) {
// if both alerts lack the label they are equal, fallback to timestamps
return compareByTimestamp(a, b);
} else if (av === undefined || av > bv) {
// if first one lacks it it's should be rendered after alerts with that label
return val;
} else if (bv === undefined || av < bv) {
// if the first one has label but the second doesn't then the second should be rendered after the first
return val * -1;
} else if (
sortOrder !== settingsStore.gridConfig.options.startsAt.value
) {
// if values are equal use timestamps as secondary sort, but only
// if we didn't already sort by timestamps
return compareByTimestamp(a, b);
} else {
return 0;
}
};
componentDidMount() {
// We have font-display:swap set for font assets, this means that on initial
// render a fallback font might be used and later swapped for the final one

View File

@@ -58,14 +58,14 @@ const MockGroup = (groupName, alertCount) => {
};
const MockGroupList = (count, alertPerGroup) => {
let groups = {};
let groups = [];
for (let i = 1; i <= count; i++) {
let id = `id${i}`;
let hash = `hash${i}`;
let group = MockGroup(`group${i}`, alertPerGroup);
group.id = id;
group.hash = hash;
groups[id] = group;
groups.push(group);
}
alertStore.data.upstreams = {
counters: { total: 0, healthy: 1, failed: 0 },
@@ -157,213 +157,6 @@ describe("<AlertGrid />", () => {
]);
});
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"
]);
});
it("label value mappings from settings are used to order alerts", () => {
alertStore.settings.values.sorting.valueMapping = {
cluster: {
prod: 1,
staging: 2,
dev: 3
}
};
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.options.label.value;
settingsStore.gridConfig.config.sortLabel = "cluster";
settingsStore.gridConfig.config.reverseSort = false;
MockGroupList(3, 1);
alertStore.data.groups.id1.alerts[0].labels.cluster = "dev";
alertStore.data.groups.id2.alerts[0].labels.cluster = "staging";
alertStore.data.groups.id3.alerts[0].labels.cluster = "prod";
const tree = ShallowAlertGrid();
const alertGroups = tree.find("AlertGroup");
expect(alertGroups.map(g => g.props().group.id)).toEqual([
"id3",
"id2",
"id1"
]);
});
it("label value mappings from settings are used to order alerts and reverse flag is respected", () => {
alertStore.settings.values.sorting.valueMapping = {
cluster: {
prod: 1,
staging: 2,
dev: 3
}
};
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.options.label.value;
settingsStore.gridConfig.config.sortLabel = "cluster";
settingsStore.gridConfig.config.reverseSort = true;
MockGroupList(3, 1);
alertStore.data.groups.id1.alerts[0].labels.cluster = "dev";
alertStore.data.groups.id2.alerts[0].labels.cluster = "prod";
alertStore.data.groups.id3.alerts[0].labels.cluster = "staging";
const tree = ShallowAlertGrid();
const alertGroups = tree.find("AlertGroup");
expect(alertGroups.map(g => g.props().group.id)).toEqual([
"id1",
"id3",
"id2"
]);
});
it("startsAt is used as secondary sort key if all labels have equal value", () => {
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.options.label.value;
settingsStore.gridConfig.config.sortLabel = "cluster";
MockGroupList(3, 1);
alertStore.data.groups.id1.alerts[0].labels.cluster = "dev";
alertStore.data.groups.id1.alerts[0].startsAt = "2000-01-01T00:00:02.000Z";
alertStore.data.groups.id2.alerts[0].labels.cluster = "dev";
alertStore.data.groups.id2.alerts[0].startsAt = "2000-01-01T00:00:03.000Z";
alertStore.data.groups.id3.alerts[0].labels.cluster = "dev";
alertStore.data.groups.id3.alerts[0].startsAt = "2000-01-01T00:00:01.000Z";
const tree = ShallowAlertGrid();
const alertGroups = tree.find("AlertGroup");
expect(alertGroups.map(g => g.props().group.id)).toEqual([
"id2",
"id1",
"id3"
]);
});
it("original order is preserved when startsAt is used as fallback and all alerts have the same timestamp", () => {
settingsStore.gridConfig.config.sortOrder =
settingsStore.gridConfig.options.label.value;
settingsStore.gridConfig.config.sortLabel = "cluster";
MockGroupList(3, 1);
alertStore.data.groups.id1.alerts[0].labels.cluster = "dev";
alertStore.data.groups.id1.alerts[0].startsAt = "2000-01-01T00:00:01.000Z";
alertStore.data.groups.id2.alerts[0].labels.cluster = "dev";
alertStore.data.groups.id2.alerts[0].startsAt = "2000-01-01T00:00:01.000Z";
alertStore.data.groups.id3.alerts[0].labels.cluster = "dev";
alertStore.data.groups.id3.alerts[0].startsAt = "2000-01-01T00:00:01.000Z";
const tree = ShallowAlertGrid();
const alertGroups = tree.find("AlertGroup");
expect(alertGroups.map(g => g.props().group.id)).toEqual([
"id1",
"id2",
"id3"
]);
});
it("doesn't throw errors after FontFaceObserver timeout", () => {
MockGroupList(60, 5);
ShallowAlertGrid();