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`] = ` +" + +" +`; 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 ? ( - - ) : ( -

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.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==