mirror of
https://github.com/prymitive/karma
synced 2026-05-07 03:26:52 +00:00
feat(ui): pass sort parameters when fetching alerts
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user