mirror of
https://github.com/prymitive/karma
synced 2026-08-17 11:27:01 +00:00
feat(api): use a dedicated API endpoint for overview modal
This commit is contained in:
committed by
Łukasz Mierzwa
parent
8fb76ef4c9
commit
907ac420d3
+2
-1
@@ -4,7 +4,8 @@
|
||||
|
||||
### Added
|
||||
|
||||
- Added dedicated API endpoint for silence previews.
|
||||
- Added a dedicated API endpoint for silence previews.
|
||||
- Added a dedicated API endpoint for overview modal.
|
||||
|
||||
## v0.87
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ include make/docker.mk
|
||||
include make/lint-versions.mk
|
||||
|
||||
.PHONY: lint
|
||||
lint: lint-bootstrap-version
|
||||
lint: lint-go lint-bootstrap-version
|
||||
make -C ui lint-js
|
||||
|
||||
.PHONY: test
|
||||
|
||||
@@ -0,0 +1,801 @@
|
||||
/* snapshot: TestCounters/#00 */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Cache-Control: no-cache, no-store, must-revalidate
|
||||
Content-Type: application/json
|
||||
Vary: Origin
|
||||
|
||||
{
|
||||
"counters": [
|
||||
{
|
||||
"hits": 48,
|
||||
"name": "@cluster",
|
||||
"values": [
|
||||
{
|
||||
"hits": 24,
|
||||
"offset": 0,
|
||||
"percent": 50,
|
||||
"raw": "@cluster=cluster",
|
||||
"value": "cluster"
|
||||
},
|
||||
{
|
||||
"hits": 24,
|
||||
"offset": 50,
|
||||
"percent": 50,
|
||||
"raw": "@cluster=single",
|
||||
"value": "single"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 24,
|
||||
"name": "@receiver",
|
||||
"values": [
|
||||
{
|
||||
"hits": 12,
|
||||
"offset": 0,
|
||||
"percent": 50,
|
||||
"raw": "@receiver=by-cluster-service",
|
||||
"value": "by-cluster-service"
|
||||
},
|
||||
{
|
||||
"hits": 12,
|
||||
"offset": 50,
|
||||
"percent": 50,
|
||||
"raw": "@receiver=by-name",
|
||||
"value": "by-name"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 24,
|
||||
"name": "@state",
|
||||
"values": [
|
||||
{
|
||||
"hits": 16,
|
||||
"offset": 0,
|
||||
"percent": 67,
|
||||
"raw": "@state=active",
|
||||
"value": "active"
|
||||
},
|
||||
{
|
||||
"hits": 8,
|
||||
"offset": 67,
|
||||
"percent": 33,
|
||||
"raw": "@state=suppressed",
|
||||
"value": "suppressed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 24,
|
||||
"name": "alertname",
|
||||
"values": [
|
||||
{
|
||||
"hits": 16,
|
||||
"offset": 0,
|
||||
"percent": 67,
|
||||
"raw": "alertname=Host_Down",
|
||||
"value": "Host_Down"
|
||||
},
|
||||
{
|
||||
"hits": 4,
|
||||
"offset": 67,
|
||||
"percent": 17,
|
||||
"raw": "alertname=HTTP_Probe_Failed",
|
||||
"value": "HTTP_Probe_Failed"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 84,
|
||||
"percent": 8,
|
||||
"raw": "alertname=Free_Disk_Space_Too_Low",
|
||||
"value": "Free_Disk_Space_Too_Low"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 92,
|
||||
"percent": 8,
|
||||
"raw": "alertname=Memory_Usage_Too_High",
|
||||
"value": "Memory_Usage_Too_High"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 24,
|
||||
"name": "cluster",
|
||||
"values": [
|
||||
{
|
||||
"hits": 10,
|
||||
"offset": 0,
|
||||
"percent": 42,
|
||||
"raw": "cluster=dev",
|
||||
"value": "dev"
|
||||
},
|
||||
{
|
||||
"hits": 8,
|
||||
"offset": 42,
|
||||
"percent": 33,
|
||||
"raw": "cluster=staging",
|
||||
"value": "staging"
|
||||
},
|
||||
{
|
||||
"hits": 6,
|
||||
"offset": 75,
|
||||
"percent": 25,
|
||||
"raw": "cluster=prod",
|
||||
"value": "prod"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 24,
|
||||
"name": "instance",
|
||||
"values": [
|
||||
{
|
||||
"hits": 4,
|
||||
"offset": 0,
|
||||
"percent": 17,
|
||||
"raw": "instance=server2",
|
||||
"value": "server2"
|
||||
},
|
||||
{
|
||||
"hits": 4,
|
||||
"offset": 17,
|
||||
"percent": 17,
|
||||
"raw": "instance=server5",
|
||||
"value": "server5"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 34,
|
||||
"percent": 9,
|
||||
"raw": "instance=server1",
|
||||
"value": "server1"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 43,
|
||||
"percent": 9,
|
||||
"raw": "instance=server3",
|
||||
"value": "server3"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 52,
|
||||
"percent": 8,
|
||||
"raw": "instance=server4",
|
||||
"value": "server4"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 60,
|
||||
"percent": 8,
|
||||
"raw": "instance=server6",
|
||||
"value": "server6"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 68,
|
||||
"percent": 8,
|
||||
"raw": "instance=server7",
|
||||
"value": "server7"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 76,
|
||||
"percent": 8,
|
||||
"raw": "instance=server8",
|
||||
"value": "server8"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 84,
|
||||
"percent": 8,
|
||||
"raw": "instance=web1",
|
||||
"value": "web1"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 92,
|
||||
"percent": 8,
|
||||
"raw": "instance=web2",
|
||||
"value": "web2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 24,
|
||||
"name": "job",
|
||||
"values": [
|
||||
{
|
||||
"hits": 16,
|
||||
"offset": 0,
|
||||
"percent": 67,
|
||||
"raw": "job=node_ping",
|
||||
"value": "node_ping"
|
||||
},
|
||||
{
|
||||
"hits": 8,
|
||||
"offset": 67,
|
||||
"percent": 33,
|
||||
"raw": "job=node_exporter",
|
||||
"value": "node_exporter"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 16,
|
||||
"name": "ip",
|
||||
"values": [
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 0,
|
||||
"percent": 13,
|
||||
"raw": "ip=127.0.0.1",
|
||||
"value": "127.0.0.1"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 13,
|
||||
"percent": 13,
|
||||
"raw": "ip=127.0.0.2",
|
||||
"value": "127.0.0.2"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 26,
|
||||
"percent": 13,
|
||||
"raw": "ip=127.0.0.3",
|
||||
"value": "127.0.0.3"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 39,
|
||||
"percent": 13,
|
||||
"raw": "ip=127.0.0.4",
|
||||
"value": "127.0.0.4"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 52,
|
||||
"percent": 12,
|
||||
"raw": "ip=127.0.0.5",
|
||||
"value": "127.0.0.5"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 64,
|
||||
"percent": 12,
|
||||
"raw": "ip=127.0.0.6",
|
||||
"value": "127.0.0.6"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 76,
|
||||
"percent": 12,
|
||||
"raw": "ip=127.0.0.7",
|
||||
"value": "127.0.0.7"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 88,
|
||||
"percent": 12,
|
||||
"raw": "ip=127.0.0.8",
|
||||
"value": "127.0.0.8"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"name": "disk",
|
||||
"values": [
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 0,
|
||||
"percent": 100,
|
||||
"raw": "disk=sda",
|
||||
"value": "sda"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"total": 24
|
||||
}
|
||||
|
||||
/* snapshot: TestCounters/q=@cluster=single */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Cache-Control: no-cache, no-store, must-revalidate
|
||||
Content-Type: application/json
|
||||
Vary: Origin
|
||||
|
||||
{
|
||||
"counters": [
|
||||
{
|
||||
"hits": 24,
|
||||
"name": "@cluster",
|
||||
"values": [
|
||||
{
|
||||
"hits": 24,
|
||||
"offset": 0,
|
||||
"percent": 100,
|
||||
"raw": "@cluster=single",
|
||||
"value": "single"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 24,
|
||||
"name": "@receiver",
|
||||
"values": [
|
||||
{
|
||||
"hits": 12,
|
||||
"offset": 0,
|
||||
"percent": 50,
|
||||
"raw": "@receiver=by-cluster-service",
|
||||
"value": "by-cluster-service"
|
||||
},
|
||||
{
|
||||
"hits": 12,
|
||||
"offset": 50,
|
||||
"percent": 50,
|
||||
"raw": "@receiver=by-name",
|
||||
"value": "by-name"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 24,
|
||||
"name": "@state",
|
||||
"values": [
|
||||
{
|
||||
"hits": 16,
|
||||
"offset": 0,
|
||||
"percent": 67,
|
||||
"raw": "@state=active",
|
||||
"value": "active"
|
||||
},
|
||||
{
|
||||
"hits": 8,
|
||||
"offset": 67,
|
||||
"percent": 33,
|
||||
"raw": "@state=suppressed",
|
||||
"value": "suppressed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 24,
|
||||
"name": "alertname",
|
||||
"values": [
|
||||
{
|
||||
"hits": 16,
|
||||
"offset": 0,
|
||||
"percent": 67,
|
||||
"raw": "alertname=Host_Down",
|
||||
"value": "Host_Down"
|
||||
},
|
||||
{
|
||||
"hits": 4,
|
||||
"offset": 67,
|
||||
"percent": 17,
|
||||
"raw": "alertname=HTTP_Probe_Failed",
|
||||
"value": "HTTP_Probe_Failed"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 84,
|
||||
"percent": 8,
|
||||
"raw": "alertname=Free_Disk_Space_Too_Low",
|
||||
"value": "Free_Disk_Space_Too_Low"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 92,
|
||||
"percent": 8,
|
||||
"raw": "alertname=Memory_Usage_Too_High",
|
||||
"value": "Memory_Usage_Too_High"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 24,
|
||||
"name": "cluster",
|
||||
"values": [
|
||||
{
|
||||
"hits": 10,
|
||||
"offset": 0,
|
||||
"percent": 42,
|
||||
"raw": "cluster=dev",
|
||||
"value": "dev"
|
||||
},
|
||||
{
|
||||
"hits": 8,
|
||||
"offset": 42,
|
||||
"percent": 33,
|
||||
"raw": "cluster=staging",
|
||||
"value": "staging"
|
||||
},
|
||||
{
|
||||
"hits": 6,
|
||||
"offset": 75,
|
||||
"percent": 25,
|
||||
"raw": "cluster=prod",
|
||||
"value": "prod"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 24,
|
||||
"name": "instance",
|
||||
"values": [
|
||||
{
|
||||
"hits": 4,
|
||||
"offset": 0,
|
||||
"percent": 17,
|
||||
"raw": "instance=server2",
|
||||
"value": "server2"
|
||||
},
|
||||
{
|
||||
"hits": 4,
|
||||
"offset": 17,
|
||||
"percent": 17,
|
||||
"raw": "instance=server5",
|
||||
"value": "server5"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 34,
|
||||
"percent": 9,
|
||||
"raw": "instance=server1",
|
||||
"value": "server1"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 43,
|
||||
"percent": 9,
|
||||
"raw": "instance=server3",
|
||||
"value": "server3"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 52,
|
||||
"percent": 8,
|
||||
"raw": "instance=server4",
|
||||
"value": "server4"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 60,
|
||||
"percent": 8,
|
||||
"raw": "instance=server6",
|
||||
"value": "server6"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 68,
|
||||
"percent": 8,
|
||||
"raw": "instance=server7",
|
||||
"value": "server7"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 76,
|
||||
"percent": 8,
|
||||
"raw": "instance=server8",
|
||||
"value": "server8"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 84,
|
||||
"percent": 8,
|
||||
"raw": "instance=web1",
|
||||
"value": "web1"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 92,
|
||||
"percent": 8,
|
||||
"raw": "instance=web2",
|
||||
"value": "web2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 24,
|
||||
"name": "job",
|
||||
"values": [
|
||||
{
|
||||
"hits": 16,
|
||||
"offset": 0,
|
||||
"percent": 67,
|
||||
"raw": "job=node_ping",
|
||||
"value": "node_ping"
|
||||
},
|
||||
{
|
||||
"hits": 8,
|
||||
"offset": 67,
|
||||
"percent": 33,
|
||||
"raw": "job=node_exporter",
|
||||
"value": "node_exporter"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 16,
|
||||
"name": "ip",
|
||||
"values": [
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 0,
|
||||
"percent": 13,
|
||||
"raw": "ip=127.0.0.1",
|
||||
"value": "127.0.0.1"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 13,
|
||||
"percent": 13,
|
||||
"raw": "ip=127.0.0.2",
|
||||
"value": "127.0.0.2"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 26,
|
||||
"percent": 13,
|
||||
"raw": "ip=127.0.0.3",
|
||||
"value": "127.0.0.3"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 39,
|
||||
"percent": 13,
|
||||
"raw": "ip=127.0.0.4",
|
||||
"value": "127.0.0.4"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 52,
|
||||
"percent": 12,
|
||||
"raw": "ip=127.0.0.5",
|
||||
"value": "127.0.0.5"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 64,
|
||||
"percent": 12,
|
||||
"raw": "ip=127.0.0.6",
|
||||
"value": "127.0.0.6"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 76,
|
||||
"percent": 12,
|
||||
"raw": "ip=127.0.0.7",
|
||||
"value": "127.0.0.7"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 88,
|
||||
"percent": 12,
|
||||
"raw": "ip=127.0.0.8",
|
||||
"value": "127.0.0.8"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"name": "disk",
|
||||
"values": [
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 0,
|
||||
"percent": 100,
|
||||
"raw": "disk=sda",
|
||||
"value": "sda"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"total": 24
|
||||
}
|
||||
|
||||
/* snapshot: TestCounters/q=@receiver=by-cluster-service&q=alertname=Host_Down&q=@state=active */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Cache-Control: no-cache, no-store, must-revalidate
|
||||
Content-Type: application/json
|
||||
Vary: Origin
|
||||
|
||||
{
|
||||
"counters": [
|
||||
{
|
||||
"hits": 10,
|
||||
"name": "@cluster",
|
||||
"values": [
|
||||
{
|
||||
"hits": 5,
|
||||
"offset": 0,
|
||||
"percent": 50,
|
||||
"raw": "@cluster=cluster",
|
||||
"value": "cluster"
|
||||
},
|
||||
{
|
||||
"hits": 5,
|
||||
"offset": 50,
|
||||
"percent": 50,
|
||||
"raw": "@cluster=single",
|
||||
"value": "single"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 5,
|
||||
"name": "@receiver",
|
||||
"values": [
|
||||
{
|
||||
"hits": 5,
|
||||
"offset": 0,
|
||||
"percent": 100,
|
||||
"raw": "@receiver=by-cluster-service",
|
||||
"value": "by-cluster-service"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 5,
|
||||
"name": "@state",
|
||||
"values": [
|
||||
{
|
||||
"hits": 5,
|
||||
"offset": 0,
|
||||
"percent": 100,
|
||||
"raw": "@state=active",
|
||||
"value": "active"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 5,
|
||||
"name": "alertname",
|
||||
"values": [
|
||||
{
|
||||
"hits": 5,
|
||||
"offset": 0,
|
||||
"percent": 100,
|
||||
"raw": "alertname=Host_Down",
|
||||
"value": "Host_Down"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 5,
|
||||
"name": "cluster",
|
||||
"values": [
|
||||
{
|
||||
"hits": 3,
|
||||
"offset": 0,
|
||||
"percent": 60,
|
||||
"raw": "cluster=staging",
|
||||
"value": "staging"
|
||||
},
|
||||
{
|
||||
"hits": 2,
|
||||
"offset": 60,
|
||||
"percent": 40,
|
||||
"raw": "cluster=prod",
|
||||
"value": "prod"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 5,
|
||||
"name": "instance",
|
||||
"values": [
|
||||
{
|
||||
"hits": 1,
|
||||
"offset": 0,
|
||||
"percent": 20,
|
||||
"raw": "instance=server1",
|
||||
"value": "server1"
|
||||
},
|
||||
{
|
||||
"hits": 1,
|
||||
"offset": 20,
|
||||
"percent": 20,
|
||||
"raw": "instance=server2",
|
||||
"value": "server2"
|
||||
},
|
||||
{
|
||||
"hits": 1,
|
||||
"offset": 40,
|
||||
"percent": 20,
|
||||
"raw": "instance=server3",
|
||||
"value": "server3"
|
||||
},
|
||||
{
|
||||
"hits": 1,
|
||||
"offset": 60,
|
||||
"percent": 20,
|
||||
"raw": "instance=server4",
|
||||
"value": "server4"
|
||||
},
|
||||
{
|
||||
"hits": 1,
|
||||
"offset": 80,
|
||||
"percent": 20,
|
||||
"raw": "instance=server5",
|
||||
"value": "server5"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 5,
|
||||
"name": "ip",
|
||||
"values": [
|
||||
{
|
||||
"hits": 1,
|
||||
"offset": 0,
|
||||
"percent": 20,
|
||||
"raw": "ip=127.0.0.1",
|
||||
"value": "127.0.0.1"
|
||||
},
|
||||
{
|
||||
"hits": 1,
|
||||
"offset": 20,
|
||||
"percent": 20,
|
||||
"raw": "ip=127.0.0.2",
|
||||
"value": "127.0.0.2"
|
||||
},
|
||||
{
|
||||
"hits": 1,
|
||||
"offset": 40,
|
||||
"percent": 20,
|
||||
"raw": "ip=127.0.0.3",
|
||||
"value": "127.0.0.3"
|
||||
},
|
||||
{
|
||||
"hits": 1,
|
||||
"offset": 60,
|
||||
"percent": 20,
|
||||
"raw": "ip=127.0.0.4",
|
||||
"value": "127.0.0.4"
|
||||
},
|
||||
{
|
||||
"hits": 1,
|
||||
"offset": 80,
|
||||
"percent": 20,
|
||||
"raw": "ip=127.0.0.5",
|
||||
"value": "127.0.0.5"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hits": 5,
|
||||
"name": "job",
|
||||
"values": [
|
||||
{
|
||||
"hits": 5,
|
||||
"offset": 0,
|
||||
"percent": 100,
|
||||
"raw": "job=node_ping",
|
||||
"value": "node_ping"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"total": 5
|
||||
}
|
||||
|
||||
/* snapshot: TestCounters/q=foo=bar */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Cache-Control: no-cache, no-store, must-revalidate
|
||||
Content-Type: application/json
|
||||
Vary: Origin
|
||||
|
||||
{
|
||||
"counters": [],
|
||||
"total": 0
|
||||
}
|
||||
@@ -632,217 +632,6 @@ var groupTests = []groupTest{
|
||||
},
|
||||
}
|
||||
|
||||
var countsMap = models.LabelNameStatsList{
|
||||
{
|
||||
Name: "@receiver",
|
||||
Hits: 24,
|
||||
Values: models.LabelValueStatsList{
|
||||
models.LabelValueStats{
|
||||
Value: "by-cluster-service",
|
||||
Hits: 12,
|
||||
Percent: 50,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "by-name",
|
||||
Hits: 12,
|
||||
Percent: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "@state",
|
||||
Hits: 24,
|
||||
Values: models.LabelValueStatsList{
|
||||
models.LabelValueStats{
|
||||
Value: "active",
|
||||
Hits: 16,
|
||||
Percent: 67,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "suppressed",
|
||||
Hits: 8,
|
||||
Percent: 33,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "alertname",
|
||||
Hits: 24,
|
||||
Values: models.LabelValueStatsList{
|
||||
models.LabelValueStats{
|
||||
Value: "Free_Disk_Space_Too_Low",
|
||||
Hits: 2,
|
||||
Percent: 8,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "HTTP_Probe_Failed",
|
||||
Hits: 4,
|
||||
Percent: 17,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "Host_Down",
|
||||
Hits: 16,
|
||||
Percent: 67,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "Memory_Usage_Too_High",
|
||||
Hits: 2,
|
||||
Percent: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "cluster",
|
||||
Hits: 24,
|
||||
Values: models.LabelValueStatsList{
|
||||
models.LabelValueStats{
|
||||
Value: "dev",
|
||||
Hits: 10,
|
||||
Percent: 42,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "prod",
|
||||
Hits: 6,
|
||||
Percent: 25,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "staging",
|
||||
Hits: 8,
|
||||
Percent: 33,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "disk",
|
||||
Hits: 2,
|
||||
Values: models.LabelValueStatsList{
|
||||
models.LabelValueStats{
|
||||
Value: "sda",
|
||||
Hits: 2,
|
||||
Percent: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "instance",
|
||||
Hits: 24,
|
||||
Values: models.LabelValueStatsList{
|
||||
models.LabelValueStats{
|
||||
Value: "server1",
|
||||
Hits: 2,
|
||||
Percent: 9,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "server2",
|
||||
Hits: 4,
|
||||
Percent: 17,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "server3",
|
||||
Hits: 2,
|
||||
Percent: 9,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "server4",
|
||||
Hits: 2,
|
||||
Percent: 8,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "server5",
|
||||
Hits: 4,
|
||||
Percent: 17,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "server6",
|
||||
Hits: 2,
|
||||
Percent: 8,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "server7",
|
||||
Hits: 2,
|
||||
Percent: 8,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "server8",
|
||||
Hits: 2,
|
||||
Percent: 8,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "web1",
|
||||
Hits: 2,
|
||||
Percent: 8,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "web2",
|
||||
Hits: 2,
|
||||
Percent: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ip",
|
||||
Hits: 16,
|
||||
Values: models.LabelValueStatsList{
|
||||
models.LabelValueStats{
|
||||
Value: "127.0.0.1",
|
||||
Hits: 2,
|
||||
Percent: 13,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "127.0.0.2",
|
||||
Hits: 2,
|
||||
Percent: 13,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "127.0.0.3",
|
||||
Hits: 2,
|
||||
Percent: 13,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "127.0.0.4",
|
||||
Hits: 2,
|
||||
Percent: 13,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "127.0.0.5",
|
||||
Hits: 2,
|
||||
Percent: 12,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "127.0.0.6",
|
||||
Hits: 2,
|
||||
Percent: 12,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "127.0.0.7",
|
||||
Hits: 2,
|
||||
Percent: 12,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "127.0.0.8",
|
||||
Hits: 2,
|
||||
Percent: 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "job",
|
||||
Hits: 24,
|
||||
Values: models.LabelValueStatsList{
|
||||
models.LabelValueStats{
|
||||
Value: "node_exporter",
|
||||
Hits: 8,
|
||||
Percent: 33,
|
||||
},
|
||||
models.LabelValueStats{
|
||||
Value: "node_ping",
|
||||
Hits: 16,
|
||||
Percent: 67,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var filtersExpected = []models.Filter{}
|
||||
|
||||
func compareAlertGroups(testCase groupTest, group models.APIAlertGroup) bool {
|
||||
@@ -1032,54 +821,6 @@ func TestVerifyAllGroups(t *testing.T) {
|
||||
t.Errorf("[%s] Silences mismatch, expected >0 but got %d", version, len(am))
|
||||
}
|
||||
|
||||
for _, nameStats := range ur.Counters {
|
||||
var totalPercent int
|
||||
for _, valueStats := range nameStats.Values {
|
||||
totalPercent += valueStats.Percent
|
||||
}
|
||||
if totalPercent != 100 {
|
||||
t.Errorf("[%s] Counters %s sum is != 100: %d", version, nameStats.Name, totalPercent)
|
||||
}
|
||||
}
|
||||
|
||||
for _, expectedNameStats := range countsMap {
|
||||
var foundName bool
|
||||
for _, nameStats := range ur.Counters {
|
||||
if nameStats.Name == expectedNameStats.Name {
|
||||
if nameStats.Hits != expectedNameStats.Hits {
|
||||
t.Errorf("[%s] Counters mismatch for '%s', expected %v hits but got %v",
|
||||
version, nameStats.Name, expectedNameStats.Hits, nameStats.Hits)
|
||||
}
|
||||
for _, expectedValueStats := range expectedNameStats.Values {
|
||||
var foundValue bool
|
||||
for _, valueStats := range nameStats.Values {
|
||||
if valueStats.Value == expectedValueStats.Value {
|
||||
if valueStats.Hits != expectedValueStats.Hits {
|
||||
t.Errorf("[%s] Counters mismatch for '%s: %s', expected %v hits but got %v",
|
||||
version, nameStats.Name, valueStats.Value, expectedValueStats.Hits, valueStats.Hits)
|
||||
}
|
||||
if valueStats.Percent != expectedValueStats.Percent {
|
||||
t.Errorf("[%s] Percent mismatch for '%s: %s', expected %v%% but got %v%%",
|
||||
version, nameStats.Name, valueStats.Value, expectedValueStats.Percent, valueStats.Percent)
|
||||
}
|
||||
foundValue = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundValue {
|
||||
if !foundName {
|
||||
t.Errorf("[%s] Counters missing for label '%s: %s'", version, expectedNameStats.Name, expectedValueStats.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
foundName = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundName {
|
||||
t.Errorf("[%s] Counters missing for label '%s'", version, expectedNameStats.Name)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(ur.Filters, filtersExpected) {
|
||||
t.Errorf("[%s] Filters mismatch, expected %v but got %v", version, filtersExpected, ur.Filters)
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@ func setupRouter(router *chi.Mux, historyPoller *historyPoller) {
|
||||
router.Get(getViewURL("/labelNames.json"), knownLabelNames)
|
||||
router.Get(getViewURL("/labelValues.json"), knownLabelValues)
|
||||
router.Get(getViewURL("/silences.json"), silences)
|
||||
router.Get(getViewURL("/counters.json"), counters)
|
||||
router.Post(getViewURL("/history.json"), func(w http.ResponseWriter, r *http.Request) {
|
||||
alertHistory(historyPoller, w, r)
|
||||
})
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/beme/abide"
|
||||
"github.com/rogpeppe/go-internal/testscript"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -31,10 +33,17 @@ func mainShouldWork() int {
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(testscript.RunMain(m, map[string]func() int{
|
||||
ecode := testscript.RunMain(m, map[string]func() int{
|
||||
"karma.bin-should-fail": mainShoulFail,
|
||||
"karma.bin-should-work": mainShouldWork,
|
||||
}))
|
||||
})
|
||||
err := abide.Cleanup()
|
||||
if err != nil {
|
||||
fmt.Printf("abide.Cleanup() error: %v\n", err)
|
||||
ecode = 1
|
||||
}
|
||||
os.Exit(ecode)
|
||||
|
||||
}
|
||||
|
||||
func TestScripts(t *testing.T) {
|
||||
|
||||
+57
-16
@@ -222,7 +222,6 @@ func alerts(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
grids := map[string]models.APIGrid{}
|
||||
colors := models.LabelsColorMap{}
|
||||
counters := map[string]map[string]int{}
|
||||
|
||||
dedupedAlerts := alertmanager.DedupAlerts()
|
||||
dedupedColors := alertmanager.DedupColors()
|
||||
@@ -342,19 +341,6 @@ func alerts(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
agCopy.Alerts = append(agCopy.Alerts, alert)
|
||||
|
||||
if len(upstreams.Clusters) > 1 {
|
||||
clusters := map[string]bool{}
|
||||
for _, am := range alert.Alertmanager {
|
||||
clusters[am.Cluster] = true
|
||||
}
|
||||
for cluster := range clusters {
|
||||
countLabel(counters, "@cluster", cluster)
|
||||
}
|
||||
}
|
||||
|
||||
countLabel(counters, "@state", alert.State)
|
||||
|
||||
countLabel(counters, "@receiver", alert.Receiver)
|
||||
if ck, foundKey := dedupedColors["@receiver"]; foundKey {
|
||||
if cv, foundVal := ck[alert.Receiver]; foundVal {
|
||||
if _, found := colors["@receiver"]; !found {
|
||||
@@ -405,7 +391,6 @@ func alerts(w http.ResponseWriter, r *http.Request) {
|
||||
colors[key][value] = color
|
||||
}
|
||||
}
|
||||
countLabel(counters, key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -493,7 +478,6 @@ func alerts(w http.ResponseWriter, r *http.Request) {
|
||||
resp.Grids = sortedGrids
|
||||
resp.Silences = silences
|
||||
resp.Colors = colors
|
||||
resp.Counters = countersToLabelStats(counters)
|
||||
resp.Filters = populateAPIFilters(matchFilters)
|
||||
resp.Receivers = receivers
|
||||
|
||||
@@ -761,3 +745,60 @@ func sortSliceOfLabels(labels []map[string]string, sortKeys []string, fallback s
|
||||
return sortorder.NaturalLess(labels[i][fallback], labels[j][fallback])
|
||||
})
|
||||
}
|
||||
|
||||
func counters(w http.ResponseWriter, r *http.Request) {
|
||||
noCache(w)
|
||||
|
||||
// use full URI (including query args) as cache key
|
||||
cacheKey := r.RequestURI
|
||||
|
||||
d, found := apiCache.Get(cacheKey)
|
||||
if found {
|
||||
r := bytes.NewReader(d.([]byte))
|
||||
rawData, _ := decompressCachedResponse(r)
|
||||
mimeJSON(w)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(rawData)
|
||||
return
|
||||
}
|
||||
|
||||
q, _ := lookupQueryStringSlice(r, "q")
|
||||
matchFilters := getFiltersFromQuery(q)
|
||||
dedupedAlerts := alertmanager.DedupAlerts()
|
||||
filtered := filterAlerts(dedupedAlerts, matchFilters)
|
||||
upstreams := getUpstreams()
|
||||
counters := map[string]map[string]int{}
|
||||
|
||||
var total int
|
||||
for _, ag := range filtered {
|
||||
total += len(ag.Alerts)
|
||||
for _, alert := range ag.Alerts {
|
||||
if len(upstreams.Clusters) > 1 {
|
||||
clusters := map[string]struct{}{}
|
||||
for _, am := range alert.Alertmanager {
|
||||
clusters[am.Cluster] = struct{}{}
|
||||
}
|
||||
for cluster := range clusters {
|
||||
countLabel(counters, "@cluster", cluster)
|
||||
}
|
||||
}
|
||||
countLabel(counters, "@state", alert.State)
|
||||
countLabel(counters, "@receiver", alert.Receiver)
|
||||
for key, value := range alert.Labels {
|
||||
countLabel(counters, key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resp := models.Counters{
|
||||
Total: total,
|
||||
Counters: countersToLabelStats(counters),
|
||||
}
|
||||
|
||||
mimeJSON(w)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
data, _ := json.Marshal(resp)
|
||||
compressedData, _ := compressResponse(data, nil)
|
||||
_ = apiCache.Add(cacheKey, compressedData)
|
||||
_, _ = w.Write(data)
|
||||
}
|
||||
|
||||
+99
-3
@@ -15,6 +15,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/beme/abide"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"github.com/prymitive/karma/internal/alertmanager"
|
||||
"github.com/prymitive/karma/internal/config"
|
||||
@@ -275,9 +276,6 @@ func TestAlerts(t *testing.T) {
|
||||
if ur.Status != "success" {
|
||||
t.Errorf("[%s] Invalid status in response: %s", version, ur.Status)
|
||||
}
|
||||
if len(ur.Counters) != 6 {
|
||||
t.Errorf("[%s] Invalid number of counters in response (%d): %v", version, len(ur.Counters), ur.Counters)
|
||||
}
|
||||
for _, ag := range ur.Grids[0].AlertGroups {
|
||||
for _, a := range ag.Alerts {
|
||||
linkCount := 0
|
||||
@@ -3299,3 +3297,101 @@ func TestSortSliceOfLabels(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounters(t *testing.T) {
|
||||
type testCaseT struct {
|
||||
args string
|
||||
}
|
||||
|
||||
testCases := []testCaseT{
|
||||
{
|
||||
args: "q=foo=bar",
|
||||
},
|
||||
{
|
||||
args: "",
|
||||
},
|
||||
{
|
||||
args: "q=@receiver=by-cluster-service&q=alertname=Host_Down&q=@state=active",
|
||||
},
|
||||
{
|
||||
args: "q=@cluster=single",
|
||||
},
|
||||
}
|
||||
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
||||
mockCache()
|
||||
version := mock.ListAllMocks()[0]
|
||||
|
||||
am1, err := alertmanager.NewAlertmanager(
|
||||
"cluster",
|
||||
"am1",
|
||||
"http://localhost/1",
|
||||
)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
mock.RegisterURL("http://localhost/1/metrics", version, "metrics")
|
||||
mock.RegisterURL("http://localhost/1/api/v2/status", version, "api/v2/status")
|
||||
mock.RegisterURL("http://localhost/1/api/v2/silences", version, "api/v2/silences")
|
||||
mock.RegisterURL("http://localhost/1/api/v2/alerts/groups", version, "api/v2/alerts/groups")
|
||||
err = alertmanager.RegisterAlertmanager(am1)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
am2, err := alertmanager.NewAlertmanager(
|
||||
"cluster",
|
||||
"am2",
|
||||
"http://localhost/2",
|
||||
)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
mock.RegisterURL("http://localhost/2/metrics", version, "metrics")
|
||||
mock.RegisterURL("http://localhost/2/api/v2/status", version, "api/v2/status")
|
||||
mock.RegisterURL("http://localhost/2/api/v2/silences", version, "api/v2/silences")
|
||||
mock.RegisterURL("http://localhost/2/api/v2/alerts/groups", version, "api/v2/alerts/groups")
|
||||
err = alertmanager.RegisterAlertmanager(am2)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
am3, err := alertmanager.NewAlertmanager(
|
||||
"single",
|
||||
"single",
|
||||
"http://localhost/3",
|
||||
)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
mock.RegisterURL("http://localhost/3/metrics", version, "metrics")
|
||||
mock.RegisterURL("http://localhost/3/api/v2/status", version, "api/v2/status")
|
||||
mock.RegisterURL("http://localhost/3/api/v2/silences", version, "api/v2/silences")
|
||||
mock.RegisterURL("http://localhost/3/api/v2/alerts/groups", version, "api/v2/alerts/groups")
|
||||
err = alertmanager.RegisterAlertmanager(am3)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pullFromAlertmanager()
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.args, func(t *testing.T) {
|
||||
r := testRouter()
|
||||
setupRouter(r, nil)
|
||||
// re-run a few times to test the cache
|
||||
for i := 1; i <= 3; i++ {
|
||||
req := httptest.NewRequest("GET", "/counters.json?"+tc.args, nil)
|
||||
resp := httptest.NewRecorder()
|
||||
r.ServeHTTP(resp, req)
|
||||
if resp.Code != http.StatusOK {
|
||||
t.Errorf("GET /counters.json returned status %d", resp.Code)
|
||||
}
|
||||
abide.AssertHTTPResponse(t, t.Name(), resp.Result())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ go 1.16
|
||||
require (
|
||||
github.com/Masterminds/semver/v3 v3.1.1
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
||||
github.com/beme/abide v0.0.0-20190723115211-635a09831760
|
||||
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08
|
||||
github.com/fvbommel/sortorder v1.0.2
|
||||
github.com/getsentry/sentry-go v0.11.0
|
||||
|
||||
@@ -65,6 +65,8 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
|
||||
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
||||
github.com/beme/abide v0.0.0-20190723115211-635a09831760 h1:FvTM5NSN5HYvfKpgL+8x73U5v063vHsd7AX05eV1DnM=
|
||||
github.com/beme/abide v0.0.0-20190723115211-635a09831760/go.mod h1:6+8gCKsZnxzhGTmKRh4BSkLos9CbWRJNcrp55We4SqQ=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@@ -525,6 +527,7 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
|
||||
@@ -381,7 +381,6 @@ type AlertsResponse struct {
|
||||
LabelNames []string `json:"labelNames"`
|
||||
Colors LabelsColorMap `json:"colors"`
|
||||
Filters []Filter `json:"filters"`
|
||||
Counters LabelNameStatsList `json:"counters"`
|
||||
Settings Settings `json:"settings"`
|
||||
Authentication AuthenticationInfo `json:"authentication"`
|
||||
Receivers []string `json:"receivers"`
|
||||
@@ -393,3 +392,8 @@ type Autocomplete struct {
|
||||
Value string `json:"value"`
|
||||
Tokens []string `json:"tokens"`
|
||||
}
|
||||
|
||||
type Counters struct {
|
||||
Total int `json:"total"`
|
||||
Counters LabelNameStatsList `json:"counters"`
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { mount } from "enzyme";
|
||||
|
||||
import toDiffableHtml from "diffable-html";
|
||||
|
||||
import { useFetchGetMock } from "__fixtures__/useFetchGet";
|
||||
import { AlertStore, NewUnappliedFilter } from "Stores/AlertStore";
|
||||
import { OverviewModalContent } from "./OverviewModalContent";
|
||||
|
||||
@@ -29,44 +30,73 @@ const MountedOverviewModalContent = () =>
|
||||
|
||||
describe("<OverviewModalContent />", () => {
|
||||
it("matches snapshot with labels to show", () => {
|
||||
alertStore.filters.setFilterValues([
|
||||
NewUnappliedFilter("abc=xyz"),
|
||||
NewUnappliedFilter("foo=bar"),
|
||||
]);
|
||||
alertStore.data.setCounters([
|
||||
{
|
||||
name: "foo",
|
||||
hits: 16,
|
||||
values: [
|
||||
{ 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: "bar",
|
||||
hits: 20,
|
||||
values: Array.from(Array(20).keys()).map((i) => ({
|
||||
value: `baz${i + 1}`,
|
||||
raw: `bar=baz${i + 1}`,
|
||||
hits: 1,
|
||||
percent: 5,
|
||||
offset: i * 5,
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "alertname",
|
||||
hits: 5,
|
||||
values: [
|
||||
useFetchGetMock.fetch.setMockedData({
|
||||
response: {
|
||||
total: 1,
|
||||
counters: [
|
||||
{
|
||||
value: "Host_Down",
|
||||
raw: "alertname=Host_Down",
|
||||
name: "foo",
|
||||
hits: 16,
|
||||
values: [
|
||||
{
|
||||
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: "bar",
|
||||
hits: 20,
|
||||
values: Array.from(Array(20).keys()).map((i) => ({
|
||||
value: `baz${i + 1}`,
|
||||
raw: `bar=baz${i + 1}`,
|
||||
hits: 1,
|
||||
percent: 5,
|
||||
offset: i * 5,
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "alertname",
|
||||
hits: 5,
|
||||
percent: 100,
|
||||
offset: 0,
|
||||
values: [
|
||||
{
|
||||
value: "Host_Down",
|
||||
raw: "alertname=Host_Down",
|
||||
hits: 5,
|
||||
percent: 100,
|
||||
offset: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
error: null,
|
||||
isLoading: false,
|
||||
isRetrying: false,
|
||||
retryCount: 0,
|
||||
get: jest.fn(),
|
||||
cancelGet: jest.fn(),
|
||||
});
|
||||
alertStore.filters.setFilterValues([
|
||||
NewUnappliedFilter("abc=xyz"),
|
||||
NewUnappliedFilter("foo=bar"),
|
||||
]);
|
||||
|
||||
const tree = MountedOverviewModalContent();
|
||||
@@ -74,35 +104,62 @@ describe("<OverviewModalContent />", () => {
|
||||
});
|
||||
|
||||
it("matches snapshot with no labels to show", () => {
|
||||
alertStore.data.setCounters([]);
|
||||
useFetchGetMock.fetch.setMockedData({
|
||||
response: {
|
||||
total: 20,
|
||||
counters: [],
|
||||
},
|
||||
error: null,
|
||||
isLoading: false,
|
||||
isRetrying: false,
|
||||
retryCount: 0,
|
||||
get: jest.fn(),
|
||||
cancelGet: jest.fn(),
|
||||
});
|
||||
const tree = MountedOverviewModalContent();
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders all labels after expand button click", () => {
|
||||
alertStore.info.setTotalAlerts(5);
|
||||
alertStore.data.setCounters([
|
||||
{
|
||||
name: "foo",
|
||||
hits: 5,
|
||||
values: [
|
||||
{ value: "bar", raw: "foo=bar", hits: 5, percent: 100, offset: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
hits: 3,
|
||||
values: [
|
||||
useFetchGetMock.fetch.setMockedData({
|
||||
response: {
|
||||
total: 5,
|
||||
counters: [
|
||||
{
|
||||
value: "foo",
|
||||
raw: "bar=foo",
|
||||
name: "foo",
|
||||
hits: 5,
|
||||
values: [
|
||||
{
|
||||
value: "bar",
|
||||
raw: "foo=bar",
|
||||
hits: 5,
|
||||
percent: 100,
|
||||
offset: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
hits: 3,
|
||||
percent: 100,
|
||||
offset: 0,
|
||||
values: [
|
||||
{
|
||||
value: "foo",
|
||||
raw: "bar=foo",
|
||||
hits: 3,
|
||||
percent: 100,
|
||||
offset: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
error: null,
|
||||
isLoading: false,
|
||||
isRetrying: false,
|
||||
retryCount: 0,
|
||||
get: jest.fn(),
|
||||
cancelGet: jest.fn(),
|
||||
});
|
||||
const tree = MountedOverviewModalContent();
|
||||
|
||||
expect(tree.find("span.components-label")).toHaveLength(2 + 1); // +1 for toggle icon
|
||||
|
||||
@@ -2,8 +2,16 @@ import { FC, useState } from "react";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import type { APILabelCounterT } from "Models/APITypes";
|
||||
import type { AlertStore } from "Stores/AlertStore";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner";
|
||||
|
||||
import type { CountersResponseT, APILabelCounterT } from "Models/APITypes";
|
||||
import {
|
||||
AlertStore,
|
||||
FormatBackendURI,
|
||||
FormatAPIFilterQuery,
|
||||
} from "Stores/AlertStore";
|
||||
import { useFetchGet } from "Hooks/useFetchGet";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import LabelWithPercent from "Components/Labels/LabelWithPercent";
|
||||
import { ToggleIcon } from "Components/ToggleIcon";
|
||||
@@ -53,9 +61,10 @@ const TableRows: FC<{
|
||||
|
||||
const LabelsTable: FC<{
|
||||
alertStore: AlertStore;
|
||||
counters: CountersResponseT;
|
||||
showAllLabels: boolean;
|
||||
toggleAllLabels: () => void;
|
||||
}> = observer(({ alertStore, showAllLabels, toggleAllLabels }) => (
|
||||
}> = observer(({ alertStore, counters, showAllLabels, toggleAllLabels }) => (
|
||||
<>
|
||||
<table
|
||||
className="table table-borderless top-labels"
|
||||
@@ -64,12 +73,12 @@ const LabelsTable: FC<{
|
||||
<tbody className="mw-100">
|
||||
<TableRows
|
||||
alertStore={alertStore}
|
||||
nameStats={alertStore.data.counters.filter(
|
||||
(nameStats) => nameStats.hits >= alertStore.info.totalAlerts
|
||||
nameStats={counters.counters.filter(
|
||||
(nameStats) => nameStats.hits >= counters.total
|
||||
)}
|
||||
></TableRows>
|
||||
{alertStore.data.counters.filter(
|
||||
(nameStats) => nameStats.hits < alertStore.info.totalAlerts
|
||||
{counters.counters.filter(
|
||||
(nameStats) => nameStats.hits < counters.total
|
||||
).length > 0 ? (
|
||||
<tr>
|
||||
<td colSpan={2} className="px-1 py-0">
|
||||
@@ -87,8 +96,8 @@ const LabelsTable: FC<{
|
||||
{showAllLabels ? (
|
||||
<TableRows
|
||||
alertStore={alertStore}
|
||||
nameStats={alertStore.data.counters.filter(
|
||||
(nameStats) => nameStats.hits < alertStore.info.totalAlerts
|
||||
nameStats={counters.counters.filter(
|
||||
(nameStats) => nameStats.hits < counters.total
|
||||
)}
|
||||
></TableRows>
|
||||
) : null}
|
||||
@@ -105,11 +114,34 @@ const NothingToShow: FC = () => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const LoadingMessage: FC = () => (
|
||||
<div className="px-2 py-5 bg-transparent">
|
||||
<h1 className="display-5 text-placeholder text-center">
|
||||
<FontAwesomeIcon icon={faSpinner} size="lg" spin />
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ErrorMessage: FC<{ error: string }> = ({ error }) => (
|
||||
<div className="px-2 py-5 bg-transparent">
|
||||
<h1 className="display-5 text-danger text-center">{error}</h1>
|
||||
</div>
|
||||
);
|
||||
|
||||
const OverviewModalContent: FC<{
|
||||
alertStore: AlertStore;
|
||||
onHide: () => void;
|
||||
}> = observer(({ alertStore, onHide }) => {
|
||||
const [showAllLabels, setShowAllLabels] = useState<boolean>(false);
|
||||
const { response, error } = useFetchGet<CountersResponseT>(
|
||||
FormatBackendURI(
|
||||
`counters.json?${FormatAPIFilterQuery(
|
||||
alertStore.filters.values.map((f) => f.raw)
|
||||
)}`
|
||||
),
|
||||
{ deps: [alertStore.info.timestamp] }
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="modal-header">
|
||||
@@ -117,11 +149,16 @@ const OverviewModalContent: FC<{
|
||||
<button type="button" className="btn-close" onClick={onHide}></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{alertStore.data.counters.length === 0 ? (
|
||||
{error !== null ? (
|
||||
<ErrorMessage error={error} />
|
||||
) : response === null ? (
|
||||
<LoadingMessage />
|
||||
) : response.counters.length === 0 ? (
|
||||
<NothingToShow />
|
||||
) : (
|
||||
<LabelsTable
|
||||
alertStore={alertStore}
|
||||
counters={response}
|
||||
showAllLabels={showAllLabels}
|
||||
toggleAllLabels={() => setShowAllLabels(!showAllLabels)}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,8 @@ import type { FC } from "react";
|
||||
|
||||
import { storiesOf } from "@storybook/react";
|
||||
|
||||
import fetchMock from "fetch-mock";
|
||||
|
||||
import { MockGrid } from "../../__fixtures__/Stories";
|
||||
import { AlertStore } from "../../Stores/AlertStore";
|
||||
import { OverviewModalContent } from "./OverviewModalContent";
|
||||
@@ -17,10 +19,99 @@ storiesOf("OverviewModal", module).add("OverviewModal", () => {
|
||||
</div>
|
||||
);
|
||||
|
||||
const alertStore = new AlertStore([]);
|
||||
fetchMock.mock(
|
||||
"begin:/counters.json?q=full",
|
||||
{
|
||||
total: 0,
|
||||
counters: [
|
||||
{
|
||||
name: "@receiver",
|
||||
hits: 2,
|
||||
values: [
|
||||
{
|
||||
value: "by-cluster-service",
|
||||
raw: "@receiver=by-cluster-service",
|
||||
hits: 2,
|
||||
percent: 100,
|
||||
offset: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "alertname",
|
||||
hits: 90,
|
||||
values: [
|
||||
{
|
||||
value: "Fake Alert",
|
||||
raw: "alertname=Fake Alert",
|
||||
hits: 45,
|
||||
percent: 50,
|
||||
offset: 0,
|
||||
},
|
||||
{
|
||||
value: "Second Fake Alert",
|
||||
raw: "alertname=Second Fake Alert",
|
||||
hits: 45,
|
||||
percent: 50,
|
||||
offset: 50,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "group",
|
||||
hits: 100,
|
||||
values: [
|
||||
{
|
||||
value: "group1",
|
||||
raw: "group=group1",
|
||||
hits: 25,
|
||||
percent: 25,
|
||||
offset: 0,
|
||||
},
|
||||
{
|
||||
value: "group2",
|
||||
raw: "group=group2",
|
||||
hits: 70,
|
||||
percent: 70,
|
||||
offset: 25,
|
||||
},
|
||||
{
|
||||
value: "group3",
|
||||
raw: "group=group3",
|
||||
hits: 4,
|
||||
percent: 4,
|
||||
offset: 95,
|
||||
},
|
||||
{
|
||||
value: "group4",
|
||||
raw: "group=group4",
|
||||
hits: 1,
|
||||
percent: 1,
|
||||
offset: 99,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
overwriteRoutes: true,
|
||||
}
|
||||
);
|
||||
fetchMock.mock(
|
||||
"begin:/counters.json?q=empty",
|
||||
{
|
||||
total: 0,
|
||||
counters: [],
|
||||
},
|
||||
{
|
||||
overwriteRoutes: true,
|
||||
}
|
||||
);
|
||||
|
||||
const alertStore = new AlertStore(["full"]);
|
||||
MockGrid(alertStore);
|
||||
|
||||
const emptyAlertStore = new AlertStore([]);
|
||||
const emptyAlertStore = new AlertStore(["empty"]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { act } from "react-dom/test-utils";
|
||||
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import { useFetchGetMock } from "__fixtures__/useFetchGet";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { OverviewModal } from ".";
|
||||
|
||||
@@ -35,7 +36,53 @@ describe("<OverviewModal />", () => {
|
||||
expect(tree.find(".modal-content").find("svg.fa-spinner")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("renders a spinner placeholder while fetch is in progress", () => {
|
||||
useFetchGetMock.fetch.setMockedData({
|
||||
response: null,
|
||||
error: null,
|
||||
isLoading: true,
|
||||
isRetrying: false,
|
||||
retryCount: 0,
|
||||
get: jest.fn(),
|
||||
cancelGet: jest.fn(),
|
||||
});
|
||||
const tree = MountedOverviewModal();
|
||||
const toggle = tree.find("div.navbar-brand");
|
||||
toggle.simulate("click");
|
||||
expect(tree.find("LoadingMessage")).toHaveLength(1);
|
||||
expect(tree.find(".modal-content").find("svg.fa-spinner")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("renders an error message on fetch error", () => {
|
||||
useFetchGetMock.fetch.setMockedData({
|
||||
response: null,
|
||||
error: "mock error",
|
||||
isLoading: false,
|
||||
isRetrying: false,
|
||||
retryCount: 0,
|
||||
get: jest.fn(),
|
||||
cancelGet: jest.fn(),
|
||||
});
|
||||
const tree = MountedOverviewModal();
|
||||
const toggle = tree.find("div.navbar-brand");
|
||||
toggle.simulate("click");
|
||||
expect(tree.find("ErrorMessage")).toHaveLength(1);
|
||||
expect(tree.find("h1.text-danger").text()).toBe("mock error");
|
||||
});
|
||||
|
||||
it("renders modal content if fallback is not used", () => {
|
||||
useFetchGetMock.fetch.setMockedData({
|
||||
response: {
|
||||
total: 20,
|
||||
counters: [],
|
||||
},
|
||||
error: null,
|
||||
isLoading: false,
|
||||
isRetrying: false,
|
||||
retryCount: 0,
|
||||
get: jest.fn(),
|
||||
cancelGet: jest.fn(),
|
||||
});
|
||||
const tree = MountedOverviewModal();
|
||||
const toggle = tree.find("div.navbar-brand");
|
||||
toggle.simulate("click");
|
||||
@@ -43,6 +90,31 @@ describe("<OverviewModal />", () => {
|
||||
expect(tree.find(".modal-content").find("svg.fa-spinner")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("re-fetches counters after timestamp change", () => {
|
||||
alertStore.info.setTimestamp("old");
|
||||
useFetchGetMock.fetch.setMockedData({
|
||||
response: {
|
||||
total: 20,
|
||||
counters: [],
|
||||
},
|
||||
error: null,
|
||||
isLoading: false,
|
||||
isRetrying: false,
|
||||
retryCount: 0,
|
||||
get: jest.fn(),
|
||||
cancelGet: jest.fn(),
|
||||
});
|
||||
const tree = MountedOverviewModal();
|
||||
const toggle = tree.find("div.navbar-brand");
|
||||
toggle.simulate("click");
|
||||
expect(useFetchGetMock.fetch.calls).toHaveLength(1);
|
||||
|
||||
act(() => {
|
||||
alertStore.info.setTimestamp("new");
|
||||
});
|
||||
expect(useFetchGetMock.fetch.calls).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("hides the modal when toggle() is called twice", () => {
|
||||
const tree = MountedOverviewModal();
|
||||
const toggle = tree.find("div.navbar-brand");
|
||||
|
||||
@@ -206,7 +206,6 @@ export interface APIAlertsResponseT {
|
||||
totalAlerts: number;
|
||||
colors: APIAlertsResponseColorsT;
|
||||
filters: APIFilterT[];
|
||||
counters: APILabelCounterT[];
|
||||
settings: APISettingsT;
|
||||
authentication: APIAlertsResponseAuthenticationT;
|
||||
receivers: string[];
|
||||
@@ -225,3 +224,8 @@ export interface HistoryResponseT {
|
||||
export interface AlertListResponseT {
|
||||
alerts: LabelsT[];
|
||||
}
|
||||
|
||||
export interface CountersResponseT {
|
||||
total: number;
|
||||
counters: APILabelCounterT[];
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import type {
|
||||
APILabelColorT,
|
||||
APIAlertsResponseT,
|
||||
APIAlertsResponseColorsT,
|
||||
APILabelCounterT,
|
||||
APIGridT,
|
||||
APIAlertsResponseSilenceMapT,
|
||||
APIAlertsResponseUpstreamsT,
|
||||
@@ -137,7 +136,6 @@ interface AlertStoreFiltersT {
|
||||
|
||||
interface AlertStoreDataT {
|
||||
colors: APIAlertsResponseColorsT;
|
||||
counters: APILabelCounterT[];
|
||||
grids: APIGridT[];
|
||||
labelNames: string[];
|
||||
setLabelNames: (v: string[]) => void;
|
||||
@@ -157,7 +155,6 @@ interface AlertStoreDataT {
|
||||
setUpstreams: (u: APIAlertsResponseUpstreamsT) => void;
|
||||
setClusters: (c: APIAlertsResponseUpstreamsClusterMapT) => void;
|
||||
setSilences: (s: APIAlertsResponseSilenceMapT) => void;
|
||||
setCounters: (c: APILabelCounterT[]) => void;
|
||||
setReceivers: (r: string[]) => void;
|
||||
setColors: (c: APIAlertsResponseColorsT) => void;
|
||||
readonly upstreamsWithErrors: APIAlertmanagerUpstreamT[];
|
||||
@@ -169,6 +166,7 @@ interface AlertStoreInfoT {
|
||||
username: string;
|
||||
};
|
||||
totalAlerts: number;
|
||||
timestamp: string;
|
||||
version: string;
|
||||
upgradeReady: boolean;
|
||||
upgradeNeeded: boolean;
|
||||
@@ -182,6 +180,7 @@ interface AlertStoreInfoT {
|
||||
setTotalAlerts: (n: number) => void;
|
||||
setAuthentication: (enabled: boolean, username: string) => void;
|
||||
setVersion: (v: string) => void;
|
||||
setTimestamp: (v: string) => void;
|
||||
}
|
||||
|
||||
interface AlertStoreSettingsT {
|
||||
@@ -291,7 +290,6 @@ class AlertStore {
|
||||
this.data = observable(
|
||||
{
|
||||
colors: {} as APIAlertsResponseColorsT,
|
||||
counters: [] as APILabelCounterT[],
|
||||
grids: [] as APIGridT[],
|
||||
labelNames: [] as string[],
|
||||
setLabelNames(v: string[]) {
|
||||
@@ -373,9 +371,6 @@ class AlertStore {
|
||||
setSilences(s: APIAlertsResponseSilenceMapT) {
|
||||
this.silences = s;
|
||||
},
|
||||
setCounters(c: APILabelCounterT[]) {
|
||||
this.counters = c;
|
||||
},
|
||||
setReceivers(r: string[]) {
|
||||
this.receivers = r;
|
||||
},
|
||||
@@ -397,7 +392,6 @@ class AlertStore {
|
||||
setUpstreams: action.bound,
|
||||
setClusters: action.bound,
|
||||
setSilences: action.bound,
|
||||
setCounters: action.bound,
|
||||
setReceivers: action.bound,
|
||||
setColors: action.bound,
|
||||
setLabelNames: action.bound,
|
||||
@@ -413,6 +407,7 @@ class AlertStore {
|
||||
},
|
||||
totalAlerts: 0,
|
||||
version: "unknown",
|
||||
timestamp: "",
|
||||
upgradeReady: false as boolean,
|
||||
upgradeNeeded: false as boolean,
|
||||
isRetrying: false as boolean,
|
||||
@@ -442,6 +437,9 @@ class AlertStore {
|
||||
setVersion(v: string) {
|
||||
this.version = v;
|
||||
},
|
||||
setTimestamp(v: string) {
|
||||
this.timestamp = v;
|
||||
},
|
||||
},
|
||||
{
|
||||
setIsRetrying: action.bound,
|
||||
@@ -451,6 +449,7 @@ class AlertStore {
|
||||
setTotalAlerts: action.bound,
|
||||
setAuthentication: action.bound,
|
||||
setVersion: action.bound,
|
||||
setTimestamp: action.bound,
|
||||
},
|
||||
{ name: "API response info" }
|
||||
);
|
||||
@@ -668,7 +667,6 @@ class AlertStore {
|
||||
|
||||
const updates: Partial<APIAlertsResponseT> = {};
|
||||
updates.colors = result.colors;
|
||||
updates.counters = result.counters;
|
||||
updates.grids = result.grids;
|
||||
updates.labelNames = result.labelNames;
|
||||
updates.silences = result.silences;
|
||||
@@ -687,6 +685,7 @@ class AlertStore {
|
||||
// update extra root level keys that are stored under 'info'
|
||||
this.info.totalAlerts = result.totalAlerts;
|
||||
this.info.version = result.version;
|
||||
this.info.timestamp = result.timestamp;
|
||||
this.info.authentication = result.authentication;
|
||||
|
||||
// settings exported via API
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { MockAlert, MockAlertGroup, MockSilence } from "__fixtures__/Alerts";
|
||||
import type {
|
||||
APIAlertsResponseT,
|
||||
APILabelCounterT,
|
||||
APIManagedSilenceT,
|
||||
} from "Models/APITypes";
|
||||
import type { APIAlertsResponseT, APIManagedSilenceT } from "Models/APITypes";
|
||||
|
||||
const EmptyAPIResponse = (): APIAlertsResponseT => ({
|
||||
status: "success",
|
||||
@@ -44,7 +40,6 @@ const EmptyAPIResponse = (): APIAlertsResponseT => ({
|
||||
isValid: true,
|
||||
},
|
||||
],
|
||||
counters: [] as APILabelCounterT[],
|
||||
settings: {
|
||||
sorting: {
|
||||
grid: {
|
||||
|
||||
@@ -132,76 +132,6 @@ const MockGrid = (alertStore: AlertStore): void => {
|
||||
},
|
||||
});
|
||||
|
||||
alertStore.data.setCounters([
|
||||
{
|
||||
name: "@receiver",
|
||||
hits: 2,
|
||||
values: [
|
||||
{
|
||||
value: "by-cluster-service",
|
||||
raw: "@receiver=by-cluster-service",
|
||||
hits: 2,
|
||||
percent: 100,
|
||||
offset: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "alertname",
|
||||
hits: 90,
|
||||
values: [
|
||||
{
|
||||
value: "Fake Alert",
|
||||
raw: "alertname=Fake Alert",
|
||||
hits: 45,
|
||||
percent: 50,
|
||||
offset: 0,
|
||||
},
|
||||
{
|
||||
value: "Second Fake Alert",
|
||||
raw: "alertname=Second Fake Alert",
|
||||
hits: 45,
|
||||
percent: 50,
|
||||
offset: 50,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "group",
|
||||
hits: 100,
|
||||
values: [
|
||||
{
|
||||
value: "group1",
|
||||
raw: "group=group1",
|
||||
hits: 25,
|
||||
percent: 25,
|
||||
offset: 0,
|
||||
},
|
||||
{
|
||||
value: "group2",
|
||||
raw: "group=group2",
|
||||
hits: 70,
|
||||
percent: 70,
|
||||
offset: 25,
|
||||
},
|
||||
{
|
||||
value: "group3",
|
||||
raw: "group=group3",
|
||||
hits: 4,
|
||||
percent: 4,
|
||||
offset: 95,
|
||||
},
|
||||
{
|
||||
value: "group4",
|
||||
raw: "group=group4",
|
||||
hits: 1,
|
||||
percent: 1,
|
||||
offset: 99,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const groups = [];
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const active = Math.max(1, Math.ceil(i / 3));
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
APIAlertsResponseT,
|
||||
APIManagedSilenceT,
|
||||
AlertListResponseT,
|
||||
CountersResponseT,
|
||||
} from "Models/APITypes";
|
||||
|
||||
type responseT =
|
||||
@@ -13,7 +14,8 @@ type responseT =
|
||||
| string[]
|
||||
| APIAlertsResponseT
|
||||
| APIManagedSilenceT[]
|
||||
| AlertListResponseT;
|
||||
| AlertListResponseT
|
||||
| CountersResponseT;
|
||||
|
||||
interface mockedDataT {
|
||||
response: undefined | responseT;
|
||||
@@ -126,6 +128,82 @@ const useFetchGetMock = (
|
||||
re: /^.\/autocomplete\.json\?term=/,
|
||||
response: ["foo=bar", "foo=~bar"],
|
||||
},
|
||||
// counters
|
||||
{
|
||||
uri: "./counters.json",
|
||||
response: {
|
||||
total: 90,
|
||||
counters: [
|
||||
{
|
||||
name: "@receiver",
|
||||
hits: 2,
|
||||
values: [
|
||||
{
|
||||
value: "by-cluster-service",
|
||||
raw: "@receiver=by-cluster-service",
|
||||
hits: 2,
|
||||
percent: 100,
|
||||
offset: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "alertname",
|
||||
hits: 90,
|
||||
values: [
|
||||
{
|
||||
value: "Fake Alert",
|
||||
raw: "alertname=Fake Alert",
|
||||
hits: 45,
|
||||
percent: 50,
|
||||
offset: 0,
|
||||
},
|
||||
{
|
||||
value: "Second Fake Alert",
|
||||
raw: "alertname=Second Fake Alert",
|
||||
hits: 45,
|
||||
percent: 50,
|
||||
offset: 50,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "group",
|
||||
hits: 100,
|
||||
values: [
|
||||
{
|
||||
value: "group1",
|
||||
raw: "group=group1",
|
||||
hits: 25,
|
||||
percent: 25,
|
||||
offset: 0,
|
||||
},
|
||||
{
|
||||
value: "group2",
|
||||
raw: "group=group2",
|
||||
hits: 70,
|
||||
percent: 70,
|
||||
offset: 25,
|
||||
},
|
||||
{
|
||||
value: "group3",
|
||||
raw: "group=group3",
|
||||
hits: 4,
|
||||
percent: 4,
|
||||
offset: 95,
|
||||
},
|
||||
{
|
||||
value: "group4",
|
||||
raw: "group=group4",
|
||||
hits: 1,
|
||||
percent: 1,
|
||||
offset: 99,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (const m of mockResponses) {
|
||||
|
||||
Reference in New Issue
Block a user