diff --git a/frontend/package.json b/frontend/package.json index a9d844448f..ac78e76e4d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -40,6 +40,10 @@ "main": "./index.js", "dependencies": {}, "devDependencies": { + "@material-ui/core": "^4.11.3", + "@material-ui/icons": "^4.11.2", + "classnames": "^2.2.6", + "date-fns": "^2.17.0", "@babel/core": "^7.9.0", "@babel/plugin-proposal-class-properties": "^7.8.3", "@babel/plugin-proposal-decorators": "^7.8.3", diff --git a/frontend/src/app.css b/frontend/src/app.css new file mode 100644 index 0000000000..266f5ebcd1 --- /dev/null +++ b/frontend/src/app.css @@ -0,0 +1,26 @@ +:root { + /* FONT SIZE */ + --h1-size: 1.25rem; + --p-size: 1.1rem; + + /* PADDING */ + --card-padding: 2rem; + --card-padding-x: 2rem; + --card-padding-y: 2rem; + + /* MARGIN */ + --card-margin-y: 1rem; + --card-margin-x: 1rem; + + /* COLORS*/ + --success: #3bd86e; + --danger: #d95e5e; + --warning: #d67c3d; +} + +h1, +h2 { + padding: 0; + margin: 0; + line-height: 24px; +} diff --git a/frontend/src/component/common/conditionally-render.jsx b/frontend/src/component/common/conditionally-render.jsx new file mode 100644 index 0000000000..8e74dec4a0 --- /dev/null +++ b/frontend/src/component/common/conditionally-render.jsx @@ -0,0 +1,32 @@ +const ConditionallyRender = ({ condition, show, elseShow }) => { + const handleFunction = renderFunc => { + const result = renderFunc(); + if (!result) { + /* eslint-disable-next-line */ + console.warn( + 'Nothing was returned from your render function. Verify that you are returning a valid react component' + ); + return null; + } + return result; + }; + + const isFunc = param => typeof param === 'function'; + + if (condition && show) { + if (isFunc(show)) { + return handleFunction(show); + } + + return show; + } + if (!condition && elseShow) { + if (isFunc(elseShow)) { + return handleFunction(elseShow); + } + return elseShow; + } + return null; +}; + +export default ConditionallyRender; diff --git a/frontend/src/component/common/select.jsx b/frontend/src/component/common/select.jsx index f77fa51203..de30592b1e 100644 --- a/frontend/src/component/common/select.jsx +++ b/frontend/src/component/common/select.jsx @@ -1,11 +1,15 @@ import React from 'react'; +import classnames from 'classnames'; import PropTypes from 'prop-types'; -const Select = ({ name, value, label, options, style, onChange, disabled = false, filled }) => { +const Select = ({ name, value, label, options, style, onChange, disabled = false, filled, className }) => { const wrapper = Object.assign({ width: 'auto' }, style); return (
+
+ ); + const multipleProjects = projects.length > 1; + + return ( + + + + + + + ); +}; + +Reporting.propTypes = { + fetchFeatureToggles: PropTypes.func.isRequired, + projects: PropTypes.array.isRequired, + features: PropTypes.array, +}; + +export default Reporting; diff --git a/frontend/src/component/reporting/reporting.module.scss b/frontend/src/component/reporting/reporting.module.scss new file mode 100644 index 0000000000..c7a7c96d76 --- /dev/null +++ b/frontend/src/component/reporting/reporting.module.scss @@ -0,0 +1,171 @@ +.header { + font-size: var(--h1-size); + font-weight: 500; + margin: 0 0 0.5rem 0; +} + +.card { + width: 100%; + padding: var(--card-padding); + margin: var(--card-margin-y) 0; +} + +.projectSelector { + margin-bottom: 1rem; +} + +.select { + background-color: #fff; + border: none; + padding: 0.5rem 1rem; + position: static; +} + +.select select { + border: none; + min-width: 120px; +} + +.select select:active { + border: none; +} + +/** ReportCard **/ + +.reportCardContainer { + display: flex; + justify-content: space-between; +} + +.reportCardHealthInnerContainer { + display: flex; + align-items: center; + justify-content: center; + align-items: center; + height: 80%; +} + +.reportCardHealthRating { + font-size: 2rem; + font-weight: bold; + color: var(--success); +} + +.reportCardList { + list-style-type: none; + margin: 0; + padding: 0; +} + +.reportCardList li { + display: flex; + align-items: center; + margin: 0.5rem 0; +} + +.reportCardList li span { + margin: 0; + padding: 0; + margin-left: 0.5rem; + font-size: var(--p-size); +} + +.check, +.danger { + margin-right: 5px; +} + +.check { + color: var(--success); +} + +.danger { + color: var(--danger); +} + +.reportCardActionContainer { + display: flex; + justify-content: center; + flex-direction: column; +} + +.reportCardActionText { + max-width: 300px; + font-size: var(--p-size); +} + +.reportCardBtn { + background-color: #f2f2f2; +} + +.healthDanger { + color: var(--danger); +} + +.healthWarning { + color: var(--warning); +} + +/** ReportToggleList **/ +.reportToggleList { + width: 100%; + margin: var(--card-margin-y) 0; +} + +.bulkAction { + background-color: #f2f2f2; + font-size: var(--p-size); +} + +.sortIcon { + margin-left: 8px; +} + +.reportToggleListHeader { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #f1f1f1; + padding: 1rem var(--card-padding-x); +} + +.reportToggleListInnerContainer { + padding: var(--card-padding); +} + +.reportToggleListHeading { + font-size: var(--h1-size); + margin: 0; + font-weight: 500; +} + +.reportingToggleTable { + width: 100%; + border-spacing: 0 0.8rem; +} + +.reportingToggleTable th { + text-align: left; +} + +.expired { + color: var(--danger); +} + +.active { + color: var(--success); +} + +.stale { + color: var(--danger); +} + +.reportStatus { + display: flex; + align-items: center; +} + +.reportIcon { + font-size: 1.5rem; + margin-right: 5px; +} diff --git a/frontend/src/component/reporting/testData.js b/frontend/src/component/reporting/testData.js new file mode 100644 index 0000000000..4a428f4559 --- /dev/null +++ b/frontend/src/component/reporting/testData.js @@ -0,0 +1,67 @@ +export const testProjects = [ + { id: 'default', inital: true, name: 'Default' }, + { id: 'myProject', inital: false, name: 'MyProject' }, +]; + +export const testFeatures = [ + { + name: 'one', + description: '1234', + type: 'permission', + project: 'default', + enabled: false, + stale: false, + strategies: [], + variants: [], + createdAt: '2021-02-01T04:12:36.878Z', + lastSeenAt: '2021-02-21T19:34:21.830Z', + }, + { + name: 'two', + description: '', + type: 'release', + project: 'default', + enabled: false, + stale: false, + strategies: [], + variants: [], + createdAt: '2021-02-22T16:05:39.717Z', + lastSeenAt: '2021-02-22T19:37:58.189Z', + }, + { + name: 'three', + description: 'asdasds', + type: 'experiment', + project: 'default', + enabled: true, + stale: false, + strategies: [], + variants: [], + createdAt: '2021-02-06T18:38:18.133Z', + lastSeenAt: '2021-02-21T19:34:21.830Z', + }, + { + name: 'four', + description: '', + type: 'experiment', + project: 'myProject', + enabled: true, + stale: false, + strategies: [], + variants: [], + createdAt: '2021-02-14T02:42:34.515Z', + lastSeenAt: '2021-02-21T19:34:21.830Z', + }, + { + name: 'five', + description: '', + type: 'release', + project: 'myProject', + enabled: true, + stale: false, + strategies: [], + variants: [], + createdAt: '2021-02-16T15:26:11.474Z', + lastSeenAt: '2021-02-21T19:34:21.830Z', + }, +]; diff --git a/frontend/src/component/reporting/useSort.js b/frontend/src/component/reporting/useSort.js new file mode 100644 index 0000000000..8838467041 --- /dev/null +++ b/frontend/src/component/reporting/useSort.js @@ -0,0 +1,80 @@ +import { useState } from 'react'; +import { + sortFeaturesByNameAscending, + sortFeaturesByNameDescending, + sortFeaturesByLastSeenAscending, + sortFeaturesByLastSeenDescending, + sortFeaturesByCreatedAtAscending, + sortFeaturesByCreatedAtDescending, + sortFeaturesByExpiredAtAscending, + sortFeaturesByExpiredAtDescending, + sortFeaturesByStatusAscending, + sortFeaturesByStatusDescending, +} from './utils'; + +import { LAST_SEEN, NAME, CREATED, EXPIRED, STATUS, REPORT } from './constants'; + +const useSort = () => { + const [sortData, setSortData] = useState({ + sortKey: NAME, + ascending: true, + }); + + const handleSortName = features => { + if (sortData.ascending) { + return sortFeaturesByNameAscending(features); + } + + return sortFeaturesByNameDescending(features); + }; + + const handleSortLastSeen = features => { + if (sortData.ascending) { + return sortFeaturesByLastSeenAscending(features); + } + return sortFeaturesByLastSeenDescending(features); + }; + + const handleSortCreatedAt = features => { + if (sortData.ascending) { + return sortFeaturesByCreatedAtAscending(features); + } + return sortFeaturesByCreatedAtDescending(features); + }; + + const handleSortExpiredAt = features => { + if (sortData.ascending) { + return sortFeaturesByExpiredAtAscending(features); + } + return sortFeaturesByExpiredAtDescending(features); + }; + + const handleSortStatus = features => { + if (sortData.ascending) { + return sortFeaturesByStatusAscending(features); + } + return sortFeaturesByStatusDescending(features); + }; + + const sort = features => { + switch (sortData.sortKey) { + case NAME: + return handleSortName(features); + case LAST_SEEN: + return handleSortLastSeen(features); + case CREATED: + return handleSortCreatedAt(features); + case EXPIRED: + case REPORT: + return handleSortExpiredAt(features); + case STATUS: + return handleSortStatus(features); + default: + return features; + } + }; + + return [sort, setSortData]; +}; + +export default useSort; diff --git a/frontend/src/component/reporting/utils.js b/frontend/src/component/reporting/utils.js new file mode 100644 index 0000000000..0c7abfbaa7 --- /dev/null +++ b/frontend/src/component/reporting/utils.js @@ -0,0 +1,144 @@ +import parseISO from 'date-fns/parseISO'; +import differenceInDays from 'date-fns/differenceInDays'; + +import { EXPERIMENT, OPERATIONAL, RELEASE, FOURTYDAYS, SEVENDAYS } from './constants'; + +export const toggleExpiryByTypeMap = { + [EXPERIMENT]: FOURTYDAYS, + [RELEASE]: FOURTYDAYS, + [OPERATIONAL]: SEVENDAYS, +}; + +export const applyCheckedToFeatures = (features, checkedState) => + features.map(feature => ({ ...feature, checked: checkedState })); + +export const getCheckedState = (name, features) => { + const feature = features.find(feature => feature.name === name); + + if (feature) { + return feature.checked ? feature.checked : false; + } + return false; +}; + +export const getDiffInDays = (date, now) => Math.abs(differenceInDays(date, now)); + +export const formatProjectOptions = projects => projects.map(project => ({ key: project.id, label: project.name })); + +export const expired = (diff, type) => { + if (diff >= toggleExpiryByTypeMap[type]) return true; + return false; +}; + +export const getObjectProperties = (target, ...keys) => { + const newObject = {}; + + keys.forEach(key => { + if (target[key] !== undefined) { + newObject[key] = target[key]; + } + }); + + return newObject; +}; + +export const sortFeaturesByNameAscending = features => { + const sorted = [...features]; + sorted.sort((a, b) => { + if (a.name < b.name) { + return -1; + } + if (a.name > b.name) { + return 1; + } + return 0; + }); + return sorted; +}; + +export const sortFeaturesByNameDescending = features => sortFeaturesByNameAscending([...features]).reverse(); + +export const sortFeaturesByLastSeenAscending = features => { + const sorted = [...features]; + sorted.sort((a, b) => { + if (!a.lastSeenAt) return -1; + if (!b.lastSeenAt) return 1; + + const dateA = parseISO(a.lastSeenAt); + const dateB = parseISO(b.lastSeenAt); + + return dateA.getTime() - dateB.getTime(); + }); + return sorted; +}; + +export const sortFeaturesByLastSeenDescending = features => sortFeaturesByLastSeenAscending([...features]).reverse(); + +export const sortFeaturesByCreatedAtAscending = features => { + const sorted = [...features]; + sorted.sort((a, b) => { + const dateA = parseISO(a.createdAt); + const dateB = parseISO(b.createdAt); + + return dateA.getTime() - dateB.getTime(); + }); + return sorted; +}; + +export const sortFeaturesByCreatedAtDescending = features => sortFeaturesByCreatedAtAscending([...features]).reverse(); + +export const sortFeaturesByExpiredAtAscending = features => { + const sorted = [...features]; + sorted.sort((a, b) => { + const now = new Date(); + const dateA = parseISO(a.createdAt); + const dateB = parseISO(b.createdAt); + + const diffA = getDiffInDays(dateA, now); + const diffB = getDiffInDays(dateB, now); + + if (!expired(diffA, a.type)) return -1; + if (!expired(diffB, b.type)) return 1; + + const expiredByA = diffA - toggleExpiryByTypeMap[a.type]; + const expiredByB = diffB - toggleExpiryByTypeMap[b.type]; + + return expiredByA - expiredByB; + }); + return sorted; +}; + +export const sortFeaturesByExpiredAtDescending = features => sortFeaturesByExpiredAtAscending([...features]).reverse(); + +export const sortFeaturesByStatusAscending = features => { + const sorted = [...features]; + sorted.sort((a, b) => { + if (a.stale) return 1; + if (b.stale) return -1; + return 0; + }); + return sorted; +}; + +export const sortFeaturesByStatusDescending = features => sortFeaturesByStatusAscending([...features]).reverse(); + +export const pluralize = (items, word) => { + if (items === 1) return `${items} ${word}`; + return `${items} ${word}s`; +}; + +export const getDates = dateString => { + const date = parseISO(dateString); + const now = new Date(); + + return [date, now]; +}; + +export const filterByProject = selectedProject => feature => feature.project === selectedProject; + +export const isFeatureExpired = feature => { + const [date, now] = getDates(feature.createdAt); + const diff = getDiffInDays(date, now); + + return expired(diff, feature.type); +}; diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx index 51b487dc41..f3bb8901d2 100644 --- a/frontend/src/index.jsx +++ b/frontend/src/index.jsx @@ -2,6 +2,7 @@ import 'whatwg-fetch'; import 'react-mdl/extra/material.js'; import 'react-mdl/extra/css/material.blue_grey-pink.min.css'; +import './app.css'; import React from 'react'; import ReactDOM from 'react-dom'; diff --git a/frontend/src/page/reporting/index.js b/frontend/src/page/reporting/index.js new file mode 100644 index 0000000000..24becc789c --- /dev/null +++ b/frontend/src/page/reporting/index.js @@ -0,0 +1,6 @@ +import React from 'react'; +import ReportingContainer from '../../component/reporting/reporting-container'; + +const render = () => ; + +export default render; diff --git a/frontend/vercel.json b/frontend/vercel.json new file mode 100644 index 0000000000..78592d1d5c --- /dev/null +++ b/frontend/vercel.json @@ -0,0 +1,15 @@ +{ + "rewrites": [ + { "source": "/api/admin/user", "destination": "https://unleash.herokuapp.com/api/admin/user" }, + { "source": "/api/admin/uiconfig", "destination": "https://unleash.herokuapp.com/api/admin/uiconfig" }, + { "source": "/api/admin/context", "destination": "https://unleash.herokuapp.com/api/admin/context" }, + { "source": "/api/admin/feature-types", "destination": "https://unleash.herokuapp.com/api/admin/feature-types" }, + { "source": "/api/admin/strategies", "destination": "https://unleash.herokuapp.com/api/admin/strategies" }, + { "source": "/api/admin/tag-types", "destination": "https://unleash.herokuapp.com/api/admin/tag-types" }, + { "source": "/api/admin/features", "destination": "https://unleash.herokuapp.com/api/admin/features" }, + { "source": "/api/admin/login", "destination": "https://unleash.herokuapp.com/api/admin/login" }, + { "source": "/api/admin/metrics/feature-toggles", "destination": "https://unleash.herokuapp.com/api/admin/metrics/feature-toggles" }, + { "source": "/logout", "destination": "https://unleash.herokuapp.com/logout" }, + { "source": "/auth", "destination": "https://unleash.herokuapp.com/auth" } + ] +} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 456d9d73ea..933d6ac0f5 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1044,6 +1044,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.3.1", "@babel/runtime@^7.8.3": + version "7.13.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.2.tgz#9511c87d2808b2cf5fb9e9c5cf0d1ab789d75499" + integrity sha512-U9plpxyudmZNYe12YI6cXyeWTWYCTq2u1h+C0XVtC3+BoiuzTh1BHlMJgxMrbKTombYkf7wQGqoxYkptFehuZw== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.4.4", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f" @@ -1179,7 +1186,7 @@ "@emotion/utils" "0.11.3" babel-plugin-emotion "^10.0.27" -"@emotion/hash@0.8.0": +"@emotion/hash@0.8.0", "@emotion/hash@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== @@ -1443,6 +1450,77 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@material-ui/core@^4.11.3": + version "4.11.3" + resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.11.3.tgz#f22e41775b0bd075e36a7a093d43951bf7f63850" + integrity sha512-Adt40rGW6Uds+cAyk3pVgcErpzU/qxc7KBR94jFHBYretU4AtWZltYcNsbeMn9tXL86jjVL1kuGcIHsgLgFGRw== + dependencies: + "@babel/runtime" "^7.4.4" + "@material-ui/styles" "^4.11.3" + "@material-ui/system" "^4.11.3" + "@material-ui/types" "^5.1.0" + "@material-ui/utils" "^4.11.2" + "@types/react-transition-group" "^4.2.0" + clsx "^1.0.4" + hoist-non-react-statics "^3.3.2" + popper.js "1.16.1-lts" + prop-types "^15.7.2" + react-is "^16.8.0 || ^17.0.0" + react-transition-group "^4.4.0" + +"@material-ui/icons@^4.11.2": + version "4.11.2" + resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.11.2.tgz#b3a7353266519cd743b6461ae9fdfcb1b25eb4c5" + integrity sha512-fQNsKX2TxBmqIGJCSi3tGTO/gZ+eJgWmMJkgDiOfyNaunNaxcklJQFaFogYcFl0qFuaEz1qaXYXboa/bUXVSOQ== + dependencies: + "@babel/runtime" "^7.4.4" + +"@material-ui/styles@^4.11.3": + version "4.11.3" + resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.3.tgz#1b8d97775a4a643b53478c895e3f2a464e8916f2" + integrity sha512-HzVzCG+PpgUGMUYEJ2rTEmQYeonGh41BYfILNFb/1ueqma+p1meSdu4RX6NjxYBMhf7k+jgfHFTTz+L1SXL/Zg== + dependencies: + "@babel/runtime" "^7.4.4" + "@emotion/hash" "^0.8.0" + "@material-ui/types" "^5.1.0" + "@material-ui/utils" "^4.11.2" + clsx "^1.0.4" + csstype "^2.5.2" + hoist-non-react-statics "^3.3.2" + jss "^10.5.1" + jss-plugin-camel-case "^10.5.1" + jss-plugin-default-unit "^10.5.1" + jss-plugin-global "^10.5.1" + jss-plugin-nested "^10.5.1" + jss-plugin-props-sort "^10.5.1" + jss-plugin-rule-value-function "^10.5.1" + jss-plugin-vendor-prefixer "^10.5.1" + prop-types "^15.7.2" + +"@material-ui/system@^4.11.3": + version "4.11.3" + resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.11.3.tgz#466bc14c9986798fd325665927c963eb47cc4143" + integrity sha512-SY7otguNGol41Mu2Sg6KbBP1ZRFIbFLHGK81y4KYbsV2yIcaEPOmsCK6zwWlp+2yTV3J/VwT6oSBARtGIVdXPw== + dependencies: + "@babel/runtime" "^7.4.4" + "@material-ui/utils" "^4.11.2" + csstype "^2.5.2" + prop-types "^15.7.2" + +"@material-ui/types@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2" + integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A== + +"@material-ui/utils@^4.11.2": + version "4.11.2" + resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.11.2.tgz#f1aefa7e7dff2ebcb97d31de51aecab1bb57540a" + integrity sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA== + dependencies: + "@babel/runtime" "^7.4.4" + prop-types "^15.7.2" + react-is "^16.8.0 || ^17.0.0" + "@react-dnd/asap@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-4.0.0.tgz#b300eeed83e9801f51bd66b0337c9a6f04548651" @@ -1629,6 +1707,13 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== +"@types/react-transition-group@^4.2.0": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.1.tgz#e1a3cb278df7f47f17b5082b1b3da17170bd44b1" + integrity sha512-vIo69qKKcYoJ8wKCJjwSgCTM+z3chw3g18dkrDfVX665tMH7tmbDxEAnPdey4gTlwZz5QuHGzd+hul0OVZDqqQ== + dependencies: + "@types/react" "*" + "@types/react@*": version "16.9.34" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.34.tgz#f7d5e331c468f53affed17a8a4d488cd44ea9349" @@ -2924,6 +3009,11 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" +clsx@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" + integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -3310,6 +3400,14 @@ css-tree@1.0.0-alpha.39: mdn-data "2.0.6" source-map "^0.6.1" +css-vendor@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d" + integrity sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ== + dependencies: + "@babel/runtime" "^7.8.3" + is-in-browser "^1.0.2" + css-what@2.1: version "2.1.3" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" @@ -3422,6 +3520,16 @@ csstype@^2.2.0, csstype@^2.5.7, csstype@^2.6.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b" integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w== +csstype@^2.5.2: + version "2.6.15" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.15.tgz#655901663db1d652f10cb57ac6af5a05972aea1f" + integrity sha512-FNeiVKudquehtR3t9TRRnsHL+lJhuHF5Zn9dt01jpojlurLEPDhhEtUkWmAUJ7/fOLaLG4dCDEnUsR0N1rZSsg== + +csstype@^3.0.2: + version "3.0.6" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.6.tgz#865d0b5833d7d8d40f4e5b8a6d76aea3de4725ef" + integrity sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw== + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -3450,6 +3558,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +date-fns@^2.17.0: + version "2.17.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.17.0.tgz#afa55daea539239db0a64e236ce716ef3d681ba1" + integrity sha512-ZEhqxUtEZeGgg9eHNSOAJ8O9xqSgiJdrL0lzSSfMF54x6KXWJiOH/xntSJ9YomJPrYH/p08t6gWjGWq1SDJlSA== + debounce@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131" @@ -4981,7 +5094,7 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: +hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -5146,6 +5259,11 @@ human-signals@^1.1.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== +hyphenate-style-name@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" + integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== + iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -5234,6 +5352,13 @@ in-publish@^2.0.0: resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.1.tgz#948b1a535c8030561cea522f73f78f4be357e00c" integrity sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ== +indefinite-observable@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/indefinite-observable/-/indefinite-observable-2.0.1.tgz#574af29bfbc17eb5947793797bddc94c9d859400" + integrity sha512-G8vgmork+6H9S8lUAg1gtXEj2JxIQTo0g2PbFiYOdjkziSI0F7UYBiVwhZRuixhBCNGczAls34+5HJPyZysvxQ== + dependencies: + symbol-observable "1.2.0" + indent-string@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" @@ -5544,6 +5669,11 @@ is-glob@^4.0.0, is-glob@^4.0.1: dependencies: is-extglob "^2.1.1" +is-in-browser@^1.0.2, is-in-browser@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" + integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU= + is-number-object@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" @@ -6310,6 +6440,77 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jss-plugin-camel-case@^10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.5.1.tgz#427b24a9951b4c2eaa7e3d5267acd2e00b0934f9" + integrity sha512-9+oymA7wPtswm+zxVti1qiowC5q7bRdCJNORtns2JUj/QHp2QPXYwSNRD8+D2Cy3/CEMtdJzlNnt5aXmpS6NAg== + dependencies: + "@babel/runtime" "^7.3.1" + hyphenate-style-name "^1.0.3" + jss "10.5.1" + +jss-plugin-default-unit@^10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.5.1.tgz#2be385d71d50aee2ee81c2a9ac70e00592ed861b" + integrity sha512-D48hJBc9Tj3PusvlillHW8Fz0y/QqA7MNmTYDQaSB/7mTrCZjt7AVRROExoOHEtd2qIYKOYJW3Jc2agnvsXRlQ== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.5.1" + +jss-plugin-global@^10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.5.1.tgz#0e1793dea86c298360a7e2004721351653c7e764" + integrity sha512-jX4XpNgoaB8yPWw/gA1aPXJEoX0LNpvsROPvxlnYe+SE0JOhuvF7mA6dCkgpXBxfTWKJsno7cDSCgzHTocRjCQ== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.5.1" + +jss-plugin-nested@^10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.5.1.tgz#8753a80ad31190fb6ac6fdd39f57352dcf1295bb" + integrity sha512-xXkWKOCljuwHNjSYcXrCxBnjd8eJp90KVFW1rlhvKKRXnEKVD6vdKXYezk2a89uKAHckSvBvBoDGsfZrldWqqQ== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.5.1" + tiny-warning "^1.0.2" + +jss-plugin-props-sort@^10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.5.1.tgz#ab1c167fd2d4506fb6a1c1d66c5f3ef545ff1cd8" + integrity sha512-t+2vcevNmMg4U/jAuxlfjKt46D/jHzCPEjsjLRj/J56CvP7Iy03scsUP58Iw8mVnaV36xAUZH2CmAmAdo8994g== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.5.1" + +jss-plugin-rule-value-function@^10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.5.1.tgz#37f4030523fb3032c8801fab48c36c373004de7e" + integrity sha512-3gjrSxsy4ka/lGQsTDY8oYYtkt2esBvQiceGBB4PykXxHoGRz14tbCK31Zc6DHEnIeqsjMUGbq+wEly5UViStQ== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.5.1" + tiny-warning "^1.0.2" + +jss-plugin-vendor-prefixer@^10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.5.1.tgz#45a183a3a0eb097bdfab0986b858d99920c0bbd8" + integrity sha512-cLkH6RaPZWHa1TqSfd2vszNNgxT1W0omlSjAd6hCFHp3KIocSrW21gaHjlMU26JpTHwkc+tJTCQOmE/O1A4FKQ== + dependencies: + "@babel/runtime" "^7.3.1" + css-vendor "^2.0.8" + jss "10.5.1" + +jss@10.5.1, jss@^10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/jss/-/jss-10.5.1.tgz#93e6b2428c840408372d8b548c3f3c60fa601c40" + integrity sha512-hbbO3+FOTqVdd7ZUoTiwpHzKXIo5vGpMNbuXH1a0wubRSWLWSBvwvaq4CiHH/U42CmjOnp6lVNNs/l+Z7ZdDmg== + dependencies: + "@babel/runtime" "^7.3.1" + csstype "^3.0.2" + indefinite-observable "^2.0.1" + is-in-browser "^1.1.3" + tiny-warning "^1.0.2" + jsx-ast-utils@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz#8a9364e402448a3ce7f14d357738310d9248054f" @@ -7641,6 +7842,11 @@ pkg-up@^2.0.0: dependencies: find-up "^2.1.0" +popper.js@1.16.1-lts: + version "1.16.1-lts" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05" + integrity sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA== + portfinder@^1.0.26: version "1.0.28" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" @@ -8249,7 +8455,7 @@ react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-i resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^17.0.1: +"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.1: version "17.0.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== @@ -8358,7 +8564,7 @@ react-timeago@^4.4.0: resolved "https://registry.yarnpkg.com/react-timeago/-/react-timeago-4.4.0.tgz#4520dd9ba63551afc4d709819f52b14b9343ba2b" integrity sha512-Zj8RchTqZEH27LAANemzMR2RpotbP2aMd+UIajfYMZ9KW4dMcViUVKzC7YmqfiqlFfz8B0bjDw2xUBjmcxDngA== -react-transition-group@^4.3.0: +react-transition-group@^4.3.0, react-transition-group@^4.4.0: version "4.4.1" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== @@ -9624,7 +9830,7 @@ svgo@^1.0.0: unquote "~1.1.1" util.promisify "~1.0.0" -symbol-observable@^1.2.0: +symbol-observable@1.2.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==