Merge pull request #823 from prymitive/overview-close

feat(ui): allow removing filters from the overview modal
This commit is contained in:
Łukasz Mierzwa
2019-07-14 18:59:13 +01:00
committed by GitHub
9 changed files with 287 additions and 54 deletions

View File

@@ -1,6 +1,7 @@
package main
import (
"fmt"
"math"
"sort"
@@ -44,18 +45,30 @@ func countersToLabelStats(counters map[string]map[string]int) models.LabelNameSt
Name: name,
Values: models.LabelValueStatsList{},
}
for value, hits := range valueMap {
nameStats.Hits += hits
valueStats := models.LabelValueStats{
Value: value,
Raw: fmt.Sprintf("%s=%s", name, value),
Hits: hits,
}
nameStats.Values = append(nameStats.Values, valueStats)
}
// now that we have total hits we can calculate %
for i, value := range nameStats.Values {
nameStats.Values[i].Percent = int(math.Round((float64(value.Hits) / float64(nameStats.Hits)) * 100.0))
}
sort.Sort(nameStats.Values)
// now that we have all % and values are sorted we can calculate offsets
offset := 0
for i, value := range nameStats.Values {
nameStats.Values[i].Offset = offset
offset += value.Percent
}
data = append(data, nameStats)
}

View File

@@ -40,8 +40,10 @@ type LabelsCountMap map[string]map[string]int
type LabelValueStats struct {
Value string `json:"value"`
Raw string `json:"raw"`
Hits int `json:"hits"`
Percent int `json:"percent"`
Offset int `json:"offset"`
}
type LabelValueStatsList []LabelValueStats

View File

@@ -265,13 +265,17 @@ func TestNameStatsSort(t *testing.T) {
Values: models.LabelValueStatsList{
models.LabelValueStats{
Value: "suppressed",
Raw: "@state=suppressed",
Hits: 8,
Percent: 33,
Offset: 67,
},
models.LabelValueStats{
Value: "active",
Raw: "@state=actuve",
Hits: 16,
Percent: 67,
Offset: 0,
},
},
},
@@ -281,18 +285,24 @@ func TestNameStatsSort(t *testing.T) {
Values: models.LabelValueStatsList{
models.LabelValueStats{
Value: "dev",
Raw: "cluster=dev",
Hits: 10,
Percent: 42,
Offset: 0,
},
models.LabelValueStats{
Value: "prod",
Raw: "cluster=prod",
Hits: 6,
Percent: 25,
Offset: 42,
},
models.LabelValueStats{
Value: "staging",
Raw: "cluster=staging",
Hits: 8,
Percent: 33,
Offset: 67,
},
},
},
@@ -302,23 +312,32 @@ func TestNameStatsSort(t *testing.T) {
Values: models.LabelValueStatsList{
models.LabelValueStats{
Value: "HTTP_Probe_Failed",
Raw: "alertname=HTTP_Probe_Failed",
Hits: 4,
Percent: 17,
Offset: 0,
},
models.LabelValueStats{
Value: "Host_Down",
Raw: "alertname=Host_Down",
Hits: 16,
Percent: 67,
Offset: 17,
},
models.LabelValueStats{
Value: "Free_Disk_Space_Too_Low",
Value: "Free_Disk_Space_Too_Low",
Raw: "alertname=Free_Disk_Space_Too_Low",
Hits: 2,
Percent: 8,
Offset: 84,
},
models.LabelValueStats{
Value: "Memory_Usage_Too_High",
Raw: "alertname=Memory_Usage_Too_High",
Hits: 2,
Percent: 8,
Offset: 92,
},
},
},
@@ -328,53 +347,72 @@ func TestNameStatsSort(t *testing.T) {
Values: models.LabelValueStatsList{
models.LabelValueStats{
Value: "server4",
Raw: "instance=server4",
Hits: 2,
Percent: 8,
},
models.LabelValueStats{
Value: "server5",
Raw: "instance=server5",
Hits: 4,
Percent: 17,
Offset: 17,
},
models.LabelValueStats{
Value: "server6",
Raw: "instance=server6",
Hits: 2,
Percent: 8,
Offset: 17,
},
models.LabelValueStats{
Value: "server1",
Raw: "instance=server1",
Hits: 2,
Percent: 8,
Offset: 17,
},
models.LabelValueStats{
Value: "server2",
Raw: "instance=server2",
Hits: 4,
Percent: 17,
Offset: 17,
},
models.LabelValueStats{
Value: "server3",
Raw: "instance=server3",
Hits: 2,
Percent: 8,
Offset: 17,
},
models.LabelValueStats{
Value: "server7",
Raw: "instance=server7",
Hits: 2,
Percent: 8,
Offset: 17,
},
models.LabelValueStats{
Value: "server8",
Raw: "instance=server8",
Hits: 2,
Percent: 8,
Offset: 17,
},
models.LabelValueStats{
Value: "web1",
Raw: "instance=web1",
Hits: 2,
Percent: 8,
Offset: 17,
},
models.LabelValueStats{
Value: "web2",
Raw: "instance=web2",
Hits: 2,
Percent: 8,
Offset: 17,
},
},
},
@@ -384,13 +422,17 @@ func TestNameStatsSort(t *testing.T) {
Values: models.LabelValueStatsList{
models.LabelValueStats{
Value: "by-name",
Raw: "@receiver=by-name",
Hits: 12,
Percent: 50,
Offset: 0,
},
models.LabelValueStats{
Value: "by-cluster-service",
Raw: "@receiver=by-cluster-service",
Hits: 12,
Percent: 50,
Offset: 50,
},
},
},
@@ -400,13 +442,17 @@ func TestNameStatsSort(t *testing.T) {
Values: models.LabelValueStatsList{
models.LabelValueStats{
Value: "node_exporter",
Raw: "job=node_exporter",
Hits: 8,
Percent: 50,
Offset: 0,
},
models.LabelValueStats{
Value: "node_ping",
Raw: "job=node_ping",
Hits: 8,
Percent: 50,
Offset: 50,
},
},
},

