Merge pull request #454 from prymitive/sort-value-mapping

feat(config): allow configuring label value mapping for sorting
This commit is contained in:
Łukasz Mierzwa
2019-02-19 08:16:02 +00:00
committed by GitHub
11 changed files with 202 additions and 42 deletions

View File

@@ -30,6 +30,16 @@ labels:
info: "#87c4e0"
warning: "#ffae42"
critical: "#ff220c"
sorting:
valueMapping:
cluster:
prod: 1
staging: 2
dev: 3
severity:
critical: 1
warning: 2
info: 3
log:
config: false
level: warning

View File

@@ -29,7 +29,7 @@ pair of Alertmanager instances running in
[HA mode](https://prometheus.io/docs/alerting/alertmanager/#high-availability).
Syntax:
```yaml
```YAML
alertmanager:
interval: duration
servers:
@@ -97,7 +97,7 @@ alertmanager:
Example with two production Alertmanager instances running in HA mode and a
staging instance that is also proxied and requires a custom auth header:
```yaml
```YAML
alertmanager:
interval: 1m
servers:
@@ -131,7 +131,7 @@ alertmanager:
Defaults:
```yaml
```YAML
alertmanager:
interval: 1m
servers: []
@@ -148,7 +148,7 @@ needs to be configured without a config file see
the UI.
Syntax:
```yaml
```YAML
annotations:
default:
hidden: bool
@@ -174,7 +174,7 @@ Example where all annotations except `summary` are hidden by default. If there
are additional annotation keys user will need to click on the `+` icon to see
them.
```yaml
```YAML
annotations:
default:
hidden: true
@@ -192,7 +192,7 @@ Example where all annotations except `details` are visible by default. If
icon to see it. Additionally `secret` annotation is stripped and never shown
in the UI.
```yaml
```YAML
annotations:
default:
hidden: false
@@ -206,7 +206,7 @@ annotations:
Defaults:
```yaml
```YAML
annotations:
default:
hidden: false
@@ -220,7 +220,7 @@ annotations:
Syntax:
```yaml
```YAML
filters:
default: list of strings
```
@@ -231,7 +231,7 @@ filters:
Example:
```yaml
```YAML
filters:
default:
- "@state=active"
@@ -240,7 +240,7 @@ filters:
Defaults:
```yaml
```YAML
filters:
default: []
```
@@ -258,7 +258,7 @@ with shared labels, for example coloring hostname label will allow to quickly
spot all alerts for the same host.
Syntax:
```yaml
```YAML
labels:
color:
static: []
@@ -266,6 +266,8 @@ labels:
custom: {}
keep: list of strings
strip: list of strings
sorting:
valueMapping: {}
```
- `color:static` - list of label names that will all have the same color applied
@@ -284,12 +286,19 @@ labels:
it via the config file.
- `keep` - list of allowed labels, if empty all labels are allowed.
- `strip` - list of ignored labels.
- `sorting:valueMapping` - when sorting using alert labels values are compared
as strings, which work for labels like `cluster=A`, `cluster=B` & `cluster=C`,
but not for `cluster=prod`, `cluster=staging` & `cluster=dev`. Alphabetic
sort would order the second case as follows: `dev`, `prod`, `staging`.
To allow for more natural sorting `sorting:valueMapping` can be used to
map label values to integer values which will be used for sorting instead
of original string values.
Example with static color for the `job` label (every `job` label will have the
same color regardless of the value) and unique color for the `@receiver` label
(every `@receiver` label will have color unique for each value).
```yaml
```YAML
labels:
color:
static:
@@ -300,7 +309,7 @@ labels:
Example where `task_id` label is ignored by karma:
```yaml
```YAML
labels:
keep: []
strip:
@@ -309,7 +318,7 @@ labels:
Example where all but `instance` and `alertname` labels are allowed:
```yaml
```YAML
labels:
keep:
- alertname
@@ -320,7 +329,7 @@ labels:
Example where `severity` label will have a red color for `critical`, yellow
for `warning` and blue for `info`:
```yaml
```YAML
labels:
color:
custom:
@@ -332,9 +341,27 @@ labels:
critical: "#ff220c"
```
Example with custom value mapping for sorting, this allows to put `cluster=prod`
or `severity=critical` (depending on which label is used for sorting) on top of
the dashboard grid (or bottom if `Reverse` is enabled by user).
```YAML
labels:
sorting:
valueMapping:
cluster:
prod: 1
staging: 2
dev: 3
severity:
critical: 1
warning: 2
info: 3
```
Defaults:
```yaml
```YAML
labels:
color:
static: []
@@ -342,6 +369,8 @@ labels:
custom: {}
keep: []
strip: []
sorting:
valueMapping: {}
```
### Listen
@@ -349,7 +378,7 @@ labels:
`listen` section allows configuring karma web server behavior.
Syntax:
```yaml
```YAML
listen:
address: string
port: integer
@@ -364,7 +393,7 @@ listen:
Example where karma would listen for HTTP requests on `http://1.2.3.4:80/karma/`
```yaml
```YAML
listen:
address: 1.2.3.4
port: 80
@@ -373,7 +402,7 @@ listen:
Defaults:
```yaml
```YAML
listen:
address: "0.0.0.0"
port: 8080
@@ -385,7 +414,7 @@ listen:
`log` section allows configuring logging subsystem.
Syntax:
```yaml
```YAML
log:
config: bool
level: string
@@ -397,7 +426,7 @@ log:
Defaults:
```yaml
```YAML
log:
config: true
level: info
@@ -410,7 +439,7 @@ issues in silence comments. If a string inside a comment matches one of the
rules it will be rendered as a link.
Syntax:
```yaml
```YAML
jira:
- regex: string
- uri: string
@@ -423,7 +452,7 @@ jira:
Example where a string `DEVOPS-123` inside a comment would be rendered as a link
to `https://jira.example.com/browse/DEVOPS-123`.
```yaml
```YAML
jira:
- regex: DEVOPS-[0-9]+
uri: https://jira.example.com
@@ -431,7 +460,7 @@ jira:
Defaults:
```yaml
```YAML
jira: []
```
@@ -442,7 +471,7 @@ handled by karma. If alerts are routed to multiple receivers they can be
duplicated in the UI, each instance will have different value for `@receiver`.
Syntax:
```yaml
```YAML
receivers:
keep: list of strings
strip: list of strings
@@ -455,7 +484,7 @@ receivers:
Example where alerts that are routed to the `alertmanage2es` receiver are
ignored by karma.
```yaml
```YAML
receivers:
strip:
- alertmanage2es
@@ -463,7 +492,7 @@ receivers:
Defaults:
```yaml
```YAML
receivers:
strip: []
```
@@ -475,7 +504,7 @@ receivers:
details.
Syntax:
```yaml
```YAML
sentry:
private: string
public: string
@@ -488,7 +517,7 @@ sentry:
Example:
```yaml
```YAML
sentry:
private: https://<key>:<secret>@sentry.io/<project>
public: https://<key>:<secret>@sentry.io/<project>
@@ -502,7 +531,7 @@ JavaScript code, which can be used to either override built in CSS styles
or integrate with extra services, for example using error handlers other than
Sentry.
```yaml
```YAML
custom:
css: string
js: string
@@ -513,7 +542,7 @@ custom:
Example:
```yaml
```YAML
custom:
css: /theme/custom.css
js: /assets/custom.js

View File

@@ -167,6 +167,11 @@ func (config *configSchema) Read() {
log.Fatal(err)
}
err = v.UnmarshalKey("labels.sorting.valuemapping", &config.Labels.Sorting.ValueMapping)
if err != nil {
log.Fatal(err)
}
// accept single Alertmanager server from flag/env if nothing is set yet
if len(config.Alertmanager.Servers) == 0 && v.GetString("alertmanager.uri") != "" {
log.Info("Using simple config with a single Alertmanager server")

View File

@@ -98,6 +98,8 @@ labels:
unique:
- f
- gg
sorting:
valueMapping: {}
listen:
address: 0.0.0.0
port: 80

View File

@@ -51,6 +51,9 @@ type configSchema struct {
Static []string
Unique []string
}
Sorting struct {
ValueMapping map[string]map[string]int `yaml:"valueMapping"`
}
}
Listen struct {
Address string

View File

@@ -155,12 +155,18 @@ func (ag *APIAlertGroup) DedupSharedMaps() {
}
}
// SortSettings nests all settings specific to sorting
type SortSettings struct {
ValueMapping map[string]map[string]int `json:"valueMapping"`
}
// Settings is used to export karma configuration that is used by UI
type Settings struct {
StaticColorLabels []string `json:"staticColorLabels"`
AnnotationsDefaultHidden bool `json:"annotationsDefaultHidden"`
AnnotationsHidden []string `json:"annotationsHidden"`
AnnotationsVisible []string `json:"annotationsVisible"`
StaticColorLabels []string `json:"staticColorLabels"`
AnnotationsDefaultHidden bool `json:"annotationsDefaultHidden"`
AnnotationsHidden []string `json:"annotationsHidden"`
AnnotationsVisible []string `json:"annotationsVisible"`
Sorting SortSettings `json:"sorting"`
}
// AlertsResponse is the structure of JSON response UI will use to get alert data

View File

@@ -70,7 +70,7 @@ const AlertGrid = observer(
});
compare = (a, b) => {
const { settingsStore } = this.props;
const { alertStore, settingsStore } = this.props;
// don't sort if sorting is disabled
if (
@@ -88,17 +88,42 @@ const AlertGrid = observer(
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 labelName = settingsStore.gridConfig.config.sortLabel;
const labelValue =
g.labels[labelName] ||
g.shared.labels[labelName] ||
g.alerts[0].labels[labelName];
let mappedValue;
// check if we have a mapping for label value
if (
labelValue !== undefined &&
alertStore.settings.values.sorting.valueMapping[labelName] !==
undefined
) {
mappedValue =
alertStore.settings.values.sorting.valueMapping[labelName][
labelValue
];
}
// if we have a mapped value then return it, if not return original value
return mappedValue !== undefined ? mappedValue : labelValue;
};
const val = settingsStore.gridConfig.config.reverseSort ? -1 : 1;
const av = getLabelValue(a);
const bv = getLabelValue(b);
const val = settingsStore.gridConfig.config.reverseSort ? -1 : 1;
if (av > bv) {
if (av === undefined && av === undefined) {
// if both alerts lack the label they are equal
return 0;
} 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 (av < bv) {
} 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 {
return 0;

View File

@@ -230,4 +230,60 @@ describe("<AlertGrid />", () => {
"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"
]);
});
});

