From 630db7a37c55e4af4f6174b8bfcb3b592afe1b8a Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Thu, 9 Jan 2020 16:02:25 +0200 Subject: [PATCH 01/20] ui: Upgrade React version and react-dependent libraries With current changes, react is upgraded to latest version (16.12.0) and following libraries are upgraded as well: react-redux, redux, redux-saga. These upgrades of major versions has required to do some code adjustments to properly migrate to newest versions. - redux. Provide valid type arguments for Dispatch type. - redux-saga. Changed import and usage of 'delay' function. - react-redux. `connect` function failed to correctly resolve returned type for `mapDispatchToProps` argument. As result it is provided as a function instead of object argument. This hack allows implicitly resolve types. - `CachedDataReducer` class is extended with one more optional type argument to specify the list of allowed `actionNamespace` literals instead of `string`. It was necessary to have strictly defined CachedDataReducer interface. - `normalizeConnectedComponent` function is used to do simple mapping from react's ExoticComponent to valid react component. This resolves issue of providing ConnectedComponents (which are ExoticComponent's, not regular react components) to Route component which has strict type validation of component types. Release note: None --- .../containers/map/nodeCanvasContainer.tsx | 15 +++++---------- .../cluster/containers/dataDistribution/index.tsx | 4 ++-- .../src/views/cluster/containers/events/index.tsx | 8 ++++---- .../views/cluster/containers/nodeGraphs/index.tsx | 4 ++-- .../views/cluster/containers/nodeLogs/index.tsx | 4 ++-- .../cluster/containers/nodeOverview/index.tsx | 4 ++-- .../cluster/containers/nodesOverview/index.tsx | 4 ++-- .../views/cluster/containers/timescale/index.tsx | 4 ++-- pkg/ui/src/views/statements/statementsPage.tsx | 4 ++-- 9 files changed, 23 insertions(+), 28 deletions(-) diff --git a/pkg/ui/ccl/src/views/clusterviz/containers/map/nodeCanvasContainer.tsx b/pkg/ui/ccl/src/views/clusterviz/containers/map/nodeCanvasContainer.tsx index 3870ec3097e5..3de9e86f4abf 100644 --- a/pkg/ui/ccl/src/views/clusterviz/containers/map/nodeCanvasContainer.tsx +++ b/pkg/ui/ccl/src/views/clusterviz/containers/map/nodeCanvasContainer.tsx @@ -11,7 +11,6 @@ import React from "react"; import { connect } from "react-redux"; import { withRouter, WithRouterProps } from "react-router"; import { createSelector } from "reselect"; -import { Action, bindActionCreators, Dispatch } from "redux"; import { cockroach } from "src/js/protos"; import { refreshNodes, refreshLiveness, refreshLocations } from "src/redux/apiReducers"; @@ -121,13 +120,9 @@ export default withRouter(connect( dataExists: selectDataExists(state), dataErrors: dataErrors(state), }), - (dispatch: Dispatch) => - bindActionCreators( - { - refreshNodes, - refreshLiveness, - refreshLocations, - }, - dispatch, - ), + { + refreshNodes, + refreshLiveness, + refreshLocations, + }, )(NodeCanvasContainer)); diff --git a/pkg/ui/src/views/cluster/containers/dataDistribution/index.tsx b/pkg/ui/src/views/cluster/containers/dataDistribution/index.tsx index 48945f38d65f..333363315d0a 100644 --- a/pkg/ui/src/views/cluster/containers/dataDistribution/index.tsx +++ b/pkg/ui/src/views/cluster/containers/dataDistribution/index.tsx @@ -211,11 +211,11 @@ const DataDistributionPageConnected = connect( localityTree: selectLocalityTree(state), localityTreeErrors: localityTreeErrors(state), }), - { + () => ({ refreshDataDistribution, refreshNodes, refreshLiveness, - }, + }), )(DataDistributionPage); export default DataDistributionPageConnected; diff --git a/pkg/ui/src/views/cluster/containers/events/index.tsx b/pkg/ui/src/views/cluster/containers/events/index.tsx index 5289dfad25d0..4f6b7f3315e5 100644 --- a/pkg/ui/src/views/cluster/containers/events/index.tsx +++ b/pkg/ui/src/views/cluster/containers/events/index.tsx @@ -176,9 +176,9 @@ const eventBoxConnected = connect( eventsValid: eventsValidSelector(state), }; }, - { + () => ({ refreshEvents, - }, + }), )(EventBoxUnconnected); // Connect the EventsList class with our redux store. @@ -190,10 +190,10 @@ const eventPageConnected = connect( sortSetting: eventsSortSetting.selector(state), }; }, - { + () => ({ refreshEvents, setSort: eventsSortSetting.set, - }, + }), )(EventPageUnconnected); export { eventBoxConnected as EventBox }; diff --git a/pkg/ui/src/views/cluster/containers/nodeGraphs/index.tsx b/pkg/ui/src/views/cluster/containers/nodeGraphs/index.tsx index d8b1177ca40d..b63498ef7f60 100644 --- a/pkg/ui/src/views/cluster/containers/nodeGraphs/index.tsx +++ b/pkg/ui/src/views/cluster/containers/nodeGraphs/index.tsx @@ -263,10 +263,10 @@ export default connect( hoverState: hoverStateSelector(state), }; }, - { + () => ({ refreshNodes, refreshLiveness, hoverOn, hoverOff, - }, + }), )(NodeGraphs); diff --git a/pkg/ui/src/views/cluster/containers/nodeLogs/index.tsx b/pkg/ui/src/views/cluster/containers/nodeLogs/index.tsx index ad6da0fa6f86..b35b430feff3 100644 --- a/pkg/ui/src/views/cluster/containers/nodeLogs/index.tsx +++ b/pkg/ui/src/views/cluster/containers/nodeLogs/index.tsx @@ -129,10 +129,10 @@ const logsConnected = connect( currentNode: currentNode(state, ownProps), }; }, - { + () => ({ refreshLogs, refreshNodes, - }, + }), )(Logs); export default logsConnected; diff --git a/pkg/ui/src/views/cluster/containers/nodeOverview/index.tsx b/pkg/ui/src/views/cluster/containers/nodeOverview/index.tsx index ff7a32ef0d0a..dc1fb769c73e 100644 --- a/pkg/ui/src/views/cluster/containers/nodeOverview/index.tsx +++ b/pkg/ui/src/views/cluster/containers/nodeOverview/index.tsx @@ -198,8 +198,8 @@ export default connect( nodesSummaryValid: selectNodesSummaryValid(state), }; }, - { + () => ({ refreshNodes, refreshLiveness, - }, + }), )(NodeOverview); diff --git a/pkg/ui/src/views/cluster/containers/nodesOverview/index.tsx b/pkg/ui/src/views/cluster/containers/nodesOverview/index.tsx index da70ea2c8669..1ad0c5107503 100644 --- a/pkg/ui/src/views/cluster/containers/nodesOverview/index.tsx +++ b/pkg/ui/src/views/cluster/containers/nodesOverview/index.tsx @@ -530,10 +530,10 @@ const NodesMainConnected = connect( nodesSummaryValid: selectNodesSummaryValid(state), }; }, - { + () => ({ refreshNodes, refreshLiveness, - }, + }), )(NodesMain); export { NodesMainConnected as NodesOverview }; diff --git a/pkg/ui/src/views/cluster/containers/timescale/index.tsx b/pkg/ui/src/views/cluster/containers/timescale/index.tsx index 943b0598f085..84bd239bb38d 100644 --- a/pkg/ui/src/views/cluster/containers/timescale/index.tsx +++ b/pkg/ui/src/views/cluster/containers/timescale/index.tsx @@ -317,10 +317,10 @@ export default connect( defaultTimescaleSet: timescaleDefaultSet.selector(state), }; }, - { + () => ({ setTimeScale: timewindow.setTimeScale, setTimeRange: timewindow.setTimeRange, refreshNodes: refreshNodes, setDefaultSet: timescaleDefaultSet.set, - }, + }), )(withRouter(TimeScaleDropdown)); diff --git a/pkg/ui/src/views/statements/statementsPage.tsx b/pkg/ui/src/views/statements/statementsPage.tsx index bf6b4248e552..38d831c014d7 100644 --- a/pkg/ui/src/views/statements/statementsPage.tsx +++ b/pkg/ui/src/views/statements/statementsPage.tsx @@ -359,9 +359,9 @@ const StatementsPageConnected = connect( totalFingerprints: selectTotalFingerprints(state), lastReset: selectLastReset(state), }), - { + () => ({ refreshStatements, - }, + }), )(StatementsPage); export default StatementsPageConnected; From 62b0bfc9b06da26a71d21632b00335bfda0eb19f Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Thu, 23 Jan 2020 13:54:11 +0200 Subject: [PATCH 02/20] ui: Resolve conflicts after rebasing against master - tests command is disabled for now because current state doesn't contain all fixes to run tests without failure. - Decommissioned Node History page was added so code has to be adjusted. Release note: None --- .../nodeHistory/decommissionedNodeHistory.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/ui/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx b/pkg/ui/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx index 6cd1b7238e08..49ae3ed2171f 100644 --- a/pkg/ui/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx +++ b/pkg/ui/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx @@ -14,6 +14,7 @@ import { connect } from "react-redux"; import { Link } from "react-router"; import moment from "moment"; import _ from "lodash"; +import { Action, bindActionCreators, Dispatch } from "redux"; import { AdminUIState } from "src/redux/state"; import { @@ -127,10 +128,14 @@ const mapStateToProps = (state: AdminUIState) => ({ nodesSummary: nodesSummarySelector(state), }); -const mapDispatchToProps = { - refreshNodes, - refreshLiveness, - setSort: decommissionedNodesSortSetting.set, -}; +const mapDispatchToProps = (dispatch: Dispatch) => + bindActionCreators( + { + refreshNodes, + refreshLiveness, + setSort: decommissionedNodesSortSetting.set, + }, + dispatch, + ); export default connect(mapStateToProps, mapDispatchToProps)(DecommissionedNodeHistory); From 43fcde872c714cb3fa4882802af1744188e156cf Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Wed, 15 Jan 2020 12:04:53 +0200 Subject: [PATCH 03/20] ui: Fix version of enzyme-react-adapter library Release note: None --- pkg/ui/package.json | 6 +- pkg/ui/yarn.lock | 307 +++++++++++++++++++++++++++++++++----------- 2 files changed, 233 insertions(+), 80 deletions(-) diff --git a/pkg/ui/package.json b/pkg/ui/package.json index c58116a6dbb1..22b9f30c77ec 100644 --- a/pkg/ui/package.json +++ b/pkg/ui/package.json @@ -80,7 +80,7 @@ "d3": "<4.0.0", "d3-geo-projection": "^2.5.0", "enzyme": "^3.3.0", - "enzyme-adapter-react-16": "^1.1.1", + "enzyme-adapter-react-16": "^1.15.2", "express": "^4.15.2", "fetch-mock": "^5.9.4", "file-loader": "^5.0.2", @@ -94,12 +94,12 @@ "karma-mocha-reporter": "^2.2.3", "karma-sinon": "^1.0.5", "karma-sourcemap-loader": "^0.3.7", - "karma-webpack": "^4.0.2", + "karma-webpack": "^2.0.13", "mocha": "^6.2.1", "nib": "^1.1.2", "prop-types": "^15.5.10", "protobufjs": "^6.7.3", - "redux-saga-test-plan": "^3.6.0", + "redux-saga-test-plan": "^4.0.0-beta.2", "rimraf": "^2.6.1", "sinon": "^7.5.0", "source-map-loader": "^0.2.0", diff --git a/pkg/ui/yarn.lock b/pkg/ui/yarn.lock index d71342e4b850..ce3a5e6fcf9b 100644 --- a/pkg/ui/yarn.lock +++ b/pkg/ui/yarn.lock @@ -1323,6 +1323,22 @@ after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" +airbnb-prop-types@^2.15.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.15.0.tgz#5287820043af1eb469f5b0af0d6f70da6c52aaef" + integrity sha512-jUh2/hfKsRjNFC4XONQrxo/n/3GG4Tn6Hl0WlFQN5PY9OMC9loSCoAYKnZsWaP8wEfd5xcrPloK0Zg6iS1xwVA== + dependencies: + array.prototype.find "^2.1.0" + function.prototype.name "^1.1.1" + has "^1.0.3" + is-regex "^1.0.4" + object-is "^1.0.1" + object.assign "^4.1.0" + object.entries "^1.1.0" + prop-types "^15.7.2" + prop-types-exact "^1.2.0" + react-is "^16.9.0" + ajv-errors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" @@ -1554,6 +1570,14 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= +array.prototype.find@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.0.tgz#630f2eaf70a39e608ac3573e45cf8ccd0ede9ad7" + integrity sha512-Wn41+K1yuO5p7wRZDl7890c3xvv5UBrfVXTVIe28rSQb6LS0fZMDrQB6PAcxQFRFy6vJTLDc3A2+3CjQdzVKRg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.13.0" + arraybuffer.slice@~0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" @@ -1617,7 +1641,7 @@ async@^0.9.0: version "0.9.2" resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" -async@^2.6.2: +async@^2.0.0, async@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== @@ -1686,7 +1710,7 @@ babel-polyfill@^6.26.0: core-js "^2.5.0" regenerator-runtime "^0.10.5" -babel-runtime@6.x, babel-runtime@^6.26.0: +babel-runtime@6.x, babel-runtime@^6.0.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: @@ -2215,15 +2239,6 @@ cliui@^5.0.0: strip-ansi "^5.2.0" wrap-ansi "^5.1.0" -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - clone@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149" @@ -3118,25 +3133,40 @@ envify@^3.0.0: jstransform "^11.0.3" through "~2.3.4" -enzyme-adapter-react-16@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.1.tgz#a8f4278b47e082fbca14f5bfb1ee50ee650717b4" +enzyme-adapter-react-16@^1.15.2: + version "1.15.2" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz#b16db2f0ea424d58a808f9df86ab6212895a4501" + integrity sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q== dependencies: - enzyme-adapter-utils "^1.3.0" - lodash "^4.17.4" - object.assign "^4.0.4" - object.values "^1.0.4" - prop-types "^15.6.0" - react-reconciler "^0.7.0" + enzyme-adapter-utils "^1.13.0" + enzyme-shallow-equal "^1.0.1" + has "^1.0.3" + object.assign "^4.1.0" + object.values "^1.1.1" + prop-types "^15.7.2" + react-is "^16.12.0" react-test-renderer "^16.0.0-0" + semver "^5.7.0" -enzyme-adapter-utils@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.3.0.tgz#d6c85756826c257a8544d362cc7a67e97ea698c7" +enzyme-adapter-utils@^1.13.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.0.tgz#01c885dde2114b4690bf741f8dc94cee3060eb78" + integrity sha512-YuEtfQp76Lj5TG1NvtP2eGJnFKogk/zT70fyYHXK2j3v6CtuHqc8YmgH/vaiBfL8K1SgVVbQXtTcgQZFwzTVyQ== dependencies: - lodash "^4.17.4" - object.assign "^4.0.4" - prop-types "^15.6.0" + airbnb-prop-types "^2.15.0" + function.prototype.name "^1.1.2" + object.assign "^4.1.0" + object.fromentries "^2.0.2" + prop-types "^15.7.2" + semver "^5.7.1" + +enzyme-shallow-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.1.tgz#7afe03db3801c9b76de8440694096412a8d9d49e" + integrity sha512-hGA3i1so8OrYOZSM9whlkNmVHOicJpsjgTzC+wn2JMJXhq1oO4kA4bJ5MsfzSIcC71aLDKzJ6gZpIxrqt3QTAQ== + dependencies: + has "^1.0.3" + object-is "^1.0.2" enzyme@^3.3.0: version "3.3.0" @@ -3173,6 +3203,23 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" +es-abstract@^1.13.0, es-abstract@^1.17.0-next.1: + version "1.17.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.1.tgz#1331afa4cba2628b63e988104b9846c2d631b380" + integrity sha512-WmWNHWmm/LDwK8jaeZic/g6sU1ZckM+vvOyCV1qFRhJJ6hzve6DRgthNQB7Lra1ocrw68HexLKYgtdxIPcb3Fg== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.1.5" + is-regex "^1.0.5" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.1" + string.prototype.trimright "^2.1.1" + es-abstract@^1.5.1: version "1.14.2" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.14.2.tgz#7ce108fad83068c8783c3cdf62e504e084d8c497" @@ -3207,6 +3254,15 @@ es-to-primitive@^1.1.1, es-to-primitive@^1.2.0: is-date-object "^1.0.1" is-symbol "^1.0.2" +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -3745,6 +3801,20 @@ function.prototype.name@^1.0.3: function-bind "^1.1.1" is-callable "^1.1.3" +function.prototype.name@^1.1.1, function.prototype.name@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.2.tgz#5cdf79d7c05db401591dfde83e3b70c5123e9a45" + integrity sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + functions-have-names "^1.2.0" + +functions-have-names@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.0.tgz#83da7583e4ea0c9ac5ff530f73394b033e0bf77d" + integrity sha512-zKXyzksTeaCSw5wIX79iCA40YAa6CJMJgNg9wdkU/ERBrIdPSimPICYiLp65lRbSBqtiHql/HZfS2DyI/AH6tQ== + gauge@~2.7.1: version "2.7.3" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.3.tgz#1c23855f962f17b3ad3d0dc7443f304542edfe09" @@ -4000,6 +4070,11 @@ has-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= +has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -4412,6 +4487,11 @@ is-callable@^1.1.3, is-callable@^1.1.4: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== +is-callable@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" + integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -4539,6 +4619,13 @@ is-regex@^1.0.3, is-regex@^1.0.4: dependencies: has "^1.0.1" +is-regex@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" + integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== + dependencies: + has "^1.0.3" + is-retry-allowed@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" @@ -4758,17 +4845,17 @@ karma-sourcemap-loader@^0.3.7: dependencies: graceful-fs "^4.1.2" -karma-webpack@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-4.0.2.tgz#23219bd95bdda853e3073d3874d34447c77bced0" - integrity sha512-970/okAsdUOmiMOCY8sb17A2I8neS25Ad9uhyK3GHgmRSIFJbDcNEFE8dqqUhNe9OHiCC9k3DMrSmtd/0ymP1A== +karma-webpack@^2.0.13: + version "2.0.13" + resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.13.tgz#cf56e3056c15b7747a0bb2140fc9a6be41dd9f02" + integrity sha512-2cyII34jfrAabbI2+4Rk4j95Nazl98FvZQhgSiqKUDarT317rxfv/EdzZ60CyATN4PQxJdO5ucR5bOOXkEVrXw== dependencies: - clone-deep "^4.0.1" - loader-utils "^1.1.0" - neo-async "^2.6.1" - schema-utils "^1.0.0" - source-map "^0.7.3" - webpack-dev-middleware "^3.7.0" + async "^2.0.0" + babel-runtime "^6.0.0" + loader-utils "^1.0.0" + lodash "^4.0.0" + source-map "^0.5.6" + webpack-dev-middleware "^1.12.0" karma@^4.3.0: version "4.3.0" @@ -4861,7 +4948,7 @@ loader-runner@^2.3.1, loader-runner@^2.4.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@1.2.3, loader-utils@^1.0.2, loader-utils@^1.2.3: +loader-utils@1.2.3, loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== @@ -4967,7 +5054,7 @@ lodash.throttle@^4.0.0: resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= -lodash@4.17.15, lodash@^4.15.0, lodash@^4.16.5, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1: +lodash@4.17.15, lodash@^4.0.0, lodash@^4.15.0, lodash@^4.16.5, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -5111,7 +5198,7 @@ mem@^4.0.0: mimic-fn "^2.0.0" p-is-promise "^2.0.0" -memory-fs@^0.4.0, memory-fs@^0.4.1: +memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= @@ -5187,7 +5274,7 @@ mime-types@~2.1.17, mime-types@~2.1.24: dependencies: mime-db "1.40.0" -mime@1.6.0: +mime@1.6.0, mime@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -5638,10 +5725,20 @@ object-inspect@^1.6.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ== +object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + object-is@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" +object-is@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4" + integrity sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ== + object-keys@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.5.0.tgz#09e211f3e00318afc4f592e36e7cdc10d9ad7293" @@ -5659,7 +5756,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@4.1.0, object.assign@^4.0.4, object.assign@^4.1.0: +object.assign@4.1.0, object.assign@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" dependencies: @@ -5677,6 +5774,26 @@ object.entries@^1.0.4: function-bind "^1.1.0" has "^1.0.1" +object.entries@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.1.tgz#ee1cf04153de02bb093fec33683900f57ce5399b" + integrity sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +object.fromentries@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" + integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + object.getownpropertydescriptors@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" @@ -5701,6 +5818,16 @@ object.values@^1.0.4: function-bind "^1.1.0" has "^1.0.1" +object.values@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" @@ -6200,6 +6327,15 @@ promise@^7.1.1: dependencies: asap "~2.0.3" +prop-types-exact@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/prop-types-exact/-/prop-types-exact-1.2.0.tgz#825d6be46094663848237e3925a98c6e944e9869" + integrity sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA== + dependencies: + has "^1.0.3" + object.assign "^4.1.0" + reflect.ownkeys "^0.2.0" + prop-types@15.x, prop-types@^15.5.7, prop-types@^15.5.9, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" @@ -6373,15 +6509,15 @@ randombytes@^2.0.0, randombytes@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec" -range-parser@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" - -range-parser@^1.2.1, range-parser@~1.2.1: +range-parser@^1.0.3, range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== +range-parser@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + raw-body@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" @@ -6897,15 +7033,15 @@ react-input-autosize@^2.1.2: dependencies: prop-types "^15.5.8" -react-is@^16.3.1: - version "16.3.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.1.tgz#ee66e6d8283224a83b3030e110056798488359ba" - -react-is@^16.7.0, react-is@^16.8.1, react-is@^16.9.0: +react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.9.0: version "16.12.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q== +react-is@^16.3.1: + version "16.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.1.tgz#ee66e6d8283224a83b3030e110056798488359ba" + react-lazy-load@^3.0.13: version "3.0.13" resolved "https://registry.yarnpkg.com/react-lazy-load/-/react-lazy-load-3.0.13.tgz#3b0a92d336d43d3f0d73cbe6f35b17050b08b824" @@ -6935,15 +7071,6 @@ react-paginate@^5.2.2: dependencies: prop-types "^15.6.1" -react-reconciler@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.7.0.tgz#9614894103e5f138deeeb5eabaf3ee80eb1d026d" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.0" - react-redux@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.1.3.tgz#717a3d7bbe3a1b2d535c94885ce04cdc5a33fc79" @@ -7094,9 +7221,10 @@ recast@^0.11.17: private "~0.1.5" source-map "~0.5.0" -redux-saga-test-plan@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/redux-saga-test-plan/-/redux-saga-test-plan-3.6.0.tgz#ca316ce212efbddf4ffa532bb0e673bba8114b1d" +redux-saga-test-plan@^4.0.0-beta.2: + version "4.0.0-beta.2" + resolved "https://registry.yarnpkg.com/redux-saga-test-plan/-/redux-saga-test-plan-4.0.0-beta.2.tgz#51717b4819df798c252fbe38171edddb4ef6e76e" + integrity sha512-krCpdou4GMH2nL8RFB20Xp0ucvRcwU/4d8jUObYPzaN9tY0nnohOJio583V2vaT7LlNpXBavInPrUbwOK3NLfg== dependencies: core-js "^2.4.1" fsm-iterator "^1.1.0" @@ -7133,6 +7261,11 @@ redux@^3.6.0: loose-envify "^1.1.0" symbol-observable "^1.0.3" +reflect.ownkeys@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" + integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA= + regenerate-unicode-properties@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e" @@ -7419,7 +7552,7 @@ selfsigned@^1.10.7: dependencies: node-forge "0.9.0" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.7.0: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -7536,13 +7669,6 @@ sha.js@^2.3.6: dependencies: inherits "^2.0.1" -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - shallow-equal@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.0.tgz#fd828d2029ff4e19569db7e19e535e94e2d1f5cc" @@ -7750,11 +7876,6 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - spdx-correct@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" @@ -7937,6 +8058,14 @@ string.prototype.trimleft@^2.0.0: define-properties "^1.1.3" function-bind "^1.1.1" +string.prototype.trimleft@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" + integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + string.prototype.trimright@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz#669d164be9df9b6f7559fa8e89945b168a5a6c58" @@ -7945,6 +8074,14 @@ string.prototype.trimright@^2.0.0: define-properties "^1.1.3" function-bind "^1.1.1" +string.prototype.trimright@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" + integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -8158,6 +8295,11 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.3.tgz#f5df732453407b09191dae73e2a8cc73f381a826" integrity sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow== +time-stamp@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.2.0.tgz#917e0a66905688790ec7bbbde04046259af83f57" + integrity sha512-zxke8goJQpBeEgD82CXABeMh0LSJcj7CXEd0OHOg45HgcofF7pxNwZm9+RknpxpDhwN4gFpySkApKfFYfRQnUA== + timers-browserify@^2.0.4: version "2.0.11" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" @@ -8637,7 +8779,18 @@ webpack-cli@^3.3.10: v8-compile-cache "2.0.3" yargs "13.2.4" -webpack-dev-middleware@^3.7.0, webpack-dev-middleware@^3.7.2: +webpack-dev-middleware@^1.12.0: + version "1.12.2" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz#f8fc1120ce3b4fc5680ceecb43d777966b21105e" + integrity sha512-FCrqPy1yy/sN6U/SaEZcHKRXGlqU0DUaEBL45jkUYoB8foVb6wCnbIJ1HKIx+qUFTW+3JpVcCJCxZ8VATL4e+A== + dependencies: + memory-fs "~0.4.1" + mime "^1.5.0" + path-is-absolute "^1.0.0" + range-parser "^1.0.3" + time-stamp "^2.0.0" + +webpack-dev-middleware@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" integrity sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw== From e5b998007a43a4aee97d76bf0296a787b1789c81 Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Wed, 15 Jan 2020 17:19:40 +0200 Subject: [PATCH 04/20] ui: Adjust code structure for testing routes - Decoupled App from rendering app into DOM. - Defined tests structure and initial tests. Release note: None --- .../views/clusterviz/containers/map/index.tsx | 2 +- pkg/ui/src/app.spec.tsx | 77 +++++++ pkg/ui/src/app.tsx | 215 ++++++++++++++++++ pkg/ui/src/index.tsx | 197 +--------------- pkg/ui/src/redux/state.ts | 2 + 5 files changed, 298 insertions(+), 195 deletions(-) create mode 100644 pkg/ui/src/app.spec.tsx create mode 100644 pkg/ui/src/app.tsx diff --git a/pkg/ui/ccl/src/views/clusterviz/containers/map/index.tsx b/pkg/ui/ccl/src/views/clusterviz/containers/map/index.tsx index a15a204f2023..6a5ec8eae75e 100644 --- a/pkg/ui/ccl/src/views/clusterviz/containers/map/index.tsx +++ b/pkg/ui/ccl/src/views/clusterviz/containers/map/index.tsx @@ -32,7 +32,7 @@ interface ClusterVisualizationProps { clusterDataError: Error | null; } -class ClusterVisualization extends React.Component { +export class ClusterVisualization extends React.Component { readonly items = [ { value: "list", name: "Node List" }, { value: "map", name: "Node Map" }, diff --git a/pkg/ui/src/app.spec.tsx b/pkg/ui/src/app.spec.tsx new file mode 100644 index 000000000000..0bf22ebe9793 --- /dev/null +++ b/pkg/ui/src/app.spec.tsx @@ -0,0 +1,77 @@ +// Copyright 2020 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +import _ from "lodash"; +import React from "react"; +import { assert } from "chai"; +import { Action, Store } from "redux"; +import { createMemoryHistory } from "react-router"; +import { syncHistoryWithStore } from "react-router-redux"; +import { mount, ReactWrapper } from "enzyme"; + +import "src/enzymeInit"; +import { App } from "src/app"; +import { AdminUIState, createAdminUIStore, History } from "src/redux/state"; + +import ClusterOverview from "src/views/cluster/containers/clusterOverview"; +import NodeList from "src/views/clusterviz/containers/map/nodeList"; +import { ClusterVisualization } from "src/views/clusterviz/containers/map"; + +describe("Routing to", () => { + let history: History; + let store: Store; + let appWrapper: ReactWrapper; + + beforeEach(() => { + store = createAdminUIStore(); + }); + + const initAppWithPath = (path: string) => { + const memoryHistory = createMemoryHistory({ + entries: [path], + }); + history = syncHistoryWithStore(memoryHistory, store); + appWrapper = mount(); + }; + + describe("'/' path", () => { + it("routes to component", () => { + initAppWithPath("/"); + assert.lengthOf(appWrapper.find(ClusterOverview), 1); + }); + }); + + describe("'/overview' path", () => { + it("routes to component", () => { + initAppWithPath("/overview"); + assert.lengthOf(appWrapper.find(ClusterOverview), 1); + }); + }); + + describe("'/overview/list' path", () => { + it("routes to component", () => { + initAppWithPath("/overview"); + const clusterOverview = appWrapper.find(ClusterOverview); + assert.lengthOf(clusterOverview, 1); + const nodeList = clusterOverview.find(NodeList); + assert.lengthOf(nodeList, 1); + }); + }); + + describe("'/overview/map' path", () => { + it("routes to component", () => { + initAppWithPath("/overview/map"); + const clusterOverview = appWrapper.find(ClusterOverview); + const clusterViz = appWrapper.find(ClusterVisualization); + assert.lengthOf(clusterOverview, 1); + assert.lengthOf(clusterViz, 1); + }); + }); +}); diff --git a/pkg/ui/src/app.tsx b/pkg/ui/src/app.tsx new file mode 100644 index 000000000000..94ddad7fb611 --- /dev/null +++ b/pkg/ui/src/app.tsx @@ -0,0 +1,215 @@ +// Copyright 2018 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +import React, { useEffect } from "react"; +import { Action, Store } from "redux"; +import { Provider } from "react-redux"; +import { Router, Route, IndexRoute, IndexRedirect, Redirect } from "react-router"; + +import { + tableNameAttr, databaseNameAttr, nodeIDAttr, dashboardNameAttr, rangeIDAttr, statementAttr, appAttr, implicitTxnAttr, +} from "src/util/constants"; + +import { alertDataSync } from "src/redux/alerts"; +import "src/redux/analytics"; +import { AdminUIState, History } from "src/redux/state"; + +import loginRoutes from "src/routes/login"; +import visualizationRoutes from "src/routes/visualization"; + +import NotFound from "src/views/app/components/NotFound"; +import Layout from "src/views/app/containers/layout"; +import { DatabaseTablesList, DatabaseGrantsList } from "src/views/databases/containers/databases"; +import TableDetails from "src/views/databases/containers/tableDetails"; +import { EventPage } from "src/views/cluster/containers/events"; +import DataDistributionPage from "src/views/cluster/containers/dataDistribution"; +import Raft from "src/views/devtools/containers/raft"; +import RaftRanges from "src/views/devtools/containers/raftRanges"; +import RaftMessages from "src/views/devtools/containers/raftMessages"; +import NodeGraphs from "src/views/cluster/containers/nodeGraphs"; +import NodeOverview from "src/views/cluster/containers/nodeOverview"; +import NodeLogs from "src/views/cluster/containers/nodeLogs"; +import JobsPage from "src/views/jobs"; +import Certificates from "src/views/reports/containers/certificates"; +import CustomChart from "src/views/reports/containers/customChart"; +import Debug from "src/views/reports/containers/debug"; +import EnqueueRange from "src/views/reports/containers/enqueueRange"; +import ProblemRanges from "src/views/reports/containers/problemRanges"; +import Localities from "src/views/reports/containers/localities"; +import Network from "src/views/reports/containers/network"; +import Nodes from "src/views/reports/containers/nodes"; +import ReduxDebug from "src/views/reports/containers/redux"; +import Range from "src/views/reports/containers/range"; +import Settings from "src/views/reports/containers/settings"; +import Stores from "src/views/reports/containers/stores"; +import StatementsPage from "src/views/statements/statementsPage"; +import StatementDetails from "src/views/statements/statementDetails"; +import { normalizeConnectedComponent } from "src/util/normalizeConnectedComponent"; + +import "nvd3/build/nv.d3.min.css"; +import "react-select/dist/react-select.css"; + +import "styl/app.styl"; + +// NOTE: If you are adding a new path to the router, and that path contains any +// components that are personally identifying information, you MUST update the +// redactions list in src/redux/analytics.ts. +// +// Examples of PII: Database names, Table names, IP addresses; Any value that +// could identify a specific user. +// +// Serial numeric values, such as NodeIDs or Descriptor IDs, are not PII and do +// not need to be redacted. + +export interface AppProps { + history: History; + store: Store; +} + +// tslint:disable-next-line:variable-name +export const App: React.FC = (props: AppProps) => { + const {store, history} = props; + + useEffect(() => store.subscribe(alertDataSync(store)), []); + + return ( + + + { /* login */} + {loginRoutes(store)} + + + + + { /* overview page */} + {visualizationRoutes()} + + { /* time series metrics */} + + + + + + + + + + + + + { /* node details */} + + + + + + + + + { /* events & jobs */} + + + + { /* databases */} + + + + + + + + + + + + + + + + + + { /* data distribution */} + + + { /* statement statistics */} + + + + + + + + + + + + + { /* debug pages */} + + + + + + + + + + + + + + + + + + + + + + + + + + { /* old route redirects */} + + + + + + + + + + + + + + + + + + { /* 404 */} + + + + + ); +}; diff --git a/pkg/ui/src/index.tsx b/pkg/ui/src/index.tsx index bd69f572d89d..668abca72c26 100644 --- a/pkg/ui/src/index.tsx +++ b/pkg/ui/src/index.tsx @@ -13,202 +13,11 @@ import "src/protobufInit"; import React from "react"; import * as ReactDOM from "react-dom"; -import { Provider } from "react-redux"; -import { Router, Route, IndexRoute, IndexRedirect, Redirect } from "react-router"; -import { - tableNameAttr, databaseNameAttr, nodeIDAttr, dashboardNameAttr, rangeIDAttr, statementAttr, appAttr, implicitTxnAttr, -} from "src/util/constants"; +import {App} from "src/app"; +import {store, history} from "src/redux/state"; -import { alertDataSync } from "src/redux/alerts"; -import "src/redux/analytics"; -import { store, history } from "src/redux/state"; - -import loginRoutes from "src/routes/login"; -import visualizationRoutes from "src/routes/visualization"; - -import NotFound from "src/views/app/components/NotFound"; -import Layout from "src/views/app/containers/layout"; -import { DatabaseTablesList, DatabaseGrantsList } from "src/views/databases/containers/databases"; -import TableDetails from "src/views/databases/containers/tableDetails"; -import { EventPage } from "src/views/cluster/containers/events"; -import DataDistributionPage from "src/views/cluster/containers/dataDistribution"; -import Raft from "src/views/devtools/containers/raft"; -import RaftRanges from "src/views/devtools/containers/raftRanges"; -import RaftMessages from "src/views/devtools/containers/raftMessages"; -import NodeGraphs from "src/views/cluster/containers/nodeGraphs"; -import NodeOverview from "src/views/cluster/containers/nodeOverview"; -import NodeLogs from "src/views/cluster/containers/nodeLogs"; -import JobsPage from "src/views/jobs"; -import StatementsPage from "src/views/statements/statementsPage"; -import StatementDetails from "src/views/statements/statementDetails"; -import { normalizeConnectedComponent } from "src/util/normalizeConnectedComponent"; -import { - Certificates, - CustomChart, - Debug, - DecommissionedNodeHistory, - EnqueueRange, - Localities, - Network, - Nodes, - ProblemRanges, - Range, - ReduxDebug, - Settings, - Stores, -} from "src/views/reports"; - -import "nvd3/build/nv.d3.min.css"; -import "react-select/dist/react-select.css"; - -import "styl/app.styl"; - -// NOTE: If you are adding a new path to the router, and that path contains any -// components that are personally identifying information, you MUST update the -// redactions list in src/redux/analytics.ts. -// -// Examples of PII: Database names, Table names, IP addresses; Any value that -// could identify a specific user. -// -// Serial numeric values, such as NodeIDs or Descriptor IDs, are not PII and do -// not need to be redacted. ReactDOM.render( - - - { /* login */} - { loginRoutes(store) } - - - - - { /* overview page */ } - { visualizationRoutes() } - - { /* time series metrics */ } - - - - - - - - - - - - - { /* node details */ } - - - - - - - - - { /* events & jobs */ } - - - - { /* databases */ } - - - - - - - - - - - - - - - - - - { /* data distribution */ } - - - { /* statement statistics */ } - - - - - - - - - - - - - { /* debug pages */ } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { /* old route redirects */ } - - - - - - - - - - - - - - - - - - { /* 404 */ } - - - - , + , document.getElementById("react-layout"), ); - -store.subscribe(alertDataSync(store)); diff --git a/pkg/ui/src/redux/state.ts b/pkg/ui/src/redux/state.ts index 4ae7e570abc6..4e3d3be738d7 100644 --- a/pkg/ui/src/redux/state.ts +++ b/pkg/ui/src/redux/state.ts @@ -81,3 +81,5 @@ export const store = createAdminUIStore(); // Connect react-router history with redux. export const history = syncHistoryWithStore(hashHistory, store); + +export type History = typeof history; From 49d1bbfed0ffaac65c969f4560b2b1e6e73476a2 Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Thu, 16 Jan 2020 13:24:23 +0200 Subject: [PATCH 05/20] ui: Fix warning messages during karma tests execution - Karma produced warning messages related to compilation of .ts|.tsx files to .js due to incorrect webpack configuration. - Added macha reporter to get readable test reports. Release note: None --- pkg/ui/karma.conf.js | 15 ++++++--------- pkg/ui/webpack.app.js | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/pkg/ui/karma.conf.js b/pkg/ui/karma.conf.js index a55925fd91b0..dd5bcc3b8583 100644 --- a/pkg/ui/karma.conf.js +++ b/pkg/ui/karma.conf.js @@ -77,22 +77,19 @@ module.exports = function(config) { // test results reporter to use // possible values: "dots", "progress" // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ["progress"], + reporters: ["mocha", "progress"], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: true, // https://github.com/airbnb/enzyme/blob/master/docs/guides/webpack.md - webpack: Object.assign(webpackConfig, { + webpack: { + module: webpackConfig.module, + resolve: webpackConfig.resolve, devtool: "inline-source-map", - externals: { - "react/addons": true, - "react/lib/ExecutionEnvironment": true, - "react/lib/ReactContext": true, - }, - mode: "none", - }), + mode: "development", + }, // "stats" needs to be copied to webpackMiddleware configuration in order // to correctly configure console output diff --git a/pkg/ui/webpack.app.js b/pkg/ui/webpack.app.js index 7b0067ccfc11..552b6b2820cf 100644 --- a/pkg/ui/webpack.app.js +++ b/pkg/ui/webpack.app.js @@ -112,7 +112,7 @@ module.exports = (env, argv) => { use: ["cache-loader", "babel-loader"], }, { - test: /\.tsx?$/, + test: /\.(ts|tsx)?$/, include: localRoots, exclude: /\/node_modules/, use: [ From 522f6a073061667bd93a711c44cd249b0f487f55 Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Thu, 16 Jan 2020 13:40:18 +0200 Subject: [PATCH 06/20] ui: Add tests for /overview, /metrics, and /database(s) routes - Tests are grouped in suites per route to maintain related tests per route; - There is two kinds of tests which test correct rendering of component for specific test and another test which validates correct redirection for routes; - Tests can be refactored to be constructed from configuration to reduce repeated tests, but it will be actual after migrating to latest version of react-router lib. - Added additional exports for components which is wrapped by Connect HOC because connect doesn't return valid react component, as result `enzym` library cannot find wrapped component in mounted components tree. This can be changed back with latest version of `react-router` + `react-connected-router` library which don't have this issue. Release note: None --- pkg/ui/src/app.spec.tsx | 228 ++++++++++++++++-- pkg/ui/src/app.tsx | 6 +- .../cluster/containers/nodeGraphs/index.tsx | 2 +- .../cluster/containers/nodeLogs/index.tsx | 2 +- .../cluster/containers/nodeOverview/index.tsx | 2 +- .../databases/containers/databases/index.tsx | 8 +- .../containers/tableDetails/index.tsx | 2 +- pkg/ui/src/views/jobs/index.tsx | 2 +- 8 files changed, 224 insertions(+), 28 deletions(-) diff --git a/pkg/ui/src/app.spec.tsx b/pkg/ui/src/app.spec.tsx index 0bf22ebe9793..c00a15970aac 100644 --- a/pkg/ui/src/app.spec.tsx +++ b/pkg/ui/src/app.spec.tsx @@ -23,41 +23,59 @@ import { AdminUIState, createAdminUIStore, History } from "src/redux/state"; import ClusterOverview from "src/views/cluster/containers/clusterOverview"; import NodeList from "src/views/clusterviz/containers/map/nodeList"; import { ClusterVisualization } from "src/views/clusterviz/containers/map"; +import { NodeGraphs } from "src/views/cluster/containers/nodeGraphs"; +import { NodeOverview } from "src/views/cluster/containers/nodeOverview"; +import { Logs } from "src/views/cluster/containers/nodeLogs"; +import { EventPageUnconnected } from "src/views/cluster/containers/events"; +import { JobsTable } from "src/views/jobs"; +import { + DatabaseGrantsList, + DatabaseTablesList, +} from "src/views/databases/containers/databases"; +import { TableMain } from "src/views/databases/containers/tableDetails"; describe("Routing to", () => { - let history: History; - let store: Store; - let appWrapper: ReactWrapper; - - beforeEach(() => { - store = createAdminUIStore(); + const store: Store = createAdminUIStore(); + const memoryHistory = createMemoryHistory({ + entries: ["/"], }); + const history: History = syncHistoryWithStore(memoryHistory, store); + const appWrapper: ReactWrapper = mount(); - const initAppWithPath = (path: string) => { - const memoryHistory = createMemoryHistory({ - entries: [path], - }); - history = syncHistoryWithStore(memoryHistory, store); - appWrapper = mount(); + const navigateToPath = (path: string) => { + history.push(path); + appWrapper.update(); }; describe("'/' path", () => { it("routes to component", () => { - initAppWithPath("/"); + navigateToPath("/"); assert.lengthOf(appWrapper.find(ClusterOverview), 1); }); + + it("redirected to '/overview'", () => { + navigateToPath("/"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/overview/list"); + }); }); describe("'/overview' path", () => { it("routes to component", () => { - initAppWithPath("/overview"); + navigateToPath("/overview"); assert.lengthOf(appWrapper.find(ClusterOverview), 1); }); + + it("redirected to '/overview'", () => { + navigateToPath("/overview"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/overview/list"); + }); }); describe("'/overview/list' path", () => { it("routes to component", () => { - initAppWithPath("/overview"); + navigateToPath("/overview"); const clusterOverview = appWrapper.find(ClusterOverview); assert.lengthOf(clusterOverview, 1); const nodeList = clusterOverview.find(NodeList); @@ -67,11 +85,189 @@ describe("Routing to", () => { describe("'/overview/map' path", () => { it("routes to component", () => { - initAppWithPath("/overview/map"); + navigateToPath("/overview/map"); const clusterOverview = appWrapper.find(ClusterOverview); const clusterViz = appWrapper.find(ClusterVisualization); assert.lengthOf(clusterOverview, 1); assert.lengthOf(clusterViz, 1); }); }); + + { /* time series metrics */} + describe("'/metrics' path", () => { + it("routes to component", () => { + navigateToPath("/metrics"); + assert.lengthOf(appWrapper.find(NodeGraphs), 1); + }); + + it("redirected to '/metrics/overview/cluster'", () => { + navigateToPath("/metrics"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/metrics/overview/cluster"); + }); + }); + + describe("'/metrics/overview/cluster' path", () => { + it("routes to component", () => { + navigateToPath("/metrics/overview/cluster"); + assert.lengthOf(appWrapper.find(NodeGraphs), 1); + }); + }); + + describe("'/metrics/overview/node' path", () => { + it("routes to component", () => { + navigateToPath("/metrics/overview/node"); + assert.lengthOf(appWrapper.find(NodeGraphs), 1); + }); + }); + + describe("'/metrics/:dashboardNameAttr' path", () => { + it("routes to component", () => { + navigateToPath("/metrics/some-dashboard"); + assert.lengthOf(appWrapper.find(NodeGraphs), 1); + }); + + it("redirected to '/metrics/:${dashboardNameAttr}/cluster'", () => { + navigateToPath("/metrics/some-dashboard"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/metrics/some-dashboard/cluster"); + }); + }); + + describe("'/metrics/:dashboardNameAttr/cluster' path", () => { + it("routes to component", () => { + navigateToPath("/metrics/some-dashboard/cluster"); + assert.lengthOf(appWrapper.find(NodeGraphs), 1); + }); + }); + + describe("'/metrics/:dashboardNameAttr/node' path", () => { + it("routes to component", () => { + navigateToPath("/metrics/some-dashboard/node"); + assert.lengthOf(appWrapper.find(NodeGraphs), 1); + }); + + it("redirected to '/metrics/:${dashboardNameAttr}/cluster'", () => { + navigateToPath("/metrics/some-dashboard/node"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/metrics/some-dashboard/cluster"); + }); + }); + + describe("'/metrics/:dashboardNameAttr/node/:nodeIDAttr' path", () => { + it("routes to component", () => { + navigateToPath("/metrics/some-dashboard/node/123"); + assert.lengthOf(appWrapper.find(NodeGraphs), 1); + }); + }); + + { /* node details */} + describe("'/node' path", () => { + it("routes to component", () => { + navigateToPath("/node"); + assert.lengthOf(appWrapper.find(NodeList), 1); + }); + + it("redirected to '/overview/list'", () => { + navigateToPath("/node"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/overview/list"); + }); + }); + + describe("'/node/:nodeIDAttr' path", () => { + it("routes to component", () => { + navigateToPath("/node/1"); + assert.lengthOf(appWrapper.find(NodeOverview), 1); + }); + }); + + describe("'/node/:nodeIDAttr/logs' path", () => { + it("routes to component", () => { + navigateToPath("/node/1/logs"); + assert.lengthOf(appWrapper.find(Logs), 1); + }); + }); + + { /* events & jobs */} + describe("'/events' path", () => { + it("routes to component", () => { + navigateToPath("/events"); + assert.lengthOf(appWrapper.find(EventPageUnconnected), 1); + }); + }); + + describe("'/jobs' path", () => { + it("routes to component", () => { + navigateToPath("/jobs"); + assert.lengthOf(appWrapper.find(JobsTable), 1); + }); + }); + + { /* databases */} + describe("'/databases' path", () => { + it("routes to component", () => { + navigateToPath("/databases"); + assert.lengthOf(appWrapper.find(DatabaseTablesList), 1); + }); + + it("redirected to '/databases/tables'", () => { + navigateToPath("/databases"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/databases/tables"); + }); + }); + + describe("'/databases/tables' path", () => { + it("routes to component", () => { + navigateToPath("/databases/tables"); + assert.lengthOf(appWrapper.find(DatabaseTablesList), 1); + }); + }); + + describe("'/databases/grants' path", () => { + it("routes to component", () => { + navigateToPath("/databases/grants"); + assert.lengthOf(appWrapper.find(DatabaseGrantsList), 1); + }); + }); + + describe("'/databases/database/:${databaseNameAttr}/table/:${tableNameAttr}' path", () => { + it("redirected to '/database/:${databaseNameAttr}/table/:${tableNameAttr}'", () => { + navigateToPath("/databases/database/some-db-name/table/some-table-name"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/database/some-db-name/table/some-table-name"); + }); + }); + + describe("'/database' path", () => { + it("redirected to '/databases'", () => { + navigateToPath("/databases/tables"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/databases/tables"); + }); + }); + + describe("'/database/:${databaseNameAttr}' path", () => { + it("redirected to '/databases'", () => { + navigateToPath("/database/some-db-name"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/databases/tables"); + }); + }); + + describe("'/database/:${databaseNameAttr}/table' path", () => { + it("redirected to '/databases/tables'", () => { + navigateToPath("/database/some-db-name/table"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/databases/tables"); + }); + }); + + describe("'/database/:${databaseNameAttr}/table/:${tableNameAttr}' path", () => { + it("routes to component", () => { + navigateToPath("/database/some-db-name/table/some-table-name"); + assert.lengthOf(appWrapper.find(TableMain), 1); + }); + }); }); diff --git a/pkg/ui/src/app.tsx b/pkg/ui/src/app.tsx index 94ddad7fb611..1e00d6335968 100644 --- a/pkg/ui/src/app.tsx +++ b/pkg/ui/src/app.tsx @@ -26,7 +26,7 @@ import visualizationRoutes from "src/routes/visualization"; import NotFound from "src/views/app/components/NotFound"; import Layout from "src/views/app/containers/layout"; -import { DatabaseTablesList, DatabaseGrantsList } from "src/views/databases/containers/databases"; +import { ConnectedDatabaseTablesList, ConnectedDatabaseGrantsList } from "src/views/databases/containers/databases"; import TableDetails from "src/views/databases/containers/tableDetails"; import { EventPage } from "src/views/cluster/containers/events"; import DataDistributionPage from "src/views/cluster/containers/dataDistribution"; @@ -120,8 +120,8 @@ export const App: React.FC = (props: AppProps) => { { /* databases */} - - + + { +export class NodeGraphs extends React.Component { // Magic to add react router to the context. // See https://github.com/ReactTraining/react-router/issues/975 // TODO(mrtracy): Switch this, and the other uses of contextTypes, to use the diff --git a/pkg/ui/src/views/cluster/containers/nodeLogs/index.tsx b/pkg/ui/src/views/cluster/containers/nodeLogs/index.tsx index b35b430feff3..5735d2a6d3b6 100644 --- a/pkg/ui/src/views/cluster/containers/nodeLogs/index.tsx +++ b/pkg/ui/src/views/cluster/containers/nodeLogs/index.tsx @@ -38,7 +38,7 @@ interface LogProps { /** * Renders the main content of the logs page. */ -class Logs extends React.Component { +export class Logs extends React.Component { componentWillMount() { this.props.refreshNodes(); this.props.refreshLogs(new protos.cockroach.server.serverpb.LogsRequest({ node_id: this.props.params[nodeIDAttr] })); diff --git a/pkg/ui/src/views/cluster/containers/nodeOverview/index.tsx b/pkg/ui/src/views/cluster/containers/nodeOverview/index.tsx index dc1fb769c73e..082155e07c4e 100644 --- a/pkg/ui/src/views/cluster/containers/nodeOverview/index.tsx +++ b/pkg/ui/src/views/cluster/containers/nodeOverview/index.tsx @@ -62,7 +62,7 @@ interface NodeOverviewProps extends RouterState { /** * Renders the Node Overview page. */ -class NodeOverview extends React.Component { +export class NodeOverview extends React.Component { componentWillMount() { // Refresh nodes status query when mounting. this.props.refreshNodes(); diff --git a/pkg/ui/src/views/databases/containers/databases/index.tsx b/pkg/ui/src/views/databases/containers/databases/index.tsx index ade47cc34da1..f906f6b04816 100644 --- a/pkg/ui/src/views/databases/containers/databases/index.tsx +++ b/pkg/ui/src/views/databases/containers/databases/index.tsx @@ -83,7 +83,7 @@ interface DatabaseListActions { type DatabaseListProps = DatabaseListData & DatabaseListActions; // DatabaseTablesList displays the "Tables" sub-tab of the main database page. -class DatabaseTablesList extends React.Component { +export class DatabaseTablesList extends React.Component { componentWillMount() { this.props.refreshDatabases(); } @@ -110,7 +110,7 @@ class DatabaseTablesList extends React.Component { } // DatabaseTablesList displays the "Grants" sub-tab of the main database page. -class DatabaseGrantsList extends React.Component { +export class DatabaseGrantsList extends React.Component { componentWillMount() { this.props.refreshDatabases(); } @@ -174,6 +174,6 @@ const databaseGrantsListConnected = connect( )(DatabaseGrantsList); export { - databaseTablesListConnected as DatabaseTablesList, - databaseGrantsListConnected as DatabaseGrantsList, + databaseTablesListConnected as ConnectedDatabaseTablesList, + databaseGrantsListConnected as ConnectedDatabaseGrantsList, }; diff --git a/pkg/ui/src/views/databases/containers/tableDetails/index.tsx b/pkg/ui/src/views/databases/containers/tableDetails/index.tsx index 7080f375ad24..bed0d6a21113 100644 --- a/pkg/ui/src/views/databases/containers/tableDetails/index.tsx +++ b/pkg/ui/src/views/databases/containers/tableDetails/index.tsx @@ -60,7 +60,7 @@ type TableMainProps = TableMainData & TableMainActions & RouterState; * TableMain renders the main content of the databases page, which is primarily a * data table of all databases. */ -class TableMain extends React.Component { +export class TableMain extends React.Component { componentWillMount() { this.props.refreshTableDetails(new protos.cockroach.server.serverpb.TableDetailsRequest({ database: this.props.params[databaseNameAttr], diff --git a/pkg/ui/src/views/jobs/index.tsx b/pkg/ui/src/views/jobs/index.tsx index d25bfdac7437..bfbce6a5dec1 100644 --- a/pkg/ui/src/views/jobs/index.tsx +++ b/pkg/ui/src/views/jobs/index.tsx @@ -245,7 +245,7 @@ const titleTooltip = ( ); -class JobsTable extends React.Component { +export class JobsTable extends React.Component { refresh(props = this.props) { props.refreshJobs(new JobsRequest({ status: props.status, From 92609559761272244836df1c48a1425ac4c4c74e Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Thu, 16 Jan 2020 13:43:27 +0200 Subject: [PATCH 07/20] ui: Fix linting errors Release note: None --- pkg/ui/karma.conf.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/ui/karma.conf.js b/pkg/ui/karma.conf.js index dd5bcc3b8583..47dc42e7b532 100644 --- a/pkg/ui/karma.conf.js +++ b/pkg/ui/karma.conf.js @@ -85,10 +85,10 @@ module.exports = function(config) { // https://github.com/airbnb/enzyme/blob/master/docs/guides/webpack.md webpack: { + devtool: "source-map", + mode: "development", module: webpackConfig.module, resolve: webpackConfig.resolve, - devtool: "inline-source-map", - mode: "development", }, // "stats" needs to be copied to webpackMiddleware configuration in order From 3e47a7dd2d980e36c8efefccbcb18c80a9324c67 Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Thu, 16 Jan 2020 15:05:18 +0200 Subject: [PATCH 08/20] ui: Refactor alertDateSync subscription out of App component Change back subscription to alertDataSync out of App context so it doesn't break existing behaviour. Release note: None --- pkg/ui/src/app.tsx | 5 +---- pkg/ui/src/index.tsx | 8 +++++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pkg/ui/src/app.tsx b/pkg/ui/src/app.tsx index 1e00d6335968..12cf0326f831 100644 --- a/pkg/ui/src/app.tsx +++ b/pkg/ui/src/app.tsx @@ -8,7 +8,7 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. -import React, { useEffect } from "react"; +import React from "react"; import { Action, Store } from "redux"; import { Provider } from "react-redux"; import { Router, Route, IndexRoute, IndexRedirect, Redirect } from "react-router"; @@ -17,7 +17,6 @@ import { tableNameAttr, databaseNameAttr, nodeIDAttr, dashboardNameAttr, rangeIDAttr, statementAttr, appAttr, implicitTxnAttr, } from "src/util/constants"; -import { alertDataSync } from "src/redux/alerts"; import "src/redux/analytics"; import { AdminUIState, History } from "src/redux/state"; @@ -77,8 +76,6 @@ export interface AppProps { export const App: React.FC = (props: AppProps) => { const {store, history} = props; - useEffect(() => store.subscribe(alertDataSync(store)), []); - return ( diff --git a/pkg/ui/src/index.tsx b/pkg/ui/src/index.tsx index 668abca72c26..15edcb53e21f 100644 --- a/pkg/ui/src/index.tsx +++ b/pkg/ui/src/index.tsx @@ -7,12 +7,12 @@ // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. +import React from "react"; +import * as ReactDOM from "react-dom"; import "src/polyfills"; import "src/protobufInit"; - -import React from "react"; -import * as ReactDOM from "react-dom"; +import { alertDataSync } from "src/redux/alerts"; import {App} from "src/app"; import {store, history} from "src/redux/state"; @@ -21,3 +21,5 @@ ReactDOM.render( , document.getElementById("react-layout"), ); + +store.subscribe(alertDataSync(store)); From fd6252f44468403a471cf2ba0221655a2e9d6111 Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Thu, 16 Jan 2020 16:02:00 +0200 Subject: [PATCH 09/20] ui: Clean up resources after tests complete Release note: None --- pkg/ui/src/app.spec.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/ui/src/app.spec.tsx b/pkg/ui/src/app.spec.tsx index c00a15970aac..90d60424a338 100644 --- a/pkg/ui/src/app.spec.tsx +++ b/pkg/ui/src/app.spec.tsx @@ -8,7 +8,6 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. -import _ from "lodash"; import React from "react"; import { assert } from "chai"; import { Action, Store } from "redux"; @@ -42,6 +41,10 @@ describe("Routing to", () => { const history: History = syncHistoryWithStore(memoryHistory, store); const appWrapper: ReactWrapper = mount(); + after(() => { + appWrapper.unmount(); + }); + const navigateToPath = (path: string) => { history.push(path); appWrapper.update(); From db17d70264aee519ab76955b9ff8c086d25a65f3 Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Thu, 16 Jan 2020 16:08:53 +0200 Subject: [PATCH 10/20] ui: Add tests for /statement(s) routes Release note: None --- pkg/ui/src/app.spec.tsx | 62 +++++++++++++++++++ .../containers/dataDistribution/index.tsx | 2 +- .../src/views/statements/statementsPage.tsx | 2 +- 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/pkg/ui/src/app.spec.tsx b/pkg/ui/src/app.spec.tsx index 90d60424a338..c064ed104d17 100644 --- a/pkg/ui/src/app.spec.tsx +++ b/pkg/ui/src/app.spec.tsx @@ -32,6 +32,9 @@ import { DatabaseTablesList, } from "src/views/databases/containers/databases"; import { TableMain } from "src/views/databases/containers/tableDetails"; +import { DataDistributionPage } from "oss/src/views/cluster/containers/dataDistribution"; +import { StatementsPage } from "oss/src/views/statements/statementsPage"; +import { StatementDetails } from "oss/src/views/statements/statementDetails"; describe("Routing to", () => { const store: Store = createAdminUIStore(); @@ -273,4 +276,63 @@ describe("Routing to", () => { assert.lengthOf(appWrapper.find(TableMain), 1); }); }); + + { /* data distribution */} + describe("'/data-distribution' path", () => { + it("routes to component", () => { + navigateToPath("/data-distribution"); + assert.lengthOf(appWrapper.find(DataDistributionPage), 1); + }); + }); + + { /* statement statistics */} + describe("'/statements' path", () => { + it("routes to component", () => { + navigateToPath("/statements"); + assert.lengthOf(appWrapper.find(StatementsPage), 1); + }); + }); + + describe("'/statements/:${appAttr}' path", () => { + it("routes to component", () => { + navigateToPath("/statements/(internal)"); + assert.lengthOf(appWrapper.find(StatementsPage), 1); + }); + }); + + describe("'/statements/:${appAttr}/:${statementAttr}' path", () => { + it("routes to component", () => { + navigateToPath("/statements/(internal)/true"); + assert.lengthOf(appWrapper.find(StatementDetails), 1); + }); + }); + + describe("'/statements/:${implicitTxnAttr}/:${statementAttr}' path", () => { + it("routes to component", () => { + navigateToPath("/statements/implicit-txn-attr/statement-attr"); + assert.lengthOf(appWrapper.find(StatementDetails), 1); + }); + }); + + describe("'/statement' path", () => { + it("redirected to '/statements'", () => { + navigateToPath("/statement"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/statements"); + }); + }); + + describe("'/statement/:${statementAttr}' path", () => { + it("routes to component", () => { + navigateToPath("/statement/statement-attr"); + assert.lengthOf(appWrapper.find(StatementDetails), 1); + }); + }); + + describe("'/statement/:${implicitTxnAttr}/:${statementAttr}' path", () => { + it("routes to component", () => { + navigateToPath("/statement/implicit-attr/statement-attr/"); + assert.lengthOf(appWrapper.find(StatementDetails), 1); + }); + }); }); diff --git a/pkg/ui/src/views/cluster/containers/dataDistribution/index.tsx b/pkg/ui/src/views/cluster/containers/dataDistribution/index.tsx index 333363315d0a..51b824b075ec 100644 --- a/pkg/ui/src/views/cluster/containers/dataDistribution/index.tsx +++ b/pkg/ui/src/views/cluster/containers/dataDistribution/index.tsx @@ -148,7 +148,7 @@ interface DataDistributionPageProps { refreshLiveness: typeof refreshLiveness; } -class DataDistributionPage extends React.Component { +export class DataDistributionPage extends React.Component { componentDidMount() { this.props.refreshDataDistribution(); diff --git a/pkg/ui/src/views/statements/statementsPage.tsx b/pkg/ui/src/views/statements/statementsPage.tsx index 38d831c014d7..107840305c93 100644 --- a/pkg/ui/src/views/statements/statementsPage.tsx +++ b/pkg/ui/src/views/statements/statementsPage.tsx @@ -57,7 +57,7 @@ interface StatementsPageState { search?: string; } -class StatementsPage extends React.Component { +export class StatementsPage extends React.Component { constructor(props: StatementsPageProps & RouteProps) { super(props); From 094baac9ff09ed397d35ed2c52b045dcdf6025e0 Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Thu, 16 Jan 2020 16:56:48 +0200 Subject: [PATCH 11/20] ui: Add tests for /debug/* routes Release note: None --- pkg/ui/src/app.spec.tsx | 42 +++++++++++++++++-- .../devtools/containers/raftRanges/index.tsx | 2 +- .../reports/containers/customChart/index.tsx | 2 +- .../reports/containers/enqueueRange/index.tsx | 2 +- .../views/reports/containers/redux/index.tsx | 2 +- 5 files changed, 43 insertions(+), 7 deletions(-) diff --git a/pkg/ui/src/app.spec.tsx b/pkg/ui/src/app.spec.tsx index c064ed104d17..67562450ea96 100644 --- a/pkg/ui/src/app.spec.tsx +++ b/pkg/ui/src/app.spec.tsx @@ -32,9 +32,13 @@ import { DatabaseTablesList, } from "src/views/databases/containers/databases"; import { TableMain } from "src/views/databases/containers/tableDetails"; -import { DataDistributionPage } from "oss/src/views/cluster/containers/dataDistribution"; -import { StatementsPage } from "oss/src/views/statements/statementsPage"; -import { StatementDetails } from "oss/src/views/statements/statementDetails"; +import { DataDistributionPage } from "src/views/cluster/containers/dataDistribution"; +import { StatementsPage } from "src/views/statements/statementsPage"; +import { StatementDetails } from "src/views/statements/statementDetails"; +import Debug from "src/views/reports/containers/debug"; +import { ReduxDebug } from "src/views/reports/containers/redux"; +import { CustomChart } from "src/views/reports/containers/customChart"; +import { EnqueueRange } from "src/views/reports/containers/enqueueRange"; describe("Routing to", () => { const store: Store = createAdminUIStore(); @@ -335,4 +339,36 @@ describe("Routing to", () => { assert.lengthOf(appWrapper.find(StatementDetails), 1); }); }); + + { /* debug pages */} + describe("'/debug' path", () => { + it("routes to component", () => { + navigateToPath("/debug"); + assert.lengthOf(appWrapper.find(Debug), 1); + }); + }); + + // TODO (koorosh): Disabled due to strange failure on internal + // behaviour of ReduxDebug component under test env. + xdescribe("'/debug/redux' path", () => { + it("routes to component", () => { + navigateToPath("/debug/redux"); + assert.lengthOf(appWrapper.find(ReduxDebug), 1); + }); + }); + + describe("'/debug/chart' path", () => { + it("routes to component", () => { + navigateToPath("/debug/chart"); + // assert.lengthOf(appWrapper.find(Debug), 1); + assert.lengthOf(appWrapper.find(CustomChart), 1); + }); + }); + + describe("'/debug/enqueue_range' path", () => { + it("routes to component", () => { + navigateToPath("/debug/enqueue_range"); + assert.lengthOf(appWrapper.find(EnqueueRange), 1); + }); + }); }); diff --git a/pkg/ui/src/views/devtools/containers/raftRanges/index.tsx b/pkg/ui/src/views/devtools/containers/raftRanges/index.tsx index 4f6e2857c88e..c9816295a21b 100644 --- a/pkg/ui/src/views/devtools/containers/raftRanges/index.tsx +++ b/pkg/ui/src/views/devtools/containers/raftRanges/index.tsx @@ -61,7 +61,7 @@ type RangesMainProps = RangesMainData & RangesMainActions; * Renders the main content of the raft ranges page, which is primarily a data * table of all ranges and their replicas. */ -class RangesMain extends React.Component { +export class RangesMain extends React.Component { state: RangesMainState = { showState: true, showReplicas: true, diff --git a/pkg/ui/src/views/reports/containers/customChart/index.tsx b/pkg/ui/src/views/reports/containers/customChart/index.tsx index 4a6ec4c1ca93..c88f004e2a66 100644 --- a/pkg/ui/src/views/reports/containers/customChart/index.tsx +++ b/pkg/ui/src/views/reports/containers/customChart/index.tsx @@ -43,7 +43,7 @@ interface UrlState { charts: string; } -class CustomChart extends React.Component { +export class CustomChart extends React.Component { // Selector which computes dropdown options based on the nodes available on // the cluster. private nodeOptions = createSelector( diff --git a/pkg/ui/src/views/reports/containers/enqueueRange/index.tsx b/pkg/ui/src/views/reports/containers/enqueueRange/index.tsx index e5a62e54e86b..76750d49bc8a 100644 --- a/pkg/ui/src/views/reports/containers/enqueueRange/index.tsx +++ b/pkg/ui/src/views/reports/containers/enqueueRange/index.tsx @@ -47,7 +47,7 @@ interface EnqueueRangeState { error: Error; } -class EnqueueRange extends React.Component { +export class EnqueueRange extends React.Component { state: EnqueueRangeState = { queue: QUEUES[0], rangeID: "", diff --git a/pkg/ui/src/views/reports/containers/redux/index.tsx b/pkg/ui/src/views/reports/containers/redux/index.tsx index 10de2c288cd4..72f2aa134f95 100644 --- a/pkg/ui/src/views/reports/containers/redux/index.tsx +++ b/pkg/ui/src/views/reports/containers/redux/index.tsx @@ -26,7 +26,7 @@ interface ReduxDebugState { copied: boolean; } -class ReduxDebug extends React.Component { +export class ReduxDebug extends React.Component { constructor(props: any) { super(props); this.state = { copied: false }; From 7ba430f5855d6dd35f3dd701c47eaff37ec70ee2 Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Thu, 16 Jan 2020 17:07:55 +0200 Subject: [PATCH 12/20] ui: Add tests for /raft/* routes Release note: None --- pkg/ui/src/app.spec.tsx | 38 +++++++++++++++++++ .../containers/raftMessages/index.tsx | 13 +++---- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/pkg/ui/src/app.spec.tsx b/pkg/ui/src/app.spec.tsx index 67562450ea96..6fad1b411051 100644 --- a/pkg/ui/src/app.spec.tsx +++ b/pkg/ui/src/app.spec.tsx @@ -39,6 +39,9 @@ import Debug from "src/views/reports/containers/debug"; import { ReduxDebug } from "src/views/reports/containers/redux"; import { CustomChart } from "src/views/reports/containers/customChart"; import { EnqueueRange } from "src/views/reports/containers/enqueueRange"; +import { RangesMain } from "src/views/devtools/containers/raftRanges"; +import { RaftMessages } from "src/views/devtools/containers/raftMessages"; +import Raft from "src/views/devtools/containers/raft"; describe("Routing to", () => { const store: Store = createAdminUIStore(); @@ -371,4 +374,39 @@ describe("Routing to", () => { assert.lengthOf(appWrapper.find(EnqueueRange), 1); }); }); + + { /* raft pages */} + describe("'/raft' path", () => { + it("routes to component", () => { + navigateToPath("/raft"); + assert.lengthOf(appWrapper.find(Raft), 1); + }); + + it("redirected to '/raft/ranges'", () => { + navigateToPath("/raft"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/raft/ranges"); + }); + }); + + describe("'/raft/ranges' path", () => { + it("routes to component", () => { + navigateToPath("/raft/ranges"); + assert.lengthOf(appWrapper.find(RangesMain), 1); + }); + }); + + describe("'/raft/messages/all' path", () => { + it("routes to component", () => { + navigateToPath("/raft/messages/all"); + assert.lengthOf(appWrapper.find(RaftMessages), 1); + }); + }); + + describe("'/raft/messages/node/:${nodeIDAttr}' path", () => { + it("routes to component", () => { + navigateToPath("/raft/messages/node/node-id-attr"); + assert.lengthOf(appWrapper.find(RaftMessages), 1); + }); + }); }); diff --git a/pkg/ui/src/views/devtools/containers/raftMessages/index.tsx b/pkg/ui/src/views/devtools/containers/raftMessages/index.tsx index 0d55b7edd56d..084ab3f19aeb 100644 --- a/pkg/ui/src/views/devtools/containers/raftMessages/index.tsx +++ b/pkg/ui/src/views/devtools/containers/raftMessages/index.tsx @@ -37,11 +37,9 @@ interface NodeGraphsOwnProps { hoverState: HoverState; } -type NodeGraphsProps = NodeGraphsOwnProps & RouterState; -/** - * NodeGraphs renders the main content of the cluster graphs page. - */ -class NodeGraphs extends React.Component { +type RaftMessagesProps = NodeGraphsOwnProps & RouterState; + +export class RaftMessages extends React.Component { // Magic to add react router to the context. // See https://github.com/ReactTraining/react-router/issues/975 // TODO(mrtracy): Switch this, and the other uses of contextTypes, to use the @@ -94,7 +92,7 @@ class NodeGraphs extends React.Component { this.refresh(); } - componentWillReceiveProps(props: NodeGraphsProps) { + componentWillReceiveProps(props: RaftMessagesProps) { this.refresh(props); } @@ -168,6 +166,7 @@ class NodeGraphs extends React.Component { ); } } + const mapStateToProps = (state: AdminUIState) => ({ // RootState contains declaration for whole state nodesSummary: nodesSummarySelector(state), nodesQueryValid: state.cachedData.nodes.valid, @@ -182,4 +181,4 @@ const mapDispatchToProps = { hoverOff: hoverOffAction, }; -export default connect(mapStateToProps, mapDispatchToProps)(NodeGraphs); +export default connect(mapStateToProps, mapDispatchToProps)(RaftMessages); From 06e7a444ca8cb5ac9aa07cd7c2231304a5bca29a Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Thu, 16 Jan 2020 19:57:05 +0200 Subject: [PATCH 13/20] ui: Add tests for /cluster/* routes Release note: None --- pkg/ui/src/app.spec.tsx | 78 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/pkg/ui/src/app.spec.tsx b/pkg/ui/src/app.spec.tsx index 6fad1b411051..fa76bea095e8 100644 --- a/pkg/ui/src/app.spec.tsx +++ b/pkg/ui/src/app.spec.tsx @@ -42,6 +42,7 @@ import { EnqueueRange } from "src/views/reports/containers/enqueueRange"; import { RangesMain } from "src/views/devtools/containers/raftRanges"; import { RaftMessages } from "src/views/devtools/containers/raftMessages"; import Raft from "src/views/devtools/containers/raft"; +import NotFound from "oss/src/views/app/components/NotFound"; describe("Routing to", () => { const store: Store = createAdminUIStore(); @@ -409,4 +410,81 @@ describe("Routing to", () => { assert.lengthOf(appWrapper.find(RaftMessages), 1); }); }); + + { /* old route redirects */} + describe("'/cluster' path", () => { + it("redirected to '/metrics/overview/cluster'", () => { + navigateToPath("/cluster"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/metrics/overview/cluster"); + }); + }); + + describe("'/cluster/all/:${dashboardNameAttr}' path", () => { + it("redirected to '/metrics/:${dashboardNameAttr}/cluster'", () => { + const dashboardNameAttr = "some-dashboard-name"; + navigateToPath(`/cluster/all/${dashboardNameAttr}`); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, `/metrics/${dashboardNameAttr}/cluster`); + }); + }); + + describe("'/cluster/node/:${nodeIDAttr}/:${dashboardNameAttr}' path", () => { + it("redirected to '/metrics/:${dashboardNameAttr}/cluster'", () => { + const dashboardNameAttr = "some-dashboard-name"; + const nodeIDAttr = 1; + navigateToPath(`/cluster/node/${nodeIDAttr}/${dashboardNameAttr}`); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, `/metrics/${dashboardNameAttr}/node/${nodeIDAttr}`); + }); + }); + + describe("'/cluster/nodes' path", () => { + it("redirected to '/overview/list'", () => { + navigateToPath("/cluster/nodes"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/overview/list"); + }); + }); + + describe("'/cluster/nodes/:${nodeIDAttr}' path", () => { + it("redirected to '/node/:${nodeIDAttr}'", () => { + const nodeIDAttr = 1; + navigateToPath(`/cluster/nodes/${nodeIDAttr}`); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, `/node/${nodeIDAttr}`); + }); + }); + + describe("'/cluster/nodes/:${nodeIDAttr}/logs' path", () => { + it("redirected to '/node/:${nodeIDAttr}/logs'", () => { + const nodeIDAttr = 1; + navigateToPath(`/cluster/nodes/${nodeIDAttr}/logs`); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, `/node/${nodeIDAttr}/logs`); + }); + }); + + describe("'/cluster/events' path", () => { + it("redirected to '/events'", () => { + navigateToPath("/cluster/events"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/events"); + }); + }); + + describe("'/cluster/nodes' path", () => { + it("redirected to '/overview/list'", () => { + navigateToPath("/cluster/nodes"); + const location = history.getCurrentLocation(); + assert.equal(location.pathname, "/overview/list"); + }); + }); + + describe("'/unknown-url' path", () => { + it("routes to component", () => { + navigateToPath("/some-random-ulr"); + assert.lengthOf(appWrapper.find(NotFound), 1); + }); + }); }); From 5f0fa45c9be6977532beb741e560d22e10354cad Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Thu, 23 Jan 2020 18:04:13 +0200 Subject: [PATCH 14/20] ui: Resolve conflicts after rebasing against master branch - Wrap NodeCanvasContainer and statementsPage components with `bindActionCreators` - Add route for DecommissionedNodeHistory page Release note: None --- pkg/ui/src/app.tsx | 8 ++++++-- pkg/ui/src/index.tsx | 2 +- pkg/ui/src/views/statements/statementsPage.tsx | 10 +++++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/pkg/ui/src/app.tsx b/pkg/ui/src/app.tsx index 12cf0326f831..9f3a4a0a6286 100644 --- a/pkg/ui/src/app.tsx +++ b/pkg/ui/src/app.tsx @@ -17,7 +17,6 @@ import { tableNameAttr, databaseNameAttr, nodeIDAttr, dashboardNameAttr, rangeIDAttr, statementAttr, appAttr, implicitTxnAttr, } from "src/util/constants"; -import "src/redux/analytics"; import { AdminUIState, History } from "src/redux/state"; import loginRoutes from "src/routes/login"; @@ -51,6 +50,7 @@ import Stores from "src/views/reports/containers/stores"; import StatementsPage from "src/views/statements/statementsPage"; import StatementDetails from "src/views/statements/statementDetails"; import { normalizeConnectedComponent } from "src/util/normalizeConnectedComponent"; +import { DecommissionedNodeHistory } from "src/views/reports"; import "nvd3/build/nv.d3.min.css"; import "react-select/dist/react-select.css"; @@ -82,7 +82,7 @@ export const App: React.FC = (props: AppProps) => { { /* login */} {loginRoutes(store)} - + { /* overview page */} @@ -173,6 +173,10 @@ export const App: React.FC = (props: AppProps) => { + + + + diff --git a/pkg/ui/src/index.tsx b/pkg/ui/src/index.tsx index 15edcb53e21f..6d03023e21d1 100644 --- a/pkg/ui/src/index.tsx +++ b/pkg/ui/src/index.tsx @@ -13,9 +13,9 @@ import * as ReactDOM from "react-dom"; import "src/polyfills"; import "src/protobufInit"; import { alertDataSync } from "src/redux/alerts"; - import {App} from "src/app"; import {store, history} from "src/redux/state"; +import "src/redux/analytics"; ReactDOM.render( , diff --git a/pkg/ui/src/views/statements/statementsPage.tsx b/pkg/ui/src/views/statements/statementsPage.tsx index 107840305c93..094947a37fc5 100644 --- a/pkg/ui/src/views/statements/statementsPage.tsx +++ b/pkg/ui/src/views/statements/statementsPage.tsx @@ -13,6 +13,7 @@ import _ from "lodash"; import moment from "moment"; import React from "react"; import Helmet from "react-helmet"; +import { bindActionCreators } from "redux"; import { connect } from "react-redux"; import { RouteComponentProps } from "react-router"; import { createSelector } from "reselect"; @@ -359,9 +360,12 @@ const StatementsPageConnected = connect( totalFingerprints: selectTotalFingerprints(state), lastReset: selectLastReset(state), }), - () => ({ - refreshStatements, - }), + dispatch => bindActionCreators( + { + refreshStatements, + }, + dispatch, + ), )(StatementsPage); export default StatementsPageConnected; From c3c13a66236182c3d12cd866980af320e8c2eeef Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Fri, 24 Jan 2020 15:42:48 +0200 Subject: [PATCH 15/20] ui: Fix linting error Release note: None --- pkg/ui/src/app.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/ui/src/app.spec.tsx b/pkg/ui/src/app.spec.tsx index fa76bea095e8..4207f1fdead5 100644 --- a/pkg/ui/src/app.spec.tsx +++ b/pkg/ui/src/app.spec.tsx @@ -353,7 +353,7 @@ describe("Routing to", () => { }); // TODO (koorosh): Disabled due to strange failure on internal - // behaviour of ReduxDebug component under test env. + // behavior of ReduxDebug component under test env. xdescribe("'/debug/redux' path", () => { it("routes to component", () => { navigateToPath("/debug/redux"); From ed327d08cd730432fbc538aa228b221c11d9c5f0 Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Wed, 29 Jan 2020 14:45:22 +0200 Subject: [PATCH 16/20] ui: Fix conflicts after rebasing against 'ui-react-version-upgrade' branch Release note: None --- pkg/ui/src/app.spec.tsx | 2 +- pkg/ui/src/views/statements/statementDetails.tsx | 2 +- pkg/ui/yarn-vendor | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/ui/src/app.spec.tsx b/pkg/ui/src/app.spec.tsx index 4207f1fdead5..b5530c40580c 100644 --- a/pkg/ui/src/app.spec.tsx +++ b/pkg/ui/src/app.spec.tsx @@ -42,7 +42,7 @@ import { EnqueueRange } from "src/views/reports/containers/enqueueRange"; import { RangesMain } from "src/views/devtools/containers/raftRanges"; import { RaftMessages } from "src/views/devtools/containers/raftMessages"; import Raft from "src/views/devtools/containers/raft"; -import NotFound from "oss/src/views/app/components/NotFound"; +import NotFound from "src/views/app/components/NotFound"; describe("Routing to", () => { const store: Store = createAdminUIStore(); diff --git a/pkg/ui/src/views/statements/statementDetails.tsx b/pkg/ui/src/views/statements/statementDetails.tsx index 2416ac7c5241..77c9987fa8e8 100644 --- a/pkg/ui/src/views/statements/statementDetails.tsx +++ b/pkg/ui/src/views/statements/statementDetails.tsx @@ -140,7 +140,7 @@ class NumericStatTable extends React.Component { } } -class StatementDetails extends React.Component { +export class StatementDetails extends React.Component { constructor(props: StatementDetailsProps) { super(props); diff --git a/pkg/ui/yarn-vendor b/pkg/ui/yarn-vendor index 6a3909f60f13..9d4a766de287 160000 --- a/pkg/ui/yarn-vendor +++ b/pkg/ui/yarn-vendor @@ -1 +1 @@ -Subproject commit 6a3909f60f1308df24e1d2b2f459adc99bd057af +Subproject commit 9d4a766de2874e9d6f89f8ad68138715f2b5382e From 1b050597b9aeca8e7ee2d07419f0e7ce89600a48 Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Thu, 30 Jan 2020 14:52:15 +0200 Subject: [PATCH 17/20] ui: Export Connected and pure Component classes for correct unit testing Testing with Enzyme framework is straight-froward and easy, but there is once gotcha: ConnectedComponents (wrapped with Connect function) are not recognized by Enzyme as react components and it is not possible to use enzymeWrapper.find(Component) helper function to check whether connected component is rendered in DOM or not. To make it clear there is an example how Connected Component is recognized in dom: ``` <[object Object] location={{...}} params={{...}} route={{...}} ... ``` as [object] not a Component. To overcome this problem "not connected" child components are exported in addition to connected. It allows to write tests against these components. Release note: None --- pkg/ui/src/views/reports/containers/certificates/index.tsx | 2 +- pkg/ui/src/views/reports/containers/localities/index.tsx | 2 +- pkg/ui/src/views/reports/containers/network/index.tsx | 2 +- .../containers/nodeHistory/decommissionedNodeHistory.tsx | 3 +-- pkg/ui/src/views/reports/containers/nodes/index.tsx | 2 +- pkg/ui/src/views/reports/containers/problemRanges/index.tsx | 2 +- pkg/ui/src/views/reports/containers/range/index.tsx | 2 +- pkg/ui/src/views/reports/containers/settings/index.tsx | 2 +- pkg/ui/src/views/reports/containers/stores/index.tsx | 2 +- 9 files changed, 9 insertions(+), 10 deletions(-) diff --git a/pkg/ui/src/views/reports/containers/certificates/index.tsx b/pkg/ui/src/views/reports/containers/certificates/index.tsx index f064ffaa080e..b8188a52bcc6 100644 --- a/pkg/ui/src/views/reports/containers/certificates/index.tsx +++ b/pkg/ui/src/views/reports/containers/certificates/index.tsx @@ -46,7 +46,7 @@ function certificatesRequestFromProps(props: CertificatesProps) { /** * Renders the Certificate Report page. */ -class Certificates extends React.Component { +export class Certificates extends React.Component { refresh(props = this.props) { props.refreshCertificates(certificatesRequestFromProps(props)); } diff --git a/pkg/ui/src/views/reports/containers/localities/index.tsx b/pkg/ui/src/views/reports/containers/localities/index.tsx index 6a29b2760235..8e9289ae45a1 100644 --- a/pkg/ui/src/views/reports/containers/localities/index.tsx +++ b/pkg/ui/src/views/reports/containers/localities/index.tsx @@ -86,7 +86,7 @@ interface LocalitiesProps { refreshNodes: typeof refreshNodes; } -class Localities extends React.Component { +export class Localities extends React.Component { componentWillMount() { this.props.refreshLocations(); this.props.refreshNodes(); diff --git a/pkg/ui/src/views/reports/containers/network/index.tsx b/pkg/ui/src/views/reports/containers/network/index.tsx index 23925ed83dc9..86a55b2dbafe 100644 --- a/pkg/ui/src/views/reports/containers/network/index.tsx +++ b/pkg/ui/src/views/reports/containers/network/index.tsx @@ -83,7 +83,7 @@ export function getValueFromString(key: string, params: string, fullString?: boo /** * Renders the Network Diagnostics Report page. */ -class Network extends React.Component { +export class Network extends React.Component { state: INetworkState = { collapsed: false, filter: null, diff --git a/pkg/ui/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx b/pkg/ui/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx index 49ae3ed2171f..ab87bce6a596 100644 --- a/pkg/ui/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx +++ b/pkg/ui/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx @@ -14,7 +14,6 @@ import { connect } from "react-redux"; import { Link } from "react-router"; import moment from "moment"; import _ from "lodash"; -import { Action, bindActionCreators, Dispatch } from "redux"; import { AdminUIState } from "src/redux/state"; import { @@ -50,7 +49,7 @@ export interface DecommissionedNodeHistoryProps { nodesSummary: NodesSummary; } -class DecommissionedNodeHistory extends React.Component { +export class DecommissionedNodeHistory extends React.Component { componentWillMount() { this.props.refreshNodes(); this.props.refreshLiveness(); diff --git a/pkg/ui/src/views/reports/containers/nodes/index.tsx b/pkg/ui/src/views/reports/containers/nodes/index.tsx index fd8e88d0f9c6..0ea7dc444e9e 100644 --- a/pkg/ui/src/views/reports/containers/nodes/index.tsx +++ b/pkg/ui/src/views/reports/containers/nodes/index.tsx @@ -246,7 +246,7 @@ const nodesTableRows: NodesTableRowParams[] = [ /** * Renders the Nodes Diagnostics Report page. */ -class Nodes extends React.Component { +export class Nodes extends React.Component { refresh(props = this.props) { props.refreshLiveness(); props.refreshNodes(); diff --git a/pkg/ui/src/views/reports/containers/problemRanges/index.tsx b/pkg/ui/src/views/reports/containers/problemRanges/index.tsx index 4213a3b87506..d984545540ca 100644 --- a/pkg/ui/src/views/reports/containers/problemRanges/index.tsx +++ b/pkg/ui/src/views/reports/containers/problemRanges/index.tsx @@ -83,7 +83,7 @@ function problemRangeRequestFromProps(props: ProblemRangesProps) { * per node basis. This page aggregates those lists together and displays all * unique range IDs that have problems. */ -class ProblemRanges extends React.Component { +export class ProblemRanges extends React.Component { refresh(props = this.props) { props.refreshProblemRanges(problemRangeRequestFromProps(props)); } diff --git a/pkg/ui/src/views/reports/containers/range/index.tsx b/pkg/ui/src/views/reports/containers/range/index.tsx index 1a590156498a..0d99abb77843 100644 --- a/pkg/ui/src/views/reports/containers/range/index.tsx +++ b/pkg/ui/src/views/reports/containers/range/index.tsx @@ -83,7 +83,7 @@ function rangeLogRequestFromProps(props: RangeProps) { /** * Renders the Range Report page. */ -class Range extends React.Component { +export class Range extends React.Component { refresh(props = this.props) { props.refreshRange(rangeRequestFromProps(props)); props.refreshAllocatorRange(allocatorRequestFromProps(props)); diff --git a/pkg/ui/src/views/reports/containers/settings/index.tsx b/pkg/ui/src/views/reports/containers/settings/index.tsx index b7474b2c4597..a425becab2e4 100644 --- a/pkg/ui/src/views/reports/containers/settings/index.tsx +++ b/pkg/ui/src/views/reports/containers/settings/index.tsx @@ -29,7 +29,7 @@ type SettingsProps = SettingsOwnProps; /** * Renders the Cluster Settings Report page. */ -class Settings extends React.Component { +export class Settings extends React.Component { refresh(props = this.props) { props.refreshSettings(new protos.cockroach.server.serverpb.SettingsRequest()); } diff --git a/pkg/ui/src/views/reports/containers/stores/index.tsx b/pkg/ui/src/views/reports/containers/stores/index.tsx index ab0268111de4..cbd5a5e8b09c 100644 --- a/pkg/ui/src/views/reports/containers/stores/index.tsx +++ b/pkg/ui/src/views/reports/containers/stores/index.tsx @@ -40,7 +40,7 @@ function storesRequestFromProps(props: StoresProps) { /** * Renders the Stores Report page. */ -class Stores extends React.Component { +export class Stores extends React.Component { refresh(props = this.props) { props.refreshStores(storesRequestFromProps(props)); } From 9ee488d29521872df60b9e4059c9a29ea8301f79 Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Thu, 30 Jan 2020 14:55:00 +0200 Subject: [PATCH 18/20] ui: Get rid of useless function wrappers It is not necessary to wrap action creator object with function when connecting it to component. Release note: None --- .../cluster/containers/dataDistribution/index.tsx | 4 ++-- .../src/views/cluster/containers/events/index.tsx | 8 ++++---- .../views/cluster/containers/nodeGraphs/index.tsx | 4 ++-- .../views/cluster/containers/nodeLogs/index.tsx | 4 ++-- .../cluster/containers/nodeOverview/index.tsx | 4 ++-- .../cluster/containers/nodesOverview/index.tsx | 4 ++-- .../views/cluster/containers/timescale/index.tsx | 4 ++-- .../nodeHistory/decommissionedNodeHistory.tsx | 14 +++++--------- pkg/ui/src/views/statements/statementsPage.tsx | 10 +++------- 9 files changed, 24 insertions(+), 32 deletions(-) diff --git a/pkg/ui/src/views/cluster/containers/dataDistribution/index.tsx b/pkg/ui/src/views/cluster/containers/dataDistribution/index.tsx index 51b824b075ec..06e80e4801bc 100644 --- a/pkg/ui/src/views/cluster/containers/dataDistribution/index.tsx +++ b/pkg/ui/src/views/cluster/containers/dataDistribution/index.tsx @@ -211,11 +211,11 @@ const DataDistributionPageConnected = connect( localityTree: selectLocalityTree(state), localityTreeErrors: localityTreeErrors(state), }), - () => ({ + { refreshDataDistribution, refreshNodes, refreshLiveness, - }), + }, )(DataDistributionPage); export default DataDistributionPageConnected; diff --git a/pkg/ui/src/views/cluster/containers/events/index.tsx b/pkg/ui/src/views/cluster/containers/events/index.tsx index 4f6b7f3315e5..5289dfad25d0 100644 --- a/pkg/ui/src/views/cluster/containers/events/index.tsx +++ b/pkg/ui/src/views/cluster/containers/events/index.tsx @@ -176,9 +176,9 @@ const eventBoxConnected = connect( eventsValid: eventsValidSelector(state), }; }, - () => ({ + { refreshEvents, - }), + }, )(EventBoxUnconnected); // Connect the EventsList class with our redux store. @@ -190,10 +190,10 @@ const eventPageConnected = connect( sortSetting: eventsSortSetting.selector(state), }; }, - () => ({ + { refreshEvents, setSort: eventsSortSetting.set, - }), + }, )(EventPageUnconnected); export { eventBoxConnected as EventBox }; diff --git a/pkg/ui/src/views/cluster/containers/nodeGraphs/index.tsx b/pkg/ui/src/views/cluster/containers/nodeGraphs/index.tsx index 06f8ab429242..5de5413ffe82 100644 --- a/pkg/ui/src/views/cluster/containers/nodeGraphs/index.tsx +++ b/pkg/ui/src/views/cluster/containers/nodeGraphs/index.tsx @@ -263,10 +263,10 @@ export default connect( hoverState: hoverStateSelector(state), }; }, - () => ({ + { refreshNodes, refreshLiveness, hoverOn, hoverOff, - }), + }, )(NodeGraphs); diff --git a/pkg/ui/src/views/cluster/containers/nodeLogs/index.tsx b/pkg/ui/src/views/cluster/containers/nodeLogs/index.tsx index 5735d2a6d3b6..7c87acc3d9fd 100644 --- a/pkg/ui/src/views/cluster/containers/nodeLogs/index.tsx +++ b/pkg/ui/src/views/cluster/containers/nodeLogs/index.tsx @@ -129,10 +129,10 @@ const logsConnected = connect( currentNode: currentNode(state, ownProps), }; }, - () => ({ + { refreshLogs, refreshNodes, - }), + }, )(Logs); export default logsConnected; diff --git a/pkg/ui/src/views/cluster/containers/nodeOverview/index.tsx b/pkg/ui/src/views/cluster/containers/nodeOverview/index.tsx index 082155e07c4e..0a0b2226c628 100644 --- a/pkg/ui/src/views/cluster/containers/nodeOverview/index.tsx +++ b/pkg/ui/src/views/cluster/containers/nodeOverview/index.tsx @@ -198,8 +198,8 @@ export default connect( nodesSummaryValid: selectNodesSummaryValid(state), }; }, - () => ({ + { refreshNodes, refreshLiveness, - }), + }, )(NodeOverview); diff --git a/pkg/ui/src/views/cluster/containers/nodesOverview/index.tsx b/pkg/ui/src/views/cluster/containers/nodesOverview/index.tsx index 1ad0c5107503..da70ea2c8669 100644 --- a/pkg/ui/src/views/cluster/containers/nodesOverview/index.tsx +++ b/pkg/ui/src/views/cluster/containers/nodesOverview/index.tsx @@ -530,10 +530,10 @@ const NodesMainConnected = connect( nodesSummaryValid: selectNodesSummaryValid(state), }; }, - () => ({ + { refreshNodes, refreshLiveness, - }), + }, )(NodesMain); export { NodesMainConnected as NodesOverview }; diff --git a/pkg/ui/src/views/cluster/containers/timescale/index.tsx b/pkg/ui/src/views/cluster/containers/timescale/index.tsx index 84bd239bb38d..943b0598f085 100644 --- a/pkg/ui/src/views/cluster/containers/timescale/index.tsx +++ b/pkg/ui/src/views/cluster/containers/timescale/index.tsx @@ -317,10 +317,10 @@ export default connect( defaultTimescaleSet: timescaleDefaultSet.selector(state), }; }, - () => ({ + { setTimeScale: timewindow.setTimeScale, setTimeRange: timewindow.setTimeRange, refreshNodes: refreshNodes, setDefaultSet: timescaleDefaultSet.set, - }), + }, )(withRouter(TimeScaleDropdown)); diff --git a/pkg/ui/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx b/pkg/ui/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx index ab87bce6a596..b2bf7d239c71 100644 --- a/pkg/ui/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx +++ b/pkg/ui/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx @@ -127,14 +127,10 @@ const mapStateToProps = (state: AdminUIState) => ({ nodesSummary: nodesSummarySelector(state), }); -const mapDispatchToProps = (dispatch: Dispatch) => - bindActionCreators( - { - refreshNodes, - refreshLiveness, - setSort: decommissionedNodesSortSetting.set, - }, - dispatch, - ); +const mapDispatchToProps = { + refreshNodes, + refreshLiveness, + setSort: decommissionedNodesSortSetting.set, +}; export default connect(mapStateToProps, mapDispatchToProps)(DecommissionedNodeHistory); diff --git a/pkg/ui/src/views/statements/statementsPage.tsx b/pkg/ui/src/views/statements/statementsPage.tsx index 094947a37fc5..8c010bb5909b 100644 --- a/pkg/ui/src/views/statements/statementsPage.tsx +++ b/pkg/ui/src/views/statements/statementsPage.tsx @@ -13,7 +13,6 @@ import _ from "lodash"; import moment from "moment"; import React from "react"; import Helmet from "react-helmet"; -import { bindActionCreators } from "redux"; import { connect } from "react-redux"; import { RouteComponentProps } from "react-router"; import { createSelector } from "reselect"; @@ -360,12 +359,9 @@ const StatementsPageConnected = connect( totalFingerprints: selectTotalFingerprints(state), lastReset: selectLastReset(state), }), - dispatch => bindActionCreators( - { - refreshStatements, - }, - dispatch, - ), + { + refreshStatements, + }, )(StatementsPage); export default StatementsPageConnected; From a32671fb6c062a4466278cc7501acadcad734592 Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Thu, 30 Jan 2020 14:57:15 +0200 Subject: [PATCH 19/20] ui: Fix missed routes for Statements and Decommissioned node list pages - Remove unnecessary re-exporting from index.ts file - Simplify exporting of DecommissionedNodeList component Release note: None --- pkg/ui/src/app.tsx | 9 +++++---- .../views/reports/containers/nodeHistory/index.tsx | 11 ----------- pkg/ui/src/views/reports/index.ts | 5 ++++- 3 files changed, 9 insertions(+), 16 deletions(-) delete mode 100644 pkg/ui/src/views/reports/containers/nodeHistory/index.tsx diff --git a/pkg/ui/src/app.tsx b/pkg/ui/src/app.tsx index 9f3a4a0a6286..fa4a435f302c 100644 --- a/pkg/ui/src/app.tsx +++ b/pkg/ui/src/app.tsx @@ -50,7 +50,7 @@ import Stores from "src/views/reports/containers/stores"; import StatementsPage from "src/views/statements/statementsPage"; import StatementDetails from "src/views/statements/statementDetails"; import { normalizeConnectedComponent } from "src/util/normalizeConnectedComponent"; -import { DecommissionedNodeHistory } from "src/views/reports"; +import { ConnectedDecommissionedNodeHistory } from "src/views/reports"; import "nvd3/build/nv.d3.min.css"; import "react-select/dist/react-select.css"; @@ -171,11 +171,12 @@ export const App: React.FC = (props: AppProps) => { - - - + + + + diff --git a/pkg/ui/src/views/reports/containers/nodeHistory/index.tsx b/pkg/ui/src/views/reports/containers/nodeHistory/index.tsx deleted file mode 100644 index 9cc3851850b5..000000000000 --- a/pkg/ui/src/views/reports/containers/nodeHistory/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2019 The Cockroach Authors. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -export { default as DecommissionedNodeHistory } from "./decommissionedNodeHistory"; diff --git a/pkg/ui/src/views/reports/index.ts b/pkg/ui/src/views/reports/index.ts index 25fd887cb438..7e9f008b0183 100644 --- a/pkg/ui/src/views/reports/index.ts +++ b/pkg/ui/src/views/reports/index.ts @@ -10,7 +10,10 @@ export { default as Certificates } from "./containers/certificates"; export { default as CustomChart } from "./containers/customChart"; -export { DecommissionedNodeHistory } from "./containers/nodeHistory"; +export { + default as ConnectedDecommissionedNodeHistory, + DecommissionedNodeHistory, +} from "./containers/nodeHistory/decommissionedNodeHistory"; export { default as Debug } from "./containers/debug"; export { default as EnqueueRange } from "./containers/enqueueRange"; export { default as ProblemRanges } from "./containers/problemRanges"; From 043019e379a3b5349c555fccd580ac241a120684 Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Thu, 30 Jan 2020 14:58:04 +0200 Subject: [PATCH 20/20] ui: Add tests on routing to /reports views Release note: None --- pkg/ui/src/app.spec.tsx | 86 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/pkg/ui/src/app.spec.tsx b/pkg/ui/src/app.spec.tsx index b5530c40580c..d69c9995f2bb 100644 --- a/pkg/ui/src/app.spec.tsx +++ b/pkg/ui/src/app.spec.tsx @@ -43,6 +43,15 @@ import { RangesMain } from "src/views/devtools/containers/raftRanges"; import { RaftMessages } from "src/views/devtools/containers/raftMessages"; import Raft from "src/views/devtools/containers/raft"; import NotFound from "src/views/app/components/NotFound"; +import { ProblemRanges } from "src/views/reports/containers/problemRanges"; +import { Localities } from "src/views/reports/containers/localities"; +import { Nodes } from "src/views/reports/containers/nodes"; +import { DecommissionedNodeHistory } from "src/views/reports"; +import { Network } from "src/views/reports/containers/network"; +import { Settings } from "src/views/reports/containers/settings"; +import { Certificates } from "src/views/reports/containers/certificates"; +import { Range } from "src/views/reports/containers/range"; +import { Stores } from "src/views/reports/containers/stores"; describe("Routing to", () => { const store: Store = createAdminUIStore(); @@ -411,6 +420,83 @@ describe("Routing to", () => { }); }); + describe("'/reports/problemranges' path", () => { + it("routes to component", () => { + navigateToPath("/reports/problemranges"); + assert.lengthOf(appWrapper.find(ProblemRanges), 1); + }); + }); + + describe("'/reports/problemranges/:nodeIDAttr' path", () => { + it("routes to component", () => { + navigateToPath("/reports/problemranges/1"); + assert.lengthOf(appWrapper.find(ProblemRanges), 1); + }); + }); + + describe("'/reports/localities' path", () => { + it("routes to component", () => { + navigateToPath("/reports/localities"); + assert.lengthOf(appWrapper.find(Localities), 1); + }); + }); + + describe("'/reports/nodes' path", () => { + it("routes to component", () => { + navigateToPath("/reports/nodes"); + assert.lengthOf(appWrapper.find(Nodes), 1); + }); + }); + + describe("'/reports/nodes/history' path", () => { + it("routes to component", () => { + navigateToPath("/reports/nodes/history"); + assert.lengthOf(appWrapper.find(DecommissionedNodeHistory), 1); + }); + }); + + describe("'/reports/network' path", () => { + it("routes to component", () => { + navigateToPath("/reports/network"); + assert.lengthOf(appWrapper.find(Network), 1); + }); + }); + + describe("'/reports/network/:nodeIDAttr' path", () => { + it("routes to component", () => { + navigateToPath("/reports/network/1"); + assert.lengthOf(appWrapper.find(Network), 1); + }); + }); + + describe("'/reports/settings' path", () => { + it("routes to component", () => { + navigateToPath("/reports/settings"); + assert.lengthOf(appWrapper.find(Settings), 1); + }); + }); + + describe("'/reports/certificates/:nodeIDAttr' path", () => { + it("routes to component", () => { + navigateToPath("/reports/certificates/1"); + assert.lengthOf(appWrapper.find(Certificates), 1); + }); + }); + + describe("'/reports/range/:nodeIDAttr' path", () => { + it("routes to component", () => { + navigateToPath("/reports/range/1"); + assert.lengthOf(appWrapper.find(Range), 1); + }); + }); + + describe("'/reports/stores/:nodeIDAttr' path", () => { + it("routes to component", () => { + navigateToPath("/reports/stores/1"); + assert.lengthOf(appWrapper.find(Stores), 1); + }); + }); + { /* old route redirects */} describe("'/cluster' path", () => { it("redirected to '/metrics/overview/cluster'", () => {