From 7477001cd13001f2ee3289579c147004573376d7 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sun, 8 May 2022 20:10:53 +0000 Subject: [PATCH 01/26] fix: upgrade moment from 2.29.2 to 2.29.3 Snyk has created this PR to upgrade moment from 2.29.2 to 2.29.3. See this package in npm: See this project in Snyk: https://app.snyk.io/org/giranm/project/d64f0b69-278b-4bed-90e9-845c5a282b48?utm_source=github&utm_medium=referral&page=upgrade-pr --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 6382fc8a..b056fea3 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "immer": "^9.0.6", "lodash": "^4.17.21", "mezr": "^0.6.2", - "moment": "^2.29.2", + "moment": "^2.29.3", "node-sass": "^6.0.1", "react": "^17.0.2", "react-bootstrap": "^1.6.4", diff --git a/yarn.lock b/yarn.lock index 01c5b7c5..0e027d31 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9304,10 +9304,10 @@ mocked-env@1.3.2: lazy-ass "1.6.0" ramda "0.26.1" -moment@^2.29.2: - version "2.29.2" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4" - integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg== +moment@^2.29.3: + version "2.29.3" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3" + integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw== moo@^0.5.0: version "0.5.1" From 2133b6dbf9cb4cc5aed814ed09e94c8f1ff3c0a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 05:41:26 +0000 Subject: [PATCH 02/26] Bump html-webpack-plugin from 4.5.2 to 5.5.0 Bumps [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin) from 4.5.2 to 5.5.0. - [Release notes](https://github.com/jantimon/html-webpack-plugin/releases) - [Changelog](https://github.com/jantimon/html-webpack-plugin/blob/main/CHANGELOG.md) - [Commits](https://github.com/jantimon/html-webpack-plugin/compare/v4.5.2...v5.5.0) --- updated-dependencies: - dependency-name: html-webpack-plugin dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 144 +++------------------------------------------------ 2 files changed, 9 insertions(+), 137 deletions(-) diff --git a/package.json b/package.json index b056fea3..d60ac0ae 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "eslint-plugin-react-hooks": "^4.2.0", "genversion": "^3.0.2", "gh-pages": "^3.2.3", - "html-webpack-plugin": "4", + "html-webpack-plugin": "5", "identity-obj-proxy": "^3.0.0", "prettier": "^2.5.1", "prettier-eslint": "^10.1.0", diff --git a/yarn.lock b/yarn.lock index 0e027d31..fbd6cf9a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2581,11 +2581,6 @@ "@types/react" "*" hoist-non-react-statics "^3.3.0" -"@types/html-minifier-terser@^5.0.0": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz#693b316ad323ea97eed6b38ed1a3cc02b1672b57" - integrity sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w== - "@types/html-minifier-terser@^6.0.0": version "6.1.0" resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" @@ -2792,21 +2787,11 @@ dependencies: "@types/node" "*" -"@types/source-list-map@*": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" - integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== - "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== -"@types/tapable@^1", "@types/tapable@^1.0.5": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310" - integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ== - "@types/testing-library__jest-dom@^5.9.1": version "5.14.2" resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.2.tgz#564fb2b2dc827147e937a75b639a05d17ce18b44" @@ -2819,39 +2804,11 @@ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg== -"@types/uglify-js@*": - version "3.13.1" - resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.13.1.tgz#5e889e9e81e94245c75b6450600e1c5ea2878aea" - integrity sha512-O3MmRAk6ZuAKa9CHgg0Pr0+lUOqoMLpc9AS4R8ano2auvsg7IE8syF3Xh/NPr26TWklxYcqoEEFdzLLs1fV9PQ== - dependencies: - source-map "^0.6.1" - "@types/warning@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52" integrity sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI= -"@types/webpack-sources@*": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-3.2.0.tgz#16d759ba096c289034b26553d2df1bf45248d38b" - integrity sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg== - dependencies: - "@types/node" "*" - "@types/source-list-map" "*" - source-map "^0.7.3" - -"@types/webpack@^4.41.8": - version "4.41.32" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.32.tgz#a7bab03b72904070162b2f169415492209e94212" - integrity sha512-cb+0ioil/7oz5//7tZUSwbrSAN/NWHrQylz5cW8G0dWTcF/g+/dSdMlKVZspBYuMAN1+WnwHrkxiRrLcwd0Heg== - dependencies: - "@types/node" "*" - "@types/tapable" "^1" - "@types/uglify-js" "*" - "@types/webpack-sources" "*" - anymatch "^3.0.0" - source-map "^0.6.0" - "@types/ws@^8.5.1": version "8.5.3" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" @@ -3475,7 +3432,7 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -anymatch@^3.0.0, anymatch@^3.0.3, anymatch@~3.1.2: +anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -4195,7 +4152,7 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camel-case@^4.1.1, camel-case@^4.1.2: +camel-case@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== @@ -4402,13 +4359,6 @@ classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== -clean-css@^4.2.3: - version "4.2.4" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.4.tgz#733bf46eba4e607c6891ea57c24a989356831178" - integrity sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A== - dependencies: - source-map "~0.6.0" - clean-css@^5.2.2: version "5.3.0" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.0.tgz#ad3d8238d5f3549e83d5f87205189494bc7cbb59" @@ -4577,11 +4527,6 @@ commander@^2.11.0, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, comm resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - commander@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" @@ -5214,7 +5159,7 @@ define-lazy-prop@^2.0.0: resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== -define-properties@^1.1.2, define-properties@^1.1.3: +define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -7283,19 +7228,6 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -html-minifier-terser@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#922e96f1f3bb60832c2634b79884096389b1f054" - integrity sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg== - dependencies: - camel-case "^4.1.1" - clean-css "^4.2.3" - commander "^4.1.1" - he "^1.2.0" - param-case "^3.0.3" - relateurl "^0.2.7" - terser "^4.6.3" - html-minifier-terser@^6.0.2: version "6.1.0" resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" @@ -7309,22 +7241,7 @@ html-minifier-terser@^6.0.2: relateurl "^0.2.7" terser "^5.10.0" -html-webpack-plugin@4: - version "4.5.2" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.5.2.tgz#76fc83fa1a0f12dd5f7da0404a54e2699666bc12" - integrity sha512-q5oYdzjKUIPQVjOosjgvCHQOv9Ett9CYYHlgvJeXG0qQvdSojnBq4vAdQBwn1+yGveAwHCoe/rMR86ozX3+c2A== - dependencies: - "@types/html-minifier-terser" "^5.0.0" - "@types/tapable" "^1.0.5" - "@types/webpack" "^4.41.8" - html-minifier-terser "^5.0.1" - loader-utils "^1.2.3" - lodash "^4.17.20" - pretty-error "^2.1.1" - tapable "^1.1.3" - util.promisify "1.0.0" - -html-webpack-plugin@^5.5.0: +html-webpack-plugin@5, html-webpack-plugin@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" integrity sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw== @@ -8797,15 +8714,6 @@ loader-runner@^4.2.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== -loader-utils@^1.2.3: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - loader-utils@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" @@ -9669,7 +9577,7 @@ object.fromentries@^2.0.0, object.fromentries@^2.0.5: define-properties "^1.1.3" es-abstract "^1.19.1" -object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: +object.getownpropertydescriptors@^2.1.0: version "2.1.3" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz#b223cf38e17fefb97a63c10c91df72ccb386df9e" integrity sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw== @@ -9871,7 +9779,7 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -param-case@^3.0.3, param-case@^3.0.4: +param-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== @@ -10694,14 +10602,6 @@ pretty-bytes@^5.3.0, pretty-bytes@^5.4.1, pretty-bytes@^5.6.0: resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== -pretty-error@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.2.tgz#be89f82d81b1c86ec8fdfbc385045882727f93b6" - integrity sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw== - dependencies: - lodash "^4.17.20" - renderkid "^2.0.4" - pretty-error@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" @@ -11447,17 +11347,6 @@ relateurl@^0.2.7: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= -renderkid@^2.0.4: - version "2.0.7" - resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.7.tgz#464f276a6bdcee606f4a15993f9b29fc74ca8609" - integrity sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ== - dependencies: - css-select "^4.1.3" - dom-converter "^0.2.0" - htmlparser2 "^6.1.0" - lodash "^4.17.21" - strip-ansi "^3.0.1" - renderkid@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" @@ -12148,7 +12037,7 @@ source-map-resolve@^0.6.0: atob "^2.1.2" decode-uri-component "^0.2.0" -source-map-support@^0.5.6, source-map-support@~0.5.12, source-map-support@~0.5.20: +source-map-support@^0.5.6, source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== @@ -12693,7 +12582,7 @@ tailwindcss@^3.0.2: quick-lru "^5.1.1" resolve "^1.22.0" -tapable@^1.0.0, tapable@^1.1.3: +tapable@^1.0.0: version "1.1.3" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== @@ -12749,15 +12638,6 @@ terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.2.5: source-map "^0.6.1" terser "^5.7.2" -terser@^4.6.3: - version "4.8.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" - integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - terser@^5.0.0, terser@^5.10.0, terser@^5.7.2: version "5.13.1" resolved "https://registry.yarnpkg.com/terser/-/terser-5.13.1.tgz#66332cdc5a01b04a224c9fad449fc1a18eaa1799" @@ -13198,14 +13078,6 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -util.promisify@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" - integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== - dependencies: - define-properties "^1.1.2" - object.getownpropertydescriptors "^2.0.3" - util.promisify@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" From ef9d98f5a49665c52e3a5ee0300d8fb958067c6a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 05:41:47 +0000 Subject: [PATCH 03/26] Bump eslint-plugin-import from 2.25.4 to 2.26.0 Bumps [eslint-plugin-import](https://github.com/import-js/eslint-plugin-import) from 2.25.4 to 2.26.0. - [Release notes](https://github.com/import-js/eslint-plugin-import/releases) - [Changelog](https://github.com/import-js/eslint-plugin-import/blob/main/CHANGELOG.md) - [Commits](https://github.com/import-js/eslint-plugin-import/compare/v2.25.4...v2.26.0) --- updated-dependencies: - dependency-name: eslint-plugin-import dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 62 +++++++++++++++++++--------------------------------- 2 files changed, 24 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index b056fea3..ca5390f1 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "eslint-config-prettier": "^8.3.0", "eslint-config-react-app": "^7.0.0", "eslint-plugin-cypress": "^2.12.1", - "eslint-plugin-import": "^2.24.2", + "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.26.1", diff --git a/yarn.lock b/yarn.lock index 0e027d31..253f78a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5789,10 +5789,10 @@ eslint-import-resolver-node@^0.3.6: debug "^3.2.7" resolve "^1.20.0" -eslint-module-utils@^2.7.2: - version "2.7.2" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz#1d0aa455dcf41052339b63cada8ab5fd57577129" - integrity sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg== +eslint-module-utils@^2.7.3: + version "2.7.3" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee" + integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ== dependencies: debug "^3.2.7" find-up "^2.1.0" @@ -5812,24 +5812,24 @@ eslint-plugin-flowtype@^8.0.3: lodash "^4.17.21" string-natural-compare "^3.0.1" -eslint-plugin-import@^2.24.2, eslint-plugin-import@^2.25.3: - version "2.25.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" - integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA== +eslint-plugin-import@^2.25.3, eslint-plugin-import@^2.26.0: + version "2.26.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b" + integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== dependencies: array-includes "^3.1.4" array.prototype.flat "^1.2.5" debug "^2.6.9" doctrine "^2.1.0" eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.2" + eslint-module-utils "^2.7.3" has "^1.0.3" - is-core-module "^2.8.0" + is-core-module "^2.8.1" is-glob "^4.0.3" - minimatch "^3.0.4" + minimatch "^3.1.2" object.values "^1.1.5" - resolve "^1.20.0" - tsconfig-paths "^3.12.0" + resolve "^1.22.0" + tsconfig-paths "^3.14.1" eslint-plugin-jest@^25.3.0: version "25.7.0" @@ -7673,14 +7673,7 @@ is-ci@^3.0.0: dependencies: ci-info "^3.2.0" -is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.8.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" - integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== - dependencies: - has "^1.0.3" - -is-core-module@^2.8.1: +is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.8.1: version "2.9.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== @@ -9224,14 +9217,14 @@ minimalistic-assert@^1.0.0: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2: +minimatch@3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" -minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -9254,7 +9247,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: +minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== @@ -11584,16 +11577,7 @@ resolve.exports@^1.1.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0: - version "1.21.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.21.0.tgz#b51adc97f3472e6a5cf4444d34bc9d6b9037591f" - integrity sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA== - dependencies: - is-core-module "^2.8.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -resolve@^1.22.0: +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== @@ -12941,14 +12925,14 @@ ts-node@^10.4.0: make-error "^1.1.1" yn "3.1.1" -tsconfig-paths@^3.12.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" - integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg== +tsconfig-paths@^3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" + integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== dependencies: "@types/json5" "^0.0.29" json5 "^1.0.1" - minimist "^1.2.0" + minimist "^1.2.6" strip-bom "^3.0.0" tslib@^1.8.1, tslib@^1.9.0: From 66464572cfb7b68c5871a9b56077e7c7e40f9d4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 05:42:08 +0000 Subject: [PATCH 04/26] Bump use-debounce from 7.0.1 to 8.0.1 Bumps [use-debounce](https://github.com/xnimorz/use-debounce) from 7.0.1 to 8.0.1. - [Release notes](https://github.com/xnimorz/use-debounce/releases) - [Changelog](https://github.com/xnimorz/use-debounce/blob/master/CHANGELOG.md) - [Commits](https://github.com/xnimorz/use-debounce/commits/8.0.1) --- updated-dependencies: - dependency-name: use-debounce dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index b056fea3..64d756a8 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "redux-persist": "^6.0.0", "redux-saga": "^1.1.3", "styled-components": "^5.3.5", - "use-debounce": "^7.0.0", + "use-debounce": "^8.0.1", "web-vitals": "^1.1.2" }, "resolutions": { diff --git a/yarn.lock b/yarn.lock index 0e027d31..eba2498c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13183,10 +13183,10 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= -use-debounce@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-7.0.1.tgz#380e6191cc13ad29f8e2149a12b5c37cc2891190" - integrity sha512-fOrzIw2wstbAJuv8PC9Vg4XgwyTLEOdq4y/Z3IhVl8DAE4svRcgyEUvrEXu+BMNgMoc3YND6qLT61kkgEKXh7Q== +use-debounce@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-8.0.1.tgz#5f3e11b067bdf9f2c554a20b4764e38b48022664" + integrity sha512-6tGAFJKJ0qCalecaV7/gm/M6n238nmitNppvR89ff1yfwSFjwFKR7IQZzIZf1KZRQhqNireBzytzU6jgb29oVg== use@^3.1.0: version "3.1.1" From 82d00613c2bba71bf5abd6c0942a4824de156292 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 10:28:19 +0000 Subject: [PATCH 05/26] Bump prettier from 2.5.1 to 2.6.2 Bumps [prettier](https://github.com/prettier/prettier) from 2.5.1 to 2.6.2. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/2.5.1...2.6.2) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 610209bb..c45a2ba8 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "gh-pages": "^3.2.3", "html-webpack-plugin": "5", "identity-obj-proxy": "^3.0.0", - "prettier": "^2.5.1", + "prettier": "^2.6.2", "prettier-eslint": "^10.1.0", "prettier-eslint-cli": "^5.0.1", "redux-mock-store": "^1.5.4", diff --git a/yarn.lock b/yarn.lock index 29c0d9a0..1169264c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10585,10 +10585,10 @@ prettier@^1.7.0: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== -prettier@^2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" - integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== +prettier@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032" + integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew== pretty-bytes@^5.3.0, pretty-bytes@^5.4.1, pretty-bytes@^5.6.0: version "5.6.0" From 72ca275652d381e2d611e18cb60e9a54a1109e1b Mon Sep 17 00:00:00 2001 From: Giran Moodley Date: Sun, 5 Jun 2022 14:41:43 +0100 Subject: [PATCH 06/26] Initial commit of working branch - testing to be added. --- src/App.js | 18 +-- .../ConfirmQueryModalComponent.js | 79 ++++++++++ .../IncidentTable/IncidentTableComponent.js | 30 ++-- .../subcomponents/QueryActiveComponent.js | 17 ++ .../subcomponents/QueryCancelledComponent.js | 18 +++ src/config/constants.js | 1 + src/redux/incidents/sagas.js | 32 ++-- src/redux/persistence/config.js | 8 +- src/redux/query_settings/actions.js | 22 +++ src/redux/query_settings/reducers.js | 53 +++++++ src/redux/query_settings/sagas.js | 147 ++++++++++++++++-- src/redux/rootSaga.js | 8 + 12 files changed, 370 insertions(+), 63 deletions(-) create mode 100644 src/components/ConfirmQueryModal/ConfirmQueryModalComponent.js create mode 100644 src/components/IncidentTable/subcomponents/QueryActiveComponent.js create mode 100644 src/components/IncidentTable/subcomponents/QueryCancelledComponent.js diff --git a/src/App.js b/src/App.js index ba3f8ef4..068f38f0 100644 --- a/src/App.js +++ b/src/App.js @@ -22,11 +22,8 @@ import AddNoteModalComponent from 'components/AddNoteModal/AddNoteModalComponent import ReassignModalComponent from 'components/ReassignModal/ReassignModalComponent'; import AddResponderModalComponent from 'components/AddResponderModal/AddResponderModalComponent'; import MergeModalComponent from 'components/MergeModal/MergeModalComponent'; +import ConfirmQueryModalComponent from 'components/ConfirmQueryModal/ConfirmQueryModalComponent'; -import { - getIncidentsAsync as getIncidentsAsyncConnected, - getAllIncidentNotesAsync as getAllIncidentNotesAsyncConnected, -} from 'redux/incidents/actions'; import { getLogEntriesAsync as getLogEntriesAsyncConnected, cleanRecentLogEntriesAsync as cleanRecentLogEntriesAsyncConnected, @@ -90,8 +87,6 @@ const App = ({ getEscalationPoliciesAsync, getExtensionsAsync, getResponsePlaysAsync, - getIncidentsAsync, - getAllIncidentNotesAsync, getLogEntriesAsync, cleanRecentLogEntriesAsync, }) => { @@ -108,6 +103,7 @@ const App = ({ const { userAuthorized, userAcceptedDisclaimer, currentUserLocale, } = state.users; + const queryError = state.querySettings.error; useEffect(() => { userAuthorize(); if (userAuthorized) { @@ -120,8 +116,7 @@ const App = ({ getExtensionsAsync(); getResponsePlaysAsync(); getPrioritiesAsync(); - getIncidentsAsync(); - getAllIncidentNotesAsync(); + // NB: Get Incidents and Notes are implicitly done from query now checkConnectionStatus(); } }, [userAuthorized]); @@ -139,7 +134,7 @@ const App = ({ const { abilities, } = store.getState().connection; - if (userAuthorized && abilities.includes(PD_REQUIRED_ABILITY)) { + if (userAuthorized && abilities.includes(PD_REQUIRED_ABILITY) && !queryError) { const lastPolledDate = moment() .subtract(2 * LOG_ENTRIES_POLLING_INTERVAL_SECONDS, 'seconds') .toDate(); @@ -147,7 +142,7 @@ const App = ({ } }, LOG_ENTRIES_POLLING_INTERVAL_SECONDS * 1000); return () => clearInterval(pollingInterval); - }, [userAuthorized]); + }, [userAuthorized, queryError]); // Setup log entry clearing useEffect(() => { @@ -191,6 +186,7 @@ const App = ({ + ); @@ -210,8 +206,6 @@ const mapDispatchToProps = (dispatch) => ({ getEscalationPoliciesAsync: () => dispatch(getEscalationPoliciesAsyncConnected()), getExtensionsAsync: () => dispatch(getExtensionsAsyncConnected()), getResponsePlaysAsync: () => dispatch(getResponsePlaysAsyncConnected()), - getIncidentsAsync: () => dispatch(getIncidentsAsyncConnected()), - getAllIncidentNotesAsync: () => dispatch(getAllIncidentNotesAsyncConnected()), getLogEntriesAsync: (since) => dispatch(getLogEntriesAsyncConnected(since)), cleanRecentLogEntriesAsync: () => dispatch(cleanRecentLogEntriesAsyncConnected()), }); diff --git a/src/components/ConfirmQueryModal/ConfirmQueryModalComponent.js b/src/components/ConfirmQueryModal/ConfirmQueryModalComponent.js new file mode 100644 index 00000000..e1b6caaf --- /dev/null +++ b/src/components/ConfirmQueryModal/ConfirmQueryModalComponent.js @@ -0,0 +1,79 @@ +import { + connect, +} from 'react-redux'; + +import { + Modal, Button, +} from 'react-bootstrap'; + +import { + toggleDisplayConfirmQueryModal as toggleDisplayConfirmQueryModalConnected, + confirmIncidentQuery as confirmIncidentQueryConnected, +} from 'redux/query_settings/actions'; + +import { + MAX_INCIDENTS_LIMIT, +} from 'config/constants'; + +const ConfirmQueryModalComponent = ({ + querySettings, + toggleDisplayConfirmQueryModal, + confirmIncidentQuery, +}) => { + const { + displayConfirmQueryModal, totalIncidentsFromQuery, + } = querySettings; + + const handleCancel = () => { + confirmIncidentQuery(false); + toggleDisplayConfirmQueryModal(); + }; + + return ( +
+ + + Max Incidents Limit Reached + + + Current query parameters match  + {totalIncidentsFromQuery} +  incidents. +
+ Only the first  + {MAX_INCIDENTS_LIMIT} +  incidents will be retrieved. +
+
+ Continue? +
+ + + + +
+
+ ); +}; + +const mapStateToProps = (state) => ({ + querySettings: state.querySettings, +}); + +const mapDispatchToProps = (dispatch) => ({ + toggleDisplayConfirmQueryModal: () => dispatch(toggleDisplayConfirmQueryModalConnected()), + confirmIncidentQuery: (confirm) => dispatch(confirmIncidentQueryConnected(confirm)), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(ConfirmQueryModalComponent); diff --git a/src/components/IncidentTable/IncidentTableComponent.js b/src/components/IncidentTable/IncidentTableComponent.js index 366beae4..f5cd1b81 100644 --- a/src/components/IncidentTable/IncidentTableComponent.js +++ b/src/components/IncidentTable/IncidentTableComponent.js @@ -18,9 +18,6 @@ import { FixedSizeList, } from 'react-window'; -import { - Container, Row, Spinner, -} from 'react-bootstrap'; import BTable from 'react-bootstrap/Table'; import { @@ -38,6 +35,8 @@ import { import CheckboxComponent from './subcomponents/CheckboxComponent'; import EmptyIncidentsComponent from './subcomponents/EmptyIncidentsComponent'; +import QueryActiveComponent from './subcomponents/QueryActiveComponent'; +import QueryCancelledComponent from './subcomponents/QueryCancelledComponent'; import './IncidentTableComponent.scss'; @@ -76,6 +75,7 @@ const IncidentTableComponent = ({ incidentTable, incidentActions, incidents, + querySettings, }) => { const { incidentTableState, incidentTableColumnsNames, @@ -86,6 +86,9 @@ const IncidentTableComponent = ({ const { filteredIncidentsByQuery, fetchingIncidents, } = incidents; + const { + displayConfirmQueryModal, + } = querySettings; // React Table Config const defaultColumn = useMemo( @@ -247,18 +250,16 @@ const IncidentTableComponent = ({ }, [status]); // Render components based on application state + if (displayConfirmQueryModal) { + return <>; + } + + if (!displayConfirmQueryModal && querySettings.error) { + return ; + } + if (fetchingIncidents) { - return ( - -
- - -
- Querying PagerDuty API -
-
-
- ); + return ; } // TODO: Find a better way to prevent Empty Incidents from being shown during render @@ -325,6 +326,7 @@ const mapStateToProps = (state) => ({ incidentTable: state.incidentTable, incidentActions: state.incidentActions, incidents: state.incidents, + querySettings: state.querySettings, }); const mapDispatchToProps = (dispatch) => ({ diff --git a/src/components/IncidentTable/subcomponents/QueryActiveComponent.js b/src/components/IncidentTable/subcomponents/QueryActiveComponent.js new file mode 100644 index 00000000..2a50403c --- /dev/null +++ b/src/components/IncidentTable/subcomponents/QueryActiveComponent.js @@ -0,0 +1,17 @@ +import { + Container, Row, Spinner, +} from 'react-bootstrap'; + +const QueryActiveComponent = () => ( + +
+ + +
+ Querying PagerDuty API +
+
+
+); + +export default QueryActiveComponent; diff --git a/src/components/IncidentTable/subcomponents/QueryCancelledComponent.js b/src/components/IncidentTable/subcomponents/QueryCancelledComponent.js new file mode 100644 index 00000000..21c75a4e --- /dev/null +++ b/src/components/IncidentTable/subcomponents/QueryCancelledComponent.js @@ -0,0 +1,18 @@ +import { + Container, Row, Alert, +} from 'react-bootstrap'; + +const QueryCancelledComponent = () => ( + +
+ + +

+ Query has been cancelled by user +

+
+
+
+); + +export default QueryCancelledComponent; diff --git a/src/config/constants.js b/src/config/constants.js index 1efd8d92..46b73931 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -24,6 +24,7 @@ export const DD_DEFAULT_PRIVACY_LEVEL = env.REACT_APP_DD_DEFAULT_PRIVACY_LEVEL | export const LOG_ENTRIES_POLLING_INTERVAL_SECONDS = 5; export const LOG_ENTRIES_CLEARING_INTERVAL_SECONDS = 30; export const INCIDENTS_PAGINATION_LIMIT = 100; +export const MAX_INCIDENTS_LIMIT = 200; // Date formatting (Locale Agnostic) export const DATE_FORMAT = 'LL \\at h:mm:ss A'; diff --git a/src/redux/incidents/sagas.js b/src/redux/incidents/sagas.js index fb25f08e..a14b291e 100644 --- a/src/redux/incidents/sagas.js +++ b/src/redux/incidents/sagas.js @@ -17,19 +17,14 @@ import { pushToArray, } from 'util/helpers'; import fuseOptions from 'config/fuse-config'; +import { + MAX_INCIDENTS_LIMIT, +} from 'config/constants'; import selectQuerySettings from 'redux/query_settings/selectors'; import { UPDATE_CONNECTION_STATUS_REQUESTED, } from 'redux/connection/actions'; -import { - UPDATE_QUERY_SETTING_SINCE_DATE_COMPLETED, - UPDATE_QUERY_SETTING_INCIDENT_STATUS_COMPLETED, - UPDATE_QUERY_SETTING_INCIDENT_URGENCY_COMPLETED, - UPDATE_QUERY_SETTING_INCIDENT_PRIORITY_COMPLETED, - UPDATE_QUERY_SETTINGS_TEAMS_COMPLETED, - UPDATE_QUERY_SETTINGS_SERVICES_COMPLETED, -} from 'redux/query_settings/actions'; import { FETCH_INCIDENTS_REQUESTED, FETCH_INCIDENTS_COMPLETED, @@ -77,16 +72,6 @@ export function* getIncidentsAsync() { } export function* getIncidents() { - // Wait for query actions to have been completed. - take([ - UPDATE_QUERY_SETTING_SINCE_DATE_COMPLETED, - UPDATE_QUERY_SETTING_INCIDENT_STATUS_COMPLETED, - UPDATE_QUERY_SETTING_INCIDENT_URGENCY_COMPLETED, - UPDATE_QUERY_SETTING_INCIDENT_PRIORITY_COMPLETED, - UPDATE_QUERY_SETTINGS_TEAMS_COMPLETED, - UPDATE_QUERY_SETTINGS_SERVICES_COMPLETED, - ]); - try { // Build params from query settings and call pd lib const { @@ -103,20 +88,21 @@ export function* getIncidents() { since: sinceDate.toISOString(), until: new Date().toISOString(), include: ['first_trigger_log_entries', 'external_references'], + limit: MAX_INCIDENTS_LIMIT, // FIXME: This applies limit per batched call }; if (incidentStatus) params.statuses = incidentStatus; - if (incidentUrgency) params.urgencies = incidentUrgency; - if (teamIds.length) params.team_ids = teamIds; - if (serviceIds.length) params.service_ids = serviceIds; - const incidents = yield pdParallelFetch('incidents', params); + const fetchedIncidents = yield pdParallelFetch('incidents', params); // Sort incidents by reverse created_at date (i.e. recent incidents at the top) - incidents.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); + fetchedIncidents.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); + + // FIXME: Temporary fix for batched calls over prescribed limit + const incidents = fetchedIncidents.slice(0, MAX_INCIDENTS_LIMIT); yield put({ type: FETCH_INCIDENTS_COMPLETED, diff --git a/src/redux/persistence/config.js b/src/redux/persistence/config.js index 2de6655f..22ca76fb 100644 --- a/src/redux/persistence/config.js +++ b/src/redux/persistence/config.js @@ -23,7 +23,13 @@ export const persistConfig = { export const querySettingsPersistConfig = { key: 'querySettings', storage, - blacklist: ['sinceDate', 'untilDate'], + blacklist: [ + 'sinceDate', + 'untilDate', + 'displayConfirmQueryModal', + 'totalIncidentsFromQuery', + 'error', + ], }; export const userPersistConfig = { diff --git a/src/redux/query_settings/actions.js b/src/redux/query_settings/actions.js index f5627759..6ad38524 100644 --- a/src/redux/query_settings/actions.js +++ b/src/redux/query_settings/actions.js @@ -24,6 +24,19 @@ export const UPDATE_QUERY_SETTINGS_SERVICES_COMPLETED = 'UPDATE_QUERY_SETTINGS_S export const UPDATE_SEARCH_QUERY_REQUESTED = 'UPDATE_SEARCH_QUERY_REQUESTED'; export const UPDATE_SEARCH_QUERY_COMPLETED = 'UPDATE_SEARCH_QUERY_COMPLETED'; +export const VALIDATE_INCIDENT_QUERY_REQUESTED = 'VALIDATE_INCIDENT_QUERY_REQUESTED'; +export const VALIDATE_INCIDENT_QUERY_COMPLETED = 'VALIDATE_INCIDENT_QUERY_COMPLETED'; + +export const TOGGLE_DISPLAY_CONFIRM_QUERY_MODAL_REQUESTED = 'TOGGLE_DISPLAY_CONFIRM_QUERY_MODAL_REQUESTED'; +export const TOGGLE_DISPLAY_CONFIRM_QUERY_MODAL_COMPLETED = 'TOGGLE_DISPLAY_CONFIRM_QUERY_MODAL_COMPLETED'; + +export const UPDATE_TOTAL_INCIDENTS_FROM_QUERY_REQUESTED = 'UPDATE_TOTAL_INCIDENTS_FROM_QUERY_REQUESTED'; +export const UPDATE_TOTAL_INCIDENTS_FROM_QUERY_COMPLETED = 'UPDATE_TOTAL_INCIDENTS_FROM_QUERY_COMPLETED'; + +export const CONFIRM_INCIDENT_QUERY_REQUESTED = 'CONFIRM_INCIDENT_QUERY_REQUESTED'; +export const CONFIRM_INCIDENT_QUERY_COMPLETED = 'CONFIRM_INCIDENT_QUERY_COMPLETED'; +export const CONFIRM_INCIDENT_QUERY_ERROR = 'CONFIRM_INCIDENT_QUERY_ERROR'; + // Define Actions export const toggleDisplayQuerySettings = () => ({ type: TOGGLE_DISPLAY_QUERY_SETTINGS_REQUESTED, @@ -63,3 +76,12 @@ export const updateSearchQuery = (searchQuery) => ({ type: UPDATE_SEARCH_QUERY_REQUESTED, searchQuery, }); + +export const toggleDisplayConfirmQueryModal = () => ({ + type: TOGGLE_DISPLAY_CONFIRM_QUERY_MODAL_REQUESTED, +}); + +export const confirmIncidentQuery = (confirm = true) => ({ + type: CONFIRM_INCIDENT_QUERY_REQUESTED, + confirm, +}); diff --git a/src/redux/query_settings/reducers.js b/src/redux/query_settings/reducers.js index caeee6d8..6363980c 100644 --- a/src/redux/query_settings/reducers.js +++ b/src/redux/query_settings/reducers.js @@ -21,6 +21,15 @@ import { UPDATE_QUERY_SETTINGS_SERVICES_COMPLETED, UPDATE_SEARCH_QUERY_REQUESTED, UPDATE_SEARCH_QUERY_COMPLETED, + VALIDATE_INCIDENT_QUERY_REQUESTED, + VALIDATE_INCIDENT_QUERY_COMPLETED, + TOGGLE_DISPLAY_CONFIRM_QUERY_MODAL_REQUESTED, + TOGGLE_DISPLAY_CONFIRM_QUERY_MODAL_COMPLETED, + UPDATE_TOTAL_INCIDENTS_FROM_QUERY_REQUESTED, + UPDATE_TOTAL_INCIDENTS_FROM_QUERY_COMPLETED, + CONFIRM_INCIDENT_QUERY_REQUESTED, + CONFIRM_INCIDENT_QUERY_COMPLETED, + CONFIRM_INCIDENT_QUERY_ERROR, } from './actions'; const querySettings = produce( @@ -97,6 +106,48 @@ const querySettings = produce( draft.searchQuery = action.searchQuery; draft.status = UPDATE_SEARCH_QUERY_COMPLETED; break; + + case VALIDATE_INCIDENT_QUERY_REQUESTED: + draft.status = VALIDATE_INCIDENT_QUERY_REQUESTED; + break; + + case VALIDATE_INCIDENT_QUERY_COMPLETED: + draft.status = VALIDATE_INCIDENT_QUERY_COMPLETED; + break; + + case TOGGLE_DISPLAY_CONFIRM_QUERY_MODAL_REQUESTED: + draft.status = TOGGLE_DISPLAY_CONFIRM_QUERY_MODAL_REQUESTED; + break; + + case TOGGLE_DISPLAY_CONFIRM_QUERY_MODAL_COMPLETED: + draft.displayConfirmQueryModal = action.displayConfirmQueryModal; + draft.status = TOGGLE_DISPLAY_CONFIRM_QUERY_MODAL_COMPLETED; + break; + + case UPDATE_TOTAL_INCIDENTS_FROM_QUERY_REQUESTED: + draft.status = UPDATE_TOTAL_INCIDENTS_FROM_QUERY_REQUESTED; + break; + + case UPDATE_TOTAL_INCIDENTS_FROM_QUERY_COMPLETED: + draft.totalIncidentsFromQuery = action.totalIncidentsFromQuery; + draft.status = UPDATE_TOTAL_INCIDENTS_FROM_QUERY_COMPLETED; + break; + + case CONFIRM_INCIDENT_QUERY_REQUESTED: + draft.status = CONFIRM_INCIDENT_QUERY_REQUESTED; + draft.error = null; + break; + + case CONFIRM_INCIDENT_QUERY_COMPLETED: + draft.status = CONFIRM_INCIDENT_QUERY_COMPLETED; + draft.error = null; + break; + + case CONFIRM_INCIDENT_QUERY_ERROR: + draft.status = CONFIRM_INCIDENT_QUERY_ERROR; + draft.error = CONFIRM_INCIDENT_QUERY_ERROR; + break; + default: break; } @@ -111,6 +162,8 @@ const querySettings = produce( teamIds: [], serviceIds: [], searchQuery: '', + displayConfirmQueryModal: false, + totalIncidentsFromQuery: 0, status: null, fetchingData: false, error: null, diff --git a/src/redux/query_settings/sagas.js b/src/redux/query_settings/sagas.js index 565db2b0..963ba61a 100644 --- a/src/redux/query_settings/sagas.js +++ b/src/redux/query_settings/sagas.js @@ -1,7 +1,18 @@ import { - put, select, takeLatest, + put, select, takeLatest, call, debounce, } from 'redux-saga/effects'; +import { + pd, +} from 'util/pd-api-wrapper'; + +import { + MAX_INCIDENTS_LIMIT, +} from 'config/constants'; + +import { + UPDATE_CONNECTION_STATUS_REQUESTED, +} from 'redux/connection/actions'; import { FETCH_INCIDENTS_REQUESTED, FILTER_INCIDENTS_LIST_BY_QUERY, @@ -27,6 +38,15 @@ import { UPDATE_QUERY_SETTINGS_SERVICES_COMPLETED, UPDATE_SEARCH_QUERY_REQUESTED, UPDATE_SEARCH_QUERY_COMPLETED, + VALIDATE_INCIDENT_QUERY_REQUESTED, + VALIDATE_INCIDENT_QUERY_COMPLETED, + TOGGLE_DISPLAY_CONFIRM_QUERY_MODAL_REQUESTED, + TOGGLE_DISPLAY_CONFIRM_QUERY_MODAL_COMPLETED, + UPDATE_TOTAL_INCIDENTS_FROM_QUERY_REQUESTED, + UPDATE_TOTAL_INCIDENTS_FROM_QUERY_COMPLETED, + CONFIRM_INCIDENT_QUERY_REQUESTED, + CONFIRM_INCIDENT_QUERY_COMPLETED, + CONFIRM_INCIDENT_QUERY_ERROR, } from './actions'; import selectQuerySettings from './selectors'; @@ -55,8 +75,7 @@ export function* updateQuerySettingsSinceDateImpl(action) { sinceDate, } = action; yield put({ type: UPDATE_QUERY_SETTING_SINCE_DATE_COMPLETED, sinceDate }); - yield put({ type: FETCH_INCIDENTS_REQUESTED }); - yield put({ type: FETCH_ALL_INCIDENT_NOTES_REQUESTED }); + yield put({ type: VALIDATE_INCIDENT_QUERY_REQUESTED }); } export function* updateQuerySettingsIncidentStatus() { @@ -75,8 +94,7 @@ export function* updateQuerySettingsIncidentStatusImpl(action) { type: UPDATE_QUERY_SETTING_INCIDENT_STATUS_COMPLETED, incidentStatus, }); - yield put({ type: FETCH_INCIDENTS_REQUESTED }); - yield put({ type: FETCH_ALL_INCIDENT_NOTES_REQUESTED }); + yield put({ type: VALIDATE_INCIDENT_QUERY_REQUESTED }); } export function* updateQuerySettingsIncidentUrgency() { @@ -95,8 +113,7 @@ export function* updateQuerySettingsIncidentUrgencyImpl(action) { type: UPDATE_QUERY_SETTING_INCIDENT_URGENCY_COMPLETED, incidentUrgency, }); - yield put({ type: FETCH_INCIDENTS_REQUESTED }); - yield put({ type: FETCH_ALL_INCIDENT_NOTES_REQUESTED }); + yield put({ type: VALIDATE_INCIDENT_QUERY_REQUESTED }); } export function* updateQuerySettingsIncidentPriority() { @@ -115,8 +132,7 @@ export function* updateQuerySettingsIncidentPriorityImpl(action) { type: UPDATE_QUERY_SETTING_INCIDENT_PRIORITY_COMPLETED, incidentPriority, }); - yield put({ type: FETCH_INCIDENTS_REQUESTED }); - yield put({ type: FETCH_ALL_INCIDENT_NOTES_REQUESTED }); + yield put({ type: VALIDATE_INCIDENT_QUERY_REQUESTED }); } export function* updateQuerySettingsTeams() { @@ -130,8 +146,7 @@ export function* updateQuerySettingsTeamsImpl(action) { } = action; yield put({ type: FETCH_SERVICES_REQUESTED, teamIds }); yield put({ type: UPDATE_QUERY_SETTINGS_TEAMS_COMPLETED, teamIds }); - yield put({ type: FETCH_INCIDENTS_REQUESTED }); - yield put({ type: FETCH_ALL_INCIDENT_NOTES_REQUESTED }); + yield put({ type: VALIDATE_INCIDENT_QUERY_REQUESTED }); } export function* updateQuerySettingsServices() { @@ -144,8 +159,7 @@ export function* updateQuerySettingsServicesImpl(action) { serviceIds, } = action; yield put({ type: UPDATE_QUERY_SETTINGS_SERVICES_COMPLETED, serviceIds }); - yield put({ type: FETCH_INCIDENTS_REQUESTED }); - yield put({ type: FETCH_ALL_INCIDENT_NOTES_REQUESTED }); + yield put({ type: VALIDATE_INCIDENT_QUERY_REQUESTED }); } export function* updateSearchQuery() { @@ -160,3 +174,110 @@ export function* updateSearchQueryImpl(action) { yield put({ type: UPDATE_SEARCH_QUERY_COMPLETED, searchQuery }); yield put({ type: FILTER_INCIDENTS_LIST_BY_QUERY, searchQuery }); } + +export function* validateIncidentQuery() { + yield debounce(2000, VALIDATE_INCIDENT_QUERY_REQUESTED, validateIncidentQueryImpl); +} + +export function* validateIncidentQueryImpl() { + try { + // Find total incidents from data query + const { + sinceDate, + incidentStatus, + incidentUrgency, + teamIds, + serviceIds, + // incidentPriority, // Unfortunately can't do this pre-API call. + } = yield select(selectQuerySettings); + + const params = { + since: sinceDate.toISOString(), + until: new Date().toISOString(), + limit: 1, + total: true, + }; + + if (incidentStatus) params['statuses[]'] = incidentStatus; + if (incidentUrgency) params['urgencies[]'] = incidentUrgency; + if (teamIds.length) params['team_ids[]'] = teamIds; + if (serviceIds.length) params['service_ids[]'] = serviceIds; + + const response = yield call(pd.get, 'incidents', { data: { ...params } }); + if (response.status !== 200) { + throw Error('Unable to fetch incidents'); + } + + const totalIncidentsFromQuery = response.data.total; + yield put({ type: VALIDATE_INCIDENT_QUERY_COMPLETED }); + yield put({ + type: UPDATE_TOTAL_INCIDENTS_FROM_QUERY_REQUESTED, + totalIncidentsFromQuery, + }); + + // Determine if Confirm Query Modal component should be rendered + if (totalIncidentsFromQuery > MAX_INCIDENTS_LIMIT) { + yield put({ type: TOGGLE_DISPLAY_CONFIRM_QUERY_MODAL_REQUESTED }); + } else { + yield put({ type: CONFIRM_INCIDENT_QUERY_REQUESTED, confirm: true }); + } + } catch (e) { + // Handle API auth failure + if (e.status === 401) { + e.message = 'Unauthorized Access'; + } + yield put({ + type: UPDATE_CONNECTION_STATUS_REQUESTED, + connectionStatus: 'neutral', + connectionStatusMessage: e.message, + }); + } +} + +export function* toggleDisplayConfirmQueryModal() { + yield takeLatest( + TOGGLE_DISPLAY_CONFIRM_QUERY_MODAL_REQUESTED, + toggleDisplayConfirmQueryModalImpl, + ); +} + +export function* toggleDisplayConfirmQueryModalImpl() { + const { + displayConfirmQueryModal, + } = yield select(selectQuerySettings); + yield put({ + type: TOGGLE_DISPLAY_CONFIRM_QUERY_MODAL_COMPLETED, + displayConfirmQueryModal: !displayConfirmQueryModal, + }); +} + +export function* updateTotalIncidentsFromQuery() { + yield takeLatest(UPDATE_TOTAL_INCIDENTS_FROM_QUERY_REQUESTED, updateTotalIncidentsFromQueryImpl); +} + +export function* updateTotalIncidentsFromQueryImpl(action) { + const { + totalIncidentsFromQuery, + } = action; + yield put({ + type: UPDATE_TOTAL_INCIDENTS_FROM_QUERY_COMPLETED, + totalIncidentsFromQuery, + }); +} + +export function* confirmIncidentQuery() { + yield takeLatest(CONFIRM_INCIDENT_QUERY_REQUESTED, confirmIncidentQueryImpl); +} + +export function* confirmIncidentQueryImpl(action) { + const { + confirm, + } = action; + if (confirm) { + yield put({ type: FETCH_INCIDENTS_REQUESTED }); + yield put({ type: FETCH_ALL_INCIDENT_NOTES_REQUESTED }); + yield put({ type: CONFIRM_INCIDENT_QUERY_COMPLETED }); + } else { + yield put({ type: CONFIRM_INCIDENT_QUERY_ERROR }); + } +} diff --git a/src/redux/rootSaga.js b/src/redux/rootSaga.js index 366d1000..caa7094d 100644 --- a/src/redux/rootSaga.js +++ b/src/redux/rootSaga.js @@ -15,6 +15,10 @@ import { updateQuerySettingsTeams, updateQuerySettingsServices, updateSearchQuery, + validateIncidentQuery, + toggleDisplayConfirmQueryModal, + updateTotalIncidentsFromQuery, + confirmIncidentQuery, } from './query_settings/sagas'; import { @@ -121,6 +125,10 @@ export default function* rootSaga() { updateQuerySettingsTeams(), updateQuerySettingsServices(), updateSearchQuery(), + validateIncidentQuery(), + toggleDisplayConfirmQueryModal(), + updateTotalIncidentsFromQuery(), + confirmIncidentQuery(), // Incidents getIncidentsAsync(), From 69c78deae2168fde5cc2405598bb3c0e0e1a1a59 Mon Sep 17 00:00:00 2001 From: Giran Moodley Date: Sun, 5 Jun 2022 22:38:24 +0100 Subject: [PATCH 07/26] Added component test coverage --- .../ConfirmQueryModalComponent.js | 2 +- .../ConfirmQueryModalComponent.test.js | 29 +++++++++++++++++++ .../subcomponents/QueryActiveComponent.js | 2 +- .../QueryActiveComponent.test.js | 13 +++++++++ .../subcomponents/QueryCancelledComponent.js | 2 +- .../QueryCancelledComponent.test.js | 13 +++++++++ .../QuerySettingsComponent.test.js | 4 +-- 7 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 src/components/ConfirmQueryModal/ConfirmQueryModalComponent.test.js create mode 100644 src/components/IncidentTable/subcomponents/QueryActiveComponent.test.js create mode 100644 src/components/IncidentTable/subcomponents/QueryCancelledComponent.test.js diff --git a/src/components/ConfirmQueryModal/ConfirmQueryModalComponent.js b/src/components/ConfirmQueryModal/ConfirmQueryModalComponent.js index e1b6caaf..e7c05cb4 100644 --- a/src/components/ConfirmQueryModal/ConfirmQueryModalComponent.js +++ b/src/components/ConfirmQueryModal/ConfirmQueryModalComponent.js @@ -49,7 +49,7 @@ const ConfirmQueryModalComponent = ({ - diff --git a/src/components/IncidentTable/subcomponents/QueryActiveComponent.js b/src/components/IncidentTable/subcomponents/QueryActiveComponent.js index 9da71a95..c772404f 100644 --- a/src/components/IncidentTable/subcomponents/QueryActiveComponent.js +++ b/src/components/IncidentTable/subcomponents/QueryActiveComponent.js @@ -3,7 +3,7 @@ import { } from 'react-bootstrap'; const QueryActiveComponent = () => ( - +
diff --git a/src/components/IncidentTable/subcomponents/QueryCancelledComponent.js b/src/components/IncidentTable/subcomponents/QueryCancelledComponent.js index 485a5711..65a8354d 100644 --- a/src/components/IncidentTable/subcomponents/QueryCancelledComponent.js +++ b/src/components/IncidentTable/subcomponents/QueryCancelledComponent.js @@ -3,7 +3,7 @@ import { } from 'react-bootstrap'; const QueryCancelledComponent = () => ( - +
From 996a59a8d26c67ee3093bf600e012d717f822655 Mon Sep 17 00:00:00 2001 From: Giran Moodley Date: Mon, 13 Jun 2022 14:04:24 +0100 Subject: [PATCH 22/26] Renamed component and added react-bootstrap implementation --- src/App.js | 8 +++- .../Auth/{Auth.js => AuthComponent.js} | 44 ++++++++++++------- src/components/Auth/AuthComponent.scss | 34 ++++++++++++++ 3 files changed, 68 insertions(+), 18 deletions(-) rename src/components/Auth/{Auth.js => AuthComponent.js} (84%) create mode 100644 src/components/Auth/AuthComponent.scss diff --git a/src/App.js b/src/App.js index 3d3e7b7d..96972c94 100644 --- a/src/App.js +++ b/src/App.js @@ -9,7 +9,7 @@ import { } from 'react-bootstrap'; import moment from 'moment'; -import Auth from 'components/Auth/Auth'; +import AuthComponent from 'components/Auth/AuthComponent'; import UnauthorizedModalComponent from 'components/UnauthorizedModal/UnauthorizedModalComponent'; import DisclaimerModalComponent from 'components/DisclaimerModal/DisclaimerModalComponent'; import NavigationBarComponent from 'components/NavigationBar/NavigationBarComponent'; @@ -90,7 +90,11 @@ const App = ({ // Verify if session token is present const token = sessionStorage.getItem('pd_access_token'); if (!token) { - return ; + return ( +
+ +
+ ); } // Begin monitoring and load core objects from API diff --git a/src/components/Auth/Auth.js b/src/components/Auth/AuthComponent.js similarity index 84% rename from src/components/Auth/Auth.js rename to src/components/Auth/AuthComponent.js index b508e843..9d0cad45 100644 --- a/src/components/Auth/Auth.js +++ b/src/components/Auth/AuthComponent.js @@ -1,14 +1,18 @@ /* eslint-disable no-unused-vars */ import React, { - useState, - useEffect, + useState, useEffect, } from 'react'; import { - PD_OAUTH_CLIENT_ID, - PD_OAUTH_CLIENT_SECRET, + Form, Button, Dropdown, Spinner, Row, Container, +} from 'react-bootstrap'; + +import { + PD_OAUTH_CLIENT_ID, PD_OAUTH_CLIENT_SECRET, } from 'config/constants'; +import './AuthComponent.scss'; + const gen64x8bitNonce = () => { const array = new Uint8Array(64); window.crypto.getRandomValues(array); @@ -121,7 +125,7 @@ const exchangeCodeForToken = async (clientID, clientSecret, redirectURL, codeVer return null; }; -const Auth = (props) => { +const AuthComponent = (props) => { const [authURL, setAuthURL] = useState(); const id = PD_OAUTH_CLIENT_ID; @@ -155,26 +159,34 @@ const Auth = (props) => { } }, []); - /* eslint-disable react/jsx-one-expression-per-line */ if (code && codeVerifier) { return (
-

 

- Logging in to PagerDuty... +
+ + +
+ Signing into PagerDuty Live +
+
); } return (
-

 

-

PagerDuty Login

- Connect to PagerDuty to use this app. -

 

- - Authorize PagerDuty - +
+
); }; -export default Auth; +export default AuthComponent; diff --git a/src/components/Auth/AuthComponent.scss b/src/components/Auth/AuthComponent.scss new file mode 100644 index 00000000..8118c290 --- /dev/null +++ b/src/components/Auth/AuthComponent.scss @@ -0,0 +1,34 @@ +@import 'assets/styles/pagerduty.scss'; + +#pd-login-form { + position: relative; + top: 80px; + z-index: 0; + max-width: 30em; + margin: 0 auto; + padding: 2em 0 2em 0; + border-radius: 5px; + border: 1px solid $pd-light; + box-shadow: 0px 0px 10px 1px $pd-gray-medium; + background-color: $pd-white; +} + +#pd-login-logo { + display: block; + text-indent: -9999px; + overflow: hidden; + width: 175px; + height: 60px; + background-image: url(https://pd-static-assets.pagerduty.com/logos/main.svg); + background-repeat: no-repeat; + background-position: center left; + background-size: 175px 60px; +} + +#pd-login-description { + padding: 1em 0 1em 0; +} + +#pd-login-button { + width: 80%; +} From 7962a3a73312e8e0883e5f106c5cfd1a3636941e Mon Sep 17 00:00:00 2001 From: Giran Moodley Date: Mon, 13 Jun 2022 16:12:08 +0100 Subject: [PATCH 23/26] Decoupled auth specific functions from component and added initial test coverage --- package.json | 1 + src/components/Auth/AuthComponent.js | 125 ++-------------------- src/components/Auth/AuthComponent.test.js | 39 +++++++ src/util/auth.js | 117 ++++++++++++++++++++ yarn.lock | 15 ++- 5 files changed, 182 insertions(+), 115 deletions(-) create mode 100644 src/components/Auth/AuthComponent.test.js create mode 100644 src/util/auth.js diff --git a/package.json b/package.json index 8c3ac787..4ac84b1b 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "gh-pages": "^3.2.3", "html-webpack-plugin": "5", "identity-obj-proxy": "^3.0.0", + "jest-location-mock": "^1.0.9", "prettier": "^2.6.2", "prettier-eslint": "^10.1.0", "prettier-eslint-cli": "^5.0.1", diff --git a/src/components/Auth/AuthComponent.js b/src/components/Auth/AuthComponent.js index 9d0cad45..3fd87477 100644 --- a/src/components/Auth/AuthComponent.js +++ b/src/components/Auth/AuthComponent.js @@ -4,126 +4,18 @@ import React, { } from 'react'; import { - Form, Button, Dropdown, Spinner, Row, Container, + Form, Button, Dropdown, Spinner, Row, } from 'react-bootstrap'; import { PD_OAUTH_CLIENT_ID, PD_OAUTH_CLIENT_SECRET, } from 'config/constants'; -import './AuthComponent.scss'; - -const gen64x8bitNonce = () => { - const array = new Uint8Array(64); - window.crypto.getRandomValues(array); - return array; -}; - -const digestVerifier = async (vString) => { - const encoder = new TextEncoder(); - const verifier = encoder.encode(vString); - const hash = await crypto.subtle.digest('SHA-256', verifier); - return hash; -}; - -const base64Unicode = (buffer) => { - // |*| Base64 / binary data / UTF-8 strings utilities (#1) - // |*| https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding - // |*| Author: madmurphy - /* eslint-disable no-nested-ternary */ - /* eslint-disable arrow-body-style */ - const uint6ToB64 = (nUint6) => { - return nUint6 < 26 - ? nUint6 + 65 - : nUint6 < 52 - ? nUint6 + 71 - : nUint6 < 62 - ? nUint6 - 4 - : nUint6 === 62 - ? 43 - : nUint6 === 63 - ? 47 - : 65; - }; - - const base64EncArr = (aBytes) => { - const eqLen = (3 - (aBytes.length % 3)) % 3; - let sB64Enc = ''; - - for (let nMod3, nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { - nMod3 = nIdx % 3; - /* Uncomment the following line in order to split the output in lines 76-character long: */ - /* - if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; } - */ - /* eslint-disable no-bitwise */ - nUint24 |= aBytes[nIdx] << ((16 >>> nMod3) & 24); - if (nMod3 === 2 || aBytes.length - nIdx === 1) { - sB64Enc += String.fromCharCode( - uint6ToB64((nUint24 >>> 18) & 63), - uint6ToB64((nUint24 >>> 12) & 63), - uint6ToB64((nUint24 >>> 6) & 63), - uint6ToB64(nUint24 & 63), - ); - nUint24 = 0; - } - } - return eqLen === 0 - ? sB64Enc - : sB64Enc.substring(0, sB64Enc.length - eqLen) + (eqLen === 1 ? '=' : '=='); - }; - let encodedArr = base64EncArr(new Uint8Array(buffer)); - // manually finishing up the url encoding fo the encodedArr - encodedArr = encodedArr.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); - return encodedArr; -}; - -const createCodeVerifier = () => { - // generate code verifier - const generatedCode = gen64x8bitNonce(); - // base64 encode code_verifier - return base64Unicode(generatedCode.buffer); -}; - -const getAuthURL = async (clientID, clientSecret, redirectURL, codeVerifier) => { - const challengeBuffer = await digestVerifier(codeVerifier); - // base64 encode the challenge - const challenge = base64Unicode(challengeBuffer); - // build authUrl - const authUrl = 'https://app.pagerduty.com/oauth/authorize?' - + `client_id=${clientID}&` - // + `client_secret=${clientSecret}&` - + `redirect_uri=${redirectURL}&` - + 'response_type=code&' - + `code_challenge=${encodeURI(challenge)}&` - + 'code_challenge_method=S256'; - - return authUrl; -}; - -const exchangeCodeForToken = async (clientID, clientSecret, redirectURL, codeVerifier, code) => { - // eslint-disable-next-line no-unused-vars - const postData = async (url, _data) => { - const response = await fetch(url, { - method: 'POST', - }); - const json = response.json(); - return json; - }; +import { + createCodeVerifier, getAuthURL, exchangeCodeForToken, +} from 'util/auth'; - const requestTokenUrl = 'https://app.pagerduty.com/oauth/token?' - + 'grant_type=authorization_code&' - + `code=${code}&` - + `redirect_uri=${redirectURL}&` - + `client_id=${clientID}&` - // + `client_secret=${clientSecret}&` - + `code_verifier=${codeVerifier}`; - const data = await postData(requestTokenUrl, {}); - if (data.access_token) { - return data.access_token; - } - return null; -}; +import './AuthComponent.scss'; const AuthComponent = (props) => { const [authURL, setAuthURL] = useState(); @@ -181,7 +73,12 @@ const AuthComponent = (props) => {

Live Incidents Console

Connect using PagerDuty OAuth to use this app

- diff --git a/src/components/Auth/AuthComponent.test.js b/src/components/Auth/AuthComponent.test.js new file mode 100644 index 00000000..d06d8639 --- /dev/null +++ b/src/components/Auth/AuthComponent.test.js @@ -0,0 +1,39 @@ +import 'jest-location-mock'; + +import { + mount, +} from 'enzyme'; + +import { + Button, +} from 'react-bootstrap'; + +import 'mocks/pdoauth'; + +import { + waitForComponentToPaint, +} from 'mocks/store.test'; + +import AuthComponent from './AuthComponent'; + +describe('AuthComponent', () => { + it('should render component correctly', () => { + const wrapper = mount(); + waitForComponentToPaint(wrapper); + expect(wrapper.find('h1').getElement(0).props.children).toEqual('Live Incidents Console'); + expect(wrapper.find('p').getElement(0).props.children).toEqual( + 'Connect using PagerDuty OAuth to use this app', + ); + expect(wrapper.find(Button).getElement(0).props.variant).toEqual('primary'); + expect(wrapper.find(Button).getElement(0).props.children).toEqual('Sign In'); + }); + + it('should invoke window.location.assign when "Sign In" is clicked', () => { + const wrapper = mount(); + waitForComponentToPaint(wrapper); + wrapper.find(Button).simulate('click'); + expect(window.location.assign).toBeCalled(); + // FIX ME: This assertion doesn't work within Jest for some reason + // expect(window.location.href).toContain('https://app.pagerduty.com/global/authn/authentication'); + }); +}); diff --git a/src/util/auth.js b/src/util/auth.js new file mode 100644 index 00000000..5bd21496 --- /dev/null +++ b/src/util/auth.js @@ -0,0 +1,117 @@ +export const gen64x8bitNonce = () => { + const array = new Uint8Array(64); + window.crypto.getRandomValues(array); + return array; +}; + +export const digestVerifier = async (vString) => { + const encoder = new TextEncoder(); + const verifier = encoder.encode(vString); + const hash = await crypto.subtle.digest('SHA-256', verifier); + return hash; +}; + +export const base64Unicode = (buffer) => { + // |*| Base64 / binary data / UTF-8 strings utilities (#1) + // |*| https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding + // |*| Author: madmurphy + /* eslint-disable no-nested-ternary */ + /* eslint-disable arrow-body-style */ + const uint6ToB64 = (nUint6) => { + return nUint6 < 26 + ? nUint6 + 65 + : nUint6 < 52 + ? nUint6 + 71 + : nUint6 < 62 + ? nUint6 - 4 + : nUint6 === 62 + ? 43 + : nUint6 === 63 + ? 47 + : 65; + }; + + const base64EncArr = (aBytes) => { + const eqLen = (3 - (aBytes.length % 3)) % 3; + let sB64Enc = ''; + + for (let nMod3, nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { + nMod3 = nIdx % 3; + /* Uncomment the following line in order to split the output in lines 76-character long: */ + /* + if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; } + */ + /* eslint-disable no-bitwise */ + nUint24 |= aBytes[nIdx] << ((16 >>> nMod3) & 24); + if (nMod3 === 2 || aBytes.length - nIdx === 1) { + sB64Enc += String.fromCharCode( + uint6ToB64((nUint24 >>> 18) & 63), + uint6ToB64((nUint24 >>> 12) & 63), + uint6ToB64((nUint24 >>> 6) & 63), + uint6ToB64(nUint24 & 63), + ); + nUint24 = 0; + } + } + return eqLen === 0 + ? sB64Enc + : sB64Enc.substring(0, sB64Enc.length - eqLen) + (eqLen === 1 ? '=' : '=='); + }; + let encodedArr = base64EncArr(new Uint8Array(buffer)); + // manually finishing up the url encoding fo the encodedArr + encodedArr = encodedArr.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); + return encodedArr; +}; + +export const createCodeVerifier = () => { + // generate code verifier + const generatedCode = gen64x8bitNonce(); + // base64 encode code_verifier + return base64Unicode(generatedCode.buffer); +}; + +export const getAuthURL = async (clientID, clientSecret, redirectURL, codeVerifier) => { + const challengeBuffer = await digestVerifier(codeVerifier); + // base64 encode the challenge + const challenge = base64Unicode(challengeBuffer); + // build authUrl + const authUrl = 'https://app.pagerduty.com/oauth/authorize?' + + `client_id=${clientID}&` + // + `client_secret=${clientSecret}&` + + `redirect_uri=${redirectURL}&` + + 'response_type=code&' + + `code_challenge=${encodeURI(challenge)}&` + + 'code_challenge_method=S256'; + + return authUrl; +}; + +export const exchangeCodeForToken = async ( + clientID, + clientSecret, + redirectURL, + codeVerifier, + code, +) => { + // eslint-disable-next-line no-unused-vars + const postData = async (url, _data) => { + const response = await fetch(url, { + method: 'POST', + }); + const json = response.json(); + return json; + }; + + const requestTokenUrl = 'https://app.pagerduty.com/oauth/token?' + + 'grant_type=authorization_code&' + + `code=${code}&` + + `redirect_uri=${redirectURL}&` + + `client_id=${clientID}&` + // + `client_secret=${clientSecret}&` + + `code_verifier=${codeVerifier}`; + const data = await postData(requestTokenUrl, {}); + if (data.access_token) { + return data.access_token; + } + return null; +}; diff --git a/yarn.lock b/yarn.lock index 9422ce3e..9d0e0e6f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1775,6 +1775,11 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== +"@jedmao/location@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@jedmao/location/-/location-3.0.0.tgz#f2b24e937386f95252f3a1fefbf7ca2e0a4b87e9" + integrity sha512-p7mzNlgJbCioUYLUEKds3cQG4CHONVFJNYqMe6ocEtENCL/jYmMo1Q3ApwsMmU+L0ZkaDJEyv4HokaByLoPwlQ== + "@jest/console@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" @@ -8019,7 +8024,7 @@ jest-diff@^27.0.0: jest-get-type "^27.4.0" pretty-format "^27.4.6" -jest-diff@^27.5.1: +jest-diff@^27.0.1, jest-diff@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== @@ -8133,6 +8138,14 @@ jest-leak-detector@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" +jest-location-mock@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/jest-location-mock/-/jest-location-mock-1.0.9.tgz#f4466362423b273e12ca3716467a3d478ce78fa8" + integrity sha512-DN/v7Zsa3N4uGgWTCrMrPPxhZORr/4N5gi+u7Tk6sLdORYplrC0//wfFN5FOtx4ZdQzDVfY6rLa4d+wfTKzQHw== + dependencies: + "@jedmao/location" "^3.0.0" + jest-diff "^27.0.1" + jest-matcher-utils@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" From 90dc0272c73b74ca568752091211481658d7c526 Mon Sep 17 00:00:00 2001 From: Giran Moodley Date: Mon, 13 Jun 2022 17:01:02 +0100 Subject: [PATCH 24/26] WIP: Adding unit tests for auth functions --- jest.config.js | 1 + src/util/auth.js | 3 +++ src/util/auth.test.js | 45 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 src/util/auth.test.js diff --git a/jest.config.js b/jest.config.js index 1b3df822..d4a00021 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,6 +1,7 @@ module.exports = { testEnvironment: 'jsdom', testPathIgnorePatterns: ['./cypress/'], + setupFiles: ['dotenv/config'], setupFilesAfterEnv: ['./setupTests.js'], moduleDirectories: ['node_modules', 'src'], moduleNameMapper: { diff --git a/src/util/auth.js b/src/util/auth.js index 5bd21496..ac4f44fe 100644 --- a/src/util/auth.js +++ b/src/util/auth.js @@ -83,6 +83,7 @@ export const getAuthURL = async (clientID, clientSecret, redirectURL, codeVerifi + `code_challenge=${encodeURI(challenge)}&` + 'code_challenge_method=S256'; + console.log('authUrl', authUrl); return authUrl; }; @@ -102,6 +103,8 @@ export const exchangeCodeForToken = async ( return json; }; + console.log('codeVerifier', codeVerifier, 'code', code); + const requestTokenUrl = 'https://app.pagerduty.com/oauth/token?' + 'grant_type=authorization_code&' + `code=${code}&` diff --git a/src/util/auth.test.js b/src/util/auth.test.js new file mode 100644 index 00000000..61a1c05a --- /dev/null +++ b/src/util/auth.test.js @@ -0,0 +1,45 @@ +import 'mocks/pdoauth'; + +import { + PD_OAUTH_CLIENT_ID, PD_OAUTH_CLIENT_SECRET, +} from 'config/constants'; + +import { + createCodeVerifier, getAuthURL, exchangeCodeForToken, +} from './auth'; + +describe('Authentication Helper Suite', () => { + let codeVerifier; + let clientID; + let clientSecret; + let redirectURL; + let authURL; + + // beforeAll(() => { + // console.log(PD_OAUTH_CLIENT_ID, PD_OAUTH_CLIENT_SECRET); + // }); + + it('Create valid code verifier', () => { + codeVerifier = createCodeVerifier(); + expect(codeVerifier).toEqual( + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + ); + }); + + it('Get valid auth URL', async () => { + codeVerifier = createCodeVerifier(); + clientID = PD_OAUTH_CLIENT_ID; + clientSecret = PD_OAUTH_CLIENT_SECRET; + redirectURL = 'http://localhost'; + authURL = await getAuthURL(clientID, clientSecret, redirectURL, codeVerifier); + // NB - not sure why code challenge isn't generated? + expect(authURL).toEqual( + // eslint-disable-next-line max-len + `https://app.pagerduty.com/oauth/authorize?client_id=${clientID}&redirect_uri=${redirectURL}&response_type=code&code_challenge=&code_challenge_method=S256`, + ); + }); + + it('Retrieve auth token from code verifier', () => { + redirectURL = 'http://localhost'; + }); +}); From d2bc84188514934faed522637e881e85d04d4bb2 Mon Sep 17 00:00:00 2001 From: Giran Moodley Date: Mon, 13 Jun 2022 20:48:36 +0100 Subject: [PATCH 25/26] Fixed auth tests and mocking. Code clean up. --- src/App.js | 4 +- src/components/Auth/AuthComponent.js | 28 ++++--- .../QuerySettingsComponent.test.js | 2 - src/mocks/pdoauth.js | 19 +++-- src/redux/incidents/sagas.test.js | 2 - src/redux/query_settings/sagas.test.js | 2 - src/redux/settings/sagas.test.js | 2 - src/redux/users/sagas.test.js | 2 - src/util/auth.js | 12 +-- src/util/auth.test.js | 76 ++++++++++++++----- 10 files changed, 88 insertions(+), 61 deletions(-) diff --git a/src/App.js b/src/App.js index 96972c94..390a4e87 100644 --- a/src/App.js +++ b/src/App.js @@ -63,6 +63,8 @@ import { } from 'redux/store'; import { + PD_OAUTH_CLIENT_ID, + PD_OAUTH_CLIENT_SECRET, PD_REQUIRED_ABILITY, LOG_ENTRIES_POLLING_INTERVAL_SECONDS, LOG_ENTRIES_CLEARING_INTERVAL_SECONDS, @@ -92,7 +94,7 @@ const App = ({ if (!token) { return (
- +
); } diff --git a/src/components/Auth/AuthComponent.js b/src/components/Auth/AuthComponent.js index 3fd87477..6cd0f8b6 100644 --- a/src/components/Auth/AuthComponent.js +++ b/src/components/Auth/AuthComponent.js @@ -7,10 +7,6 @@ import { Form, Button, Dropdown, Spinner, Row, } from 'react-bootstrap'; -import { - PD_OAUTH_CLIENT_ID, PD_OAUTH_CLIENT_SECRET, -} from 'config/constants'; - import { createCodeVerifier, getAuthURL, exchangeCodeForToken, } from 'util/auth'; @@ -18,18 +14,18 @@ import { import './AuthComponent.scss'; const AuthComponent = (props) => { - const [authURL, setAuthURL] = useState(); - - const id = PD_OAUTH_CLIENT_ID; - const secret = PD_OAUTH_CLIENT_SECRET; + const [authURL, setAuthURL] = useState(''); const urlParams = new URLSearchParams(window.location.search); const accessToken = sessionStorage.getItem('pd_access_token'); const code = urlParams.get('code'); let codeVerifier = sessionStorage.getItem('code_verifier'); - let { redirectURL, } = props; + const { + clientId, clientSecret, + } = props; + if (!redirectURL) { // assume that the redirect URL is the current page redirectURL = `${window.location.protocol}//${window.location.host}${window.location.pathname}`; @@ -37,15 +33,17 @@ const AuthComponent = (props) => { useEffect(() => { if (code && codeVerifier && !accessToken) { - exchangeCodeForToken(id, secret, redirectURL, codeVerifier, code).then((token) => { - sessionStorage.removeItem('code_verifier'); - sessionStorage.setItem('pd_access_token', token); - window.location.assign(redirectURL); - }); + exchangeCodeForToken(clientId, clientSecret, redirectURL, codeVerifier, code).then( + (token) => { + sessionStorage.removeItem('code_verifier'); + sessionStorage.setItem('pd_access_token', token); + window.location.assign(redirectURL); + }, + ); } else if (!accessToken) { codeVerifier = createCodeVerifier(); sessionStorage.setItem('code_verifier', codeVerifier); - getAuthURL(id, secret, redirectURL, codeVerifier).then((url) => { + getAuthURL(clientId, clientSecret, redirectURL, codeVerifier).then((url) => { setAuthURL(url); }); } diff --git a/src/components/QuerySettings/QuerySettingsComponent.test.js b/src/components/QuerySettings/QuerySettingsComponent.test.js index 3983e15a..4adca7a1 100644 --- a/src/components/QuerySettings/QuerySettingsComponent.test.js +++ b/src/components/QuerySettings/QuerySettingsComponent.test.js @@ -1,5 +1,3 @@ -import 'mocks/pdoauth'; - import '@testing-library/jest-dom'; import gb from 'date-fns/locale/en-GB'; diff --git a/src/mocks/pdoauth.js b/src/mocks/pdoauth.js index c80b526b..aa12c28b 100644 --- a/src/mocks/pdoauth.js +++ b/src/mocks/pdoauth.js @@ -1,19 +1,18 @@ -/* eslint-disable no-undef */ -/* eslint-disable no-unused-vars */ // Stub for methods used in PD OAuth +import crypto from 'crypto'; + import { TextEncoder, TextDecoder, } from 'util'; -Object.defineProperty(window, 'crypto', { - value: { - getRandomValues: (arr) => jest.fn().mockReturnValueOnce(new Uint32Array(10)), - }, -}); - -Object.defineProperty(crypto, 'subtle', { +Object.defineProperty(global, 'crypto', { value: { - digest: (hash, verifier) => '#', + getRandomValues: (arr) => crypto.randomBytes(arr.length), + subtle: { + digest: (algorithm, data) => new Promise((resolve) => resolve( + crypto.createHash(algorithm.toLowerCase().replace('-', '')).update(data).digest(), + )), + }, }, }); diff --git a/src/redux/incidents/sagas.test.js b/src/redux/incidents/sagas.test.js index a9cf2182..38877480 100644 --- a/src/redux/incidents/sagas.test.js +++ b/src/redux/incidents/sagas.test.js @@ -1,5 +1,3 @@ -import 'mocks/pdoauth'; - import { select, } from 'redux-saga/effects'; diff --git a/src/redux/query_settings/sagas.test.js b/src/redux/query_settings/sagas.test.js index 4684f496..2ee04a29 100644 --- a/src/redux/query_settings/sagas.test.js +++ b/src/redux/query_settings/sagas.test.js @@ -1,5 +1,3 @@ -import 'mocks/pdoauth'; - import { select, } from 'redux-saga/effects'; diff --git a/src/redux/settings/sagas.test.js b/src/redux/settings/sagas.test.js index 745a7c60..1c5de8e9 100644 --- a/src/redux/settings/sagas.test.js +++ b/src/redux/settings/sagas.test.js @@ -1,5 +1,3 @@ -import 'mocks/pdoauth'; - import { // eslint-disable-next-line no-unused-vars select, diff --git a/src/redux/users/sagas.test.js b/src/redux/users/sagas.test.js index acb9231b..3f0583a6 100644 --- a/src/redux/users/sagas.test.js +++ b/src/redux/users/sagas.test.js @@ -1,5 +1,3 @@ -import 'mocks/pdoauth'; - import { // eslint-disable-next-line no-unused-vars select, diff --git a/src/util/auth.js b/src/util/auth.js index ac4f44fe..f909a963 100644 --- a/src/util/auth.js +++ b/src/util/auth.js @@ -70,25 +70,23 @@ export const createCodeVerifier = () => { return base64Unicode(generatedCode.buffer); }; -export const getAuthURL = async (clientID, clientSecret, redirectURL, codeVerifier) => { +export const getAuthURL = async (clientId, clientSecret, redirectURL, codeVerifier) => { const challengeBuffer = await digestVerifier(codeVerifier); // base64 encode the challenge const challenge = base64Unicode(challengeBuffer); // build authUrl const authUrl = 'https://app.pagerduty.com/oauth/authorize?' - + `client_id=${clientID}&` + + `client_id=${clientId}&` // + `client_secret=${clientSecret}&` + `redirect_uri=${redirectURL}&` + 'response_type=code&' + `code_challenge=${encodeURI(challenge)}&` + 'code_challenge_method=S256'; - - console.log('authUrl', authUrl); return authUrl; }; export const exchangeCodeForToken = async ( - clientID, + clientId, clientSecret, redirectURL, codeVerifier, @@ -103,13 +101,11 @@ export const exchangeCodeForToken = async ( return json; }; - console.log('codeVerifier', codeVerifier, 'code', code); - const requestTokenUrl = 'https://app.pagerduty.com/oauth/token?' + 'grant_type=authorization_code&' + `code=${code}&` + `redirect_uri=${redirectURL}&` - + `client_id=${clientID}&` + + `client_id=${clientId}&` // + `client_secret=${clientSecret}&` + `code_verifier=${codeVerifier}`; const data = await postData(requestTokenUrl, {}); diff --git a/src/util/auth.test.js b/src/util/auth.test.js index 61a1c05a..1c37e95d 100644 --- a/src/util/auth.test.js +++ b/src/util/auth.test.js @@ -1,45 +1,87 @@ import 'mocks/pdoauth'; +import { + faker, +} from '@faker-js/faker'; + import { PD_OAUTH_CLIENT_ID, PD_OAUTH_CLIENT_SECRET, } from 'config/constants'; import { - createCodeVerifier, getAuthURL, exchangeCodeForToken, + createCodeVerifier, + getAuthURL, + exchangeCodeForToken, + digestVerifier, + base64Unicode, } from './auth'; +const unmockedFetch = global.fetch; + describe('Authentication Helper Suite', () => { + const clientId = PD_OAUTH_CLIENT_ID; + const clientSecret = PD_OAUTH_CLIENT_SECRET; + const redirectURL = 'http://localhost:3000/'; + const code = 'SOME_REDIRECT_CODE'; + const mockAccessToken = faker.random.alphaNumeric(); let codeVerifier; - let clientID; - let clientSecret; - let redirectURL; + let hash; let authURL; - // beforeAll(() => { - // console.log(PD_OAUTH_CLIENT_ID, PD_OAUTH_CLIENT_SECRET); - // }); + // Setup tests and mock API + beforeAll(async () => { + codeVerifier = createCodeVerifier(); + hash = await digestVerifier(codeVerifier); + global.fetch = (url) => Promise.resolve({ + json: () => { + let response; + if (url.includes('https://app.pagerduty.com/oauth/token')) { + response = { + client_info: 'prefix_legacy_app', + id_token: hash, + access_token: mockAccessToken, + refresh_token: mockAccessToken, + }; + } else { + response = {}; + } + return Promise.resolve(response); + }, + }); + }); + + // Reset fetch mock + afterAll(() => { + global.fetch = unmockedFetch; + }); it('Create valid code verifier', () => { - codeVerifier = createCodeVerifier(); expect(codeVerifier).toEqual( 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', ); }); + it('Digest code verifier', async () => { + expect(hash).toHaveLength(32); + }); + it('Get valid auth URL', async () => { - codeVerifier = createCodeVerifier(); - clientID = PD_OAUTH_CLIENT_ID; - clientSecret = PD_OAUTH_CLIENT_SECRET; - redirectURL = 'http://localhost'; - authURL = await getAuthURL(clientID, clientSecret, redirectURL, codeVerifier); - // NB - not sure why code challenge isn't generated? + const codeChallenge = encodeURI(base64Unicode(hash)); + authURL = await getAuthURL(clientId, clientSecret, redirectURL, codeVerifier); expect(authURL).toEqual( // eslint-disable-next-line max-len - `https://app.pagerduty.com/oauth/authorize?client_id=${clientID}&redirect_uri=${redirectURL}&response_type=code&code_challenge=&code_challenge_method=S256`, + `https://app.pagerduty.com/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectURL}&response_type=code&code_challenge=${codeChallenge}&code_challenge_method=S256`, ); }); - it('Retrieve auth token from code verifier', () => { - redirectURL = 'http://localhost'; + it('Retrieve auth token from code', async () => { + const token = await exchangeCodeForToken( + clientId, + clientSecret, + redirectURL, + codeVerifier, + code, + ); + expect(token).toEqual(mockAccessToken); }); }); From a788d4bf608151653d36f8d1984a439a19a06a6b Mon Sep 17 00:00:00 2001 From: Giran Moodley Date: Mon, 13 Jun 2022 21:17:18 +0100 Subject: [PATCH 26/26] Publishing release v0.1.0-beta.0 --- README.md | 8 ++++---- package.json | 2 +- src/config/version.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2140a2fa..7ab80a7b 100644 --- a/README.md +++ b/README.md @@ -73,16 +73,16 @@ To prepare PagerDuty Live for release, the current workflow should be carried ou 1. Checkout to `develop` branch and verify if it's stable - i.e. no test and linting failures. 2. Update version information in `package.json` using `npm version` - example commands given below: - - Bumping patch version for alpha release + - Bumping patch version for beta release ``` - $ npm --no-git-tag-version version prepatch --preid alpha - v0.0.1-alpha.0 + $ npm --no-git-tag-version version preminor --preid beta + v0.1.0-beta.0 ``` - Bumping minor version for main release ``` $ npm --no-git-tag-version version minor - v0.1.0 + v0.2.0 ``` 3. Update application code version using `$ yarn genversion` diff --git a/package.json b/package.json index 4ac84b1b..1c61869b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pd-live-react", "homepage": "https://giranm.github.io/pd-live-react", - "version": "0.0.17-alpha.0", + "version": "0.1.0-beta.0", "private": true, "dependencies": { "@craco/craco": "7.0.0-alpha.3", diff --git a/src/config/version.js b/src/config/version.js index 723f9742..ad5029a5 100644 --- a/src/config/version.js +++ b/src/config/version.js @@ -1,2 +1,2 @@ // generated by genversion -module.exports = '0.0.17-alpha.0'; +module.exports = '0.1.0-beta.0';