View File

@@ -169,7 +169,10 @@ class AlertStore {
staticColorLabels: [],
annotationsDefaultHidden: false,
annotationsHidden: [],
annotationsVisible: []
annotationsVisible: [],
sorting: {
valueMapping: {}
}
}
},
{},

View File

@@ -23,6 +23,20 @@ const EmptyAPIResponse = () => ({
}
],
settings: {
sorting: {
valueMapping: {
cluster: {
dev: 3,
prod: 1,
staging: 2
},
severity: {
critical: 1,
info: 3,
warning: 2
}
}
},
staticColorLabels: ["job"],
annotationsDefaultHidden: false,
annotationsHidden: [],

View File

@@ -83,12 +83,19 @@ func alerts(c *gin.Context) {
resp.Version = version
resp.Upstreams = getUpstreams()
resp.Settings = models.Settings{
Sorting: models.SortSettings{
ValueMapping: map[string]map[string]int{},
},
StaticColorLabels: config.Config.Labels.Color.Static,
AnnotationsDefaultHidden: config.Config.Annotations.Default.Hidden,
AnnotationsHidden: config.Config.Annotations.Hidden,
AnnotationsVisible: config.Config.Annotations.Visible,
}
if config.Config.Labels.Sorting.ValueMapping != nil {
resp.Settings.Sorting.ValueMapping = config.Config.Labels.Sorting.ValueMapping
}
// use full URI (including query args) as cache key
cacheKey := c.Request.RequestURI