View File

@@ -1,5 +1,55 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<LabelWithPercent /> matches snapshot with isActive=true 1`] = `
"
<div class
style=\\"display: inline-block; max-width: 100%;\\"
data-tooltipped
aria-describedby=\\"tippy-tooltip-3\\"
data-original-title=\\"Click to only show alerts with this label or Alt+Click to hide them\\"
>
<span class=\\"components-label badge badge-warning components-label-dark components-label-with-hover mb-0 pl-0 text-left\\">
<span class=\\"mr-1 px-1 bg-primary text-white components-labelWithPercent-percent\\">
25
</span>
<span>
<span class=\\"components-label-name\\">
foo:
</span>
<span class=\\"components-label-value\\">
bar
</span>
</span>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"
data-icon=\\"times\\"
class=\\"svg-inline--fa fa-times fa-w-11 cursor-pointer text-reset ml-1 close\\"
role=\\"img\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
viewbox=\\"0 0 352 512\\"
style=\\"font-size: 100%;\\"
>
<path fill=\\"currentColor\\"
d=\\"M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z\\"
>
</path>
</svg>
</span>
<div class=\\"progress components-labelWithPercent-progress mr-1\\">
<div class=\\"progress-bar bg-warning\\"
role=\\"progressbar\\"
style=\\"width: 50%;\\"
aria-valuenow=\\"50\\"
aria-valuemin=\\"0\\"
aria-valuemax=\\"100\\"
>
</div>
</div>
</div>
"
`;
exports[`<LabelWithPercent /> matches snapshot with offset=0 1`] = `
"
<div class
@@ -12,11 +62,13 @@ exports[`<LabelWithPercent /> matches snapshot with offset=0 1`] = `
<span class=\\"mr-1 px-1 bg-primary text-white components-labelWithPercent-percent\\">
25
</span>
<span class=\\"components-label-name\\">
foo:
</span>
<span class=\\"components-label-value\\">
bar
<span>
<span class=\\"components-label-name\\">
foo:
</span>
<span class=\\"components-label-value\\">
bar
</span>
</span>
</span>
<div class=\\"progress components-labelWithPercent-progress mr-1\\">
@@ -45,11 +97,13 @@ exports[`<LabelWithPercent /> matches snapshot with offset=25 1`] = `
<span class=\\"mr-1 px-1 bg-primary text-white components-labelWithPercent-percent\\">
25
</span>
<span class=\\"components-label-name\\">
foo:
</span>
<span class=\\"components-label-value\\">
bar
<span>
<span class=\\"components-label-name\\">
foo:
</span>
<span class=\\"components-label-value\\">
bar
</span>
</span>
</span>
<div class=\\"progress components-labelWithPercent-progress mr-1\\">

