diff --git a/ui/package.json b/ui/package.json
index e58926fa2..86dd9ce19 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -34,6 +34,7 @@
"react-highlighter": "0.4.3",
"react-idle-timer": "4.0.9",
"react-input-range": "1.3.0",
+ "react-js-pagination": "3.0.2",
"react-json-pretty": "1.7.9",
"react-linkify": "0.2.2",
"react-masonry-infinite": "1.2.2",
diff --git a/ui/src/Components/LabelSetList/__snapshots__/index.test.js.snap b/ui/src/Components/LabelSetList/__snapshots__/index.test.js.snap
new file mode 100644
index 000000000..03fa35c73
--- /dev/null
+++ b/ui/src/Components/LabelSetList/__snapshots__/index.test.js.snap
@@ -0,0 +1,28 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` matches snapshot with populated list 1`] = `
+"
+
+ -
+
+ foo: bar
+
+
+ -
+
+ job: node_exporter
+
+
+ -
+
+ instance: server1
+
+
+ -
+
+ cluster: prod
+
+
+
+"
+`;
diff --git a/ui/src/Components/LabelSetList/index.js b/ui/src/Components/LabelSetList/index.js
index 758ece060..54b4c9cb2 100644
--- a/ui/src/Components/LabelSetList/index.js
+++ b/ui/src/Components/LabelSetList/index.js
@@ -1,8 +1,17 @@
-import React from "react";
+import React, { Component } from "react";
import PropTypes from "prop-types";
+import { observer } from "mobx-react";
+import { observable, action } from "mobx";
+
import hash from "object-hash";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faChevronLeft } from "@fortawesome/free-solid-svg-icons/faChevronLeft";
+import { faChevronRight } from "@fortawesome/free-solid-svg-icons/faChevronRight";
+
+import Pagination from "react-js-pagination";
+
import { AlertStore } from "Stores/AlertStore";
import { StaticLabel } from "Components/Labels/StaticLabel";
@@ -25,29 +34,78 @@ const GroupListToUniqueLabelsList = groups => {
return Object.values(alerts);
};
-// used in new silence form preview stage and when deleting silences
-const LabelSetList = ({ alertStore, labelsList }) =>
- labelsList.length > 0 ? (
-
- {labelsList.map(labels => (
- -
- {Object.entries(labels).map(([name, value]) => (
-
- ))}
-
- ))}
-
- ) : (
- No alerts matched
- );
-LabelSetList.propTypes = {
- alertStore: PropTypes.instanceOf(AlertStore).isRequired,
- labelsList: PropTypes.arrayOf(PropTypes.object).isRequired
-};
+const LabelSetList = observer(
+ class LabelSetList extends Component {
+ static propTypes = {
+ alertStore: PropTypes.instanceOf(AlertStore).isRequired,
+ labelsList: PropTypes.arrayOf(PropTypes.object).isRequired
+ };
+
+ maxPerPage = 10;
+
+ pagination = observable(
+ {
+ activePage: 1,
+ onPageChange(pageNumber) {
+ this.activePage = pageNumber;
+ }
+ },
+ {
+ onPageChange: action.bound
+ }
+ );
+
+ render() {
+ const { alertStore, labelsList } = this.props;
+
+ return labelsList.length > 0 ? (
+
+
+ {labelsList
+ .slice(
+ (this.pagination.activePage - 1) * this.maxPerPage,
+ this.pagination.activePage * this.maxPerPage
+ )
+ .map(labels => (
+ -
+ {Object.entries(labels).map(([name, value]) => (
+
+ ))}
+
+ ))}
+
+ {labelsList.length > this.maxPerPage ? (
+
+
}
+ nextPageText={
}
+ />
+
+ ) : null}
+
+ ) : (
+ No alerts matched
+ );
+ }
+ }
+);
export { LabelSetList, GroupListToUniqueLabelsList };
diff --git a/ui/src/Components/LabelSetList/index.test.js b/ui/src/Components/LabelSetList/index.test.js
new file mode 100644
index 000000000..0860bf03d
--- /dev/null
+++ b/ui/src/Components/LabelSetList/index.test.js
@@ -0,0 +1,66 @@
+import React from "react";
+
+import { mount } from "enzyme";
+
+import toDiffableHtml from "diffable-html";
+
+import { AlertStore } from "Stores/AlertStore";
+import { LabelSetList } from ".";
+
+let alertStore;
+
+beforeEach(() => {
+ alertStore = new AlertStore([]);
+});
+
+const MountedLabelSetList = labelsList => {
+ return mount(
+
+ );
+};
+
+describe("", () => {
+ it("renders placeholder on empty list", () => {
+ const tree = MountedLabelSetList([]);
+ expect(tree.text()).toBe("No alerts matched");
+ });
+
+ it("renders labels on populated list", () => {
+ const tree = MountedLabelSetList([{ foo: "bar" }]);
+ expect(tree.text()).not.toBe("No alerts matched");
+ expect(tree.text()).toBe("foo: bar");
+ });
+
+ it("matches snapshot with populated list", () => {
+ const tree = MountedLabelSetList([
+ { foo: "bar" },
+ { job: "node_exporter" },
+ { instance: "server1" },
+ { cluster: "prod" }
+ ]);
+ expect(toDiffableHtml(tree.html())).toMatchSnapshot();
+ });
+
+ it("doesn't render pagination when list has 9 elements", () => {
+ const tree = MountedLabelSetList(
+ Array.from(Array(9), (_, i) => ({ instance: `server${i}` }))
+ );
+ expect(tree.find(".pagination")).toHaveLength(0);
+ });
+
+ it("renders pagination when list has 11 elements", () => {
+ const tree = MountedLabelSetList(
+ Array.from(Array(11), (_, i) => ({ instance: `server${i}` }))
+ );
+ expect(tree.find(".pagination")).toHaveLength(1);
+ });
+
+ it("clicking on pagination changes displayed elements", () => {
+ const tree = MountedLabelSetList(
+ Array.from(Array(21), (_, i) => ({ instance: `server${i + 1}` }))
+ );
+ const pageLink = tree.find(".page-link").at(2);
+ pageLink.simulate("click");
+ expect(tree.text()).toBe("instance: server21");
+ });
+});
diff --git a/ui/yarn.lock b/ui/yarn.lock
index 3bf80ab91..f1db16bb3 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -7875,6 +7875,11 @@ p-try@^2.0.0:
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1"
integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==
+paginator@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/paginator/-/paginator-1.0.0.tgz#7565702af9ab9616dca61fc22c70eba2a4357265"
+ integrity sha1-dWVwKvmrlhbcph/CLHDroqQ1cmU=
+
pako@~1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"
@@ -8838,7 +8843,7 @@ prompts@^0.1.9:
kleur "^2.0.1"
sisteransi "^0.1.1"
-prop-types@15.6.2, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2:
+prop-types@15.6.2, "prop-types@15.x.x - 16.x.x", prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2:
version "15.6.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==
@@ -9149,6 +9154,16 @@ react-is@^16.5.2:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3"
integrity sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ==
+react-js-pagination@3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/react-js-pagination/-/react-js-pagination-3.0.2.tgz#003fead038c1be3eaeaf2895905b63daadb1824f"
+ integrity sha512-gsXaQVis1YDKhbeZC7VT9dsSWyZ8GZNEX06dy2HLl36LohY2Vt/iWJ5k6HAb0YOKW0mklCY7wjxJIfJeeP295g==
+ dependencies:
+ classnames "^2.2.5"
+ paginator "^1.0.0"
+ prop-types "15.x.x - 16.x.x"
+ react "15.x.x - 16.x.x"
+
react-json-pretty@1.7.9:
version "1.7.9"
resolved "https://registry.yarnpkg.com/react-json-pretty/-/react-json-pretty-1.7.9.tgz#d153c52579fda9e245c81fceb3da22fdf1d93c1e"
@@ -9321,7 +9336,7 @@ react-transition-group@2.5.0, react-transition-group@^2.2.1:
prop-types "^15.6.2"
react-lifecycles-compat "^3.0.4"
-react@16.5.2:
+"react@15.x.x - 16.x.x", react@16.5.2:
version "16.5.2"
resolved "https://registry.yarnpkg.com/react/-/react-16.5.2.tgz#19f6b444ed139baa45609eee6dc3d318b3895d42"
integrity sha512-FDCSVd3DjVTmbEAjUNX6FgfAmQ+ypJfHUsqUJOYNCBUp1h8lqmtC+0mXJ+JjsWx4KAVTkk1vKd1hLQPvEviSuw==