mirror of
https://github.com/prymitive/karma
synced 2026-08-17 11:27:01 +00:00
feat(ui): paginate api
This commit is contained in:
committed by
Łukasz Mierzwa
parent
a817fd4701
commit
04379fc3cc
@@ -7,6 +7,10 @@
|
||||
- Use [uber-go/automaxprocs](https://github.com/uber-go/automaxprocs)
|
||||
to automatically adjust `GOMAXPROCS` to match Linux container CPU quota.
|
||||
Runtime value of `GOMAXPROCS` is now exported as a `go_max_procs` metric.
|
||||
- API will now return only first 40 alert groups by default, the rest can
|
||||
be loaded when user clicks on the `Load more` button.
|
||||
`grid:groupLimit` config option was added to customise how many groups
|
||||
are returned and displayed in the UI by default.
|
||||
|
||||
## v0.86
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/prymitive/karma/internal/alertmanager"
|
||||
@@ -23,6 +24,28 @@ func lookupQueryStringSlice(r *http.Request, key string) ([]string, bool) {
|
||||
return vals, found
|
||||
}
|
||||
|
||||
func lookupLoadLimits(r *http.Request, key string) map[string]int {
|
||||
limits := map[string]int{}
|
||||
vals, found := lookupQueryStringSlice(r, key)
|
||||
if !found {
|
||||
return limits
|
||||
}
|
||||
var limit int
|
||||
var err error
|
||||
for _, val := range vals {
|
||||
valSlice := strings.SplitN(val, "=", 2)
|
||||
if len(valSlice) != 2 {
|
||||
continue
|
||||
}
|
||||
limit, err = strconv.Atoi(valSlice[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
limits[valSlice[0]] = limit
|
||||
}
|
||||
return limits
|
||||
}
|
||||
|
||||
// knownLabelNames allows querying known label names
|
||||
func knownLabelNames(w http.ResponseWriter, r *http.Request) {
|
||||
noCache(w)
|
||||
|
||||
@@ -34,6 +34,7 @@ env GRID_SORTING_REVERSE=false
|
||||
env GRID_SORTING_LABEL=severity
|
||||
env GRID_AUTO_IGNORE="region instance"
|
||||
env GRID_AUTO_ORDER="severity cluster"
|
||||
env GRID_GROUPLIMIT=1
|
||||
|
||||
env KARMA_NAME=karma-demo
|
||||
|
||||
@@ -153,6 +154,7 @@ level=info msg=" ignore:"
|
||||
level=info msg=" - '\"region'"
|
||||
level=info msg=" order:"
|
||||
level=info msg=" - '\"severity'"
|
||||
level=info msg=" groupLimit: 1"
|
||||
level=info msg="history:"
|
||||
level=info msg=" enabled: true"
|
||||
level=info msg=" workers: 30"
|
||||
|
||||
@@ -167,6 +167,7 @@ level=info msg=" - instance"
|
||||
level=info msg=" order:"
|
||||
level=info msg=" - severity"
|
||||
level=info msg=" - cluster"
|
||||
level=info msg=" groupLimit: 1"
|
||||
level=info msg="history:"
|
||||
level=info msg=" enabled: false"
|
||||
level=info msg=" workers: 123"
|
||||
@@ -367,6 +368,7 @@ grid:
|
||||
order:
|
||||
- severity
|
||||
- cluster
|
||||
groupLimit: 1
|
||||
history:
|
||||
enabled: false
|
||||
workers: 123
|
||||
|
||||
@@ -70,6 +70,7 @@ level=info msg=" labels: {}"
|
||||
level=info msg=" auto:"
|
||||
level=info msg=" ignore: []"
|
||||
level=info msg=" order: []"
|
||||
level=info msg=" groupLimit: 40"
|
||||
level=info msg="history:"
|
||||
level=info msg=" enabled: true"
|
||||
level=info msg=" workers: 30"
|
||||
|
||||
@@ -70,6 +70,7 @@ level=info msg=" labels: {}"
|
||||
level=info msg=" auto:"
|
||||
level=info msg=" ignore: []"
|
||||
level=info msg=" order: []"
|
||||
level=info msg=" groupLimit: 40"
|
||||
level=info msg="history:"
|
||||
level=info msg=" enabled: true"
|
||||
level=info msg=" workers: 30"
|
||||
|
||||
@@ -70,6 +70,7 @@ level=info msg=" labels: {}"
|
||||
level=info msg=" auto:"
|
||||
level=info msg=" ignore: []"
|
||||
level=info msg=" order: []"
|
||||
level=info msg=" groupLimit: 40"
|
||||
level=info msg="history:"
|
||||
level=info msg=" enabled: true"
|
||||
level=info msg=" workers: 30"
|
||||
|
||||
@@ -72,6 +72,7 @@ level=info msg=" labels: {}"
|
||||
level=info msg=" auto:"
|
||||
level=info msg=" ignore: []"
|
||||
level=info msg=" order: []"
|
||||
level=info msg=" groupLimit: 40"
|
||||
level=info msg="history:"
|
||||
level=info msg=" enabled: true"
|
||||
level=info msg=" workers: 30"
|
||||
|
||||
@@ -70,6 +70,7 @@ level=info msg=" labels: {}"
|
||||
level=info msg=" auto:"
|
||||
level=info msg=" ignore: []"
|
||||
level=info msg=" order: []"
|
||||
level=info msg=" groupLimit: 40"
|
||||
level=info msg="history:"
|
||||
level=info msg=" enabled: true"
|
||||
level=info msg=" workers: 30"
|
||||
|
||||
@@ -70,6 +70,7 @@ level=info msg=" labels: {}"
|
||||
level=info msg=" auto:"
|
||||
level=info msg=" ignore: []"
|
||||
level=info msg=" order: []"
|
||||
level=info msg=" groupLimit: 40"
|
||||
level=info msg="history:"
|
||||
level=info msg=" enabled: true"
|
||||
level=info msg=" workers: 30"
|
||||
|
||||
+25
-5
@@ -182,6 +182,7 @@ func alerts(w http.ResponseWriter, r *http.Request) {
|
||||
Comment: config.Config.AlertAcknowledgement.Comment,
|
||||
},
|
||||
HistoryEnabled: config.Config.History.Enabled,
|
||||
GridGroupLimit: config.Config.Grid.GroupLimit,
|
||||
}
|
||||
resp.Authentication = models.AuthenticationInfo{
|
||||
Enabled: config.Config.Authentication.Enabled,
|
||||
@@ -215,6 +216,7 @@ func alerts(w http.ResponseWriter, r *http.Request) {
|
||||
gridLabel, _ := lookupQueryString(r, "gridLabel")
|
||||
q, _ := lookupQueryStringSlice(r, "q")
|
||||
matchFilters := getFiltersFromQuery(q)
|
||||
limits := lookupLoadLimits(r, "limit")
|
||||
|
||||
grids := map[string]models.APIGrid{}
|
||||
colors := models.LabelsColorMap{}
|
||||
@@ -438,16 +440,34 @@ func alerts(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
//resp.AlertGroups = sortAlertGroups(c, alerts)
|
||||
v, _ := lookupQueryString(r, "gridSortReverse")
|
||||
gridSortReverse := v == "1"
|
||||
sortedGrids := sortGrids(r, gridLabel, grids, gridSortReverse)
|
||||
for i := 0; i < len(sortedGrids); i++ {
|
||||
sortedGrids[i].TotalGroups = len(sortedGrids[i].AlertGroups)
|
||||
|
||||
limit, found := limits[sortedGrids[i].LabelValue]
|
||||
if !found {
|
||||
limit = config.Config.Grid.GroupLimit
|
||||
}
|
||||
|
||||
l := sortedGrids[i].TotalGroups
|
||||
if limit < l {
|
||||
l = limit
|
||||
}
|
||||
if l < 1 {
|
||||
l = 1
|
||||
}
|
||||
sortedGrids[i].AlertGroups = sortedGrids[i].AlertGroups[:l]
|
||||
}
|
||||
|
||||
for _, filter := range matchFilters {
|
||||
if filter.GetValue() != "" && filter.GetMatcher() == "=" {
|
||||
transform.ColorLabel(colors, filter.GetName(), filter.GetValue())
|
||||
}
|
||||
}
|
||||
|
||||
//resp.AlertGroups = sortAlertGroups(c, alerts)
|
||||
v, _ := lookupQueryString(r, "gridSortReverse")
|
||||
gridSortReverse := v == "1"
|
||||
|
||||
receivers := []string{}
|
||||
for k := range allReceivers {
|
||||
k := k
|
||||
@@ -455,7 +475,7 @@ func alerts(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
sort.Strings(receivers)
|
||||
|
||||
resp.Grids = sortGrids(r, gridLabel, grids, gridSortReverse)
|
||||
resp.Grids = sortedGrids
|
||||
resp.Silences = silences
|
||||
resp.Colors = colors
|
||||
resp.Counters = countersToLabelStats(counters)
|
||||
|
||||
@@ -965,6 +965,7 @@ func TestEmptySettings(t *testing.T) {
|
||||
Comment: "ACK! This alert was acknowledged using karma on %NOW%",
|
||||
},
|
||||
HistoryEnabled: true,
|
||||
GridGroupLimit: 40,
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(expectedSettings, ur.Settings); diff != "" {
|
||||
@@ -2845,3 +2846,152 @@ func TestAutoGrid(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGridLimit(t *testing.T) {
|
||||
type testCaseT struct {
|
||||
groupLimit int
|
||||
q string
|
||||
groups map[string][]int
|
||||
}
|
||||
|
||||
testCases := []testCaseT{
|
||||
{
|
||||
q: "",
|
||||
groups: map[string][]int{
|
||||
"": {10, 10},
|
||||
},
|
||||
},
|
||||
{
|
||||
groupLimit: 5,
|
||||
q: "",
|
||||
groups: map[string][]int{
|
||||
"": {10, 5},
|
||||
},
|
||||
},
|
||||
{
|
||||
groupLimit: 15,
|
||||
q: "",
|
||||
groups: map[string][]int{
|
||||
"": {10, 10},
|
||||
},
|
||||
},
|
||||
{
|
||||
q: "limit==1",
|
||||
groups: map[string][]int{
|
||||
"": {10, 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
q: "limit",
|
||||
groups: map[string][]int{
|
||||
"": {10, 10},
|
||||
},
|
||||
},
|
||||
{
|
||||
q: "limit=",
|
||||
groups: map[string][]int{
|
||||
"": {10, 10},
|
||||
},
|
||||
},
|
||||
{
|
||||
q: "gridLabel=job&limit=node_exporter=1",
|
||||
groups: map[string][]int{
|
||||
"node_exporter": {6, 1},
|
||||
"node_ping": {4, 4},
|
||||
},
|
||||
},
|
||||
{
|
||||
q: "gridLabel=job&limit=node_exporter=a&limit=node_ping=1",
|
||||
groups: map[string][]int{
|
||||
"node_exporter": {6, 6},
|
||||
"node_ping": {4, 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
q: "gridLabel=job&limit=node_exporter=0&limit=node_ping=2",
|
||||
groups: map[string][]int{
|
||||
"node_exporter": {6, 1},
|
||||
"node_ping": {4, 2},
|
||||
},
|
||||
},
|
||||
{
|
||||
q: "gridLabel=job&limit=node_exporter=0&limit=node_ping=20",
|
||||
groups: map[string][]int{
|
||||
"node_exporter": {6, 1},
|
||||
"node_ping": {4, 4},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
defer func() {
|
||||
config.Config.Grid.GroupLimit = 50
|
||||
}()
|
||||
|
||||
mockConfig()
|
||||
for _, tc := range testCases {
|
||||
if tc.groupLimit > 0 {
|
||||
config.Config.Grid.GroupLimit = tc.groupLimit
|
||||
} else {
|
||||
config.Config.Grid.GroupLimit = 50
|
||||
}
|
||||
for _, version := range mock.ListAllMocks() {
|
||||
t.Logf("Testing grids using mock files from Alertmanager %s", version)
|
||||
mockAlerts(version)
|
||||
r := testRouter()
|
||||
setupRouter(r, nil)
|
||||
// re-run a few times to test the cache
|
||||
for i := 1; i <= 3; i++ {
|
||||
req := httptest.NewRequest("GET", fmt.Sprintf("/alerts.json?%s", tc.q), nil)
|
||||
resp := httptest.NewRecorder()
|
||||
r.ServeHTTP(resp, req)
|
||||
if resp.Code != http.StatusOK {
|
||||
t.Errorf("GET /alerts.json returned status %d", resp.Code)
|
||||
}
|
||||
|
||||
ur := models.AlertsResponse{}
|
||||
err := json.Unmarshal(resp.Body.Bytes(), &ur)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to unmarshal response: %s", err)
|
||||
}
|
||||
if len(ur.Grids) == 0 {
|
||||
t.Errorf("[%s] Got empty grid list", tc.q)
|
||||
}
|
||||
for _, grid := range ur.Grids {
|
||||
if grid.TotalGroups == 0 {
|
||||
t.Errorf("[%s] got empty grid for %s=%s", tc.q, grid.LabelName, grid.LabelValue)
|
||||
}
|
||||
found := false
|
||||
for labelValue := range tc.groups {
|
||||
if grid.LabelValue == labelValue {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("[%s] got extra grid %s=%s", tc.q, grid.LabelName, grid.LabelValue)
|
||||
}
|
||||
}
|
||||
for labelValue, totals := range tc.groups {
|
||||
totalGroups := totals[0]
|
||||
presentGroups := totals[1]
|
||||
found := false
|
||||
for _, grid := range ur.Grids {
|
||||
if grid.LabelValue == labelValue {
|
||||
found = true
|
||||
if grid.TotalGroups != totalGroups {
|
||||
t.Errorf("[%s] grid for label %s=%s returned totalGroups=%d, expected %d", tc.q, grid.LabelName, grid.LabelValue, grid.TotalGroups, totalGroups)
|
||||
}
|
||||
if len(grid.AlertGroups) != presentGroups {
|
||||
t.Errorf("[%s] grid for label %s=%s returned %d alert groups, expected %d", tc.q, grid.LabelName, grid.LabelValue, len(grid.AlertGroups), presentGroups)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("[%s] grid with label value %s missing", tc.q, labelValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -648,6 +648,7 @@ grid:
|
||||
auto:
|
||||
ignore: list of strings
|
||||
order: list of strings
|
||||
groupLimit: integer
|
||||
```
|
||||
|
||||
- `sorting:order` - default sort order for alert grid, valid values are:
|
||||
@@ -678,6 +679,8 @@ grid:
|
||||
source label when multi-grid is configured to `Automatic selection` in the UI
|
||||
or when `ui:multiGridLabel` is set to `@auto`. If a label name is not present
|
||||
in this list labels with equal weight will be picked in alphabetic order.
|
||||
- `groupLimit` - default number of alert groups to show in the UI, loading more
|
||||
will require user to click on `Load more` button.
|
||||
|
||||
Defaults:
|
||||
|
||||
@@ -692,6 +695,7 @@ grid:
|
||||
auto:
|
||||
ignore: []
|
||||
order: []
|
||||
groupLimit: 40
|
||||
```
|
||||
|
||||
Example with sorting using `severity` label and value mappings for it:
|
||||
|
||||
@@ -97,6 +97,7 @@ func SetupFlags(f *pflag.FlagSet) {
|
||||
f.String("grid.sorting.label", "alertname", "Label name to use when sorting alert grid by label")
|
||||
f.StringSlice("grid.auto.ignore", []string{}, "List of label names not allowed for automatic multi-grid")
|
||||
f.StringSlice("grid.auto.order", []string{}, "Order of preference for selecting label names for automatic multi-grid")
|
||||
f.Int("grid.groupLimit", 40, "Default number of groups to show for each grid")
|
||||
|
||||
f.Bool("history.enabled", true, "Enable alert history queries")
|
||||
f.Duration("history.timeout", time.Second*20, "Timeout for history queries against source Prometheus servers")
|
||||
@@ -211,6 +212,8 @@ func readEnvVariables(k *koanf.Koanf) {
|
||||
return "annotations.enableInsecureHTML"
|
||||
case "AUTHENTICATION_HEADER_VALUE_RE":
|
||||
return "authentication.header.value_re"
|
||||
case "GRID_GROUPLIMIT":
|
||||
return "grid.groupLimit"
|
||||
case "SILENCEFORM_STRIP_LABELS":
|
||||
return "silenceForm.strip.labels"
|
||||
case "UI_HIDEFILTERSWHENIDLE":
|
||||
|
||||
@@ -88,6 +88,7 @@ grid:
|
||||
auto:
|
||||
ignore: []
|
||||
order: []
|
||||
groupLimit: 40
|
||||
history:
|
||||
enabled: true
|
||||
workers: 30
|
||||
|
||||
@@ -133,6 +133,7 @@ type configSchema struct {
|
||||
Ignore []string
|
||||
Order []string
|
||||
}
|
||||
GroupLimit int `yaml:"groupLimit"`
|
||||
} `yaml:"grid"`
|
||||
History struct {
|
||||
Enabled bool
|
||||
|
||||
@@ -324,6 +324,7 @@ type Settings struct {
|
||||
SilenceForm SilenceFormSettings `json:"silenceForm"`
|
||||
AlertAcknowledgement AlertAcknowledgementSettings `json:"alertAcknowledgement"`
|
||||
HistoryEnabled bool `json:"historyEnabled"`
|
||||
GridGroupLimit int `json:"gridGroupLimit"`
|
||||
}
|
||||
|
||||
type AuthenticationInfo struct {
|
||||
@@ -335,6 +336,7 @@ type APIGrid struct {
|
||||
LabelName string `json:"labelName"`
|
||||
LabelValue string `json:"labelValue"`
|
||||
AlertGroups []APIAlertGroup `json:"alertGroups"`
|
||||
TotalGroups int `json:"totalGroups"`
|
||||
StateCount map[string]int `json:"stateCount"`
|
||||
}
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ describe("<Fetcher />", () => {
|
||||
const fetchSpy = jest.spyOn(alertStore, "fetchWithThrottle");
|
||||
settingsStore.gridConfig.setSortOrder("default");
|
||||
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", false, "", "", "");
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", false, "", "", "", {});
|
||||
});
|
||||
|
||||
it("calls alertStore.fetchWithThrottle with correct sort arguments when sortOrder=disabled reverseSort=false", () => {
|
||||
@@ -158,7 +158,7 @@ describe("<Fetcher />", () => {
|
||||
settingsStore.gridConfig.setSortOrder("disabled");
|
||||
settingsStore.gridConfig.setSortReverse(false);
|
||||
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", false, "disabled", "", "");
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", false, "disabled", "", "", {});
|
||||
});
|
||||
|
||||
it("calls alertStore.fetchWithThrottle with correct sort arguments when sortOrder=disabled reverseSort=true", () => {
|
||||
@@ -167,7 +167,7 @@ describe("<Fetcher />", () => {
|
||||
settingsStore.gridConfig.setSortOrder("disabled");
|
||||
settingsStore.gridConfig.setSortReverse(true);
|
||||
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", false, "disabled", "", "");
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", false, "disabled", "", "", {});
|
||||
});
|
||||
|
||||
it("calls alertStore.fetchWithThrottle with correct sort arguments when sortOrder=startsAt reverseSort=false", () => {
|
||||
@@ -176,7 +176,7 @@ describe("<Fetcher />", () => {
|
||||
settingsStore.gridConfig.setSortOrder("startsAt");
|
||||
settingsStore.gridConfig.setSortReverse(false);
|
||||
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", false, "startsAt", "", "0");
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", false, "startsAt", "", "0", {});
|
||||
});
|
||||
|
||||
it("calls alertStore.fetchWithThrottle with correct sort arguments when sortOrder=startsAt reverseSort=true", () => {
|
||||
@@ -185,7 +185,7 @@ describe("<Fetcher />", () => {
|
||||
settingsStore.gridConfig.setSortOrder("startsAt");
|
||||
settingsStore.gridConfig.setSortReverse(true);
|
||||
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", false, "startsAt", "", "1");
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", false, "startsAt", "", "1", {});
|
||||
});
|
||||
|
||||
it("calls alertStore.fetchWithThrottle with correct sort arguments when sortOrder=label sortLabel=cluster reverseSort=false", () => {
|
||||
@@ -195,7 +195,14 @@ describe("<Fetcher />", () => {
|
||||
settingsStore.gridConfig.setSortLabel("cluster");
|
||||
settingsStore.gridConfig.setSortReverse(false);
|
||||
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", false, "label", "cluster", "0");
|
||||
expect(fetchSpy).toHaveBeenCalledWith(
|
||||
"",
|
||||
false,
|
||||
"label",
|
||||
"cluster",
|
||||
"0",
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it("calls alertStore.fetchWithThrottle with correct sort arguments when sortOrder=label sortLabel=job reverseSort=true", () => {
|
||||
@@ -205,7 +212,7 @@ describe("<Fetcher />", () => {
|
||||
settingsStore.gridConfig.setSortLabel("job");
|
||||
settingsStore.gridConfig.setSortReverse(true);
|
||||
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", false, "label", "job", "1");
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", false, "label", "job", "1", {});
|
||||
});
|
||||
|
||||
it("calls alertStore.fetchWithThrottle with correct sort arguments when sortOrder=label sortLabel=instance reverseSort=null", () => {
|
||||
@@ -215,7 +222,14 @@ describe("<Fetcher />", () => {
|
||||
settingsStore.gridConfig.setSortLabel("instance");
|
||||
settingsStore.gridConfig.setSortReverse(null);
|
||||
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", false, "label", "instance", "");
|
||||
expect(fetchSpy).toHaveBeenCalledWith(
|
||||
"",
|
||||
false,
|
||||
"label",
|
||||
"instance",
|
||||
"",
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it("calls alertStore.fetchWithThrottle with gridLabel=cluster gridSortReverse=false", () => {
|
||||
@@ -225,7 +239,7 @@ describe("<Fetcher />", () => {
|
||||
settingsStore.multiGridConfig.setGridLabel("cluster");
|
||||
settingsStore.multiGridConfig.setGridSortReverse(false);
|
||||
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
|
||||
expect(fetchSpy).toHaveBeenCalledWith("cluster", false, "", "", "");
|
||||
expect(fetchSpy).toHaveBeenCalledWith("cluster", false, "", "", "", {});
|
||||
});
|
||||
|
||||
it("calls alertStore.fetchWithThrottle with gridLabel=cluster gridSortReverse=true", () => {
|
||||
@@ -235,7 +249,7 @@ describe("<Fetcher />", () => {
|
||||
settingsStore.multiGridConfig.setGridLabel("cluster");
|
||||
settingsStore.multiGridConfig.setGridSortReverse(true);
|
||||
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
|
||||
expect(fetchSpy).toHaveBeenCalledWith("cluster", true, "", "", "");
|
||||
expect(fetchSpy).toHaveBeenCalledWith("cluster", true, "", "", "", {});
|
||||
});
|
||||
|
||||
it("calls alertStore.fetchWithThrottle with gridLabel= gridSortReverse=true", () => {
|
||||
@@ -245,7 +259,19 @@ describe("<Fetcher />", () => {
|
||||
settingsStore.multiGridConfig.setGridLabel("");
|
||||
settingsStore.multiGridConfig.setGridSortReverse(true);
|
||||
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", true, "", "", "");
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", true, "", "", "", {});
|
||||
});
|
||||
|
||||
it("calls alertStore.fetchWithThrottle with limits set", () => {
|
||||
MockEmptyAPIResponseWithoutFilters();
|
||||
const fetchSpy = jest.spyOn(alertStore, "fetchWithThrottle");
|
||||
settingsStore.gridConfig.setSortOrder("default");
|
||||
settingsStore.multiGridConfig.setGridLabel("");
|
||||
settingsStore.multiGridConfig.setGridSortReverse(false);
|
||||
alertStore.ui.setLimit("old", "bar", 10);
|
||||
alertStore.ui.setLimit("foo", "bar", 5);
|
||||
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
|
||||
expect(fetchSpy).toHaveBeenCalledWith("", false, "", "", "", { bar: 5 });
|
||||
});
|
||||
|
||||
it("internal timer is null after unmount", () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useRef, useState, FC } from "react";
|
||||
|
||||
import { reaction } from "mobx";
|
||||
import { reaction, toJS } from "mobx";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import addSeconds from "date-fns/addSeconds";
|
||||
@@ -175,7 +175,10 @@ const Fetcher: FC<{
|
||||
settingsStore.multiGridConfig.config.gridSortReverse,
|
||||
sortSettings.sortOrder,
|
||||
sortSettings.sortLabel,
|
||||
sortSettings.sortReverse
|
||||
sortSettings.sortReverse,
|
||||
Object.values(alertStore.ui.limits).length > 0
|
||||
? toJS(Object.values(alertStore.ui.limits)[0])
|
||||
: {}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -192,14 +195,16 @@ const Fetcher: FC<{
|
||||
.map((f: { raw: string }) => f.raw)
|
||||
.join(" "),
|
||||
grid: {
|
||||
sortOrder: settingsStore.gridConfig.config.sortOrder,
|
||||
sortLabel: settingsStore.gridConfig.config.sortLabel,
|
||||
sortOrder: toJS(settingsStore.gridConfig.config.sortOrder),
|
||||
sortLabel: toJS(settingsStore.gridConfig.config.sortLabel),
|
||||
limits: toJS(alertStore.ui.limits),
|
||||
},
|
||||
multigrid: {
|
||||
gridLabel: settingsStore.multiGridConfig.config.gridLabel,
|
||||
gridSortReverse:
|
||||
settingsStore.multiGridConfig.config.gridSortReverse,
|
||||
reverseSort: settingsStore.gridConfig.config.reverseSort,
|
||||
gridLabel: toJS(settingsStore.multiGridConfig.config.gridLabel),
|
||||
gridSortReverse: toJS(
|
||||
settingsStore.multiGridConfig.config.gridSortReverse
|
||||
),
|
||||
reverseSort: toJS(settingsStore.gridConfig.config.reverseSort),
|
||||
},
|
||||
}),
|
||||
() => {
|
||||
|
||||
@@ -52,8 +52,6 @@ const Grid: FC<{
|
||||
const { ref, repack } = useGrid(gridSizesConfig);
|
||||
const debouncedRepack = useMemo(() => debounce(() => repack(), 10), [repack]);
|
||||
|
||||
const [groupsToRender, setGroupsToRender] = useState<number>(50);
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState<boolean>(
|
||||
!DefaultDetailsCollapseValue(settingsStore)
|
||||
);
|
||||
@@ -105,12 +103,6 @@ const Grid: FC<{
|
||||
};
|
||||
}, [debouncedRepack, onAlertGridCollapseEvent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (groupsToRender > grid.alertGroups.length) {
|
||||
setGroupsToRender(Math.max(50, grid.alertGroups.length));
|
||||
}
|
||||
}, [grid.alertGroups.length, groupsToRender]);
|
||||
|
||||
useEffect(() => {
|
||||
repack();
|
||||
});
|
||||
@@ -144,7 +136,7 @@ const Grid: FC<{
|
||||
>
|
||||
<TransitionGroup component={null} appear enter exit>
|
||||
{isExpanded || grid.labelName === ""
|
||||
? grid.alertGroups.slice(0, groupsToRender).map((group) => (
|
||||
? grid.alertGroups.map((group) => (
|
||||
<CSSTransition
|
||||
key={group.id}
|
||||
classNames={
|
||||
@@ -175,7 +167,7 @@ const Grid: FC<{
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
<TransitionGroup component={null} enter exit>
|
||||
{isExpanded && grid.alertGroups.length > groupsToRender && (
|
||||
{isExpanded && grid.totalGroups > grid.alertGroups.length && (
|
||||
<CSSTransition
|
||||
classNames="components-animation-fade"
|
||||
timeout={context.animations.duration}
|
||||
@@ -186,11 +178,14 @@ const Grid: FC<{
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary mb-3"
|
||||
onClick={() =>
|
||||
setGroupsToRender(
|
||||
Math.min(groupsToRender + 30, grid.alertGroups.length)
|
||||
)
|
||||
}
|
||||
onClick={() => {
|
||||
alertStore.ui.setLimit(
|
||||
grid.labelName,
|
||||
grid.labelValue,
|
||||
grid.alertGroups.length +
|
||||
alertStore.settings.values.gridGroupLimit
|
||||
);
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon className="me-2" icon={faAngleDoubleDown} />
|
||||
Load more
|
||||
|
||||
@@ -26,6 +26,7 @@ beforeEach(() => {
|
||||
labelName: "foo",
|
||||
labelValue: "bar",
|
||||
alertGroups: [],
|
||||
totalGroups: 0,
|
||||
stateCount: {
|
||||
active: 0,
|
||||
suppressed: 0,
|
||||
|
||||
@@ -94,6 +94,9 @@ const MockGrid = () => ({
|
||||
alertGroups: alertStore.data.grids.length
|
||||
? alertStore.data.grids[0].alertGroups
|
||||
: [],
|
||||
totalGroups: alertStore.data.grids.length
|
||||
? alertStore.data.grids[0].alertGroups.length
|
||||
: 0,
|
||||
stateCount: {
|
||||
unprocessed: 1,
|
||||
suppressed: 2,
|
||||
@@ -148,7 +151,11 @@ const MockGroup = (groupName: string, alertCount: number) => {
|
||||
return group;
|
||||
};
|
||||
|
||||
const MockGroupList = (count: number, alertPerGroup: number) => {
|
||||
const MockGroupList = (
|
||||
count: number,
|
||||
alertPerGroup: number,
|
||||
totalGroups?: number
|
||||
) => {
|
||||
const groups = [];
|
||||
for (let i = 1; i <= count; i++) {
|
||||
const id = `id${i}`;
|
||||
@@ -179,6 +186,7 @@ const MockGroupList = (count: number, alertPerGroup: number) => {
|
||||
labelName: "",
|
||||
labelValue: "",
|
||||
alertGroups: groups,
|
||||
totalGroups: totalGroups ? totalGroups : groups.length,
|
||||
stateCount: {
|
||||
unprocessed: 1,
|
||||
suppressed: 2,
|
||||
@@ -208,19 +216,69 @@ describe("<Grid />", () => {
|
||||
).not.toMatch(/animate components-animation-fade-appear/);
|
||||
});
|
||||
|
||||
it("renders only first 50 alert groups", () => {
|
||||
it("renders all alert groups", () => {
|
||||
MockGroupList(55, 5);
|
||||
const tree = MountedGrid();
|
||||
const alertGroups = tree.find("AlertGroup");
|
||||
expect(alertGroups).toHaveLength(50);
|
||||
expect(alertGroups).toHaveLength(55);
|
||||
});
|
||||
|
||||
it("appends 30 groups after clicking 'Load More' button", () => {
|
||||
MockGroupList(85, 5);
|
||||
const tree = MountedGrid();
|
||||
it("appends more groups after clicking 'Load More' button", () => {
|
||||
MockGroupList(40, 5, 70);
|
||||
const tree = mount(
|
||||
<Grid
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
settingsStore={settingsStore}
|
||||
gridSizesConfig={GridSizesConfig(420)}
|
||||
groupWidth={420}
|
||||
grid={alertStore.data.grids[0]}
|
||||
outerPadding={0}
|
||||
/>,
|
||||
{
|
||||
wrappingComponent: ThemeContext.Provider,
|
||||
wrappingComponentProps: { value: MockThemeContext },
|
||||
}
|
||||
);
|
||||
tree.find("button").simulate("click");
|
||||
const alertGroups = tree.find("AlertGroup");
|
||||
expect(alertGroups).toHaveLength(80);
|
||||
expect(alertStore.ui.limits).toStrictEqual({
|
||||
"": { "": 40 + alertStore.settings.values.gridGroupLimit },
|
||||
});
|
||||
});
|
||||
|
||||
it("sets correct limits after clicking 'Load More' button", () => {
|
||||
MockGroupList(50, 5, 60);
|
||||
alertStore.settings.setValues({
|
||||
...alertStore.settings.values,
|
||||
gridGroupLimit: 20,
|
||||
});
|
||||
alertStore.data.setGrids([
|
||||
{
|
||||
...alertStore.data.grids[0],
|
||||
labelName: "foo",
|
||||
labelValue: "bar",
|
||||
totalGroups: 69,
|
||||
},
|
||||
]);
|
||||
const tree = mount(
|
||||
<Grid
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
settingsStore={settingsStore}
|
||||
gridSizesConfig={GridSizesConfig(420)}
|
||||
groupWidth={420}
|
||||
grid={alertStore.data.grids[0]}
|
||||
outerPadding={0}
|
||||
/>,
|
||||
{
|
||||
wrappingComponent: ThemeContext.Provider,
|
||||
wrappingComponentProps: { value: MockThemeContext },
|
||||
}
|
||||
);
|
||||
tree.find("button").simulate("click");
|
||||
expect(alertStore.ui.limits).toStrictEqual({
|
||||
foo: { bar: 70 },
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't sort groups when sorting is set to 'disabled'", () => {
|
||||
@@ -338,6 +396,7 @@ describe("<Grid />", () => {
|
||||
labelName: "foo",
|
||||
labelValue: "bar",
|
||||
alertGroups: groups.slice(0, 10),
|
||||
totalGroups: groups.slice(0, 10).length,
|
||||
stateCount: {
|
||||
unprocessed: 1,
|
||||
suppressed: 2,
|
||||
@@ -348,6 +407,7 @@ describe("<Grid />", () => {
|
||||
labelName: "foo",
|
||||
labelValue: "",
|
||||
alertGroups: groups.slice(10, 20),
|
||||
totalGroups: groups.slice(10, 20).length,
|
||||
stateCount: {
|
||||
unprocessed: 1,
|
||||
suppressed: 2,
|
||||
@@ -591,6 +651,7 @@ describe("<AlertGrid />", () => {
|
||||
labelName: "foo",
|
||||
labelValue: "bar",
|
||||
alertGroups: groups,
|
||||
totalGroups: groups.length,
|
||||
stateCount: {
|
||||
unprocessed: 1,
|
||||
suppressed: 2,
|
||||
@@ -601,6 +662,7 @@ describe("<AlertGrid />", () => {
|
||||
labelName: "foo",
|
||||
labelValue: "",
|
||||
alertGroups: groups,
|
||||
totalGroups: groups.length,
|
||||
stateCount: {
|
||||
unprocessed: 1,
|
||||
suppressed: 2,
|
||||
@@ -629,6 +691,7 @@ describe("<AlertGrid />", () => {
|
||||
labelName: "foo",
|
||||
labelValue: "bar",
|
||||
alertGroups: groups,
|
||||
totalGroups: groups.length,
|
||||
stateCount: {
|
||||
unprocessed: 0,
|
||||
suppressed: 0,
|
||||
@@ -639,6 +702,7 @@ describe("<AlertGrid />", () => {
|
||||
labelName: "foo",
|
||||
labelValue: "",
|
||||
alertGroups: groups,
|
||||
totalGroups: groups.length,
|
||||
stateCount: {
|
||||
unprocessed: 0,
|
||||
suppressed: 0,
|
||||
@@ -672,6 +736,7 @@ describe("<AlertGrid />", () => {
|
||||
labelName: "",
|
||||
labelValue: "",
|
||||
alertGroups: groups,
|
||||
totalGroups: groups.length,
|
||||
stateCount: {
|
||||
unprocessed: 0,
|
||||
suppressed: 0,
|
||||
|
||||
@@ -56,6 +56,7 @@ const MockAPIResponse = () => {
|
||||
{}
|
||||
),
|
||||
],
|
||||
totalGroups: 2,
|
||||
stateCount: {
|
||||
unprocessed: 1,
|
||||
suppressed: 2,
|
||||
|
||||
@@ -91,6 +91,7 @@ export interface APIGridT {
|
||||
labelName: string;
|
||||
labelValue: string;
|
||||
alertGroups: APIAlertGroupT[];
|
||||
totalGroups: number;
|
||||
stateCount: StateCountT;
|
||||
}
|
||||
|
||||
@@ -189,6 +190,7 @@ export interface APISettingsT {
|
||||
comment: string;
|
||||
};
|
||||
historyEnabled: boolean;
|
||||
gridGroupLimit: number;
|
||||
}
|
||||
|
||||
export interface APIAlertsResponseT {
|
||||
|
||||
@@ -544,7 +544,9 @@ describe("AlertStore.fetch", () => {
|
||||
});
|
||||
|
||||
const store = new AlertStore(["label=value"]);
|
||||
await expect(store.fetch("", false, "", "", "")).resolves.toBeUndefined();
|
||||
await expect(
|
||||
store.fetch("", false, "", "", "", {})
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(fetchMock.calls()).toHaveLength(1);
|
||||
expect(store.status.value).toEqual(AlertStoreStatuses.Idle);
|
||||
@@ -558,7 +560,9 @@ describe("AlertStore.fetch", () => {
|
||||
});
|
||||
|
||||
const store = new AlertStore([]);
|
||||
await expect(store.fetch("", false, "", "", "")).resolves.toBeUndefined();
|
||||
await expect(
|
||||
store.fetch("", false, "", "", "", {})
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(fetchMock.calls()).toHaveLength(1);
|
||||
expect(store.status.value).toEqual(AlertStoreStatuses.Failure);
|
||||
@@ -575,9 +579,9 @@ describe("AlertStore.fetch", () => {
|
||||
});
|
||||
|
||||
const store = new AlertStore([]);
|
||||
await expect(store.fetch("", false, "", "", "")).resolves.toHaveProperty(
|
||||
"error"
|
||||
);
|
||||
await expect(
|
||||
store.fetch("", false, "", "", "", {})
|
||||
).resolves.toHaveProperty("error");
|
||||
|
||||
expect(fetchMock.calls()).toHaveLength(10);
|
||||
expect(store.status.value).toEqual(AlertStoreStatuses.Failure);
|
||||
@@ -595,9 +599,9 @@ describe("AlertStore.fetch", () => {
|
||||
throws: new Error("fetch error"),
|
||||
});
|
||||
|
||||
await expect(store.fetch("", false, "", "", "")).resolves.toHaveProperty(
|
||||
"error"
|
||||
);
|
||||
await expect(
|
||||
store.fetch("", false, "", "", "", {})
|
||||
).resolves.toHaveProperty("error");
|
||||
expect(fetchMock.calls()).toHaveLength(10);
|
||||
});
|
||||
|
||||
@@ -610,9 +614,9 @@ describe("AlertStore.fetch", () => {
|
||||
throws: new Error("fetch error"),
|
||||
});
|
||||
|
||||
await expect(store.fetch("", false, "", "", "")).resolves.toHaveProperty(
|
||||
"error"
|
||||
);
|
||||
await expect(
|
||||
store.fetch("", false, "", "", "", {})
|
||||
).resolves.toHaveProperty("error");
|
||||
expect(fetchMock.calls()).toHaveLength(10);
|
||||
|
||||
const response = EmptyAPIResponse();
|
||||
@@ -621,7 +625,9 @@ describe("AlertStore.fetch", () => {
|
||||
body: JSON.stringify(response),
|
||||
});
|
||||
|
||||
await expect(store.fetch("", false, "", "", "")).resolves.toBeUndefined();
|
||||
await expect(
|
||||
store.fetch("", false, "", "", "", {})
|
||||
).resolves.toBeUndefined();
|
||||
expect(fetchMock.calls()).toHaveLength(1);
|
||||
|
||||
fetchMock.reset();
|
||||
@@ -629,9 +635,9 @@ describe("AlertStore.fetch", () => {
|
||||
throws: new Error("fetch error"),
|
||||
});
|
||||
|
||||
await expect(store.fetch("", false, "", "", "")).resolves.toHaveProperty(
|
||||
"error"
|
||||
);
|
||||
await expect(
|
||||
store.fetch("", false, "", "", "", {})
|
||||
).resolves.toHaveProperty("error");
|
||||
expect(fetchMock.calls()).toHaveLength(10);
|
||||
});
|
||||
|
||||
@@ -649,7 +655,9 @@ describe("AlertStore.fetch", () => {
|
||||
}) as any
|
||||
);
|
||||
|
||||
await expect(store.fetch("", false, "", "", "")).resolves.toBeUndefined();
|
||||
await expect(
|
||||
store.fetch("", false, "", "", "", {})
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(store.info.reloadNeeded).toBe(true);
|
||||
});
|
||||
@@ -664,9 +672,9 @@ describe("AlertStore.fetch", () => {
|
||||
throws: new Error("fetch error"),
|
||||
});
|
||||
|
||||
await expect(store.fetch("", false, "", "", "")).resolves.toHaveProperty(
|
||||
"error"
|
||||
);
|
||||
await expect(
|
||||
store.fetch("", false, "", "", "", {})
|
||||
).resolves.toHaveProperty("error");
|
||||
expect(store.filters.values[0].applied).toBe(true);
|
||||
});
|
||||
|
||||
@@ -681,7 +689,9 @@ describe("AlertStore.fetch", () => {
|
||||
|
||||
// initial fetch, should update settings
|
||||
store.settings.setValues({ foo: "bar" } as any);
|
||||
await expect(store.fetch("", false, "", "", "")).resolves.toBeUndefined();
|
||||
await expect(
|
||||
store.fetch("", false, "", "", "", {})
|
||||
).resolves.toBeUndefined();
|
||||
expect(store.settings.values).toMatchObject({
|
||||
staticColorLabels: ["job"],
|
||||
annotationsDefaultHidden: false,
|
||||
@@ -691,7 +701,9 @@ describe("AlertStore.fetch", () => {
|
||||
});
|
||||
|
||||
// second fetch, should keep same settings
|
||||
await expect(store.fetch("", false, "", "", "")).resolves.toBeUndefined();
|
||||
await expect(
|
||||
store.fetch("", false, "", "", "", {})
|
||||
).resolves.toBeUndefined();
|
||||
expect(store.settings.values).toMatchObject({
|
||||
staticColorLabels: ["job"],
|
||||
annotationsDefaultHidden: false,
|
||||
@@ -708,7 +720,9 @@ describe("AlertStore.fetch", () => {
|
||||
body: JSON.stringify(response),
|
||||
});
|
||||
const store = new AlertStore(["label=value"]);
|
||||
await expect(store.fetch("", false, "", "", "")).resolves.toBeUndefined();
|
||||
await expect(
|
||||
store.fetch("", false, "", "", "", {})
|
||||
).resolves.toBeUndefined();
|
||||
expect(store.info.upgradeReady).toBe(false);
|
||||
|
||||
response.version = "newFakeVersion";
|
||||
@@ -716,7 +730,9 @@ describe("AlertStore.fetch", () => {
|
||||
fetchMock.mock("*", {
|
||||
body: JSON.stringify(response),
|
||||
});
|
||||
await expect(store.fetch("", false, "", "", "")).resolves.toBeUndefined();
|
||||
await expect(
|
||||
store.fetch("", false, "", "", "", {})
|
||||
).resolves.toBeUndefined();
|
||||
expect(store.info.upgradeReady).toBe(true);
|
||||
});
|
||||
|
||||
@@ -729,6 +745,7 @@ describe("AlertStore.fetch", () => {
|
||||
labelName: "",
|
||||
labelValue: "",
|
||||
alertGroups: [g1, g2],
|
||||
totalGroups: 2,
|
||||
stateCount: { unprocessed: 0, active: 2, suppressed: 0 },
|
||||
},
|
||||
];
|
||||
@@ -749,6 +766,7 @@ describe("AlertStore.fetch", () => {
|
||||
labelName: "",
|
||||
labelValue: "",
|
||||
alertGroups: [g1, g2, g3],
|
||||
totalGroups: 3,
|
||||
stateCount: { unprocessed: 0, active: 3, suppressed: 0 },
|
||||
},
|
||||
]);
|
||||
@@ -761,6 +779,7 @@ describe("AlertStore.fetch", () => {
|
||||
labelName: "",
|
||||
labelValue: "",
|
||||
alertGroups: [g1, g3],
|
||||
totalGroups: 2,
|
||||
stateCount: { unprocessed: 0, active: 2, suppressed: 0 },
|
||||
},
|
||||
];
|
||||
@@ -778,7 +797,7 @@ describe("AlertStore.fetch", () => {
|
||||
});
|
||||
const store = new AlertStore(["label=value"]);
|
||||
await expect(
|
||||
store.fetch("", false, "sortOrder", "sortLabel", "sortReverse")
|
||||
store.fetch("", false, "sortOrder", "sortLabel", "sortReverse", {})
|
||||
).resolves.toBeUndefined();
|
||||
expect(fetchMock.calls().length).toEqual(1);
|
||||
expect(fetchMock.calls()[0][0]).toBe(
|
||||
@@ -794,11 +813,30 @@ describe("AlertStore.fetch", () => {
|
||||
});
|
||||
const store = new AlertStore(["label=value"]);
|
||||
await expect(
|
||||
store.fetch("cluster", true, "sortOrder", "sortLabel", "sortReverse")
|
||||
store.fetch("cluster", true, "sortOrder", "sortLabel", "sortReverse", {})
|
||||
).resolves.toBeUndefined();
|
||||
expect(fetchMock.calls().length).toEqual(1);
|
||||
expect(fetchMock.calls()[0][0]).toBe(
|
||||
"/alerts.json?&gridLabel=cluster&gridSortReverse=1&sortOrder=sortOrder&sortLabel=sortLabel&sortReverse=sortReverse&q=label%3Dvalue"
|
||||
);
|
||||
});
|
||||
|
||||
it("uses correct query args with limits", async () => {
|
||||
const response = EmptyAPIResponse();
|
||||
fetchMock.reset();
|
||||
fetchMock.mock("*", {
|
||||
body: JSON.stringify(response),
|
||||
});
|
||||
const store = new AlertStore(["label=value"]);
|
||||
store.ui.setLimit("foo", "bar", 7);
|
||||
await expect(
|
||||
store.fetch("cluster", true, "sortOrder", "sortLabel", "sortReverse", {
|
||||
bar: 7,
|
||||
})
|
||||
).resolves.toBeUndefined();
|
||||
expect(fetchMock.calls().length).toEqual(1);
|
||||
expect(fetchMock.calls()[0][0]).toBe(
|
||||
"/alerts.json?&gridLabel=cluster&gridSortReverse=1&sortOrder=sortOrder&sortLabel=sortLabel&sortReverse=sortReverse&limit=bar=7&q=label%3Dvalue"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -207,6 +207,8 @@ interface AlertStoreStatusT {
|
||||
interface AlertStoreUIT {
|
||||
isIdle: boolean;
|
||||
setIsIdle: (val: boolean) => void;
|
||||
limits: { [key: string]: { [val: string]: number } };
|
||||
setLimit: (key: string, val: string, limit: number) => void;
|
||||
}
|
||||
|
||||
class AlertStore {
|
||||
@@ -474,6 +476,7 @@ class AlertStore {
|
||||
comment: "ACK! This alert was acknowledged using karma",
|
||||
},
|
||||
historyEnabled: true,
|
||||
gridGroupLimit: 40,
|
||||
} as APISettingsT,
|
||||
setValues(v: APISettingsT) {
|
||||
this.values = v;
|
||||
@@ -548,9 +551,14 @@ class AlertStore {
|
||||
setIsIdle(val: boolean) {
|
||||
this.isIdle = val;
|
||||
},
|
||||
limits: {} as { [key: string]: { [val: string]: number } },
|
||||
setLimit(key: string, val: string, limit: number) {
|
||||
this.limits = { [key]: { ...this.limits[key], [val]: limit } };
|
||||
},
|
||||
},
|
||||
{
|
||||
setIsIdle: action.bound,
|
||||
setLimit: action.bound,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -563,17 +571,21 @@ class AlertStore {
|
||||
gridSortReverse: boolean,
|
||||
sortOrder: string,
|
||||
sortLabel: string,
|
||||
sortReverse: string
|
||||
sortReverse: string,
|
||||
limits: { [key: string]: number }
|
||||
) => {
|
||||
this.status.setFetching();
|
||||
|
||||
const args = [
|
||||
const args: string[] = [
|
||||
`gridLabel=${gridLabel}`,
|
||||
`gridSortReverse=${gridSortReverse ? "1" : "0"}`,
|
||||
`sortOrder=${sortOrder}`,
|
||||
`sortLabel=${sortLabel}`,
|
||||
`sortReverse=${sortReverse}`,
|
||||
];
|
||||
Object.entries(limits)
|
||||
.map(([key, value]) => `limit=${key}=${value}`)
|
||||
.join("&"),
|
||||
].filter((arg) => arg !== "");
|
||||
|
||||
const alertsURI =
|
||||
FormatBackendURI(`alerts.json?&${args.join("&")}&`) +
|
||||
|
||||
@@ -81,6 +81,7 @@ const EmptyAPIResponse = (): APIAlertsResponseT => ({
|
||||
annotationsVisible: [],
|
||||
annotationsEnableHTML: false,
|
||||
historyEnabled: true,
|
||||
gridGroupLimit: 40,
|
||||
},
|
||||
authentication: {
|
||||
username: "",
|
||||
@@ -103,6 +104,7 @@ const MockAPIResponse = (): APIAlertsResponseT => {
|
||||
{}
|
||||
),
|
||||
],
|
||||
totalGroups: 1,
|
||||
stateCount: {
|
||||
unprocessed: 1,
|
||||
suppressed: 2,
|
||||
|
||||
@@ -301,6 +301,7 @@ const MockGrid = (alertStore: AlertStore): void => {
|
||||
labelName: "cluster",
|
||||
labelValue: "prod",
|
||||
alertGroups: groups.slice(0, 7),
|
||||
totalGroups: groups.slice(0, 7).length,
|
||||
stateCount: {
|
||||
unprocessed: 1,
|
||||
suppressed: 2,
|
||||
@@ -311,6 +312,7 @@ const MockGrid = (alertStore: AlertStore): void => {
|
||||
labelName: "cluster",
|
||||
labelValue: "",
|
||||
alertGroups: groups.slice(7, 11),
|
||||
totalGroups: groups.slice(7, 11).length,
|
||||
stateCount: {
|
||||
unprocessed: 0,
|
||||
suppressed: 10,
|
||||
|
||||
Reference in New Issue
Block a user