View File

@@ -3,7 +3,11 @@ import PropTypes from "prop-types";
import { inject, observer } from "mobx-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faTimes } from "@fortawesome/free-solid-svg-icons/faTimes";
import { AlertStore } from "Stores/AlertStore";
import { QueryOperators, FormatQuery } from "Common/Query";
import { TooltipWrapper } from "Components/TooltipWrapper";
import { BaseLabel } from "Components/Labels/BaseLabel";
@@ -11,18 +15,26 @@ import "./index.scss";
const LabelWithPercent = inject("alertStore")(
observer(
class FilteringLabel extends BaseLabel {
class LabelWithPercent extends BaseLabel {
static propTypes = {
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
hits: PropTypes.number.isRequired,
percent: PropTypes.number.isRequired,
offset: PropTypes.number.isRequired
offset: PropTypes.number.isRequired,
isActive: PropTypes.bool.isRequired
};
removeFromFilters = () => {
const { alertStore, name, value } = this.props;
alertStore.filters.removeFilter(
FormatQuery(name, QueryOperators.Equal, value)
);
};
render() {
const { name, value, hits, percent, offset } = this.props;
const { name, value, hits, percent, offset, isActive } = this.props;
let cs = this.getClassAndStyle(
name,
@@ -39,16 +51,22 @@ const LabelWithPercent = inject("alertStore")(
return (
<TooltipWrapper title="Click to only show alerts with this label or Alt+Click to hide them">
<span
className={cs.className}
style={cs.style}
onClick={e => this.handleClick(e)}
>
<span className={cs.className} style={cs.style}>
<span className="mr-1 px-1 bg-primary text-white components-labelWithPercent-percent">
{hits}
</span>
<span className="components-label-name">{name}:</span>{" "}
<span className="components-label-value">{value}</span>
<span onClick={e => this.handleClick(e)}>
<span className="components-label-name">{name}:</span>{" "}
<span className="components-label-value">{value}</span>
</span>
{isActive ? (
<FontAwesomeIcon
className="cursor-pointer text-reset ml-1 close"
style={{ fontSize: "100%" }}
icon={faTimes}
onClick={this.removeFromFilters}
/>
) : null}
</span>
<div className="progress components-labelWithPercent-progress mr-1">
{offset === 0 ? null : (

View File

@@ -14,7 +14,14 @@ beforeEach(() => {
alertStore = new AlertStore([]);
});
const MountedLabelWithPercent = (name, value, hits, percent, offset) => {
const MountedLabelWithPercent = (
name,
value,
hits,
percent,
offset,
isActive
) => {
return mount(
<LabelWithPercent
alertStore={alertStore}
@@ -23,27 +30,37 @@ const MountedLabelWithPercent = (name, value, hits, percent, offset) => {
hits={hits}
percent={percent}
offset={offset}
isActive={isActive}
/>
);
};
const RenderAndClick = (name, value, clickOptions) => {
const tree = MountedLabelWithPercent(name, value, 25, 50, 0);
tree.find(".components-label").simulate("click", clickOptions || {});
const tree = MountedLabelWithPercent(name, value, 25, 50, 0, false);
tree
.find(".components-label")
.find("span")
.at(2)
.simulate("click", clickOptions || {});
};
describe("<LabelWithPercent />", () => {
it("matches snapshot with offset=0", () => {
const tree = MountedLabelWithPercent("foo", "bar", 25, 50, 0);
const tree = MountedLabelWithPercent("foo", "bar", 25, 50, 0, false);
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
it("matches snapshot with offset=25", () => {
const tree = MountedLabelWithPercent("foo", "bar", 25, 50, 25);
const tree = MountedLabelWithPercent("foo", "bar", 25, 50, 25, false);
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
it("calling onClick() adds a new filter 'foo=bar'", () => {
it("matches snapshot with isActive=true", () => {
const tree = MountedLabelWithPercent("foo", "bar", 25, 50, 0, true);
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
it("calling adds a new filter 'foo=bar'", () => {
RenderAndClick("foo", "bar");
expect(alertStore.filters.values).toHaveLength(1);
expect(alertStore.filters.values).toContainEqual(
@@ -51,6 +68,18 @@ describe("<LabelWithPercent />", () => {
);
});
it("clicking the X buttom removes label from filters", () => {
const tree = MountedLabelWithPercent("foo", "bar", 25, 50, 0, true);
tree
.find(".components-label")
.find("svg")
.simulate("click");
expect(alertStore.filters.values).toHaveLength(0);
expect(alertStore.filters.values).not.toContainEqual(
NewUnappliedFilter("foo=bar")
);
});
it("calling onClick() while holding Alt key adds a new filter 'foo!=bar'", () => {
RenderAndClick("foo", "bar", { altKey: true });
expect(alertStore.filters.values).toHaveLength(1);
@@ -60,17 +89,17 @@ describe("<LabelWithPercent />", () => {
});
it("uses bg-danger when percent is >66", () => {
const tree = MountedLabelWithPercent("foo", "bar", 25, 67, 0);
const tree = MountedLabelWithPercent("foo", "bar", 25, 67, 0, false);
expect(tree.html()).toMatch(/progress-bar bg-danger/);
});
it("uses bg-warning when percent is >33", () => {
const tree = MountedLabelWithPercent("foo", "bar", 25, 66, 0);
const tree = MountedLabelWithPercent("foo", "bar", 25, 66, 0, false);
expect(tree.html()).toMatch(/progress-bar bg-warning/);
});
it("uses bg-success when percent is <=33", () => {
const tree = MountedLabelWithPercent("foo", "bar", 25, 33, 0);
const tree = MountedLabelWithPercent("foo", "bar", 25, 33, 0, false);
expect(tree.html()).toMatch(/progress-bar bg-success/);
});
});

View File

@@ -23,17 +23,19 @@ const LabelsTable = observer(({ alertStore }) => (
</span>
</td>
<td width="75%" className="mw-100 p-1">
{nameStats.values.slice(0, 9).map((valueStats, i, array) => (
{nameStats.values.slice(0, 9).map((valueStats, i) => (
<LabelWithPercent
key={valueStats.value}
name={nameStats.name}
value={valueStats.value}
hits={valueStats.hits}
percent={valueStats.percent}
offset={array
.slice(0, i)
.map(ns => ns.percent)
.reduce((a, b) => a + b, 0)}
offset={valueStats.offset}
isActive={
alertStore.filters.values.filter(
f => f.raw === valueStats.raw
).length > 0
}
/>
))}
</td>

View File

@@ -6,7 +6,7 @@ import { mount } from "enzyme";
import toDiffableHtml from "diffable-html";
import { AlertStore } from "Stores/AlertStore";
import { AlertStore, NewUnappliedFilter } from "Stores/AlertStore";
import { OverviewModalContent } from "./OverviewModalContent";
let alertStore;
@@ -23,14 +23,31 @@ afterEach(() => {
describe("<OverviewModalContent />", () => {
it("matches snapshot with labels to show", () => {
alertStore.filters.values = [
NewUnappliedFilter("abc=xyz"),
NewUnappliedFilter("foo=bar")
];
alertStore.data.counters = [
{
name: "foo",
hits: 16,
values: [
{ value: "bar1", hits: 8, percent: 50 },
{ value: "bar2", hits: 4, percent: 25 },
{ value: "bar3", hits: 4, percent: 25 }
{ value: "bar1", raw: "foo=bar1", hits: 8, percent: 50, offset: 0 },
{ value: "bar2", raw: "foo=bar2", hits: 4, percent: 25, offset: 50 },
{ value: "bar3", raw: "foo=bar3", hits: 4, percent: 25, offset: 75 }
]
},
{
name: "alertname",
hits: 5,
values: [
{
value: "Host_Down",
raw: "alertname=Host_Down",
hits: 5,
percent: 100,
offset: 0
}
]
}
];

View File

@@ -44,11 +44,13 @@ exports[`<OverviewModalContent /> matches snapshot with labels to show 1`] = `
<span class=\\"mr-1 px-1 bg-primary text-white components-labelWithPercent-percent\\">
8
</span>
<span class=\\"components-label-name\\">
foo:
</span>
<span class=\\"components-label-value\\">
bar1
<span>
<span class=\\"components-label-name\\">
foo:
</span>
<span class=\\"components-label-value\\">
bar1
</span>
</span>
</span>
<div class=\\"progress components-labelWithPercent-progress mr-1\\">
@@ -72,11 +74,13 @@ exports[`<OverviewModalContent /> matches snapshot with labels to show 1`] = `
<span class=\\"mr-1 px-1 bg-primary text-white components-labelWithPercent-percent\\">
4
</span>
<span class=\\"components-label-name\\">
foo:
</span>
<span class=\\"components-label-value\\">
bar2
<span>
<span class=\\"components-label-name\\">
foo:
</span>
<span class=\\"components-label-value\\">
bar2
</span>
</span>
</span>
<div class=\\"progress components-labelWithPercent-progress mr-1\\">
@@ -108,11 +112,13 @@ exports[`<OverviewModalContent /> matches snapshot with labels to show 1`] = `
<span class=\\"mr-1 px-1 bg-primary text-white components-labelWithPercent-percent\\">
4
</span>
<span class=\\"components-label-name\\">
foo:
</span>
<span class=\\"components-label-value\\">
bar3
<span>
<span class=\\"components-label-name\\">
foo:
</span>
<span class=\\"components-label-value\\">
bar3
</span>
</span>
</span>
<div class=\\"progress components-labelWithPercent-progress mr-1\\">
@@ -136,6 +142,52 @@ exports[`<OverviewModalContent /> matches snapshot with labels to show 1`] = `
</div>
</td>
</tr>
<tr>
<td width=\\"25%\\"
class=\\"text-nowrap mw-100 p-1\\"
>
<span class=\\"badge badge-light components-label mx-0 my-1 pl-0 text-left\\">
<span class=\\"bg-primary text-white mr-1 px-1 components-labelWithPercent-percent\\">
5
</span>
alertname
</span>
</td>
<td width=\\"75%\\"
class=\\"mw-100 p-1\\"
>
<div class
style=\\"display: inline-block; max-width: 100%;\\"
data-tooltipped
aria-describedby=\\"tippy-tooltip-4\\"
data-original-title=\\"Click to only show alerts with this label or Alt+Click to hide them\\"
>
<span class=\\"components-label badge badge-dark components-label-dark components-label-with-hover mb-0 pl-0 text-left\\">
<span class=\\"mr-1 px-1 bg-primary text-white components-labelWithPercent-percent\\">
5
</span>
<span>
<span class=\\"components-label-name\\">
alertname:
</span>
<span class=\\"components-label-value\\">
Host_Down
</span>
</span>
</span>
<div class=\\"progress components-labelWithPercent-progress mr-1\\">
<div class=\\"progress-bar bg-danger\\"
role=\\"progressbar\\"
style=\\"width: 100%;\\"
aria-valuenow=\\"100\\"
aria-valuemin=\\"0\\"
aria-valuemax=\\"100\\"
>
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>