diff --git a/frontend/.github/workflows/e2e.auth.yml b/frontend/.github/workflows/e2e.auth.yml index 6270d196b9..441ef0fdfd 100644 --- a/frontend/.github/workflows/e2e.auth.yml +++ b/frontend/.github/workflows/e2e.auth.yml @@ -16,10 +16,10 @@ jobs: uses: actions/checkout@v2 - name: Run Cypress uses: cypress-io/github-action@v2 - with: - env: AUTH_TOKEN=${{ secrets.UNLEASH_TOKEN }},DEFAULT_ENV="development" + with: + env: AUTH_TOKEN=${{ secrets.UNLEASH_TOKEN }} config: baseUrl=${{ github.event.deployment_status.target_url }} record: true - spec: cypress/integration/auth/auth.spec.js + spec: cypress/integration/auth/auth.spec.ts env: - CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} \ No newline at end of file + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} diff --git a/frontend/.github/workflows/e2e.feature.yml b/frontend/.github/workflows/e2e.feature.yml index cfa3af2438..c0826e6194 100644 --- a/frontend/.github/workflows/e2e.feature.yml +++ b/frontend/.github/workflows/e2e.feature.yml @@ -16,10 +16,10 @@ jobs: uses: actions/checkout@v2 - name: Run Cypress uses: cypress-io/github-action@v2 - with: - env: AUTH_TOKEN=${{ secrets.UNLEASH_TOKEN }},DEFAULT_ENV="development" + with: + env: AUTH_TOKEN=${{ secrets.UNLEASH_TOKEN }} config: baseUrl=${{ github.event.deployment_status.target_url }} record: true - spec: cypress/integration/feature-toggle/feature.spec.js + spec: cypress/integration/feature/feature.spec.ts env: - CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} \ No newline at end of file + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} diff --git a/frontend/.github/workflows/release_to_cdn.yml b/frontend/.github/workflows/release_to_cdn.yml index 018b0735df..b51743e30d 100644 --- a/frontend/.github/workflows/release_to_cdn.yml +++ b/frontend/.github/workflows/release_to_cdn.yml @@ -14,7 +14,7 @@ jobs: node-version: [14.x] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v2 with: diff --git a/frontend/README.md b/frontend/README.md index 2466d50fa0..8b85894d87 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,21 +1,4 @@ -# Developing - -## Why did you render - -This application is set up with [WDYR](https://github.com/welldone-software/why-did-you-render) and [craco](https://github.com/gsoft-inc/craco) in order to find, debug and remove uneccesary re-renders. This configuration can be found in /src/wdyr.ts. - -In order to turn it on, change the configuration accordingly: - -``` -if (process.env.NODE_ENV === 'development') { - const whyDidYouRender = require('@welldone-software/why-did-you-render'); - whyDidYouRender(React, { - trackAllPureComponents: true, - }); -} -``` - -Now you should be able to review rendering information in the console. If you do utilise this functionality, please remember to set the configuration back to spare other developers the noise in the console. +# unleash-frontend ## Run with a local instance of the unleash-api: diff --git a/frontend/craco.config.js b/frontend/craco.config.js deleted file mode 100644 index b4b841ff29..0000000000 --- a/frontend/craco.config.js +++ /dev/null @@ -1,37 +0,0 @@ -const presetReact = require('@babel/preset-react').default; -const presetCRA = require('babel-preset-react-app'); - -module.exports = { - babel: { - loaderOptions: (babelLoaderOptions, { env, paths }) => { - const origBabelPresetReactAppIndex = babelLoaderOptions.presets.findIndex( - preset => { - return preset[0].includes('babel-preset-react-app'); - } - ); - - if (origBabelPresetReactAppIndex === -1) { - return babelLoaderOptions; - } - - const overridenBabelPresetReactApp = (...args) => { - const babelPresetReactAppResult = presetCRA(...args); - const origPresetReact = babelPresetReactAppResult.presets.find( - preset => { - return preset[0] === presetReact; - } - ); - Object.assign(origPresetReact[1], { - importSource: '@welldone-software/why-did-you-render', - }); - return babelPresetReactAppResult; - }; - - babelLoaderOptions.presets[ - origBabelPresetReactAppIndex - ] = overridenBabelPresetReactApp; - - return babelLoaderOptions; - }, - }, -}; diff --git a/frontend/cypress.json b/frontend/cypress.json index e91af16203..ba6b740e2d 100644 --- a/frontend/cypress.json +++ b/frontend/cypress.json @@ -1,4 +1,6 @@ { "projectId": "tc2qff", - "defaultCommandTimeout": 12000 + "defaultCommandTimeout": 12000, + "screenshotOnRunFailure": false, + "video": false } diff --git a/frontend/cypress/integration/auth/auth.spec.js b/frontend/cypress/integration/auth/auth.spec.ts similarity index 93% rename from frontend/cypress/integration/auth/auth.spec.js rename to frontend/cypress/integration/auth/auth.spec.ts index 63500c543d..d05a936a4d 100644 --- a/frontend/cypress/integration/auth/auth.spec.js +++ b/frontend/cypress/integration/auth/auth.spec.ts @@ -1,15 +1,4 @@ -/* eslint-disable jest/no-conditional-expect */ /// -// Welcome to Cypress! -// -// This spec file contains a variety of sample tests -// for a todo list app that are designed to demonstrate -// the power of writing tests in Cypress. -// -// To learn more about how Cypress works and -// what makes it such an awesome testing tool, -// please read our getting started guide: -// https://on.cypress.io/introduction-to-cypress const username = 'test@test.com'; const password = 'qY70$NDcJNXA'; diff --git a/frontend/cypress/integration/feature-toggle/feature.spec.js b/frontend/cypress/integration/feature/feature.spec.ts similarity index 76% rename from frontend/cypress/integration/feature-toggle/feature.spec.js rename to frontend/cypress/integration/feature/feature.spec.ts index a808390cae..0e682af659 100644 --- a/frontend/cypress/integration/feature-toggle/feature.spec.js +++ b/frontend/cypress/integration/feature/feature.spec.ts @@ -1,82 +1,48 @@ -/* eslint-disable jest/no-conditional-expect */ /// -// Welcome to Cypress! -// -// This spec file contains a variety of sample tests -// for a todo list app that are designed to demonstrate -// the power of writing tests in Cypress. -// -// To learn more about how Cypress works and -// what makes it such an awesome testing tool, -// please read our getting started guide: -// https://on.cypress.io/introduction-to-cypress -let featureToggleName = ''; -let enterprise = false; +import { disableFeatureStrategiesProductionGuard } from '../../../src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesProductionGuard/FeatureStrategiesProductionGuard'; + +const randomId = String(Math.random()).split('.')[1]; +const featureToggleName = `unleash-e2e-${randomId}`; +const enterprise = Boolean(Cypress.env('ENTERPRISE')); +const passwordAuth = Cypress.env('PASSWORD_AUTH'); +const authToken = Cypress.env('AUTH_TOKEN'); +const baseUrl = Cypress.config().baseUrl; let strategyId = ''; -let defaultEnv = 'development'; - -describe('feature toggle', () => { - before(() => { - featureToggleName = `unleash-e2e-${Math.floor(Math.random() * 100)}`; - enterprise = Boolean(Cypress.env('ENTERPRISE')); - - const env = Cypress.env('DEFAULT_ENV'); - if (env) { - defaultEnv = env; - } - }); +describe('feature', () => { after(() => { - const authToken = Cypress.env('AUTH_TOKEN'); - cy.request({ method: 'DELETE', - url: `${ - Cypress.config().baseUrl - }/api/admin/features/${featureToggleName}`, - headers: { - Authorization: authToken, - }, + url: `${baseUrl}/api/admin/features/${featureToggleName}`, + headers: { Authorization: authToken }, }); - cy.request({ method: 'DELETE', - url: `${ - Cypress.config().baseUrl - }/api/admin/archive/${featureToggleName}`, - headers: { - Authorization: authToken, - }, + url: `${baseUrl}/api/admin/archive/${featureToggleName}`, + headers: { Authorization: authToken }, }); }); beforeEach(() => { - // Cypress starts out with a blank slate for each test - // so we must tell it to visit our website with the `cy.visit()` command. - // Since we want to visit the same URL at the start of all our tests, - // we include it in our beforeEach function so that it runs before each test - const passwordAuth = Cypress.env('PASSWORD_AUTH'); - enterprise = Boolean(Cypress.env('ENTERPRISE')); - + disableFeatureStrategiesProductionGuard(); cy.visit('/'); if (passwordAuth) { cy.get('[data-test="LOGIN_EMAIL_ID"]').type('test@test.com'); - cy.get('[data-test="LOGIN_PASSWORD_ID"]').type('qY70$NDcJNXA'); - cy.get("[data-test='LOGIN_BUTTON']").click(); } else { cy.get('[data-test=LOGIN_EMAIL_ID]').type('test@unleash-e2e.com'); cy.get('[data-test=LOGIN_BUTTON]').click(); } + + // Wait for the login redirects to complete. + cy.get('[data-test=HEADER_USER_AVATAR'); }); - it('Creates a feature toggle', () => { - if ( - document.querySelectorAll("[data-test='CLOSE_SPLASH']").length > 0 - ) { + it('can create a feature toggle', () => { + if (document.querySelector("[data-test='CLOSE_SPLASH']")) { cy.get("[data-test='CLOSE_SPLASH']").click(); } @@ -87,14 +53,13 @@ describe('feature toggle', () => { ); cy.get("[data-test='CF_NAME_ID'").type(featureToggleName); - cy.get("[data-test='CF_DESC_ID'").type('hellowrdada'); - + cy.get("[data-test='CF_DESC_ID'").type('hello-world'); cy.get("[data-test='CF_CREATE_BTN_ID']").click(); cy.wait('@createFeature'); cy.url().should('include', featureToggleName); }); - it('Gives an error if a toggle exists with the same name', () => { + it('gives an error if a toggle exists with the same name', () => { cy.get('[data-test=NAVIGATE_TO_CREATE_FEATURE').click(); cy.intercept('POST', '/api/admin/projects/default/features').as( @@ -102,16 +67,14 @@ describe('feature toggle', () => { ); cy.get("[data-test='CF_NAME_ID'").type(featureToggleName); - cy.get("[data-test='CF_DESC_ID'").type('hellowrdada'); - + cy.get("[data-test='CF_DESC_ID'").type('hello-world'); cy.get("[data-test='CF_CREATE_BTN_ID']").click(); - cy.get("[data-test='INPUT_ERROR_TEXT']").contains( 'A feature with this name already exists' ); }); - it('Gives an error if a toggle name is url unsafe', () => { + it('gives an error if a toggle name is url unsafe', () => { cy.get('[data-test=NAVIGATE_TO_CREATE_FEATURE').click(); cy.intercept('POST', '/api/admin/projects/default/features').as( @@ -119,17 +82,14 @@ describe('feature toggle', () => { ); cy.get("[data-test='CF_NAME_ID'").type('featureToggleUnsafe####$#//'); - cy.get("[data-test='CF_DESC_ID'").type('hellowrdada'); - + cy.get("[data-test='CF_DESC_ID'").type('hello-world'); cy.get("[data-test='CF_CREATE_BTN_ID']").click(); - cy.get("[data-test='INPUT_ERROR_TEXT']").contains( `"name" must be URL friendly` ); }); - it('Can add a gradual rollout strategy to the development environment', () => { - cy.wait(1000); + it('can add a gradual rollout strategy to the development environment', () => { cy.visit(`/projects/default/features/${featureToggleName}/strategies`); cy.get('[data-test=ADD_NEW_STRATEGY_ID]').click(); cy.get('[data-test=ADD_NEW_STRATEGY_CARD_BUTTON_ID-2').click(); @@ -147,10 +107,9 @@ describe('feature toggle', () => { cy.intercept( 'POST', - `/api/admin/projects/default/features/${featureToggleName}/environments/${defaultEnv}/strategies`, + `/api/admin/projects/default/features/${featureToggleName}/environments/*/strategies`, req => { expect(req.body.name).to.equal('flexibleRollout'); - expect(req.body.parameters.groupId).to.equal(featureToggleName); expect(req.body.parameters.stickiness).to.equal('default'); expect(req.body.parameters.rollout).to.equal(30); @@ -172,7 +131,6 @@ describe('feature toggle', () => { }); it('can update a strategy in the development environment', () => { - cy.wait(1000); cy.visit(`/projects/default/features/${featureToggleName}/strategies`); cy.get('[data-test=STRATEGY_ACCORDION_ID-flexibleRollout').click(); @@ -188,7 +146,6 @@ describe('feature toggle', () => { .first() .click(); - let newGroupId = 'new-group-id'; cy.get('[data-test=FLEXIBLE_STRATEGY_GROUP_ID]') .first() .clear() @@ -196,9 +153,9 @@ describe('feature toggle', () => { cy.intercept( 'PUT', - `/api/admin/projects/default/features/${featureToggleName}/environments/${defaultEnv}/strategies/${strategyId}`, + `/api/admin/projects/default/features/${featureToggleName}/environments/*/strategies/${strategyId}`, req => { - expect(req.body.parameters.groupId).to.equal(newGroupId); + expect(req.body.parameters.groupId).to.equal('new-group-id'); expect(req.body.parameters.stickiness).to.equal('sessionId'); expect(req.body.parameters.rollout).to.equal(60); @@ -219,12 +176,11 @@ describe('feature toggle', () => { }); it('can delete a strategy in the development environment', () => { - cy.wait(1000); cy.visit(`/projects/default/features/${featureToggleName}/strategies`); cy.intercept( 'DELETE', - `/api/admin/projects/default/features/${featureToggleName}/environments/${defaultEnv}/strategies/${strategyId}`, + `/api/admin/projects/default/features/${featureToggleName}/environments/*/strategies/${strategyId}`, req => { req.continue(res => { expect(res.statusCode).to.equal(200); @@ -237,8 +193,7 @@ describe('feature toggle', () => { cy.wait('@deleteStrategy'); }); - it('Can add a userid strategy to the development environment', () => { - cy.wait(1000); + it('can add a userid strategy to the development environment', () => { cy.visit(`/projects/default/features/${featureToggleName}/strategies`); cy.get('[data-test=ADD_NEW_STRATEGY_ID]').click(); cy.get('[data-test=ADD_NEW_STRATEGY_CARD_BUTTON_ID-3').click(); @@ -260,7 +215,7 @@ describe('feature toggle', () => { cy.intercept( 'POST', - `/api/admin/projects/default/features/${featureToggleName}/environments/${defaultEnv}/strategies`, + `/api/admin/projects/default/features/${featureToggleName}/environments/*/strategies`, req => { expect(req.body.name).to.equal('userWithId'); @@ -282,11 +237,12 @@ describe('feature toggle', () => { cy.wait('@addStrategyToFeature'); }); - it('Can add two variant to the feature', () => { + it('can add two variant to the feature', () => { const variantName = 'my-new-variant'; const secondVariantName = 'my-second-variant'; - cy.wait(1000); + cy.visit(`/projects/default/features/${featureToggleName}/variants`); + cy.intercept( 'PATCH', `/api/admin/projects/default/features/${featureToggleName}/variants`, @@ -304,20 +260,23 @@ describe('feature toggle', () => { expect(req.body[1].value.name).to.equal(secondVariantName); } } - ).as('variantcreation'); - cy.get('[data-test=ADD_VARIANT_BUTTON]').click(); - cy.get('[data-test=VARIANT_NAME_INPUT]').type(variantName); - cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click(); - cy.wait('@variantcreation'); - cy.get('[data-test=ADD_VARIANT_BUTTON]').click(); - cy.get('[data-test=VARIANT_NAME_INPUT]').type(secondVariantName); - cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click(); - cy.wait('@variantcreation'); - }); - it('Can set weight to fixed value for one of the variants', () => { - cy.wait(1000); + ).as('variantCreation'); + cy.get('[data-test=ADD_VARIANT_BUTTON]').click(); + cy.get('[data-test=VARIANT_NAME_INPUT]').click().type(variantName); + cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click(); + cy.wait('@variantCreation'); + cy.get('[data-test=ADD_VARIANT_BUTTON]').click(); + cy.get('[data-test=VARIANT_NAME_INPUT]') + .click() + .type(secondVariantName); + cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click(); + cy.wait('@variantCreation'); + }); + + it('can set weight to fixed value for one of the variants', () => { cy.visit(`/projects/default/features/${featureToggleName}/variants`); + cy.get('[data-test=VARIANT_EDIT_BUTTON]').first().click(); cy.get('[data-test=VARIANT_NAME_INPUT]') .children() @@ -328,6 +287,7 @@ describe('feature toggle', () => { .find('input') .check(); cy.get('[data-test=VARIANT_WEIGHT_INPUT]').clear().type('15'); + cy.intercept( 'PATCH', `/api/admin/projects/default/features/${featureToggleName}/variants`, @@ -342,21 +302,23 @@ describe('feature toggle', () => { expect(req.body[2].path).to.match(/weight/); expect(req.body[2].value).to.equal(150); } - ).as('variantupdate'); + ).as('variantUpdate'); + cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click(); - cy.wait('@variantupdate'); + cy.wait('@variantUpdate'); cy.get('[data-test=VARIANT_WEIGHT]') .first() .should('have.text', '15 %'); }); - it(`can delete variant`, () => { + it('can delete variant', () => { const variantName = 'to-be-deleted'; - cy.wait(1000); + cy.visit(`/projects/default/features/${featureToggleName}/variants`); cy.get('[data-test=ADD_VARIANT_BUTTON]').click(); - cy.get('[data-test=VARIANT_NAME_INPUT]').type(variantName); + cy.get('[data-test=VARIANT_NAME_INPUT]').click().type(variantName); cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click(); + cy.intercept( 'PATCH', `/api/admin/projects/default/features/${featureToggleName}/variants`, @@ -365,6 +327,7 @@ describe('feature toggle', () => { expect(e.path).to.match(/\//); } ).as('delete'); + cy.get(`[data-test=VARIANT_DELETE_BUTTON_${variantName}]`).click(); cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click(); cy.wait('@delete'); diff --git a/frontend/cypress/plugins/index.js b/frontend/cypress/plugins/index.ts similarity index 100% rename from frontend/cypress/plugins/index.js rename to frontend/cypress/plugins/index.ts diff --git a/frontend/cypress/support/commands.js b/frontend/cypress/support/commands.ts similarity index 100% rename from frontend/cypress/support/commands.js rename to frontend/cypress/support/commands.ts diff --git a/frontend/cypress/support/index.js b/frontend/cypress/support/index.ts similarity index 100% rename from frontend/cypress/support/index.js rename to frontend/cypress/support/index.ts diff --git a/frontend/package.json b/frontend/package.json index 3c8ff8f589..5ff2e5e64c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "unleash-frontend", "description": "unleash your features", - "version": "4.8.0-beta.10", + "version": "4.8.0", "keywords": [ "unleash", "feature toggle", @@ -29,7 +29,6 @@ "start": "react-scripts start", "start:heroku": "UNLEASH_API=https://unleash.herokuapp.com yarn run start", "start:ea": "UNLEASH_API=https://unleash4.herokuapp.com yarn run start", - "start:demo": "UNLEASH_API=http://unleash.herokuapp.com yarn run start", "test": "react-scripts test", "prepare": "yarn run build", "fmt": "prettier src --write --loglevel warn", @@ -42,6 +41,7 @@ "@material-ui/core": "4.12.3", "@material-ui/icons": "4.11.2", "@material-ui/lab": "4.0.0-alpha.60", + "@testing-library/dom": "8.11.3", "@testing-library/jest-dom": "5.16.2", "@testing-library/react": "12.1.3", "@testing-library/user-event": "13.5.0", @@ -49,28 +49,23 @@ "@types/deep-diff": "1.0.1", "@types/jest": "27.4.1", "@types/lodash.clonedeep": "4.5.6", - "@types/node": "14.18.12", + "@types/node": "17.0.18", "@types/react": "17.0.39", "@types/react-dom": "17.0.11", - "@types/react-outside-click-handler": "1.3.1", "@types/react-router-dom": "5.3.3", "@types/react-test-renderer": "17.0.1", "@types/react-timeago": "4.1.3", - "@welldone-software/why-did-you-render": "6.2.3", "chart.js": "3.7.1", "chartjs-adapter-date-fns": "2.0.0", "classnames": "2.3.1", "copy-to-clipboard": "3.3.1", - "craco": "0.0.3", - "css-loader": "6.6.0", - "cypress": "8.7.0", + "cypress": "9.5.1", "date-fns": "2.28.0", "debounce": "1.2.1", "deep-diff": "1.0.2", "fast-json-patch": "3.1.0", "http-proxy-middleware": "2.0.3", "lodash.clonedeep": "4.5.0", - "lodash.flow": "3.5.0", "prettier": "2.5.1", "prop-types": "15.8.1", "react": "17.0.2", @@ -79,15 +74,13 @@ "react-dnd-html5-backend": "14.1.0", "react-dom": "17.0.2", "react-hooks-global-state": "1.0.2", - "react-outside-click-handler": "1.3.0", "react-router-dom": "5.3.0", "react-scripts": "4.0.3", - "react-test-renderer": "16.14.0", + "react-test-renderer": "17.0.2", "react-timeago": "6.2.1", - "sass": "1.49.8", + "sass": "1.49.9", "swr": "1.2.2", - "typescript": "4.5.5", - "web-vitals": "2.1.4" + "typescript": "4.6.2" }, "jest": { "moduleNameMapper": { @@ -117,6 +110,9 @@ "no-restricted-globals": "off", "no-useless-computed-key": "off", "import/no-anonymous-default-export": "off" - } + }, + "ignorePatterns": [ + "cypress" + ] } } diff --git a/frontend/src/component/addons/AddonForm/AddonEvents/AddonEvents.tsx b/frontend/src/component/addons/AddonForm/AddonEvents/AddonEvents.tsx index 3beb70432c..8e70661277 100644 --- a/frontend/src/component/addons/AddonForm/AddonEvents/AddonEvents.tsx +++ b/frontend/src/component/addons/AddonForm/AddonEvents/AddonEvents.tsx @@ -7,7 +7,9 @@ import { IAddonProvider } from '../../../../interfaces/addons'; interface IAddonProps { provider: IAddonProvider; checkedEvents: string[]; - setEventValue: (name: string) => void; + setEventValue: ( + name: string + ) => (event: React.ChangeEvent) => void; error: Record; } diff --git a/frontend/src/component/addons/AddonForm/AddonForm.jsx b/frontend/src/component/addons/AddonForm/AddonForm.jsx index d47e76c6d1..889d20c51f 100644 --- a/frontend/src/component/addons/AddonForm/AddonForm.jsx +++ b/frontend/src/component/addons/AddonForm/AddonForm.jsx @@ -1,7 +1,7 @@ -import React, { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; -import { TextField, FormControlLabel, Switch } from '@material-ui/core'; -import { FormButtons, styles as commonStyles } from '../../common'; +import { TextField, FormControlLabel, Switch, Button } from '@material-ui/core'; +import { styles as commonStyles } from '../../common'; import { trim } from '../../common/util'; import { AddonParameters } from './AddonParameters/AddonParameters'; import { AddonEvents } from './AddonEvents/AddonEvents'; @@ -79,7 +79,7 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { setErrors({ ...errors, events: undefined }); }; - const handleCancel = () => { + const onCancel = () => { history.goBack(); }; @@ -203,10 +203,12 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { />
- + +
diff --git a/frontend/src/component/addons/AddonList/AddonList.tsx b/frontend/src/component/addons/AddonList/AddonList.tsx index 8d96e5c3d0..a5a43b0812 100644 --- a/frontend/src/component/addons/AddonList/AddonList.tsx +++ b/frontend/src/component/addons/AddonList/AddonList.tsx @@ -1,4 +1,4 @@ -import { ReactElement } from 'react'; +import React, { ReactElement } from 'react'; import { ConfiguredAddons } from './ConfiguredAddons/ConfiguredAddons'; import { AvailableAddons } from './AvailableAddons/AvailableAddons'; import { Avatar } from '@material-ui/core'; @@ -12,7 +12,7 @@ import dataDogIcon from '../../../assets/icons/datadog.svg'; import { formatAssetPath } from '../../../utils/format-path'; import useAddons from '../../../hooks/api/getters/useAddons/useAddons'; -const style = { +const style: React.CSSProperties = { width: '40px', height: '40px', marginRight: '16px', diff --git a/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.tsx b/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.tsx index 9632b0e9e5..b9d470291a 100644 --- a/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.tsx +++ b/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.tsx @@ -44,7 +44,6 @@ export const AvailableAddons = ({ onClick={() => history.push(`/addons/create/${provider.name}`) } - tooltip={`Configure ${provider.name} Addon`} > Configure diff --git a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx index 78ef5188cc..7ddbbce1e8 100644 --- a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx +++ b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx @@ -21,6 +21,7 @@ import AccessContext from '../../../../contexts/AccessContext'; import { IAddon } from '../../../../interfaces/addons'; import PermissionIconButton from '../../../common/PermissionIconButton/PermissionIconButton'; import Dialogue from '../../../common/Dialogue'; +import { formatUnknownError } from '../../../../utils/format-unknown-error'; interface IConfigureAddonsProps { getAddonIcon: (name: string) => ReactElement; @@ -59,8 +60,8 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => { title: 'Success', text: 'Addon state switched successfully', }); - } catch (e: any) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; @@ -122,7 +123,6 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => { { history.push(`/addons/edit/${addon.id}`); }} @@ -131,7 +131,6 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => { { setDeletedAddon(addon); setShowDelete(true); diff --git a/frontend/src/component/admin/api-token/ApiTokenList/ApiTokenList.tsx b/frontend/src/component/admin/api-token/ApiTokenList/ApiTokenList.tsx index 7e97a831b1..1961237f53 100644 --- a/frontend/src/component/admin/api-token/ApiTokenList/ApiTokenList.tsx +++ b/frontend/src/component/admin/api-token/ApiTokenList/ApiTokenList.tsx @@ -24,7 +24,6 @@ import { DELETE_API_TOKEN, } from '../../../providers/AccessProvider/permissions'; import { useStyles } from './ApiTokenList.styles'; -import { formatDateWithLocale } from '../../../common/util'; import Secret from './secret'; import { Delete, FileCopy } from '@material-ui/icons'; import Dialogue from '../../../common/Dialogue'; @@ -32,6 +31,7 @@ import { CREATE_API_TOKEN_BUTTON } from '../../../../testIds'; import { Alert } from '@material-ui/lab'; import copy from 'copy-to-clipboard'; import { useLocationSettings } from '../../../../hooks/useLocationSettings'; +import { formatDateYMD } from '../../../../utils/format-date'; interface IApiToken { createdAt: Date; @@ -146,7 +146,7 @@ export const ApiTokenList = () => { align="left" className={styles.hideSM} > - {formatDateWithLocale( + {formatDateYMD( item.createdAt, locationSettings.locale )} @@ -249,7 +249,7 @@ export const ApiTokenList = () => { } data-test={CREATE_API_TOKEN_BUTTON} > - Create API token + New API token } /> diff --git a/frontend/src/component/admin/api-token/CreateApiToken/CreateApiToken.tsx b/frontend/src/component/admin/api-token/CreateApiToken/CreateApiToken.tsx index 85f2ff0e0e..7fdaf53e02 100644 --- a/frontend/src/component/admin/api-token/CreateApiToken/CreateApiToken.tsx +++ b/frontend/src/component/admin/api-token/CreateApiToken/CreateApiToken.tsx @@ -1,15 +1,16 @@ -import FormTemplate from '../../../common/FormTemplate/FormTemplate'; +import FormTemplate from 'component/common/FormTemplate/FormTemplate'; import { useHistory } from 'react-router-dom'; import ApiTokenForm from '../ApiTokenForm/ApiTokenForm'; +import { CreateButton } from 'component/common/CreateButton/CreateButton'; +import useApiTokensApi from 'hooks/api/actions/useApiTokensApi/useApiTokensApi'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import useToast from 'hooks/useToast'; import useApiTokenForm from '../hooks/useApiTokenForm'; -import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig'; -import useToast from '../../../../hooks/useToast'; -import useApiTokensApi from '../../../../hooks/api/actions/useApiTokensApi/useApiTokensApi'; -import PermissionButton from '../../../common/PermissionButton/PermissionButton'; -import { ADMIN } from '../../../providers/AccessProvider/permissions'; +import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { ConfirmToken } from '../ConfirmToken/ConfirmToken'; import { useState } from 'react'; import { scrollToTop } from '../../../common/util'; +import { formatUnknownError } from '../../../../utils/format-unknown-error'; export const CreateApiToken = () => { const { setToastApiError } = useToast(); @@ -49,8 +50,8 @@ export const CreateApiToken = () => { setToken(api.secret); setShowConfirm(true); }); - } catch (e: any) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; @@ -95,9 +96,7 @@ export const CreateApiToken = () => { mode="Create" clearErrors={clearErrors} > - - Create token - + { title: 'Settings stored', type: 'success', }); - } catch (err) { - setToastApiError(formatUnknownError(err)); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; diff --git a/frontend/src/component/admin/auth/OidcAuth/OidcAuth.tsx b/frontend/src/component/admin/auth/OidcAuth/OidcAuth.tsx index c32b10da00..9fa252e6d9 100644 --- a/frontend/src/component/admin/auth/OidcAuth/OidcAuth.tsx +++ b/frontend/src/component/admin/auth/OidcAuth/OidcAuth.tsx @@ -79,8 +79,8 @@ export const OidcAuth = () => { title: 'Settings stored', type: 'success', }); - } catch (err) { - setToastApiError(formatUnknownError(err)); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; diff --git a/frontend/src/component/admin/auth/PasswordAuth/PasswordAuth.tsx b/frontend/src/component/admin/auth/PasswordAuth/PasswordAuth.tsx index 068563b2e6..cef119c529 100644 --- a/frontend/src/component/admin/auth/PasswordAuth/PasswordAuth.tsx +++ b/frontend/src/component/admin/auth/PasswordAuth/PasswordAuth.tsx @@ -51,8 +51,8 @@ export const PasswordAuth = () => { type: 'success', show: true, }); - } catch (err) { - setToastApiError(formatUnknownError(err)); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); setDisablePasswordAuth(config.disabled); } }; diff --git a/frontend/src/component/admin/auth/SamlAuth/SamlAuth.tsx b/frontend/src/component/admin/auth/SamlAuth/SamlAuth.tsx index 26f8c7a293..5120ddaa57 100644 --- a/frontend/src/component/admin/auth/SamlAuth/SamlAuth.tsx +++ b/frontend/src/component/admin/auth/SamlAuth/SamlAuth.tsx @@ -75,8 +75,8 @@ export const SamlAuth = () => { title: 'Settings stored', type: 'success', }); - } catch (err) { - setToastApiError(formatUnknownError(err)); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; diff --git a/frontend/src/component/admin/invoice/InvoiceList.tsx b/frontend/src/component/admin/invoice/InvoiceList.tsx index adf6d589b9..8e766ef5ea 100644 --- a/frontend/src/component/admin/invoice/InvoiceList.tsx +++ b/frontend/src/component/admin/invoice/InvoiceList.tsx @@ -8,7 +8,6 @@ import { Button, } from '@material-ui/core'; import OpenInNew from '@material-ui/icons/OpenInNew'; -import { formatDateWithLocale } from '../../common/util'; import PageContent from '../../common/PageContent'; import HeaderTitle from '../../common/HeaderTitle'; import ConditionallyRender from '../../common/ConditionallyRender'; @@ -16,6 +15,7 @@ import { formatApiPath } from '../../../utils/format-path'; import useInvoices from '../../../hooks/api/getters/useInvoices/useInvoices'; import { IInvoice } from '../../../interfaces/invoice'; import { useLocationSettings } from '../../../hooks/useLocationSettings'; +import { formatDateYMD } from '../../../utils/format-date'; const PORTAL_URL = formatApiPath('api/admin/invoices/portal'); @@ -87,7 +87,7 @@ const InvoiceList = () => { style={{ textAlign: 'left' }} > {item.dueDate && - formatDateWithLocale( + formatDateYMD( item.dueDate, locationSettings.locale )} diff --git a/frontend/src/component/admin/project-roles/CreateProjectRole/CreateProjectRole.tsx b/frontend/src/component/admin/project-roles/CreateProjectRole/CreateProjectRole.tsx index 78048e0ab0..a8df1952d2 100644 --- a/frontend/src/component/admin/project-roles/CreateProjectRole/CreateProjectRole.tsx +++ b/frontend/src/component/admin/project-roles/CreateProjectRole/CreateProjectRole.tsx @@ -1,12 +1,13 @@ -import FormTemplate from '../../../common/FormTemplate/FormTemplate'; -import useProjectRolesApi from '../../../../hooks/api/actions/useProjectRolesApi/useProjectRolesApi'; +import FormTemplate from 'component/common/FormTemplate/FormTemplate'; +import useProjectRolesApi from 'hooks/api/actions/useProjectRolesApi/useProjectRolesApi'; import { useHistory } from 'react-router-dom'; import ProjectRoleForm from '../ProjectRoleForm/ProjectRoleForm'; import useProjectRoleForm from '../hooks/useProjectRoleForm'; -import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig'; -import useToast from '../../../../hooks/useToast'; -import PermissionButton from '../../../common/PermissionButton/PermissionButton'; -import { ADMIN } from '../../../providers/AccessProvider/permissions'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import useToast from 'hooks/useToast'; +import { CreateButton } from 'component/common/CreateButton/CreateButton'; +import { ADMIN } from 'component/providers/AccessProvider/permissions'; +import { formatUnknownError } from 'utils/format-unknown-error'; const CreateProjectRole = () => { const { setToastData, setToastApiError } = useToast(); @@ -49,8 +50,8 @@ const CreateProjectRole = () => { confetti: true, type: 'success', }); - } catch (e: any) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } } }; @@ -95,9 +96,7 @@ const CreateProjectRole = () => { validateNameUniqueness={validateNameUniqueness} getRoleKey={getRoleKey} > - - Create role - + ); diff --git a/frontend/src/component/admin/project-roles/EditProjectRole/EditProjectRole.tsx b/frontend/src/component/admin/project-roles/EditProjectRole/EditProjectRole.tsx index eb79af949c..5f30eda6bb 100644 --- a/frontend/src/component/admin/project-roles/EditProjectRole/EditProjectRole.tsx +++ b/frontend/src/component/admin/project-roles/EditProjectRole/EditProjectRole.tsx @@ -1,18 +1,16 @@ import { useEffect } from 'react'; - -import FormTemplate from '../../../common/FormTemplate/FormTemplate'; - -import useProjectRolesApi from '../../../../hooks/api/actions/useProjectRolesApi/useProjectRolesApi'; - -import { useHistory, useParams } from 'react-router-dom'; -import ProjectRoleForm from '../ProjectRoleForm/ProjectRoleForm'; +import FormTemplate from 'component/common/FormTemplate/FormTemplate'; +import { UpdateButton } from 'component/common/UpdateButton/UpdateButton'; +import { ADMIN } from 'component/providers/AccessProvider/permissions'; +import useProjectRolesApi from 'hooks/api/actions/useProjectRolesApi/useProjectRolesApi'; +import useProjectRole from 'hooks/api/getters/useProjectRole/useProjectRole'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import useToast from 'hooks/useToast'; +import { IPermission } from 'interfaces/user'; +import { useParams, useHistory } from 'react-router-dom'; import useProjectRoleForm from '../hooks/useProjectRoleForm'; -import useProjectRole from '../../../../hooks/api/getters/useProjectRole/useProjectRole'; -import { IPermission } from '../../../../interfaces/project'; -import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig'; -import useToast from '../../../../hooks/useToast'; -import PermissionButton from '../../../common/PermissionButton/PermissionButton'; -import { ADMIN } from '../../../providers/AccessProvider/permissions'; +import ProjectRoleForm from '../ProjectRoleForm/ProjectRoleForm'; +import { formatUnknownError } from 'utils/format-unknown-error'; const EditProjectRole = () => { const { uiConfig } = useUiConfig(); @@ -88,8 +86,8 @@ const EditProjectRole = () => { text: 'Your role changes will automatically be applied to the users with this role.', confetti: true, }); - } catch (e: any) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } } }; @@ -124,9 +122,7 @@ to resources within a project" clearErrors={clearErrors} getRoleKey={getRoleKey} > - - Edit role - + ); diff --git a/frontend/src/component/admin/project-roles/ProjectRoleForm/ProjectRoleForm.tsx b/frontend/src/component/admin/project-roles/ProjectRoleForm/ProjectRoleForm.tsx index 4b0676c471..3578deb444 100644 --- a/frontend/src/component/admin/project-roles/ProjectRoleForm/ProjectRoleForm.tsx +++ b/frontend/src/component/admin/project-roles/ProjectRoleForm/ProjectRoleForm.tsx @@ -1,16 +1,16 @@ import Input from '../../../common/Input/Input'; import EnvironmentPermissionAccordion from './EnvironmentPermissionAccordion/EnvironmentPermissionAccordion'; import { + Button, Checkbox, FormControlLabel, TextField, - Button, } from '@material-ui/core'; import useProjectRolePermissions from '../../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; import { useStyles } from './ProjectRoleForm.styles'; import ConditionallyRender from '../../../common/ConditionallyRender'; -import React from 'react'; +import React, { ReactNode } from 'react'; import { IPermission } from '../../../../interfaces/project'; import { ICheckedPermission, @@ -33,6 +33,7 @@ interface IProjectRoleForm { clearErrors: () => void; validateNameUniqueness?: () => void; getRoleKey: (permission: { id: number; environment?: string }) => string; + children: ReactNode; } const ProjectRoleForm: React.FC = ({ diff --git a/frontend/src/component/admin/project-roles/ProjectRoles/ProjectRoleDeleteConfirm/ProjectRoleDeleteConfirm.styles.ts b/frontend/src/component/admin/project-roles/ProjectRoles/ProjectRoleDeleteConfirm/ProjectRoleDeleteConfirm.styles.ts index a2e0213f3a..572dbc27f0 100644 --- a/frontend/src/component/admin/project-roles/ProjectRoles/ProjectRoleDeleteConfirm/ProjectRoleDeleteConfirm.styles.ts +++ b/frontend/src/component/admin/project-roles/ProjectRoles/ProjectRoleDeleteConfirm/ProjectRoleDeleteConfirm.styles.ts @@ -1,4 +1,4 @@ -import { makeStyles } from '@material-ui/styles'; +import { makeStyles } from '@material-ui/core/styles'; export const useStyles = makeStyles(theme => ({ deleteParagraph: { diff --git a/frontend/src/component/admin/project-roles/ProjectRoles/ProjectRoleList/ProjectRoleList.tsx b/frontend/src/component/admin/project-roles/ProjectRoles/ProjectRoleList/ProjectRoleList.tsx index 91632c0be3..d29839ab83 100644 --- a/frontend/src/component/admin/project-roles/ProjectRoles/ProjectRoleList/ProjectRoleList.tsx +++ b/frontend/src/component/admin/project-roles/ProjectRoles/ProjectRoleList/ProjectRoleList.tsx @@ -16,6 +16,7 @@ import IRole, { IProjectRole } from '../../../../../interfaces/role'; import useProjectRolesApi from '../../../../../hooks/api/actions/useProjectRolesApi/useProjectRolesApi'; import useToast from '../../../../../hooks/useToast'; import ProjectRoleDeleteConfirm from '../ProjectRoleDeleteConfirm/ProjectRoleDeleteConfirm'; +import { formatUnknownError } from '../../../../../utils/format-unknown-error'; const ROOTROLE = 'root'; @@ -44,8 +45,8 @@ const ProjectRoleList = () => { title: 'Successfully deleted role', text: 'Your role is now deleted', }); - } catch (e) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } setDelDialog(false); setConfirmName(''); diff --git a/frontend/src/component/admin/project-roles/ProjectRoles/ProjectRoleList/ProjectRoleListItem/ProjectRoleListItem.tsx b/frontend/src/component/admin/project-roles/ProjectRoles/ProjectRoleList/ProjectRoleListItem/ProjectRoleListItem.tsx index fc32da759d..1de114a65f 100644 --- a/frontend/src/component/admin/project-roles/ProjectRoles/ProjectRoleList/ProjectRoleListItem/ProjectRoleListItem.tsx +++ b/frontend/src/component/admin/project-roles/ProjectRoles/ProjectRoleList/ProjectRoleListItem/ProjectRoleListItem.tsx @@ -1,11 +1,12 @@ import { useStyles } from './ProjectRoleListItem.styles'; -import { TableRow, TableCell, Typography } from '@material-ui/core'; -import { Edit, Delete } from '@material-ui/icons'; +import { TableCell, TableRow, Typography } from '@material-ui/core'; +import { Delete, Edit } from '@material-ui/icons'; import { ADMIN } from '../../../../../providers/AccessProvider/permissions'; import SupervisedUserCircleIcon from '@material-ui/icons/SupervisedUserCircle'; import PermissionIconButton from '../../../../../common/PermissionIconButton/PermissionIconButton'; import { IProjectRole } from '../../../../../../interfaces/role'; import { useHistory } from 'react-router-dom'; +import React from 'react'; interface IRoleListItemProps { id: number; @@ -50,7 +51,6 @@ const RoleListItem = ({ { history.push(`/admin/roles/${id}/edit`); @@ -62,7 +62,6 @@ const RoleListItem = ({ { setCurrentRole({ id, name, description }); diff --git a/frontend/src/component/admin/project-roles/ProjectRoles/ProjectRoles.styles.ts b/frontend/src/component/admin/project-roles/ProjectRoles/ProjectRoles.styles.ts index 0e6eea556c..7c4ce07f86 100644 --- a/frontend/src/component/admin/project-roles/ProjectRoles/ProjectRoles.styles.ts +++ b/frontend/src/component/admin/project-roles/ProjectRoles/ProjectRoles.styles.ts @@ -1,4 +1,4 @@ -import { makeStyles } from '@material-ui/styles'; +import { makeStyles } from '@material-ui/core/styles'; export const useStyles = makeStyles(theme => ({ rolesListBody: { diff --git a/frontend/src/component/admin/users/ConfirmUserAdded/ConfirmUserEmail/ConfirmUserEmail.styles.ts b/frontend/src/component/admin/users/ConfirmUserAdded/ConfirmUserEmail/ConfirmUserEmail.styles.ts index f8448ba756..9eb137338b 100644 --- a/frontend/src/component/admin/users/ConfirmUserAdded/ConfirmUserEmail/ConfirmUserEmail.styles.ts +++ b/frontend/src/component/admin/users/ConfirmUserAdded/ConfirmUserEmail/ConfirmUserEmail.styles.ts @@ -1,4 +1,4 @@ -import { makeStyles } from '@material-ui/styles'; +import { makeStyles } from '@material-ui/core/styles'; export const useStyles = makeStyles({ iconContainer: { diff --git a/frontend/src/component/admin/users/CreateUser/CreateUser.tsx b/frontend/src/component/admin/users/CreateUser/CreateUser.tsx index f480eb7535..d6c4b63c79 100644 --- a/frontend/src/component/admin/users/CreateUser/CreateUser.tsx +++ b/frontend/src/component/admin/users/CreateUser/CreateUser.tsx @@ -1,15 +1,16 @@ -import FormTemplate from '../../../common/FormTemplate/FormTemplate'; +import FormTemplate from 'component/common/FormTemplate/FormTemplate'; import { useHistory } from 'react-router-dom'; import UserForm from '../UserForm/UserForm'; -import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig'; -import useToast from '../../../../hooks/useToast'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import useAdminUsersApi from 'hooks/api/actions/useAdminUsersApi/useAdminUsersApi'; +import useToast from 'hooks/useToast'; import useAddUserForm from '../hooks/useAddUserForm'; -import useAdminUsersApi from '../../../../hooks/api/actions/useAdminUsersApi/useAdminUsersApi'; import ConfirmUserAdded from '../ConfirmUserAdded/ConfirmUserAdded'; import { useState } from 'react'; import { scrollToTop } from '../../../common/util'; -import PermissionButton from '../../../common/PermissionButton/PermissionButton'; -import { ADMIN } from '../../../providers/AccessProvider/permissions'; +import { CreateButton } from 'component/common/CreateButton/CreateButton'; +import { ADMIN } from 'component/providers/AccessProvider/permissions'; +import { formatUnknownError } from '../../../../utils/format-unknown-error'; const CreateUser = () => { const { setToastApiError } = useToast(); @@ -51,8 +52,8 @@ const CreateUser = () => { setInviteLink(user.inviteLink); setShowConfirm(true); }); - } catch (e: any) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } } }; @@ -97,9 +98,7 @@ const CreateUser = () => { setRootRole={setRootRole} clearErrors={clearErrors} > - - Create user - + { useEffect(() => { @@ -60,8 +61,8 @@ const EditUser = () => { title: 'User information updated', type: 'success', }); - } catch (e: any) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } } }; @@ -94,9 +95,7 @@ const EditUser = () => { clearErrors={clearErrors} mode={EDIT} > - - Edit user - + ); diff --git a/frontend/src/component/admin/users/UserAdmin.styles.ts b/frontend/src/component/admin/users/UserAdmin.styles.ts index 89d94b271d..8d85cf5c4f 100644 --- a/frontend/src/component/admin/users/UserAdmin.styles.ts +++ b/frontend/src/component/admin/users/UserAdmin.styles.ts @@ -1,4 +1,4 @@ -import { makeStyles } from '@material-ui/styles'; +import { makeStyles } from '@material-ui/core/styles'; export const useStyles = makeStyles(theme => ({ userListBody: { diff --git a/frontend/src/component/admin/users/UsersAdmin.tsx b/frontend/src/component/admin/users/UsersAdmin.tsx index 0b9f2c7cf6..bb16b611d9 100644 --- a/frontend/src/component/admin/users/UsersAdmin.tsx +++ b/frontend/src/component/admin/users/UsersAdmin.tsx @@ -35,7 +35,7 @@ const UsersAdmin = () => { history.push('/admin/create-user') } > - Add new user + New user } elseShow={ diff --git a/frontend/src/component/admin/users/UsersList/ChangePassword/ChangePassword.tsx b/frontend/src/component/admin/users/UsersList/ChangePassword/ChangePassword.tsx index bd0cb82fc5..d52b58d973 100644 --- a/frontend/src/component/admin/users/UsersList/ChangePassword/ChangePassword.tsx +++ b/frontend/src/component/admin/users/UsersList/ChangePassword/ChangePassword.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import classnames from 'classnames'; -import { TextField, Typography, Avatar } from '@material-ui/core'; +import { Avatar, TextField, Typography } from '@material-ui/core'; import { trim } from '../../../../common/util'; import { modalStyles } from '../../util'; import Dialogue from '../../../../common/Dialogue/Dialogue'; @@ -12,10 +12,10 @@ import { Alert } from '@material-ui/lab'; import { IUser } from '../../../../../interfaces/user'; interface IChangePasswordProps { - showDialog: () => void; + showDialog: boolean; closeDialog: () => void; - changePassword: () => void; - user: IUser; + changePassword: (user: IUser, password: string) => Promise; + user: Partial; } const ChangePassword = ({ @@ -25,7 +25,7 @@ const ChangePassword = ({ user = {}, }: IChangePasswordProps) => { const [data, setData] = useState({}); - const [error, setError] = useState({}); + const [error, setError] = useState>({}); const [validPassword, setValidPassword] = useState(false); const commonStyles = useCommonStyles(); @@ -88,7 +88,7 @@ const ChangePassword = ({ )} > {error.general}} /> diff --git a/frontend/src/component/admin/users/UsersList/DeleteUser/DeleteUser.tsx b/frontend/src/component/admin/users/UsersList/DeleteUser/DeleteUser.tsx index 03d1e1799f..c20481362c 100644 --- a/frontend/src/component/admin/users/UsersList/DeleteUser/DeleteUser.tsx +++ b/frontend/src/component/admin/users/UsersList/DeleteUser/DeleteUser.tsx @@ -9,12 +9,12 @@ import { useCommonStyles } from '../../../../../common.styles'; import { IUser } from '../../../../../interfaces/user'; interface IDeleteUserProps { - showDialog: () => void; + showDialog: boolean; closeDialog: () => void; user: IUser; userLoading: boolean; removeUser: () => void; - userApiErrors: Object; + userApiErrors: Record; } const DeleteUser = ({ @@ -33,13 +33,13 @@ const DeleteUser = ({ open={showDialog} title="Really delete user?" onClose={closeDialog} - onClick={() => removeUser(user)} + onClick={removeUser} primaryButtonText="Delete user" secondaryButtonText="Cancel" >
string; - openUpdateDialog: (user: IUser) => (e: SyntheticEvent) => void; openPwDialog: (user: IUser) => (e: SyntheticEvent) => void; openDelDialog: (user: IUser) => (e: SyntheticEvent) => void; locationSettings: ILocationSettings; @@ -30,7 +29,6 @@ const UserListItem = ({ renderRole, openDelDialog, openPwDialog, - openUpdateDialog, locationSettings, }: IUserListItemProps) => { const { hasAccess } = useContext(AccessContext); @@ -51,10 +49,7 @@ const UserListItem = ({ - {formatDateWithLocale( - user.createdAt, - locationSettings.locale - )} + {formatDateYMD(user.createdAt, locationSettings.locale)} diff --git a/frontend/src/component/admin/users/UsersList/UsersList.tsx b/frontend/src/component/admin/users/UsersList/UsersList.tsx index ca4defe92d..9d0d6208ba 100644 --- a/frontend/src/component/admin/users/UsersList/UsersList.tsx +++ b/frontend/src/component/admin/users/UsersList/UsersList.tsx @@ -24,6 +24,7 @@ import { IUser } from '../../../../interfaces/user'; import IRole from '../../../../interfaces/role'; import useToast from '../../../../hooks/useToast'; import { useLocationSettings } from '../../../../hooks/useLocationSettings'; +import { formatUnknownError } from '../../../../utils/format-unknown-error'; const UsersList = () => { const { users, roles, refetch, loading } = useUsers(); @@ -79,8 +80,8 @@ const UsersList = () => { }); refetch(); closeDelDialog(); - } catch (e: any) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; @@ -172,7 +173,7 @@ const UsersList = () => { { const history = useHistory(); @@ -42,10 +43,9 @@ export const ApplicationEdit = () => { setShowDialog(!showDialog); }; - const formatDate = (v: string) => - formatDateWithLocale(v, locationSettings.locale); + const formatDate = (v: string) => formatDateYMD(v, locationSettings.locale); - const onDeleteApplication = async (evt: Event) => { + const onDeleteApplication = async (evt: React.SyntheticEvent) => { evt.preventDefault(); try { await deleteApplication(appName); @@ -55,8 +55,8 @@ export const ApplicationEdit = () => { type: 'success', }); history.push('/applications'); - } catch (e: any) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; diff --git a/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx b/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx index a3f98978be..5885f0785c 100644 --- a/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx +++ b/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx @@ -1,5 +1,5 @@ import { ChangeEvent, useState } from 'react'; -import { TextField, Grid } from '@material-ui/core'; +import { Grid, TextField } from '@material-ui/core'; import { useCommonStyles } from '../../../common.styles'; import icons from '../icon-names'; import GeneralSelect from '../../common/GeneralSelect/GeneralSelect'; @@ -7,6 +7,7 @@ import useApplicationsApi from '../../../hooks/api/actions/useApplicationsApi/us import useToast from '../../../hooks/useToast'; import { IApplication } from '../../../interfaces/application'; import useApplication from '../../../hooks/api/getters/useApplication/useApplication'; +import { formatUnknownError } from '../../../utils/format-unknown-error'; interface IApplicationUpdateProps { application: IApplication; @@ -35,8 +36,8 @@ export const ApplicationUpdate = ({ application }: IApplicationUpdateProps) => { title: 'Updated Successfully', text: `${field} successfully updated`, }); - } catch (e: any) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; diff --git a/frontend/src/component/application/ApplicationView/ApplicationView.tsx b/frontend/src/component/application/ApplicationView/ApplicationView.tsx index ee17b0875e..e720fe2471 100644 --- a/frontend/src/component/application/ApplicationView/ApplicationView.tsx +++ b/frontend/src/component/application/ApplicationView/ApplicationView.tsx @@ -4,16 +4,16 @@ import { Grid, List, ListItem, - ListItemText, ListItemAvatar, + ListItemText, Typography, } from '@material-ui/core'; import { - Report, Extension, - Timeline, FlagRounded, + Report, SvgIconComponent, + Timeline, } from '@material-ui/icons'; import { CREATE_FEATURE, @@ -23,13 +23,16 @@ import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyR import { getTogglePath } from '../../../utils/route-path-helpers'; import useApplication from '../../../hooks/api/getters/useApplication/useApplication'; import AccessContext from '../../../contexts/AccessContext'; -import { formatFullDateTimeWithLocale } from '../../common/util'; +import { formatDateYMDHMS } from '../../../utils/format-date'; +import { useLocationSettings } from '../../../hooks/useLocationSettings'; export const ApplicationView = () => { const { hasAccess } = useContext(AccessContext); const { name } = useParams<{ name: string }>(); const { application } = useApplication(name); + const { locationSettings } = useLocationSettings(); const { instances, strategies, seenToggles } = application; + const notFoundListItem = ({ createUrl, name, @@ -114,10 +117,9 @@ export const ApplicationView = () => { createUrl: `/projects/default/create-toggle?name=${name}`, name, permission: CREATE_FEATURE, - i, })} elseShow={foundListItem({ - viewUrl: getTogglePath(project, name, true), + viewUrl: getTogglePath(project, name), name, description, Icon: FlagRounded, @@ -195,8 +197,9 @@ export const ApplicationView = () => { {clientIp} last seen at{' '} - {formatFullDateTimeWithLocale( - lastSeen + {formatDateYMDHMS( + lastSeen, + locationSettings.locale )} diff --git a/frontend/src/component/common/AnimateOnMount/AnimateOnMount.tsx b/frontend/src/component/common/AnimateOnMount/AnimateOnMount.tsx index fcd183c050..3b759af889 100644 --- a/frontend/src/component/common/AnimateOnMount/AnimateOnMount.tsx +++ b/frontend/src/component/common/AnimateOnMount/AnimateOnMount.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useRef, FC } from 'react'; +import React, { useEffect, useState, useRef, FC } from 'react'; import ConditionallyRender from '../ConditionallyRender'; interface IAnimateOnMountProps { @@ -7,7 +7,7 @@ interface IAnimateOnMountProps { start: string; leave: string; container?: string; - style?: Object; + style?: React.CSSProperties; } const AnimateOnMount: FC = ({ diff --git a/frontend/src/component/common/ApiError/ApiError.tsx b/frontend/src/component/common/ApiError/ApiError.tsx index e68d410cae..3d5ae22d00 100644 --- a/frontend/src/component/common/ApiError/ApiError.tsx +++ b/frontend/src/component/common/ApiError/ApiError.tsx @@ -1,5 +1,6 @@ import { Button } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; +import React from 'react'; interface IApiErrorProps { className?: string; diff --git a/frontend/src/component/common/ConditionallyRender/ConditionallyRender.tsx b/frontend/src/component/common/ConditionallyRender/ConditionallyRender.tsx index 88399c9c29..b80db69b3f 100644 --- a/frontend/src/component/common/ConditionallyRender/ConditionallyRender.tsx +++ b/frontend/src/component/common/ConditionallyRender/ConditionallyRender.tsx @@ -1,9 +1,10 @@ interface IConditionallyRenderProps { condition: boolean; - show: JSX.Element | RenderFunc; - elseShow?: JSX.Element | RenderFunc; + show: TargetElement; + elseShow?: TargetElement; } +type TargetElement = JSX.Element | JSX.Element[] | RenderFunc | null; type RenderFunc = () => JSX.Element; const ConditionallyRender = ({ @@ -23,8 +24,9 @@ const ConditionallyRender = ({ return result; }; - const isFunc = (param: JSX.Element | RenderFunc) => - typeof param === 'function'; + const isFunc = (param: TargetElement): boolean => { + return typeof param === 'function'; + }; if (condition) { if (isFunc(show)) { diff --git a/frontend/src/component/common/Constraint/Constraint.tsx b/frontend/src/component/common/Constraint/Constraint.tsx index 4a89e7bc1c..47092e0373 100644 --- a/frontend/src/component/common/Constraint/Constraint.tsx +++ b/frontend/src/component/common/Constraint/Constraint.tsx @@ -52,7 +52,6 @@ const Constraint = ({
@@ -61,7 +60,6 @@ const Constraint = ({ diff --git a/frontend/src/component/common/CreateButton/CreateButton.tsx b/frontend/src/component/common/CreateButton/CreateButton.tsx new file mode 100644 index 0000000000..05d9306af2 --- /dev/null +++ b/frontend/src/component/common/CreateButton/CreateButton.tsx @@ -0,0 +1,15 @@ +import PermissionButton, { + IPermissionButtonProps, +} from '../PermissionButton/PermissionButton'; + +interface ICreateButtonProps extends IPermissionButtonProps { + name: string; +} + +export const CreateButton = ({ name, ...rest }: ICreateButtonProps) => { + return ( + + Create {name} + + ); +}; diff --git a/frontend/src/component/common/Dialogue/Dialogue.tsx b/frontend/src/component/common/Dialogue/Dialogue.tsx index 80fdacc90f..4a9570bf70 100644 --- a/frontend/src/component/common/Dialogue/Dialogue.tsx +++ b/frontend/src/component/common/Dialogue/Dialogue.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { + Button, Dialog, - DialogTitle, DialogActions, DialogContent, - Button, + DialogTitle, } from '@material-ui/core'; import ConditionallyRender from '../ConditionallyRender/ConditionallyRender'; @@ -15,15 +15,15 @@ interface IDialogue { primaryButtonText?: string; secondaryButtonText?: string; open: boolean; - onClick: (e: any) => void; - onClose: () => void; + onClick: (e: React.SyntheticEvent) => void; + onClose?: (e: React.SyntheticEvent) => void; style?: object; title: string; fullWidth?: boolean; maxWidth?: 'lg' | 'sm' | 'xs' | 'md' | 'xl'; disabledPrimaryButton?: boolean; formId?: string; - permissionButton?: React.ReactNode; + permissionButton?: JSX.Element; } const Dialogue: React.FC = ({ @@ -69,7 +69,7 @@ const Dialogue: React.FC = ({ { label: string; diff --git a/frontend/src/component/common/NoItems/NoItems.tsx b/frontend/src/component/common/NoItems/NoItems.tsx index aac3c653a1..3b9bcbf656 100644 --- a/frontend/src/component/common/NoItems/NoItems.tsx +++ b/frontend/src/component/common/NoItems/NoItems.tsx @@ -1,5 +1,6 @@ import { ReactComponent as NoItemsIcon } from '../../../assets/icons/addfiles.svg'; import { useStyles } from './NoItems.styles'; +import React from 'react'; const NoItems: React.FC = ({ children }) => { const styles = useStyles(); diff --git a/frontend/src/component/common/PaginateUI/PaginateUI.tsx b/frontend/src/component/common/PaginateUI/PaginateUI.tsx index c2636c589e..6b834353a7 100644 --- a/frontend/src/component/common/PaginateUI/PaginateUI.tsx +++ b/frontend/src/component/common/PaginateUI/PaginateUI.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import ConditionallyRender from '../ConditionallyRender'; import classnames from 'classnames'; import { useStyles } from './PaginationUI.styles'; diff --git a/frontend/src/component/common/PasswordField/PasswordField.tsx b/frontend/src/component/common/PasswordField/PasswordField.tsx index 61fa587a21..ec7a636a40 100644 --- a/frontend/src/component/common/PasswordField/PasswordField.tsx +++ b/frontend/src/component/common/PasswordField/PasswordField.tsx @@ -1,6 +1,6 @@ import { IconButton, InputAdornment, TextField } from '@material-ui/core'; import { Visibility, VisibilityOff } from '@material-ui/icons'; -import { useState } from 'react'; +import React, { useState } from 'react'; const PasswordField = ({ ...rest }) => { const [showPassword, setShowPassword] = useState(false); diff --git a/frontend/src/component/common/PermissionButton/PermissionButton.tsx b/frontend/src/component/common/PermissionButton/PermissionButton.tsx index 161a2ee05c..50a61d08ed 100644 --- a/frontend/src/component/common/PermissionButton/PermissionButton.tsx +++ b/frontend/src/component/common/PermissionButton/PermissionButton.tsx @@ -1,10 +1,10 @@ import { Button, Tooltip } from '@material-ui/core'; import { Lock } from '@material-ui/icons'; -import { useContext } from 'react'; -import AccessContext from '../../../contexts/AccessContext'; +import AccessContext from 'contexts/AccessContext'; +import React, { useContext } from 'react'; import ConditionallyRender from '../ConditionallyRender'; -export interface IPermissionIconButtonProps +export interface IPermissionButtonProps extends React.HTMLProps { permission: string | string[]; tooltip?: string; @@ -14,9 +14,9 @@ export interface IPermissionIconButtonProps environmentId?: string; } -const PermissionButton: React.FC = ({ +const PermissionButton: React.FC = ({ permission, - tooltip = 'Click to perform action', + tooltip, onClick, children, disabled, @@ -54,9 +54,9 @@ const PermissionButton: React.FC = ({ access = handleAccess(); - const tooltipText = access - ? tooltip - : "You don't have access to perform this operation"; + const tooltipText = !access + ? "You don't have access to perform this operation" + : ''; return ( diff --git a/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx b/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx index d987e05a94..f4a78c7615 100644 --- a/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx +++ b/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx @@ -1,5 +1,5 @@ import { IconButton, Tooltip } from '@material-ui/core'; -import { useContext } from 'react'; +import React, { useContext } from 'react'; import AccessContext from '../../../contexts/AccessContext'; interface IPermissionIconButtonProps @@ -39,9 +39,9 @@ const PermissionIconButton: React.FC = ({ access = hasAccess(permission); } - const tooltipText = access - ? tooltip || '' - : "You don't have access to perform this operation"; + const tooltipText = !access + ? "You don't have access to perform this operation" + : ''; return ( diff --git a/frontend/src/component/common/PermissionSwitch/PermissionSwitch.tsx b/frontend/src/component/common/PermissionSwitch/PermissionSwitch.tsx index fa6f3d0da2..a131aea118 100644 --- a/frontend/src/component/common/PermissionSwitch/PermissionSwitch.tsx +++ b/frontend/src/component/common/PermissionSwitch/PermissionSwitch.tsx @@ -1,12 +1,11 @@ -import { Switch, Tooltip } from '@material-ui/core'; -import { OverridableComponent } from '@material-ui/core/OverridableComponent'; +import { Switch, Tooltip, SwitchProps } from '@material-ui/core'; import AccessContext from '../../../contexts/AccessContext'; import React, { useContext } from 'react'; -interface IPermissionSwitchProps extends OverridableComponent { +interface IPermissionSwitchProps extends SwitchProps { permission: string; - tooltip: string; - onChange?: (e: any) => void; + tooltip?: string; + onChange?: (e: React.ChangeEvent) => void; disabled?: boolean; projectId?: string; environmentId?: string; @@ -19,7 +18,7 @@ const PermissionSwitch = React.forwardRef< >((props, ref) => { const { permission, - tooltip = '', + tooltip, disabled, projectId, environmentId, @@ -39,9 +38,9 @@ const PermissionSwitch = React.forwardRef< access = hasAccess(permission); } - const tooltipText = access - ? tooltip - : "You don't have access to perform this operation"; + const tooltipText = !access + ? "You don't have access to perform this operation" + : ''; return ( diff --git a/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx b/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx index 0edb2cb94f..abbc1dd614 100644 --- a/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx +++ b/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx @@ -2,16 +2,18 @@ import { useMediaQuery } from '@material-ui/core'; import ConditionallyRender from '../ConditionallyRender'; import PermissionButton from '../PermissionButton/PermissionButton'; import PermissionIconButton from '../PermissionIconButton/PermissionIconButton'; +import React from 'react'; interface IResponsiveButtonProps { Icon: React.ElementType; onClick: () => void; tooltip?: string; disabled?: boolean; - permission?: string; + permission: string; projectId?: string; environmentId?: string; maxWidth: string; + className?: string; } const ResponsiveButton: React.FC = ({ diff --git a/frontend/src/component/common/Splash/Splash.tsx b/frontend/src/component/common/Splash/Splash.tsx index 18990afe60..455270845f 100644 --- a/frontend/src/component/common/Splash/Splash.tsx +++ b/frontend/src/component/common/Splash/Splash.tsx @@ -1,12 +1,11 @@ -import { Fragment } from 'react'; +import React, { Fragment, useState } from 'react'; import { Button, IconButton } from '@material-ui/core'; import { useStyles } from './Splash.styles'; import { + CloseOutlined, FiberManualRecord, FiberManualRecordOutlined, - CloseOutlined, } from '@material-ui/icons'; -import { useState } from 'react'; import ConditionallyRender from '../ConditionallyRender'; import { CLOSE_SPLASH } from '../../../testIds'; diff --git a/frontend/src/component/common/TagSelect/TagSelect.tsx b/frontend/src/component/common/TagSelect/TagSelect.tsx index 469e39f15b..59a572f838 100644 --- a/frontend/src/component/common/TagSelect/TagSelect.tsx +++ b/frontend/src/component/common/TagSelect/TagSelect.tsx @@ -7,7 +7,7 @@ interface ITagSelect extends React.SelectHTMLAttributes { onChange: (val: any) => void; } -const TagSelect = ({ value, types, onChange, ...rest }: ITagSelect) => { +const TagSelect = ({ value, onChange, ...rest }: ITagSelect) => { const { tagTypes } = useTagTypes(); const options = tagTypes.map(tagType => ({ diff --git a/frontend/src/component/common/ToastRenderer/Toast/Toast.tsx b/frontend/src/component/common/ToastRenderer/Toast/Toast.tsx index 0a1d83160e..397c7252d0 100644 --- a/frontend/src/component/common/ToastRenderer/Toast/Toast.tsx +++ b/frontend/src/component/common/ToastRenderer/Toast/Toast.tsx @@ -3,12 +3,12 @@ import classnames from 'classnames'; import { useContext } from 'react'; import { IconButton } from '@material-ui/core'; import CheckMarkBadge from '../../CheckmarkBadge/CheckMarkBadge'; -import UIContext, { IToastData } from '../../../../contexts/UIContext'; +import UIContext from '../../../../contexts/UIContext'; import ConditionallyRender from '../../ConditionallyRender'; import Close from '@material-ui/icons/Close'; +import { IToast } from '../../../../interfaces/toast'; -const Toast = ({ title, text, type, confetti }: IToastData) => { - // @ts-expect-error +const Toast = ({ title, text, type, confetti }: IToast) => { const { setToast } = useContext(UIContext); const styles = useStyles(); @@ -51,7 +51,7 @@ const Toast = ({ title, text, type, confetti }: IToastData) => { }; const hide = () => { - setToast((prev: IToastData) => ({ ...prev, show: false })); + setToast((prev: IToast) => ({ ...prev, show: false })); }; return ( diff --git a/frontend/src/component/common/ToastRenderer/ToastRenderer.tsx b/frontend/src/component/common/ToastRenderer/ToastRenderer.tsx index 924c9dd4a1..5f9d21cbbf 100644 --- a/frontend/src/component/common/ToastRenderer/ToastRenderer.tsx +++ b/frontend/src/component/common/ToastRenderer/ToastRenderer.tsx @@ -1,19 +1,19 @@ import { Portal } from '@material-ui/core'; import { useContext, useEffect } from 'react'; import { useCommonStyles } from '../../../common.styles'; -import UIContext, { IToastData } from '../../../contexts/UIContext'; +import UIContext from '../../../contexts/UIContext'; import { useStyles } from './ToastRenderer.styles'; import AnimateOnMount from '../AnimateOnMount/AnimateOnMount'; import Toast from './Toast/Toast'; +import { IToast } from '../../../interfaces/toast'; const ToastRenderer = () => { - // @ts-expect-error const { toastData, setToast } = useContext(UIContext); const commonStyles = useCommonStyles(); const styles = useStyles(); const hide = () => { - setToast((prev: IToastData) => ({ ...prev, show: false })); + setToast((prev: IToast) => ({ ...prev, show: false })); }; useEffect(() => { @@ -31,7 +31,7 @@ const ToastRenderer = () => { return ( { + return ( + + Save + + ); +}; diff --git a/frontend/src/component/common/__tests__/util-test.jsx b/frontend/src/component/common/__tests__/util-test.jsx deleted file mode 100644 index 7678c294a8..0000000000 --- a/frontend/src/component/common/__tests__/util-test.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import { formatFullDateTimeWithLocale } from '../util'; - -test.skip('formats dates correctly', () => { - expect(formatFullDateTimeWithLocale(1487861809466, 'nb-NO', 'UTC')).toEqual( - '2017-02-23 14:56:49' - ); - expect( - formatFullDateTimeWithLocale(1487861809466, 'nb-NO', 'Europe/Paris') - ).toEqual('2017-02-23 15:56:49'); - expect( - formatFullDateTimeWithLocale(1487861809466, 'nb-NO', 'Europe/Oslo') - ).toEqual('2017-02-23 15:56:49'); - expect( - formatFullDateTimeWithLocale(1487861809466, 'nb-NO', 'Europe/London') - ).toEqual('2017-02-23 14:56:49'); - expect( - formatFullDateTimeWithLocale(1487861809466, 'en-GB', 'Europe/Paris') - ).toEqual('02/23/2017, 3:56:49 PM'); - expect( - formatFullDateTimeWithLocale(1487861809466, 'en-GB', 'Europe/Oslo') - ).toEqual('02/23/2017, 3:56:49 PM'); - expect( - formatFullDateTimeWithLocale(1487861809466, 'en-GB', 'Europe/London') - ).toEqual('02/23/2017, 2:56:49 PM'); - - expect(formatFullDateTimeWithLocale(1487861809466, 'nb-NO')).toEqual( - expect.stringMatching(/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/) - ); - expect(formatFullDateTimeWithLocale(1487861809466, 'en-GB')).toEqual( - expect.stringContaining('02/23/2017') - ); - expect(formatFullDateTimeWithLocale(1487861809466, 'en-US')).toEqual( - expect.stringContaining('02/23/2017') - ); -}); diff --git a/frontend/src/component/common/util.js b/frontend/src/component/common/util.js index 28ee9b27ef..a538005dd6 100644 --- a/frontend/src/component/common/util.js +++ b/frontend/src/component/common/util.js @@ -1,26 +1,6 @@ import { weightTypes } from '../feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/enums'; import differenceInDays from 'date-fns/differenceInDays'; -const dateTimeOptions = { - day: '2-digit', - month: '2-digit', - year: 'numeric', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', -}; - -const dateOptions = { - day: '2-digit', - month: '2-digit', - year: 'numeric', -}; - -const timeOptions = { - hour: '2-digit', - minute: '2-digit', -}; - export const filterByFlags = flags => r => { if (r.flag && !flags[r.flag]) { return false; @@ -32,27 +12,6 @@ export const scrollToTop = () => { window.scrollTo(0, 0); }; -export const formatFullDateTimeWithLocale = (v, locale, tz) => { - if (tz) { - dateTimeOptions.timeZone = tz; - } - return new Date(v).toLocaleString(locale, dateTimeOptions); -}; - -export const formatDateWithLocale = (v, locale, tz) => { - if (tz) { - dateTimeOptions.timeZone = tz; - } - return new Date(v).toLocaleString(locale, dateOptions); -}; - -export const formatTimeWithLocale = (v, locale, tz) => { - if (tz) { - dateTimeOptions.timeZone = tz; - } - return new Date(v).toLocaleString(locale, timeOptions); -}; - export const trim = value => { if (value && value.trim) { return value.trim(); diff --git a/frontend/src/component/context/ContextForm/ContextForm.tsx b/frontend/src/component/context/ContextForm/ContextForm.tsx index bcff63da93..b9ad29089e 100644 --- a/frontend/src/component/context/ContextForm/ContextForm.tsx +++ b/frontend/src/component/context/ContextForm/ContextForm.tsx @@ -1,9 +1,9 @@ -import Input from '../../common/Input/Input'; +import Input from 'component/common/Input/Input'; import { TextField, Button, Switch, Chip, Typography } from '@material-ui/core'; import { useStyles } from './ContextForm.styles'; import React, { useState } from 'react'; import { Add } from '@material-ui/icons'; -import { trim } from '../../common/util'; +import { trim } from 'component/common/util'; interface IContextForm { contextName: string; @@ -15,20 +15,20 @@ interface IContextForm { setStickiness: React.Dispatch>; setLegalValues: React.Dispatch>; handleSubmit: (e: any) => void; - handleCancel: () => void; + onCancel: () => void; errors: { [key: string]: string }; mode: string; clearErrors: () => void; - validateNameUniqueness: () => void; + validateContext?: () => void; setErrors: React.Dispatch>; } const ENTER = 'Enter'; -const ContextForm: React.FC = ({ +export const ContextForm: React.FC = ({ children, handleSubmit, - handleCancel, + onCancel, contextName, contextDesc, legalValues, @@ -39,7 +39,7 @@ const ContextForm: React.FC = ({ setStickiness, errors, mode, - validateNameUniqueness, + validateContext, setErrors, clearErrors, }) => { @@ -108,7 +108,7 @@ const ContextForm: React.FC = ({ error={Boolean(errors.name)} errorText={errors.name} onFocus={() => clearErrors()} - onBlur={validateNameUniqueness} + onBlur={validateContext} autoFocus />

@@ -187,12 +187,10 @@ const ContextForm: React.FC = ({

{children} -
); }; - -export default ContextForm; diff --git a/frontend/src/component/context/ContextList/ContextList.jsx b/frontend/src/component/context/ContextList/ContextList.jsx index c9379b0110..355a450854 100644 --- a/frontend/src/component/context/ContextList/ContextList.jsx +++ b/frontend/src/component/context/ContextList/ContextList.jsx @@ -7,6 +7,7 @@ import { UPDATE_CONTEXT_FIELD, } from '../../providers/AccessProvider/permissions'; import { + Button, IconButton, List, ListItem, @@ -14,7 +15,6 @@ import { ListItemText, Tooltip, useMediaQuery, - Button, } from '@material-ui/core'; import { Add, Album, Delete, Edit } from '@material-ui/icons'; import { useContext, useState } from 'react'; @@ -25,6 +25,7 @@ import AccessContext from '../../../contexts/AccessContext'; import useUnleashContext from '../../../hooks/api/getters/useUnleashContext/useUnleashContext'; import useContextsApi from '../../../hooks/api/actions/useContextsApi/useContextsApi'; import useToast from '../../../hooks/useToast'; +import { formatUnknownError } from '../../../utils/format-unknown-error'; const ContextList = () => { const { hasAccess } = useContext(AccessContext); @@ -46,8 +47,8 @@ const ContextList = () => { title: 'Successfully deleted context', text: 'Your context is now deleted', }); - } catch (e) { - setToastApiError(e.toString()); + } catch (error) { + setToastApiError(formatUnknownError(error)); } setName(undefined); setShowDelDialogue(false); @@ -127,7 +128,7 @@ const ContextList = () => { color="primary" variant="contained" > - Add new context field + New context field } /> diff --git a/frontend/src/component/context/CreateContext/CreateContext.tsx b/frontend/src/component/context/CreateContext/CreateContext.tsx index e1faa2e9f5..eea1495cde 100644 --- a/frontend/src/component/context/CreateContext/CreateContext.tsx +++ b/frontend/src/component/context/CreateContext/CreateContext.tsx @@ -1,15 +1,16 @@ import { useHistory } from 'react-router-dom'; -import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; -import useToast from '../../../hooks/useToast'; -import FormTemplate from '../../common/FormTemplate/FormTemplate'; -import useContextForm from '../hooks/useContextForm'; -import ContextForm from '../ContextForm/ContextForm'; -import PermissionButton from '../../common/PermissionButton/PermissionButton'; -import { CREATE_CONTEXT_FIELD } from '../../providers/AccessProvider/permissions'; -import useContextsApi from '../../../hooks/api/actions/useContextsApi/useContextsApi'; -import useUnleashContext from '../../../hooks/api/getters/useUnleashContext/useUnleashContext'; +import { CreateButton } from 'component/common/CreateButton/CreateButton'; +import FormTemplate from 'component/common/FormTemplate/FormTemplate'; +import { useContextForm } from '../hooks/useContextForm'; +import { ContextForm } from '../ContextForm/ContextForm'; +import { CREATE_CONTEXT_FIELD } from 'component/providers/AccessProvider/permissions'; +import useContextsApi from 'hooks/api/actions/useContextsApi/useContextsApi'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext'; +import useToast from 'hooks/useToast'; +import { formatUnknownError } from 'utils/format-unknown-error'; -const CreateContext = () => { +export const CreateContext = () => { const { setToastData, setToastApiError } = useToast(); const { uiConfig } = useUiConfig(); const history = useHistory(); @@ -23,8 +24,7 @@ const CreateContext = () => { setLegalValues, setStickiness, getContextPayload, - validateNameUniqueness, - validateName, + validateContext, clearErrors, setErrors, errors, @@ -34,7 +34,8 @@ const CreateContext = () => { const handleSubmit = async (e: Event) => { e.preventDefault(); - const validName = validateName(); + const validName = await validateContext(); + if (validName) { const payload = getContextPayload(); try { @@ -46,8 +47,8 @@ const CreateContext = () => { confetti: true, type: 'success', }); - } catch (e: any) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } } }; @@ -61,7 +62,7 @@ const CreateContext = () => { --data-raw '${JSON.stringify(getContextPayload(), undefined, 2)}'`; }; - const handleCancel = () => { + const onCancel = () => { history.goBack(); }; @@ -69,7 +70,7 @@ const CreateContext = () => { { { stickiness={stickiness} setStickiness={setStickiness} mode="Create" - validateNameUniqueness={validateNameUniqueness} + validateContext={validateContext} setErrors={setErrors} clearErrors={clearErrors} > - - Create context - + /> ); }; - -export default CreateContext; diff --git a/frontend/src/component/context/EditContext/EditContext.tsx b/frontend/src/component/context/EditContext/EditContext.tsx index 146353167b..02ddbcaf25 100644 --- a/frontend/src/component/context/EditContext/EditContext.tsx +++ b/frontend/src/component/context/EditContext/EditContext.tsx @@ -1,17 +1,18 @@ +import FormTemplate from 'component/common/FormTemplate/FormTemplate'; +import { UpdateButton } from 'component/common/UpdateButton/UpdateButton'; +import { UPDATE_CONTEXT_FIELD } from 'component/providers/AccessProvider/permissions'; +import useContextsApi from 'hooks/api/actions/useContextsApi/useContextsApi'; +import useContext from 'hooks/api/getters/useContext/useContext'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import useToast from 'hooks/useToast'; import { useEffect } from 'react'; import { useHistory, useParams } from 'react-router-dom'; -import useContextsApi from '../../../hooks/api/actions/useContextsApi/useContextsApi'; -import useContext from '../../../hooks/api/getters/useContext/useContext'; -import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; -import useToast from '../../../hooks/useToast'; -import FormTemplate from '../../common/FormTemplate/FormTemplate'; -import PermissionButton from '../../common/PermissionButton/PermissionButton'; import { scrollToTop } from '../../common/util'; -import { UPDATE_CONTEXT_FIELD } from '../../providers/AccessProvider/permissions'; -import ContextForm from '../ContextForm/ContextForm'; -import useContextForm from '../hooks/useContextForm'; +import { formatUnknownError } from 'utils/format-unknown-error'; +import { ContextForm } from '../ContextForm/ContextForm'; +import { useContextForm } from '../hooks/useContextForm'; -const EditContext = () => { +export const EditContext = () => { useEffect(() => { scrollToTop(); }, []); @@ -32,8 +33,6 @@ const EditContext = () => { setLegalValues, setStickiness, getContextPayload, - validateNameUniqueness, - validateName, clearErrors, setErrors, errors, @@ -56,24 +55,21 @@ const EditContext = () => { const handleSubmit = async (e: Event) => { e.preventDefault(); const payload = getContextPayload(); - const validName = validateName(); - if (validName) { - try { - await updateContext(payload); - refetch(); - history.push('/context'); - setToastData({ - title: 'Context information updated', - type: 'success', - }); - } catch (e: any) { - setToastApiError(e.toString()); - } + try { + await updateContext(payload); + refetch(); + history.push('/context'); + setToastData({ + title: 'Context information updated', + type: 'success', + }); + } catch (e: unknown) { + setToastApiError(formatUnknownError(e)); } }; - const handleCancel = () => { + const onCancel = () => { history.goBack(); }; @@ -81,7 +77,7 @@ const EditContext = () => { { { stickiness={stickiness} setStickiness={setStickiness} mode="Edit" - validateNameUniqueness={validateNameUniqueness} setErrors={setErrors} clearErrors={clearErrors} > - - Edit context - + ); }; - -export default EditContext; diff --git a/frontend/src/component/context/hooks/useContextForm.ts b/frontend/src/component/context/hooks/useContextForm.ts index 502e2fcc76..cd9e31eb3b 100644 --- a/frontend/src/component/context/hooks/useContextForm.ts +++ b/frontend/src/component/context/hooks/useContextForm.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import useContextsApi from '../../../hooks/api/actions/useContextsApi/useContextsApi'; -const useContextForm = ( +export const useContextForm = ( initialcontextName = '', initialcontextDesc = '', initialLegalValues = [] as string[], @@ -42,25 +42,28 @@ const useContextForm = ( const NAME_EXISTS_ERROR = 'A context field with that name already exist'; - const validateNameUniqueness = async () => { + const validateContext = async () => { + if (contextName.length === 0) { + setErrors(prev => ({ ...prev, name: 'Name can not be empty.' })); + return false; + } try { await validateContextName(contextName); + return true; } catch (e: any) { if (e.toString().includes(NAME_EXISTS_ERROR)) { setErrors(prev => ({ ...prev, name: 'A context field with that name already exist', })); + } else { + setErrors(prev => ({ + ...prev, + name: e.toString(), + })); } - } - }; - - const validateName = () => { - if (contextName.length === 0) { - setErrors(prev => ({ ...prev, name: 'Name can not be empty.' })); return false; } - return true; }; const clearErrors = () => { @@ -77,12 +80,9 @@ const useContextForm = ( setLegalValues, setStickiness, getContextPayload, - validateNameUniqueness, - validateName, + validateContext, setErrors, clearErrors, errors, }; }; - -export default useContextForm; diff --git a/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx b/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx index 567a09d9fb..d7c46f6a50 100644 --- a/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx +++ b/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx @@ -1,19 +1,20 @@ import { useHistory } from 'react-router-dom'; import useEnvironmentForm from '../hooks/useEnvironmentForm'; -import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; -import useToast from '../../../hooks/useToast'; -import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi'; import EnvironmentForm from '../EnvironmentForm/EnvironmentForm'; import FormTemplate from '../../common/FormTemplate/FormTemplate'; -import useEnvironments from '../../../hooks/api/getters/useEnvironments/useEnvironments'; import { Alert } from '@material-ui/lab'; import { Button } from '@material-ui/core'; -import ConditionallyRender from '../../common/ConditionallyRender'; -import PageContent from '../../common/PageContent'; -import HeaderTitle from '../../common/HeaderTitle'; -import PermissionButton from '../../common/PermissionButton/PermissionButton'; -import { ADMIN } from '../../providers/AccessProvider/permissions'; -import useProjectRolePermissions from '../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; +import { CreateButton } from 'component/common/CreateButton/CreateButton'; +import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import useToast from 'hooks/useToast'; +import useEnvironments from 'hooks/api/getters/useEnvironments/useEnvironments'; +import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; +import ConditionallyRender from 'component/common/ConditionallyRender'; +import PageContent from 'component/common/PageContent/PageContent'; +import { ADMIN } from 'component/providers/AccessProvider/permissions'; +import HeaderTitle from 'component/common/HeaderTitle/HeaderTitle'; +import { formatUnknownError } from 'utils/format-unknown-error'; const CreateEnvironment = () => { const { setToastApiError, setToastData } = useToast(); @@ -49,8 +50,8 @@ const CreateEnvironment = () => { confetti: true, }); history.push('/environments'); - } catch (e: any) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } } }; @@ -100,9 +101,7 @@ const CreateEnvironment = () => { mode="Create" clearErrors={clearErrors} > - - Create environment - + } diff --git a/frontend/src/component/environments/EditEnvironment/EditEnvironment.tsx b/frontend/src/component/environments/EditEnvironment/EditEnvironment.tsx index ecfc11ec5d..42ba955039 100644 --- a/frontend/src/component/environments/EditEnvironment/EditEnvironment.tsx +++ b/frontend/src/component/environments/EditEnvironment/EditEnvironment.tsx @@ -1,14 +1,15 @@ +import FormTemplate from 'component/common/FormTemplate/FormTemplate'; +import { UpdateButton } from 'component/common/UpdateButton/UpdateButton'; +import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi'; +import useEnvironment from 'hooks/api/getters/useEnvironment/useEnvironment'; +import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import useToast from 'hooks/useToast'; import { useHistory, useParams } from 'react-router-dom'; -import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi'; -import useEnvironment from '../../../hooks/api/getters/useEnvironment/useEnvironment'; -import useProjectRolePermissions from '../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; -import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; -import useToast from '../../../hooks/useToast'; -import FormTemplate from '../../common/FormTemplate/FormTemplate'; -import PermissionButton from '../../common/PermissionButton/PermissionButton'; import { ADMIN } from '../../providers/AccessProvider/permissions'; import EnvironmentForm from '../EnvironmentForm/EnvironmentForm'; import useEnvironmentForm from '../hooks/useEnvironmentForm'; +import { formatUnknownError } from '../../../utils/format-unknown-error'; const EditEnvironment = () => { const { uiConfig } = useUiConfig(); @@ -49,8 +50,8 @@ const EditEnvironment = () => { type: 'success', title: 'Successfully updated environment.', }); - } catch (e: any) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; @@ -61,7 +62,7 @@ const EditEnvironment = () => { return ( { errors={errors} clearErrors={clearErrors} > - - Edit environment - + ); diff --git a/frontend/src/component/environments/EnvironmentForm/EnvironmentTypeSelector/EnvironmentTypeSelector.tsx b/frontend/src/component/environments/EnvironmentForm/EnvironmentTypeSelector/EnvironmentTypeSelector.tsx index 07353874f2..feae4d692a 100644 --- a/frontend/src/component/environments/EnvironmentForm/EnvironmentTypeSelector/EnvironmentTypeSelector.tsx +++ b/frontend/src/component/environments/EnvironmentForm/EnvironmentTypeSelector/EnvironmentTypeSelector.tsx @@ -1,10 +1,11 @@ import { FormControl, FormControlLabel, - RadioGroup, Radio, + RadioGroup, } from '@material-ui/core'; import { useStyles } from './EnvironmentTypeSelector.styles'; +import React from 'react'; interface IEnvironmentTypeSelectorProps { onChange: (event: React.FormEvent) => void; diff --git a/frontend/src/component/environments/EnvironmentList/EnvironmentDeleteConfirm/EnvironmentDeleteConfirm.tsx b/frontend/src/component/environments/EnvironmentList/EnvironmentDeleteConfirm/EnvironmentDeleteConfirm.tsx index 61196f50c4..0e1c8bd119 100644 --- a/frontend/src/component/environments/EnvironmentList/EnvironmentDeleteConfirm/EnvironmentDeleteConfirm.tsx +++ b/frontend/src/component/environments/EnvironmentList/EnvironmentDeleteConfirm/EnvironmentDeleteConfirm.tsx @@ -11,7 +11,7 @@ interface IEnviromentDeleteConfirmProps { open: boolean; setSelectedEnv: React.Dispatch>; setDeldialogue: React.Dispatch>; - handleDeleteEnvironment: (name: string) => Promise; + handleDeleteEnvironment: () => Promise; confirmName: string; setConfirmName: React.Dispatch>; } diff --git a/frontend/src/component/environments/EnvironmentList/EnvironmentList.tsx b/frontend/src/component/environments/EnvironmentList/EnvironmentList.tsx index 8041053b15..533b34f496 100644 --- a/frontend/src/component/environments/EnvironmentList/EnvironmentList.tsx +++ b/frontend/src/component/environments/EnvironmentList/EnvironmentList.tsx @@ -21,6 +21,7 @@ import EnvironmentToggleConfirm from './EnvironmentToggleConfirm/EnvironmentTogg import useProjectRolePermissions from '../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import { formatUnknownError } from '../../../utils/format-unknown-error'; const EnvironmentList = () => { const defaultEnv = { @@ -75,16 +76,16 @@ const EnvironmentList = () => { try { await sortOrderAPICall(sortOrder); refetch(); - } catch (e) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; const sortOrderAPICall = async (sortOrder: ISortOrderPayload) => { try { await changeSortOrder(sortOrder); - } catch (e) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; @@ -97,8 +98,8 @@ const EnvironmentList = () => { title: 'Project environment deleted', text: 'You have successfully deleted the project environment.', }); - } catch (e) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } finally { setDeldialogue(false); setSelectedEnv(defaultEnv); @@ -124,8 +125,8 @@ const EnvironmentList = () => { title: 'Project environment enabled', text: 'Your environment is enabled', }); - } catch (e) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } finally { refetch(); } @@ -140,8 +141,8 @@ const EnvironmentList = () => { title: 'Project environment disabled', text: 'Your environment is disabled.', }); - } catch (e) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } finally { refetch(); } @@ -174,12 +175,11 @@ const EnvironmentList = () => { - Add Environment + New Environment } diff --git a/frontend/src/component/feature/CreateFeature/CreateFeature.tsx b/frontend/src/component/feature/CreateFeature/CreateFeature.tsx index 2fe8477254..d2ad09e816 100644 --- a/frontend/src/component/feature/CreateFeature/CreateFeature.tsx +++ b/frontend/src/component/feature/CreateFeature/CreateFeature.tsx @@ -1,15 +1,16 @@ -import FormTemplate from '../../common/FormTemplate/FormTemplate'; +import FormTemplate from 'component/common/FormTemplate/FormTemplate'; import { useHistory } from 'react-router-dom'; import FeatureForm from '../FeatureForm/FeatureForm'; import useFeatureForm from '../hooks/useFeatureForm'; -import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; -import useToast from '../../../hooks/useToast'; -import useFeatureApi from '../../../hooks/api/actions/useFeatureApi/useFeatureApi'; -import { CREATE_FEATURE } from '../../providers/AccessProvider/permissions'; -import PermissionButton from '../../common/PermissionButton/PermissionButton'; -import { CF_CREATE_BTN_ID } from '../../../testIds'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import useToast from 'hooks/useToast'; +import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; +import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions'; import { useContext } from 'react'; -import UIContext from '../../../contexts/UIContext'; +import { CreateButton } from 'component/common/CreateButton/CreateButton'; +import UIContext from 'contexts/UIContext'; +import { CF_CREATE_BTN_ID } from 'testIds'; +import { formatUnknownError } from '../../../utils/format-unknown-error'; const CreateFeature = () => { const { setToastData, setToastApiError } = useToast(); @@ -53,8 +54,8 @@ const CreateFeature = () => { type: 'success', }); setShowFeedback(true); - } catch (e: any) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } } }; @@ -99,15 +100,12 @@ const CreateFeature = () => { mode="Create" clearErrors={clearErrors} > - - Create toggle - + /> ); diff --git a/frontend/src/component/feature/EditFeature/EditFeature.tsx b/frontend/src/component/feature/EditFeature/EditFeature.tsx index 239dac8030..22b59a8466 100644 --- a/frontend/src/component/feature/EditFeature/EditFeature.tsx +++ b/frontend/src/component/feature/EditFeature/EditFeature.tsx @@ -2,14 +2,15 @@ import FormTemplate from '../../common/FormTemplate/FormTemplate'; import { useHistory, useParams } from 'react-router-dom'; import FeatureForm from '../FeatureForm/FeatureForm'; import useFeatureForm from '../hooks/useFeatureForm'; -import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; -import useToast from '../../../hooks/useToast'; -import useFeatureApi from '../../../hooks/api/actions/useFeatureApi/useFeatureApi'; -import useFeature from '../../../hooks/api/getters/useFeature/useFeature'; -import { IFeatureViewParams } from '../../../interfaces/params'; import * as jsonpatch from 'fast-json-patch'; -import PermissionButton from '../../common/PermissionButton/PermissionButton'; -import { UPDATE_FEATURE } from '../../providers/AccessProvider/permissions'; +import { UpdateButton } from 'component/common/UpdateButton/UpdateButton'; +import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions'; +import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; +import useFeature from 'hooks/api/getters/useFeature/useFeature'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import useToast from 'hooks/useToast'; +import { IFeatureViewParams } from 'interfaces/params'; +import { formatUnknownError } from 'utils/format-unknown-error'; const EditFeature = () => { const { setToastData, setToastApiError } = useToast(); @@ -57,8 +58,8 @@ const EditFeature = () => { title: 'Toggle updated successfully', type: 'success', }); - } catch (e: any) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; @@ -101,13 +102,7 @@ const EditFeature = () => { mode="Edit" clearErrors={clearErrors} > - - Edit toggle - + ); diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx index fad1e7b31d..a4c55d08df 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx @@ -183,7 +183,7 @@ const FeatureToggleList = ({ skeleton: loading, })} > - Create feature toggle + New feature toggle } /> diff --git a/frontend/src/component/feature/FeatureToggleList/__tests__/__snapshots__/list-component-test.jsx.snap b/frontend/src/component/feature/FeatureToggleList/__tests__/__snapshots__/list-component-test.jsx.snap index 3b925f0413..e365ef90c2 100644 --- a/frontend/src/component/feature/FeatureToggleList/__tests__/__snapshots__/list-component-test.jsx.snap +++ b/frontend/src/component/feature/FeatureToggleList/__tests__/__snapshots__/list-component-test.jsx.snap @@ -165,7 +165,7 @@ exports[`renders correctly with one feature 1`] = ` - Create feature toggle + New feature toggle
@@ -362,7 +362,7 @@ exports[`renders correctly with one feature without permissions 1`] = ` - Create feature toggle + New feature toggle diff --git a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx index eaebceccea..e25cd84df7 100644 --- a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx +++ b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx @@ -135,7 +135,7 @@ const FeatureToggleListNew = ({ type={feature.type} environments={feature.environments} projectId={projectId} - createdAt={new Date()} + createdAt={new Date().toISOString()} /> ); }); diff --git a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/CreatedAt.tsx b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/CreatedAt.tsx index d20eb7aa44..ca243ca46a 100644 --- a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/CreatedAt.tsx +++ b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/CreatedAt.tsx @@ -1,12 +1,9 @@ import { Tooltip } from '@material-ui/core'; -import { - formatDateWithLocale, - formatFullDateTimeWithLocale, -} from '../../../common/util'; import { useLocationSettings } from '../../../../hooks/useLocationSettings'; +import { formatDateYMD, formatDateYMDHMS } from '../../../../utils/format-date'; interface CreatedAtProps { - time: Date; + time: string; } const CreatedAt = ({ time }: CreatedAtProps) => { @@ -14,12 +11,12 @@ const CreatedAt = ({ time }: CreatedAtProps) => { return ( - {formatDateWithLocale(time, locationSettings.locale)} + {formatDateYMD(time, locationSettings.locale)} ); }; diff --git a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/FeatureToggleListNewItem.tsx b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/FeatureToggleListNewItem.tsx index 76506419c0..e5c27fa97b 100644 --- a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/FeatureToggleListNewItem.tsx +++ b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/FeatureToggleListNewItem.tsx @@ -1,13 +1,11 @@ -import { useRef, useState } from 'react'; +import React, { useRef, useState } from 'react'; import { TableCell, TableRow } from '@material-ui/core'; import { useHistory } from 'react-router'; - import { useStyles } from '../FeatureToggleListNew.styles'; import useToggleFeatureByEnv from '../../../../hooks/api/actions/useToggleFeatureByEnv/useToggleFeatureByEnv'; import { IEnvironments } from '../../../../interfaces/featureToggle'; import useToast from '../../../../hooks/useToast'; import { getTogglePath } from '../../../../utils/route-path-helpers'; -import { SyntheticEvent } from 'react-router/node_modules/@types/react'; import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig'; import FeatureStatus from '../../FeatureView/FeatureStatus/FeatureStatus'; import FeatureType from '../../FeatureView/FeatureType/FeatureType'; @@ -23,10 +21,10 @@ import EnvironmentStrategyDialog from '../../../common/EnvironmentStrategiesDial interface IFeatureToggleListNewItemProps { name: string; type: string; - environments: IFeatureEnvironment[]; + environments: IEnvironments[]; projectId: string; - lastSeenAt?: Date; - createdAt: Date; + lastSeenAt?: string; + createdAt: string; } const FeatureToggleListNewItem = ({ @@ -47,7 +45,7 @@ const FeatureToggleListNewItem = ({ const { refetch } = useProject(projectId); const styles = useStyles(); const history = useHistory(); - const ref = useRef(null); + const ref = useRef(null); const [showInfoBox, setShowInfoBox] = useState(false); const [environmentName, setEnvironmentName] = useState(''); @@ -55,8 +53,8 @@ const FeatureToggleListNewItem = ({ setShowInfoBox(false); }; - const onClick = (e: SyntheticEvent) => { - if (!ref.current?.contains(e.target)) { + const onClick = (e: React.MouseEvent) => { + if (!ref.current?.contains(e.target as Node)) { history.push(getTogglePath(projectId, name)); } }; diff --git a/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsChart/createChartOptions.ts b/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsChart/createChartOptions.ts index c40c901dd6..4f0f46a68f 100644 --- a/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsChart/createChartOptions.ts +++ b/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsChart/createChartOptions.ts @@ -1,9 +1,9 @@ import { ILocationSettings } from '../../../../../hooks/useLocationSettings'; import 'chartjs-adapter-date-fns'; import { ChartOptions, defaults } from 'chart.js'; -import { formatTimeWithLocale } from '../../../../common/util'; import { IFeatureMetricsRaw } from '../../../../../interfaces/featureToggle'; import theme from '../../../../../themes/main-theme'; +import { formatDateHM } from '../../../../../utils/format-date'; export const createChartOptions = ( metrics: IFeatureMetricsRaw[], @@ -30,7 +30,7 @@ export const createChartOptions = ( usePointStyle: true, callbacks: { title: items => - formatTimeWithLocale( + formatDateHM( items[0].parsed.x, locationSettings.locale ), @@ -73,10 +73,7 @@ export const createChartOptions = ( grid: { display: false }, ticks: { callback: (_, i, data) => - formatTimeWithLocale( - data[i].value, - locationSettings.locale - ), + formatDateHM(data[i].value, locationSettings.locale), }, }, }, diff --git a/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsTable/FeatureMetricsTable.tsx b/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsTable/FeatureMetricsTable.tsx index 186b20dd4e..d877d1fc25 100644 --- a/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsTable/FeatureMetricsTable.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsTable/FeatureMetricsTable.tsx @@ -9,8 +9,8 @@ import { useTheme, } from '@material-ui/core'; import { useLocationSettings } from '../../../../../hooks/useLocationSettings'; -import { formatFullDateTimeWithLocale } from '../../../../common/util'; import { useMemo } from 'react'; +import { formatDateYMDHMS } from 'utils/format-date'; export const FEATURE_METRICS_TABLE_ID = 'feature-metrics-table-id'; @@ -48,7 +48,7 @@ export const FeatureMetricsTable = ({ metrics }: IFeatureMetricsTableProps) => { {sortedMetrics.map(metric => ( - {formatFullDateTimeWithLocale( + {formatDateYMDHMS( metric.timestamp, locationSettings.locale )} diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/AddTagDialog/AddTagDialog.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/AddTagDialog/AddTagDialog.tsx index 002f25c8af..052ea5fb3a 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/AddTagDialog/AddTagDialog.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/AddTagDialog/AddTagDialog.tsx @@ -1,6 +1,6 @@ import { DialogContentText } from '@material-ui/core'; import { useParams } from 'react-router'; -import { useState } from 'react'; +import React, { useState } from 'react'; import { IFeatureViewParams } from '../../../../../interfaces/params'; import Dialogue from '../../../../common/Dialogue'; import Input from '../../../../common/Input/Input'; @@ -11,6 +11,7 @@ import TagSelect from '../../../../common/TagSelect/TagSelect'; import useFeatureApi from '../../../../../hooks/api/actions/useFeatureApi/useFeatureApi'; import useTags from '../../../../../hooks/api/getters/useTags/useTags'; import useToast from '../../../../../hooks/useToast'; +import { formatUnknownError } from '../../../../../utils/format-unknown-error'; interface IAddTagDialogProps { open: boolean; @@ -20,6 +21,7 @@ interface IAddTagDialogProps { interface IDefaultTag { type: string; value: string; + [index: string]: string; } @@ -62,9 +64,10 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => { text: 'We successfully added a tag to your toggle', confetti: true, }); - } catch (e) { - setToastApiError(e.message); - setErrors({ tagError: e.message }); + } catch (error: unknown) { + const message = formatUnknownError(error); + setToastApiError(message); + setErrors({ tagError: message }); } }; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitch/FeatureOverviewEnvSwitch.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitch/FeatureOverviewEnvSwitch.tsx index 96d6a68999..2ec938d3df 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitch/FeatureOverviewEnvSwitch.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitch/FeatureOverviewEnvSwitch.tsx @@ -8,6 +8,8 @@ import { IFeatureViewParams } from '../../../../../../interfaces/params'; import PermissionSwitch from '../../../../../common/PermissionSwitch/PermissionSwitch'; import StringTruncator from '../../../../../common/StringTruncator/StringTruncator'; import { UPDATE_FEATURE_ENVIRONMENT } from '../../../../../providers/AccessProvider/permissions'; +import React from 'react'; +import { formatUnknownError } from '../../../../../../utils/format-unknown-error'; interface IFeatureOverviewEnvSwitchProps { env: IFeatureEnvironment; @@ -40,7 +42,7 @@ const FeatureOverviewEnvSwitch = ({ if (callback) { callback(); } - } catch (e: any) { + } catch (e) { if (e.message === ENVIRONMENT_STRATEGY_ERROR) { showInfoBox(true); } else { @@ -61,8 +63,8 @@ const FeatureOverviewEnvSwitch = ({ if (callback) { callback(); } - } catch (e: any) { - setToastApiError(e.message); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; @@ -93,7 +95,6 @@ const FeatureOverviewEnvSwitch = ({ checked={env.enabled} onChange={toggleEnvironment} environmentId={env.name} - tooltip={''} /> {content} diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategy/FeatureOverviewEnvironmentStrategy.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategy/FeatureOverviewEnvironmentStrategy.tsx index beda59ba22..1f4e852a24 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategy/FeatureOverviewEnvironmentStrategy.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategy/FeatureOverviewEnvironmentStrategy.tsx @@ -1,5 +1,5 @@ import { Settings } from '@material-ui/icons'; -import { useTheme } from '@material-ui/styles'; +import { useTheme } from '@material-ui/core/styles'; import { Link, useParams } from 'react-router-dom'; import { IFeatureViewParams } from '../../../../../../../../interfaces/params'; import { IFeatureStrategy } from '../../../../../../../../interfaces/strategy'; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewTags/FeatureOverviewTags.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewTags/FeatureOverviewTags.tsx index 33f484fd51..7b3570e72d 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewTags/FeatureOverviewTags.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewTags/FeatureOverviewTags.tsx @@ -1,6 +1,6 @@ -import { useState, useContext } from 'react'; +import React, { useContext, useState } from 'react'; import { Chip } from '@material-ui/core'; -import { Label, Close } from '@material-ui/icons'; +import { Close, Label } from '@material-ui/icons'; import { useParams } from 'react-router-dom'; import useTags from '../../../../../../hooks/api/getters/useTags/useTags'; import { IFeatureViewParams } from '../../../../../../interfaces/params'; @@ -17,6 +17,7 @@ import useToast from '../../../../../../hooks/useToast'; import { UPDATE_FEATURE } from '../../../../../providers/AccessProvider/permissions'; import ConditionallyRender from '../../../../../common/ConditionallyRender'; import AccessContext from '../../../../../../contexts/AccessContext'; +import { formatUnknownError } from '../../../../../../utils/format-unknown-error'; interface IFeatureOverviewTagsProps extends React.HTMLProps { projectId: string; @@ -53,8 +54,8 @@ const FeatureOverviewTags: React.FC = ({ title: 'Tag deleted', text: 'Successfully deleted tag', }); - } catch (e) { - setToastApiError(e.message); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; diff --git a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureSettingsMetadata.tsx b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureSettingsMetadata.tsx index 0c7bd6e577..204431e32e 100644 --- a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureSettingsMetadata.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureSettingsMetadata.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useContext } from 'react'; +import { useContext, useEffect, useState } from 'react'; import * as jsonpatch from 'fast-json-patch'; import { TextField } from '@material-ui/core'; import PermissionButton from '../../../../common/PermissionButton/PermissionButton'; @@ -11,6 +11,7 @@ import { IFeatureViewParams } from '../../../../../interfaces/params'; import useToast from '../../../../../hooks/useToast'; import useFeatureApi from '../../../../../hooks/api/actions/useFeatureApi/useFeatureApi'; import ConditionallyRender from '../../../../common/ConditionallyRender'; +import { formatUnknownError } from '../../../../../utils/format-unknown-error'; const FeatureSettingsMetadata = () => { const { hasAccess } = useContext(AccessContext); @@ -54,8 +55,8 @@ const FeatureSettingsMetadata = () => { }); setDirty(false); refetch(); - } catch (e) { - setToastApiError(e.toString()); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; diff --git a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureTypeSelect/FeatureTypeSelect.tsx b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureTypeSelect/FeatureTypeSelect.tsx index 739096cb0c..2e926f795f 100644 --- a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureTypeSelect/FeatureTypeSelect.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureTypeSelect/FeatureTypeSelect.tsx @@ -1,5 +1,7 @@ import useFeatureTypes from '../../../../../../hooks/api/getters/useFeatureTypes/useFeatureTypes'; -import GeneralSelect from '../../../../../common/GeneralSelect/GeneralSelect'; +import GeneralSelect, { + ISelectOption, +} from '../../../../../common/GeneralSelect/GeneralSelect'; const FeatureTypeSelect = ({ editable, @@ -11,7 +13,7 @@ const FeatureTypeSelect = ({ }) => { const { featureTypes } = useFeatureTypes(); - const options = featureTypes.map(t => ({ + const options: ISelectOption[] = featureTypes.map(t => ({ key: t.id, label: t.name, title: t.description, diff --git a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProject.tsx b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProject.tsx index b33c203f1c..ee20818126 100644 --- a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProject.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProject.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useContext } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { useHistory, useParams } from 'react-router'; import AccessContext from '../../../../../contexts/AccessContext'; import useFeatureApi from '../../../../../hooks/api/actions/useFeatureApi/useFeatureApi'; @@ -12,6 +12,7 @@ import FeatureProjectSelect from './FeatureProjectSelect/FeatureProjectSelect'; import FeatureSettingsProjectConfirm from './FeatureSettingsProjectConfirm/FeatureSettingsProjectConfirm'; import { IPermission } from '../../../../../interfaces/user'; import { useAuthPermissions } from '../../../../../hooks/api/getters/useAuth/useAuthPermissions'; +import { formatUnknownError } from '../../../../../utils/format-unknown-error'; const FeatureSettingsProject = () => { const { hasAccess } = useContext(AccessContext); @@ -61,16 +62,16 @@ const FeatureSettingsProject = () => { history.replace( `/projects/${newProject}/features/${featureId}/settings` ); - } catch (e) { - setToastApiError(e.message); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; const createMoveTargets = () => { return permissions.reduce( - (acc: { [key: string]: boolean }, permission: IPermission) => { - if (permission.permission === MOVE_FEATURE_TOGGLE) { - acc[permission.project] = true; + (acc: { [key: string]: boolean }, p: IPermission) => { + if (p.project && p.permission === MOVE_FEATURE_TOGGLE) { + acc[p.project] = true; } return acc; }, @@ -101,7 +102,6 @@ const FeatureSettingsProject = () => { show={ setShowConfirmDialog(true)} projectId={projectId} > diff --git a/frontend/src/component/feature/FeatureView/FeatureStatus/FeatureStatus.tsx b/frontend/src/component/feature/FeatureView/FeatureStatus/FeatureStatus.tsx index b7b25230c9..7fa2d08228 100644 --- a/frontend/src/component/feature/FeatureView/FeatureStatus/FeatureStatus.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureStatus/FeatureStatus.tsx @@ -1,7 +1,8 @@ import { useStyles } from './FeatureStatus.styles'; import TimeAgo from 'react-timeago'; import ConditionallyRender from '../../../common/ConditionallyRender'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip, TooltipProps } from '@material-ui/core'; +import React from 'react'; function generateUnit(unit?: string): string { switch (unit) { @@ -46,8 +47,8 @@ function getColor(unit?: string): string { } interface FeatureStatusProps { - lastSeenAt?: Date; - tooltipPlacement?: string; + lastSeenAt?: string; + tooltipPlacement?: TooltipProps['placement']; } const FeatureStatus = ({ @@ -76,7 +77,7 @@ const FeatureStatus = ({ condition={!!lastSeenAt} show={ { const history = useHistory(); @@ -99,8 +100,8 @@ const FeatureStrategiesConfigure = () => { history.replace(history.location.pathname); refetch(); scrollToTop(); - } catch (e) { - setToastApiError(e.message); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; diff --git a/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironmentList/FeatureStrategiesEnvironmentList.tsx b/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironmentList/FeatureStrategiesEnvironmentList.tsx index f7083ddad1..5b03e163a5 100644 --- a/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironmentList/FeatureStrategiesEnvironmentList.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironmentList/FeatureStrategiesEnvironmentList.tsx @@ -39,7 +39,6 @@ const FeatureStrategiesEnvironmentList = ({ const { activeEnvironmentsRef, - setToastData, deleteStrategy, updateStrategy, delDialog, @@ -162,7 +161,6 @@ const FeatureStrategiesEnvironmentList = ({ : 'Toggle is disabled and no strategies are executing' } env={activeEnvironment} - setToastData={setToastData} callback={updateFeatureEnvironmentCache} /> diff --git a/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironmentList/useFeatureStrategiesEnvironmentList.ts b/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironmentList/useFeatureStrategiesEnvironmentList.ts index 5d21ef1536..6a7ee41dd4 100644 --- a/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironmentList/useFeatureStrategiesEnvironmentList.ts +++ b/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironmentList/useFeatureStrategiesEnvironmentList.ts @@ -4,9 +4,13 @@ import FeatureStrategiesUIContext from '../../../../../../contexts/FeatureStrate import useFeatureStrategyApi from '../../../../../../hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi'; import useToast from '../../../../../../hooks/useToast'; import { IFeatureViewParams } from '../../../../../../interfaces/params'; -import { IFeatureStrategy } from '../../../../../../interfaces/strategy'; +import { + IFeatureStrategy, + IStrategyPayload, +} from '../../../../../../interfaces/strategy'; import cloneDeep from 'lodash.clonedeep'; import { IFeatureEnvironment } from '../../../../../../interfaces/featureToggle'; +import { formatUnknownError } from '../../../../../../utils/format-unknown-error'; const useFeatureStrategiesEnvironmentList = () => { const { projectId, featureId } = useParams(); @@ -85,8 +89,8 @@ const useFeatureStrategiesEnvironmentList = () => { strategy.constraints = updateStrategyPayload.constraints; history.replace(history.location.pathname); setFeatureCache(feature); - } catch (e) { - setToastApiError(e.message); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; @@ -118,14 +122,13 @@ const useFeatureStrategiesEnvironmentList = () => { text: `Successfully deleted strategy from ${featureId}`, }); history.replace(history.location.pathname); - } catch (e) { - setToastApiError(e.message); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; return { activeEnvironmentsRef, - setToastData, deleteStrategy, updateStrategy, delDialog, diff --git a/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.tsx b/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.tsx index 5845f78f53..0cc45cf4e0 100644 --- a/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.tsx @@ -282,7 +282,7 @@ const FeatureStrategiesEnvironments = () => { environmentId={activeEnvironment.name} permission={CREATE_FEATURE_STRATEGY} > - Add new strategy + New strategy } /> diff --git a/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesProductionGuard/FeatureStrategiesProductionGuard.tsx b/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesProductionGuard/FeatureStrategiesProductionGuard.tsx index b58d3ab65b..c64362a211 100644 --- a/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesProductionGuard/FeatureStrategiesProductionGuard.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesProductionGuard/FeatureStrategiesProductionGuard.tsx @@ -11,7 +11,7 @@ interface IFeatureStrategiesProductionGuard { onClick: () => void; onClose: () => void; primaryButtonText: string; - loading: boolean; + loading?: boolean; } const FeatureStrategiesProductionGuard = ({ @@ -61,4 +61,11 @@ const FeatureStrategiesProductionGuard = ({ ); }; +export const disableFeatureStrategiesProductionGuard = () => { + localStorage.setItem( + FEATURE_STRATEGY_PRODUCTION_GUARD_SETTING, + String(true) + ); +}; + export default FeatureStrategiesProductionGuard; diff --git a/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.tsx b/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.tsx index 0c8afb086c..89597a3a24 100644 --- a/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { mutate } from 'swr'; import FeatureStrategiesUIContext from '../../../../../../contexts/FeatureStrategiesUIContext'; @@ -6,8 +6,8 @@ import useFeatureStrategy from '../../../../../../hooks/api/getters/useFeatureSt import { IFeatureViewParams } from '../../../../../../interfaces/params'; import { IConstraint, - IParameter, IFeatureStrategy, + IParameter, } from '../../../../../../interfaces/strategy'; import FeatureStrategyAccordion from '../../FeatureStrategyAccordion/FeatureStrategyAccordion'; import cloneDeep from 'lodash.clonedeep'; diff --git a/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesList/FeatureStrategyCard/FeatureStrategyCard.tsx b/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesList/FeatureStrategyCard/FeatureStrategyCard.tsx index 4dd9b6f82d..91e69f83bb 100644 --- a/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesList/FeatureStrategyCard/FeatureStrategyCard.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesList/FeatureStrategyCard/FeatureStrategyCard.tsx @@ -23,7 +23,7 @@ interface IFeatureStrategyCardProps { name: string; description: string; configureNewStrategy: boolean; - index?: number; + index: number; } export const FEATURE_STRATEGIES_DRAG_TYPE = 'FEATURE_STRATEGIES_DRAG_TYPE'; diff --git a/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.tsx b/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.tsx index 942772a9cc..14a73594a3 100644 --- a/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.tsx @@ -8,7 +8,7 @@ import useStrategies from '../../../../../../hooks/api/getters/useStrategies/use import GeneralStrategy from '../../common/GeneralStrategy/GeneralStrategy'; import UserWithIdStrategy from '../../common/UserWithIdStrategy/UserWithId'; import StrategyConstraints from '../../common/StrategyConstraints/StrategyConstraints'; -import { useContext, useState } from 'react'; +import React, { useContext, useState } from 'react'; import ConditionallyRender from '../../../../../common/ConditionallyRender'; import useUiConfig from '../../../../../../hooks/api/getters/useUiConfig/useUiConfig'; import { C } from '../../../../../common/flags'; diff --git a/frontend/src/component/feature/FeatureView/FeatureStrategies/common/GeneralStrategy/GeneralStrategy.tsx b/frontend/src/component/feature/FeatureView/FeatureStrategies/common/GeneralStrategy/GeneralStrategy.tsx index 131bf391d8..94c16f9d0e 100644 --- a/frontend/src/component/feature/FeatureView/FeatureStrategies/common/GeneralStrategy/GeneralStrategy.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureStrategies/common/GeneralStrategy/GeneralStrategy.tsx @@ -1,16 +1,16 @@ import React from 'react'; import { - Switch, FormControlLabel, - Tooltip, + Switch, TextField, + Tooltip, } from '@material-ui/core'; import StrategyInputList from '../StrategyInputList/StrategyInputList'; import RolloutSlider from '../RolloutSlider/RolloutSlider'; import { - IParameter, IFeatureStrategy, + IParameter, } from '../../../../../../interfaces/strategy'; import { useStyles } from './GeneralStrategy.styles'; @@ -77,7 +77,7 @@ const GeneralStrategy = ({ ); } else if (type === 'list') { - let list = []; + let list: string[] = []; if (typeof value === 'string') { list = value.trim().split(',').filter(Boolean); } diff --git a/frontend/src/component/feature/FeatureView/FeatureStrategies/common/RolloutSlider/RolloutSlider.tsx b/frontend/src/component/feature/FeatureView/FeatureStrategies/common/RolloutSlider/RolloutSlider.tsx index 0ecace92ba..0fad306563 100644 --- a/frontend/src/component/feature/FeatureView/FeatureStrategies/common/RolloutSlider/RolloutSlider.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureStrategies/common/RolloutSlider/RolloutSlider.tsx @@ -1,6 +1,7 @@ import { makeStyles, withStyles } from '@material-ui/core/styles'; import { Slider, Typography } from '@material-ui/core'; import { ROLLOUT_SLIDER_ID } from '../../../../../../testIds'; +import React from 'react'; const StyledSlider = withStyles({ root: { diff --git a/frontend/src/component/feature/FeatureView/FeatureStrategies/common/StrategyConstraints/StrategyConstraints.tsx b/frontend/src/component/feature/FeatureView/FeatureStrategies/common/StrategyConstraints/StrategyConstraints.tsx index cf10e8f777..9e13cc8f6a 100644 --- a/frontend/src/component/feature/FeatureView/FeatureStrategies/common/StrategyConstraints/StrategyConstraints.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureStrategies/common/StrategyConstraints/StrategyConstraints.tsx @@ -7,7 +7,7 @@ import useUiConfig from '../../../../../../hooks/api/getters/useUiConfig/useUiCo import { C } from '../../../../../common/flags'; import useUnleashContext from '../../../../../../hooks/api/getters/useUnleashContext/useUnleashContext'; import StrategyConstraintInputField from './StrategyConstraintInputField'; -import { useEffect } from 'react'; +import React, { useEffect } from 'react'; interface IStrategyConstraintProps { constraints: IConstraint[]; @@ -38,7 +38,7 @@ const StrategyConstraints: React.FC = ({ const enabled = uiConfig.flags[C]; const contextNames = contextFields.map(context => context.name); - const onClick = evt => { + const onClick = (evt: React.SyntheticEvent) => { evt.preventDefault(); addConstraint(); }; @@ -57,15 +57,15 @@ const StrategyConstraints: React.FC = ({ }; }; - const removeConstraint = index => evt => { - evt.preventDefault(); + const removeConstraint = (index: number) => (event: Event) => { + event.preventDefault(); const updatedConstraints = [...constraints]; updatedConstraints.splice(index, 1); updateConstraints(updatedConstraints); }; - const updateConstraint = index => (value, field) => { + const updateConstraint = (index: number) => (value, field) => { const updatedConstraints = [...constraints]; const constraint = updatedConstraints[index]; constraint[field] = value; diff --git a/frontend/src/component/feature/FeatureView/FeatureStrategies/common/StrategyInputList/StrategyInputList.tsx b/frontend/src/component/feature/FeatureView/FeatureStrategies/common/StrategyInputList/StrategyInputList.tsx index cdd857aa16..47cde8175d 100644 --- a/frontend/src/component/feature/FeatureView/FeatureStrategies/common/StrategyInputList/StrategyInputList.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureStrategies/common/StrategyInputList/StrategyInputList.tsx @@ -10,7 +10,7 @@ import { interface IStrategyInputList { name: string; list: string[]; - setConfig: () => void; + setConfig: (field: string, value: string) => void; disabled: boolean; } diff --git a/frontend/src/component/feature/FeatureView/FeatureStrategies/common/UserWithIdStrategy/UserWithId.tsx b/frontend/src/component/feature/FeatureView/FeatureStrategies/common/UserWithIdStrategy/UserWithId.tsx index ee9b7360fb..4d605d2791 100644 --- a/frontend/src/component/feature/FeatureView/FeatureStrategies/common/UserWithIdStrategy/UserWithId.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureStrategies/common/UserWithIdStrategy/UserWithId.tsx @@ -3,7 +3,7 @@ import StrategyInputList from '../StrategyInputList/StrategyInputList'; interface IUserWithIdStrategyProps { parameters: IParameter; - updateParameter: (field: string, value: any) => void; + updateParameter: (field: string, value: string) => void; editable: boolean; } diff --git a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/AddFeatureVariant.tsx b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/AddFeatureVariant.tsx index 60ffa01987..7d6ff0ef1b 100644 --- a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/AddFeatureVariant.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/AddFeatureVariant.tsx @@ -1,31 +1,28 @@ -import { useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; +import React, { useEffect, useState, ChangeEvent } from 'react'; import { + Button, FormControl, FormControlLabel, Grid, - TextField, InputAdornment, - Button, + TextField, Tooltip, } from '@material-ui/core'; import { Info } from '@material-ui/icons'; - import { weightTypes } from './enums'; - import { OverrideConfig } from './OverrideConfig/OverrideConfig'; import ConditionallyRender from '../../../../../common/ConditionallyRender'; -import GeneralSelect from '../../../../../common/GeneralSelect/GeneralSelect'; -import { useCommonStyles } from '../../../../../../common.styles'; +import { useCommonStyles } from 'common.styles'; import Dialogue from '../../../../../common/Dialogue'; -import { trim, modalStyles } from '../../../../../common/util'; +import { modalStyles, trim } from 'component/common/util'; import PermissionSwitch from '../../../../../common/PermissionSwitch/PermissionSwitch'; -import { UPDATE_FEATURE_VARIANTS } from '../../../../../providers/AccessProvider/permissions'; +import { UPDATE_FEATURE_VARIANTS } from 'component/providers/AccessProvider/permissions'; import useFeature from '../../../../../../hooks/api/getters/useFeature/useFeature'; import { useParams } from 'react-router-dom'; -import { IFeatureViewParams } from '../../../../../../interfaces/params'; -import { IFeatureVariant } from '../../../../../../interfaces/featureToggle'; +import { IFeatureViewParams } from 'interfaces/params'; +import { IFeatureVariant, IOverride } from 'interfaces/featureToggle'; import cloneDeep from 'lodash.clonedeep'; +import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect'; const payloadOptions = [ { key: 'string', label: 'string' }, @@ -35,6 +32,17 @@ const payloadOptions = [ const EMPTY_PAYLOAD = { type: 'string', value: '' }; +interface IAddVariantProps { + showDialog: boolean; + closeDialog: () => void; + save: (variantToSave: IFeatureVariant) => Promise; + editVariant: IFeatureVariant; + validateName: (value: string) => Record | undefined; + validateWeight: (value: string) => Record | undefined; + title: string; + editing: boolean; +} + const AddVariant = ({ showDialog, closeDialog, @@ -44,11 +52,11 @@ const AddVariant = ({ validateWeight, title, editing, -}) => { - const [data, setData] = useState({}); +}: IAddVariantProps) => { + const [data, setData] = useState>({}); const [payload, setPayload] = useState(EMPTY_PAYLOAD); - const [overrides, setOverrides] = useState([]); - const [error, setError] = useState({}); + const [overrides, setOverrides] = useState([]); + const [error, setError] = useState>({}); const commonStyles = useCommonStyles(); const { projectId, featureId } = useParams(); const { feature } = useFeature(projectId, featureId); @@ -58,7 +66,7 @@ const AddVariant = ({ if (editVariant) { setData({ name: editVariant.name, - weight: editVariant.weight / 10, + weight: String(editVariant.weight / 10), weightType: editVariant.weightType || weightTypes.VARIABLE, stickiness: editVariant.stickiness, }); @@ -80,7 +88,7 @@ const AddVariant = ({ setError({}); }; - const setClonedVariants = clonedVariants => + const setClonedVariants = (clonedVariants: IFeatureVariant[]) => setVariants(cloneDeep(clonedVariants)); useEffect(() => { @@ -95,7 +103,9 @@ const AddVariant = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [editVariant]); - const setVariantValue = e => { + const setVariantValue = ( + e: React.ChangeEvent + ) => { const { name, value } = e.target; setData({ ...data, @@ -103,7 +113,7 @@ const AddVariant = ({ }); }; - const setVariantWeightType = e => { + const setVariantWeightType = (e: React.ChangeEvent) => { const { checked, name } = e.target; const weightType = checked ? weightTypes.FIX : weightTypes.VARIABLE; setData({ @@ -112,7 +122,7 @@ const AddVariant = ({ }); }; - const submit = async e => { + const submit = async (e: React.FormEvent) => { setError({}); e.preventDefault(); @@ -128,9 +138,9 @@ const AddVariant = ({ } try { - const variant = { + const variant: IFeatureVariant = { name: data.name, - weight: data.weight * 10, + weight: Number(data.weight) * 10, weightType: data.weightType, stickiness: data.stickiness, payload: payload.value ? payload : undefined, @@ -159,7 +169,7 @@ const AddVariant = ({ } }; - const onPayload = e => { + const onPayload = (e: ChangeEvent<{ name?: string; value: unknown }>) => { e.preventDefault(); setPayload({ ...payload, @@ -167,26 +177,27 @@ const AddVariant = ({ }); }; - const onCancel = e => { + const onCancel = (e: React.SyntheticEvent) => { e.preventDefault(); clear(); closeDialog(); }; - const updateOverrideType = index => e => { - e.preventDefault(); - setOverrides( - overrides.map((o, i) => { - if (i === index) { - o[e.target.name] = e.target.value; - } + const updateOverrideType = + (index: number) => (e: ChangeEvent) => { + e.preventDefault(); + setOverrides( + overrides.map((o, i) => { + if (i === index) { + o[e.target.name] = e.target.value; + } - return o; - }) - ); - }; + return o; + }) + ); + }; - const updateOverrideValues = (index, values) => { + const updateOverrideValues = (index: number, values: string[]) => { setOverrides( overrides.map((o, i) => { if (i === index) { @@ -197,12 +208,12 @@ const AddVariant = ({ ); }; - const removeOverride = index => e => { + const removeOverride = (index: number) => (e: React.SyntheticEvent) => { e.preventDefault(); setOverrides(overrides.filter((o, i) => i !== index)); }; - const onAddOverride = e => { + const onAddOverride = (e: React.SyntheticEvent) => { e.preventDefault(); setOverrides([ ...overrides, @@ -217,7 +228,6 @@ const AddVariant = ({ return ( @@ -388,7 +398,6 @@ const AddVariant = ({ removeOverride={removeOverride} updateOverrideType={updateOverrideType} updateOverrideValues={updateOverrideValues} - updateValues={updateOverrideValues} /> } elseShow={ diff --git a/frontend/src/component/strategies/StrategyView/StrategyView.tsx b/frontend/src/component/strategies/StrategyView/StrategyView.tsx index e9db415695..838af3e6ce 100644 --- a/frontend/src/component/strategies/StrategyView/StrategyView.tsx +++ b/frontend/src/component/strategies/StrategyView/StrategyView.tsx @@ -20,7 +20,7 @@ export const StrategyView = () => { const { applications } = useApplications(); const toggles = features.filter(toggle => { - return toggle?.strategies.findIndex(s => s.name === strategyName) > -1; + return toggle?.strategies?.find(s => s.name === strategyName); }); const strategy = strategies.find(n => n.name === strategyName); diff --git a/frontend/src/component/strategies/TogglesLinkList/TogglesLinkList.tsx b/frontend/src/component/strategies/TogglesLinkList/TogglesLinkList.tsx index 2310ec4ef3..1596379680 100644 --- a/frontend/src/component/strategies/TogglesLinkList/TogglesLinkList.tsx +++ b/frontend/src/component/strategies/TogglesLinkList/TogglesLinkList.tsx @@ -5,13 +5,14 @@ import { ListItemText, Tooltip, } from '@material-ui/core'; -import { PlayArrow, Pause } from '@material-ui/icons'; +import { Pause, PlayArrow } from '@material-ui/icons'; import styles from '../../common/common.module.scss'; import { Link } from 'react-router-dom'; import ConditionallyRender from '../../common/ConditionallyRender'; +import { IFeatureToggle } from '../../../interfaces/featureToggle'; interface ITogglesLinkListProps { - toggles: []; + toggles: IFeatureToggle[]; } export const TogglesLinkList = ({ toggles }: ITogglesLinkListProps) => ( diff --git a/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap b/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap index c3cceed402..073d9db764 100644 --- a/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap +++ b/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap @@ -38,7 +38,7 @@ exports[`renders correctly with one strategy 1`] = ` onMouseOver={[Function]} onTouchEnd={[Function]} onTouchStart={[Function]} - title="Add new strategy" + title="" > } /> diff --git a/frontend/src/component/tags/TagTypeList/__tests__/__snapshots__/TagTypeList.test.js.snap b/frontend/src/component/tags/TagTypeList/__tests__/__snapshots__/TagTypeList.test.js.snap index 54f61d645e..505b5326f3 100644 --- a/frontend/src/component/tags/TagTypeList/__tests__/__snapshots__/TagTypeList.test.js.snap +++ b/frontend/src/component/tags/TagTypeList/__tests__/__snapshots__/TagTypeList.test.js.snap @@ -50,7 +50,7 @@ exports[`renders a list with elements correctly 1`] = ` - Add new tag type + New tag type - Add new tag type + New tag type diff --git a/frontend/src/component/user/DemoAuth/DemoAuth.jsx b/frontend/src/component/user/DemoAuth/DemoAuth.jsx index 8fa4dd880c..718c137844 100644 --- a/frontend/src/component/user/DemoAuth/DemoAuth.jsx +++ b/frontend/src/component/user/DemoAuth/DemoAuth.jsx @@ -8,6 +8,7 @@ import { useHistory } from 'react-router-dom'; import { useAuthApi } from '../../../hooks/api/actions/useAuthApi/useAuthApi'; import { useAuthUser } from '../../../hooks/api/getters/useAuth/useAuthUser'; import useToast from '../../../hooks/useToast'; +import { formatUnknownError } from '../../../utils/format-unknown-error'; const DemoAuth = ({ authDetails }) => { const [email, setEmail] = useState(''); @@ -23,8 +24,8 @@ const DemoAuth = ({ authDetails }) => { await emailAuth(authDetails.path, email); refetchUser(); history.push(`/`); - } catch (e) { - setToastApiError(e.toString()); + } catch (error) { + setToastApiError(formatUnknownError(error)); } }; diff --git a/frontend/src/component/user/SimpleAuth/SimpleAuth.jsx b/frontend/src/component/user/SimpleAuth/SimpleAuth.jsx index ede999d2ca..95cbe4e413 100644 --- a/frontend/src/component/user/SimpleAuth/SimpleAuth.jsx +++ b/frontend/src/component/user/SimpleAuth/SimpleAuth.jsx @@ -7,6 +7,7 @@ import { useAuthApi } from '../../../hooks/api/actions/useAuthApi/useAuthApi'; import { useAuthUser } from '../../../hooks/api/getters/useAuth/useAuthUser'; import { LOGIN_BUTTON, LOGIN_EMAIL_ID } from '../../../testIds'; import useToast from '../../../hooks/useToast'; +import { formatUnknownError } from '../../../utils/format-unknown-error'; const SimpleAuth = ({ authDetails }) => { const [email, setEmail] = useState(''); @@ -22,8 +23,8 @@ const SimpleAuth = ({ authDetails }) => { await emailAuth(authDetails.path, email); refetchUser(); history.push(`/`); - } catch (e) { - setToastApiError(e.toString()); + } catch (error) { + setToastApiError(formatUnknownError(error)); } }; diff --git a/frontend/src/component/user/UserProfile/EditProfile/EditProfile.tsx b/frontend/src/component/user/UserProfile/EditProfile/EditProfile.tsx index 03bad8e5f7..fd0b52c337 100644 --- a/frontend/src/component/user/UserProfile/EditProfile/EditProfile.tsx +++ b/frontend/src/component/user/UserProfile/EditProfile/EditProfile.tsx @@ -1,4 +1,4 @@ -import { SyntheticEvent, useState } from 'react'; +import React, { SyntheticEvent, useState } from 'react'; import { Button, Typography } from '@material-ui/core'; import classnames from 'classnames'; import { useStyles } from './EditProfile.styles'; diff --git a/frontend/src/component/user/UserProfile/UserProfile.tsx b/frontend/src/component/user/UserProfile/UserProfile.tsx index d78a10ca77..6857cba7f0 100644 --- a/frontend/src/component/user/UserProfile/UserProfile.tsx +++ b/frontend/src/component/user/UserProfile/UserProfile.tsx @@ -1,14 +1,13 @@ import React, { useEffect, useState } from 'react'; import classnames from 'classnames'; -import OutsideClickHandler from 'react-outside-click-handler'; - -import { Avatar, Button } from '@material-ui/core'; +import { Avatar, Button, ClickAwayListener } from '@material-ui/core'; import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; import { useStyles } from './UserProfile.styles'; import { useCommonStyles } from '../../../common.styles'; import UserProfileContent from './UserProfileContent/UserProfileContent'; import { IUser } from '../../../interfaces/user'; import { ILocationSettings } from '../../../hooks/useLocationSettings'; +import { HEADER_USER_AVATAR } from 'testIds'; interface IUserProfileProps { profile: IUser; @@ -57,7 +56,7 @@ const UserProfile = ({ const imageUrl = email ? profile.imageUrl : 'unknown-user.png'; return ( - setShowProfile(false)}> + setShowProfile(false)}>
-
+ ); }; diff --git a/frontend/src/component/user/UserProfile/UserProfileContent/UserProfileContent.tsx b/frontend/src/component/user/UserProfile/UserProfileContent/UserProfileContent.tsx index c343f41581..e7708962c8 100644 --- a/frontend/src/component/user/UserProfile/UserProfileContent/UserProfileContent.tsx +++ b/frontend/src/component/user/UserProfile/UserProfileContent/UserProfileContent.tsx @@ -47,15 +47,11 @@ const UserProfileContent = ({ const [editingProfile, setEditingProfile] = useState(false); const styles = useStyles(); - // @ts-expect-error const profileAvatarClasses = classnames(styles.avatar, { - // @ts-expect-error [styles.editingAvatar]: editingProfile, }); - // @ts-expect-error const profileEmailClasses = classnames(styles.profileEmail, { - // @ts-expect-error [styles.editingEmail]: editingProfile, }); @@ -71,7 +67,6 @@ const UserProfileContent = ({ show={
>; + toastData: IToast; + setToast: React.Dispatch>; showFeedback: boolean; setShowFeedback: React.Dispatch>; } -const UIContext = React.createContext(null); +export const createEmptyToast = (): IToast => { + return { + title: '', + text: '', + components: [], + show: false, + persist: false, + type: '', + }; +}; + +const setToastPlaceholder = () => { + throw new Error('setToast called outside UIContext'); +}; + +const setShowFeedbackPlaceholder = () => { + throw new Error('setShowFeedback called outside UIContext'); +}; + +const UIContext = React.createContext({ + toastData: createEmptyToast(), + setToast: setToastPlaceholder, + showFeedback: false, + setShowFeedback: setShowFeedbackPlaceholder, +}); export default UIContext; diff --git a/frontend/src/hooks/api/actions/useAdminUsersApi/errorHandlers.ts b/frontend/src/hooks/api/actions/useAdminUsersApi/errorHandlers.ts index 0761089d09..4f51e2d289 100644 --- a/frontend/src/hooks/api/actions/useAdminUsersApi/errorHandlers.ts +++ b/frontend/src/hooks/api/actions/useAdminUsersApi/errorHandlers.ts @@ -7,73 +7,62 @@ import { } from '../../../../utils/api-utils'; export const handleBadRequest = async ( - setErrors?: Dispatch>, - res?: Response, - requestId?: string + setErrors: Dispatch>, + res: Response, + requestId: string ) => { - if (!setErrors || !requestId) return; - if (res) { - const data = await res.json(); - const message = data.isJoi ? data.details[0].message : data[0].msg; + const data = await res.json(); + const message = data.isJoi ? data.details[0].message : data[0].msg; - setErrors(prev => ({ - ...prev, - [requestId]: message, - })); - } + setErrors(prev => ({ + ...prev, + [requestId]: message, + })); throw new Error(); }; export const handleNotFound = ( - setErrors?: Dispatch>, - res?: Response, - requestId?: string + setErrors: Dispatch>, + res: Response, + requestId: string ) => { - if (!setErrors || !requestId) return; - setErrors(prev => ({ ...prev, [requestId]: 'Could not find the requested resource.', })); - throw new NotFoundError(res?.status); + throw new NotFoundError(res.status); }; export const handleUnauthorized = async ( - setErrors?: Dispatch>, - res?: Response, - requestId?: string + setErrors: Dispatch>, + res: Response, + requestId: string ) => { - if (!setErrors || !requestId) return; - if (res) { - const data = await res.json(); - const message = data.isJoi ? data.details[0].message : data[0].msg; + const data = await res.json(); + const message = data.isJoi ? data.details[0].message : data[0].msg; - setErrors(prev => ({ - ...prev, - [requestId]: message, - })); - } + setErrors(prev => ({ + ...prev, + [requestId]: message, + })); - throw new AuthenticationError(res?.status); + throw new AuthenticationError(res.status); }; export const handleForbidden = async ( - setErrors?: Dispatch>, - res?: Response, - requestId?: string + setErrors: Dispatch>, + res: Response, + requestId: string ) => { - if (!setErrors || !requestId) return; - if (res) { - const data = await res.json(); - const message = data.isJoi ? data.details[0].message : data[0].msg; + const data = await res.json(); + const message = data.isJoi ? data.details[0].message : data[0].msg; - setErrors(prev => ({ - ...prev, - [requestId]: message, - })); - } + setErrors(prev => ({ + ...prev, + [requestId]: message, + })); - throw new ForbiddenError(res?.status); + throw new ForbiddenError(res.status); }; diff --git a/frontend/src/hooks/api/actions/useApi/useApi.ts b/frontend/src/hooks/api/actions/useApi/useApi.ts index 653315aaec..8360e4fcb2 100644 --- a/frontend/src/hooks/api/actions/useApi/useApi.ts +++ b/frontend/src/hooks/api/actions/useApi/useApi.ts @@ -1,4 +1,4 @@ -import { useState, Dispatch, SetStateAction } from 'react'; +import { Dispatch, SetStateAction, useState } from 'react'; import { BAD_REQUEST, FORBIDDEN, @@ -15,27 +15,17 @@ import { } from '../../../../utils/api-utils'; import { formatApiPath } from '../../../../utils/format-path'; +type ApiErrorHandler = ( + setErrors: Dispatch>, + res: Response, + requestId: string +) => void; + interface IUseAPI { - handleBadRequest?: ( - setErrors?: Dispatch>, - res?: Response, - requestId?: string - ) => void; - handleNotFound?: ( - setErrors?: Dispatch>, - res?: Response, - requestId?: string - ) => void; - handleUnauthorized?: ( - setErrors?: Dispatch>, - res?: Response, - requestId?: string - ) => void; - handleForbidden?: ( - setErrors?: Dispatch>, - res?: Response, - requestId?: string - ) => void; + handleBadRequest?: ApiErrorHandler; + handleNotFound?: ApiErrorHandler; + handleUnauthorized?: ApiErrorHandler; + handleForbidden?: ApiErrorHandler; propagateErrors?: boolean; } @@ -55,8 +45,8 @@ const useAPI = ({ }; const makeRequest = async ( - apiCaller: any, - requestId?: string, + apiCaller: () => Promise, + requestId: string, loadingOn: boolean = true ): Promise => { if (loadingOn) { @@ -97,7 +87,7 @@ const useAPI = ({ }; }; - const handleResponses = async (res: Response, requestId?: string) => { + const handleResponses = async (res: Response, requestId: string) => { if (res.status === BAD_REQUEST) { if (handleBadRequest) { return handleBadRequest(setErrors, res, requestId); @@ -147,7 +137,7 @@ const useAPI = ({ if (res.status === FORBIDDEN) { if (handleForbidden) { - return handleForbidden(setErrors); + return handleForbidden(setErrors, res, requestId); } else { setErrors(prev => ({ ...prev, diff --git a/frontend/src/hooks/api/getters/useFeature/defaultFeature.ts b/frontend/src/hooks/api/getters/useFeature/defaultFeature.ts index 2cd0441640..9dc8ff1764 100644 --- a/frontend/src/hooks/api/getters/useFeature/defaultFeature.ts +++ b/frontend/src/hooks/api/getters/useFeature/defaultFeature.ts @@ -11,4 +11,5 @@ export const defaultFeature: IFeatureToggle = { project: '', variants: [], description: '', + impressionData: false, }; diff --git a/frontend/src/hooks/api/getters/useFeatureMetrics/useFeatureMetrics.ts b/frontend/src/hooks/api/getters/useFeatureMetrics/useFeatureMetrics.ts index 21741fe095..252016e8ba 100644 --- a/frontend/src/hooks/api/getters/useFeatureMetrics/useFeatureMetrics.ts +++ b/frontend/src/hooks/api/getters/useFeatureMetrics/useFeatureMetrics.ts @@ -4,7 +4,10 @@ import useSWR, { mutate, SWRConfiguration } from 'swr'; import { IFeatureMetrics } from '../../../../interfaces/featureToggle'; import handleErrorResponses from '../httpErrorResponseHandler'; -const emptyMetrics = { lastHourUsage: [], seenApplications: [] }; +const emptyMetrics: IFeatureMetrics = { + lastHourUsage: [], + seenApplications: [], +}; const useFeatureMetrics = ( projectId: string, diff --git a/frontend/src/hooks/useFeaturesSort.ts b/frontend/src/hooks/useFeaturesSort.ts index a72c2fc295..fd5415c246 100644 --- a/frontend/src/hooks/useFeaturesSort.ts +++ b/frontend/src/hooks/useFeaturesSort.ts @@ -109,17 +109,19 @@ const sortByLastSeen = ( ): IFeatureToggle[] => { return [...features].sort((a, b) => a.lastSeenAt && b.lastSeenAt - ? a.lastSeenAt.localeCompare(b.lastSeenAt) - : 0 + ? b.lastSeenAt.localeCompare(a.lastSeenAt) + : a.lastSeenAt + ? -1 + : b.lastSeenAt + ? 1 + : b.createdAt.localeCompare(a.createdAt) ); }; const sortByCreated = ( features: Readonly ): IFeatureToggle[] => { - return [...features].sort((a, b) => - new Date(a.createdAt) > new Date(b.createdAt) ? -1 : 1 - ); + return [...features].sort((a, b) => b.createdAt.localeCompare(a.createdAt)); }; const sortByName = (features: Readonly): IFeatureToggle[] => { diff --git a/frontend/src/hooks/useToast.tsx b/frontend/src/hooks/useToast.tsx index 161c697383..4033b3e023 100644 --- a/frontend/src/hooks/useToast.tsx +++ b/frontend/src/hooks/useToast.tsx @@ -1,27 +1,17 @@ import { useContext } from 'react'; -import UIContext, { IToastData } from '../contexts/UIContext'; - -interface IToastOptions { - title: string; - text?: string; - type: string; - persist?: boolean; - confetti?: boolean; - autoHideDuration?: number; - show?: boolean; -} +import UIContext from '../contexts/UIContext'; +import { IToast } from '../interfaces/toast'; const useToast = () => { - // @ts-expect-error const { setToast } = useContext(UIContext); const hideToast = () => - setToast((prev: IToastData) => ({ + setToast((prev: IToast) => ({ ...prev, show: false, })); - const setToastApiError = (errorText: string, overrides?: IToastOptions) => { + const setToastApiError = (errorText: string, overrides?: IToast) => { setToast({ title: 'Something went wrong', text: `We had trouble talking to our API. Here's why: ${errorText}`, @@ -32,11 +22,11 @@ const useToast = () => { }); }; - const setToastData = (options: IToastOptions) => { - if (options.persist) { - setToast({ ...options, show: true }); + const setToastData = (toast: IToast) => { + if (toast.persist) { + setToast({ ...toast, show: true }); } else { - setToast({ ...options, show: true, autoHideDuration: 6000 }); + setToast({ ...toast, show: true, autoHideDuration: 6000 }); } }; diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 4731d0294b..5c8fff500a 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -1,6 +1,4 @@ -import './wdyr'; import 'whatwg-fetch'; - import './app.css'; import ReactDOM from 'react-dom'; @@ -9,7 +7,6 @@ import { ThemeProvider, CssBaseline } from '@material-ui/core'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { StylesProvider } from '@material-ui/core/styles'; - import mainTheme from './themes/main-theme'; import { App } from './component/App'; import ScrollToTop from './component/scroll-to-top'; diff --git a/frontend/src/interfaces/featureToggle.ts b/frontend/src/interfaces/featureToggle.ts index eac5c72ed6..64e18ae7de 100644 --- a/frontend/src/interfaces/featureToggle.ts +++ b/frontend/src/interfaces/featureToggle.ts @@ -57,7 +57,7 @@ export interface IOverride { } export interface IPayload { - name: string; + type: string; value: string; } @@ -69,15 +69,8 @@ export interface IFeatureEnvironmentMetrics { } export interface IFeatureMetrics { - version: number; - maturity: string; - lastHourUsage: IFeatureEnvironmentMetrics[]; - seenApplications: string[]; -} - -export interface IFeatureMetrics { - version: number; - maturity: string; + version?: number; + maturity?: string; lastHourUsage: IFeatureEnvironmentMetrics[]; seenApplications: string[]; } diff --git a/frontend/src/interfaces/project.ts b/frontend/src/interfaces/project.ts index 835ba2845b..f29b7c266b 100644 --- a/frontend/src/interfaces/project.ts +++ b/frontend/src/interfaces/project.ts @@ -11,6 +11,7 @@ export interface IProjectCard { } export interface IProject { + id?: string; members: number; version: string; name: string; diff --git a/frontend/src/interfaces/strategy.ts b/frontend/src/interfaces/strategy.ts index 876e5def38..8ff2d8ba42 100644 --- a/frontend/src/interfaces/strategy.ts +++ b/frontend/src/interfaces/strategy.ts @@ -11,7 +11,7 @@ export interface IStrategy { editable: boolean; deprecated: boolean; description: string; - parameters: IParameter; + parameters: IParameter[]; } export interface IConstraint { @@ -24,6 +24,7 @@ export interface IParameter { groupId?: string; rollout?: number; stickiness?: string; + [index: string]: any; } diff --git a/frontend/src/interfaces/toast.ts b/frontend/src/interfaces/toast.ts new file mode 100644 index 0000000000..1f4e21685f --- /dev/null +++ b/frontend/src/interfaces/toast.ts @@ -0,0 +1,10 @@ +export interface IToast { + type: string; + title: string; + text?: string; + components?: JSX.Element[]; + show?: boolean; + persist?: boolean; + confetti?: boolean; + autoHideDuration?: number; +} diff --git a/frontend/src/testIds.js b/frontend/src/testIds.js index 21c8d21648..ee11526678 100644 --- a/frontend/src/testIds.js +++ b/frontend/src/testIds.js @@ -39,3 +39,4 @@ export const CLOSE_SPLASH = 'CLOSE_SPLASH'; /* GENERAL */ export const INPUT_ERROR_TEXT = 'INPUT_ERROR_TEXT'; +export const HEADER_USER_AVATAR = 'HEADER_USER_AVATAR'; diff --git a/frontend/src/themes/main-theme.ts b/frontend/src/themes/main-theme.ts index e5faf1c906..745f5fd80f 100644 --- a/frontend/src/themes/main-theme.ts +++ b/frontend/src/themes/main-theme.ts @@ -10,6 +10,10 @@ declare module '@material-ui/core/styles/makeStyles' { interface Theme extends MainTheme {} } +declare module '@material-ui/core/styles/useTheme' { + interface Theme extends MainTheme {} +} + const mainTheme = { typography: { fontFamily: ['Sen', 'Roboto, sans-serif'], diff --git a/frontend/src/utils/api-utils.ts b/frontend/src/utils/api-utils.ts index ba356f943e..00cc787ca1 100644 --- a/frontend/src/utils/api-utils.ts +++ b/frontend/src/utils/api-utils.ts @@ -1,21 +1,24 @@ -export const headers = { - Accept: 'application/json', - 'Content-Type': 'application/json', -}; +export interface IErrorBody { + details?: { message: string }[]; +} export class AuthenticationError extends Error { - constructor(statusCode, body) { + statusCode: number; + + constructor(statusCode: number) { super('Authentication required'); this.name = 'AuthenticationError'; this.statusCode = statusCode; - this.body = body; } } export class ForbiddenError extends Error { - constructor(statusCode, body = {}) { + statusCode: number; + body: IErrorBody; + + constructor(statusCode: number, body: IErrorBody = {}) { super( - body.details?.length > 0 + body.details?.length ? body.details[0].message : 'You cannot perform this action' ); @@ -26,10 +29,11 @@ export class ForbiddenError extends Error { } export class BadRequestError extends Error { - constructor(statusCode, body = {}) { - super( - body.details?.length > 0 ? body.details[0].message : 'Bad request' - ); + statusCode: number; + body: IErrorBody; + + constructor(statusCode: number, body: IErrorBody = {}) { + super(body.details?.length ? body.details[0].message : 'Bad request'); this.name = 'BadRequestError'; this.statusCode = statusCode; this.body = body; @@ -37,7 +41,9 @@ export class BadRequestError extends Error { } export class NotFoundError extends Error { - constructor(statusCode) { + statusCode: number; + + constructor(statusCode: number) { super( 'The requested resource could not be found but may be available in the future' ); @@ -45,3 +51,8 @@ export class NotFoundError extends Error { this.statusCode = statusCode; } } + +export const headers = { + Accept: 'application/json', + 'Content-Type': 'application/json', +}; diff --git a/frontend/src/utils/format-date.ts b/frontend/src/utils/format-date.ts new file mode 100644 index 0000000000..0bc10ff721 --- /dev/null +++ b/frontend/src/utils/format-date.ts @@ -0,0 +1,34 @@ +export const formatDateYMDHMS = ( + date: number | string | Date, + locale: string +): string => { + return new Date(date).toLocaleString(locale, { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + }); +}; + +export const formatDateYMD = ( + date: number | string | Date, + locale: string +): string => { + return new Date(date).toLocaleString(locale, { + day: '2-digit', + month: '2-digit', + year: 'numeric', + }); +}; + +export const formatDateHM = ( + date: number | string | Date, + locale: string +): string => { + return new Date(date).toLocaleString(locale, { + hour: '2-digit', + minute: '2-digit', + }); +}; diff --git a/frontend/src/utils/project-filter-generator.ts b/frontend/src/utils/project-filter-generator.ts index 88cf2d8e9b..68ddcc5aa2 100644 --- a/frontend/src/utils/project-filter-generator.ts +++ b/frontend/src/utils/project-filter-generator.ts @@ -11,14 +11,15 @@ export const projectFilterGenerator = ( ) => { let admin = false; const permissionMap: objectIdx = permissions.reduce( - (acc: objectIdx, current: IPermission) => { - if (current.permission === ADMIN) { + (acc: objectIdx, p: IPermission) => { + if (p.permission === ADMIN) { admin = true; } - if (current.permission === matcherPermission) { - acc[current.project] = matcherPermission; + if (p.project && p.permission === matcherPermission) { + acc[p.project] = matcherPermission; } + return acc; }, {} diff --git a/frontend/src/utils/resolve-default-param-value.ts b/frontend/src/utils/resolve-default-param-value.ts index 136ac6c2b0..6d5c50e1ac 100644 --- a/frontend/src/utils/resolve-default-param-value.ts +++ b/frontend/src/utils/resolve-default-param-value.ts @@ -1,4 +1,7 @@ -export const resolveDefaultParamValue = (name, featureToggleName) => { +export const resolveDefaultParamValue = ( + name: string, + featureToggleName: string +): string | number => { switch (name) { case 'percentage': case 'rollout': diff --git a/frontend/src/utils/route-path-helpers.ts b/frontend/src/utils/route-path-helpers.ts index a20737ee60..9c56dc141f 100644 --- a/frontend/src/utils/route-path-helpers.ts +++ b/frontend/src/utils/route-path-helpers.ts @@ -5,7 +5,7 @@ export const getTogglePath = (projectId: string, featureToggleName: string) => { export const getCreateTogglePath = ( projectId: string, newPath: boolean = false, - query?: Object + query?: Record ) => { const path = `/projects/${projectId}/create-toggle`; @@ -16,9 +16,11 @@ export const getCreateTogglePath = ( return acc; }, ''); } + if (queryString) { return `${path}?${queryString}`; } + return path; }; diff --git a/frontend/src/wdyr.ts b/frontend/src/wdyr.ts deleted file mode 100644 index 6d7fa887d0..0000000000 --- a/frontend/src/wdyr.ts +++ /dev/null @@ -1,10 +0,0 @@ -/// - -import React from 'react'; - -if (process.env.NODE_ENV === 'development') { - const whyDidYouRender = require('@welldone-software/why-did-you-render'); - whyDidYouRender(React, { - trackAllPureComponents: false, - }); -} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 9fd9a61447..224c1144a7 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -28,11 +28,6 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.12.tgz" integrity sha512-3eJJ841uKxeV8dcN/2yGEUy+RfgQspPEgQat85umsE1rotuquQ2AbIub4S6j7c50a2d+4myc+zSlnXeIHrOnhQ== -"@babel/compat-data@^7.13.15": - version "7.14.0" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz" - integrity sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q== - "@babel/core@7.12.3": version "7.12.3" resolved "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz" @@ -76,27 +71,6 @@ semver "^6.3.0" source-map "^0.5.0" -"@babel/core@^7.6.0": - version "7.14.0" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.14.0.tgz" - integrity sha512-8YqpRig5NmIHlMLw09zMlPTvUVMILjqCOtVgu+TVNWEBvy9b5I3RRyhqnrV4hjgEK7n8P9OqvkWJAFmEL6Wwfw== - dependencies: - "@babel/code-frame" "^7.12.13" - "@babel/generator" "^7.14.0" - "@babel/helper-compilation-targets" "^7.13.16" - "@babel/helper-module-transforms" "^7.14.0" - "@babel/helpers" "^7.14.0" - "@babel/parser" "^7.14.0" - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.14.0" - "@babel/types" "^7.14.0" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.1.2" - semver "^6.3.0" - source-map "^0.5.0" - "@babel/generator@^7.12.1", "@babel/generator@^7.13.9": version "7.13.9" resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz" @@ -106,15 +80,6 @@ jsesc "^2.5.1" source-map "^0.5.0" -"@babel/generator@^7.14.0": - version "7.14.1" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.14.1.tgz" - integrity sha512-TMGhsXMXCP/O1WtQmZjpEYDhCYC9vFhayWZPJSZCGkPJgUqX0rF0wwtrYvnzVxIjcF80tkUertXVk5cwqi5cAQ== - dependencies: - "@babel/types" "^7.14.1" - jsesc "^2.5.1" - source-map "^0.5.0" - "@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.12.13": version "7.12.13" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz" @@ -140,16 +105,6 @@ browserslist "^4.14.5" semver "^6.3.0" -"@babel/helper-compilation-targets@^7.13.16": - version "7.13.16" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz" - integrity sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA== - dependencies: - "@babel/compat-data" "^7.13.15" - "@babel/helper-validator-option" "^7.12.17" - browserslist "^4.14.5" - semver "^6.3.0" - "@babel/helper-create-class-features-plugin@^7.12.1", "@babel/helper-create-class-features-plugin@^7.13.0": version "7.13.11" resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.11.tgz" @@ -242,20 +197,6 @@ "@babel/traverse" "^7.13.13" "@babel/types" "^7.13.14" -"@babel/helper-module-transforms@^7.14.0": - version "7.14.0" - resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.0.tgz" - integrity sha512-L40t9bxIuGOfpIGA3HNkJhU9qYrf4y5A5LUSw7rGMSn+pcG8dfJ0g6Zval6YJGd2nEjI7oP00fRdnhLKndx6bw== - dependencies: - "@babel/helper-module-imports" "^7.13.12" - "@babel/helper-replace-supers" "^7.13.12" - "@babel/helper-simple-access" "^7.13.12" - "@babel/helper-split-export-declaration" "^7.12.13" - "@babel/helper-validator-identifier" "^7.14.0" - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.14.0" - "@babel/types" "^7.14.0" - "@babel/helper-optimise-call-expression@^7.12.13": version "7.12.13" resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz" @@ -313,11 +254,6 @@ resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz" integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== -"@babel/helper-validator-identifier@^7.14.0": - version "7.14.0" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz" - integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A== - "@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.12.17": version "7.12.17" resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz" @@ -342,15 +278,6 @@ "@babel/traverse" "^7.13.0" "@babel/types" "^7.13.0" -"@babel/helpers@^7.14.0": - version "7.14.0" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz" - integrity sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg== - dependencies: - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.14.0" - "@babel/types" "^7.14.0" - "@babel/highlight@^7.10.4", "@babel/highlight@^7.12.13": version "7.13.10" resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz" @@ -365,11 +292,6 @@ resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.13.13.tgz" integrity sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw== -"@babel/parser@^7.14.0": - version "7.14.1" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.14.1.tgz" - integrity sha512-muUGEKu8E/ftMTPlNp+mc6zL3E9zKWmF5sDHZ5MSsoTP9Wyz64AhEf9kD08xYJ7w6Hdcu8H550ircnPyWSIF0Q== - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.13.12": version "7.13.12" resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz" @@ -1235,20 +1157,6 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.14.0": - version "7.14.0" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.0.tgz" - integrity sha512-dZ/a371EE5XNhTHomvtuLTUyx6UEoJmYX+DT5zBCQN3McHemsuIaKKYqsc/fs26BEkHs/lBZy0J571LP5z9kQA== - dependencies: - "@babel/code-frame" "^7.12.13" - "@babel/generator" "^7.14.0" - "@babel/helper-function-name" "^7.12.13" - "@babel/helper-split-export-declaration" "^7.12.13" - "@babel/parser" "^7.14.0" - "@babel/types" "^7.14.0" - debug "^4.1.0" - globals "^11.1.0" - "@babel/types@^7.0.0", "@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.12.6", "@babel/types@^7.13.0", "@babel/types@^7.13.12", "@babel/types@^7.13.13", "@babel/types@^7.13.14", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": version "7.13.14" resolved "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz" @@ -1258,14 +1166,6 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" -"@babel/types@^7.14.0", "@babel/types@^7.14.1": - version "7.14.1" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.14.1.tgz" - integrity sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA== - dependencies: - "@babel/helper-validator-identifier" "^7.14.0" - to-fast-properties "^2.0.0" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" @@ -1279,15 +1179,6 @@ exec-sh "^0.3.2" minimist "^1.2.0" -"@craco/craco@^5.5.0": - version "5.9.0" - resolved "https://registry.npmjs.org/@craco/craco/-/craco-5.9.0.tgz" - integrity sha512-2Q8gIB4W0/nPiUxr9iAKUhGsFlXYN0/wngUdK1VWtfV2NtBv+yllNn2AjieaLbttgpQinuOYmDU65vocC0NMDg== - dependencies: - cross-spawn "^7.0.0" - lodash "^4.17.15" - webpack-merge "^4.2.2" - "@csstools/convert-colors@^1.4.0": version "1.4.0" resolved "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz" @@ -1298,10 +1189,10 @@ resolved "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz" integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== -"@cypress/request@^2.88.6": - version "2.88.6" - resolved "https://registry.npmjs.org/@cypress/request/-/request-2.88.6.tgz" - integrity sha512-z0UxBE/+qaESAHY9p9sM2h8Y4XqtsbDCt0/DPOrqA/RZgKi4PkxdpXyK4wCCnSk1xHqWHZZAE+gV6aDAR6+caQ== +"@cypress/request@^2.88.10": + version "2.88.10" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce" + integrity sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -1310,8 +1201,7 @@ extend "~3.0.2" forever-agent "~0.6.1" form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" + http-signature "~1.3.6" is-typedarray "~1.0.0" isstream "~0.1.2" json-stringify-safe "~5.0.1" @@ -1872,6 +1762,20 @@ "@svgr/plugin-svgo" "^5.5.0" loader-utils "^2.0.0" +"@testing-library/dom@8.11.3": + version "8.11.3" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.11.3.tgz#38fd63cbfe14557021e88982d931e33fb7c1a808" + integrity sha512-9LId28I+lx70wUiZjLvi1DB/WT2zGOxUh46glrSNMaWVx849kKAluezVzZrXJfTKKoQTmEOutLes/bHg4Bj3aA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^4.2.0" + aria-query "^5.0.0" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.4.4" + pretty-format "^27.0.2" + "@testing-library/dom@^8.0.0": version "8.7.0" resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-8.7.0.tgz" @@ -1927,7 +1831,7 @@ resolved "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz" integrity sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg== -"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.3", "@types/babel__core@^7.1.7": +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": version "7.1.14" resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz" integrity sha512-zGZJzzBUVDo/eV6KgbE0f0ZI7dInEYvo12Rb70uNQDshC3SkRMb67ja0GgRHZgAX3Za6rhaWlvbDO8rrGyAb1g== @@ -2092,10 +1996,10 @@ resolved "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz" integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw== -"@types/node@14.18.12": - version "14.18.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.12.tgz#0d4557fd3b94497d793efd4e7d92df2f83b4ef24" - integrity sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A== +"@types/node@17.0.18": + version "17.0.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.18.tgz#3b4fed5cfb58010e3a2be4b6e74615e4847f1074" + integrity sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA== "@types/node@^14.14.31": version "14.17.19" @@ -2134,13 +2038,6 @@ dependencies: "@types/react" "*" -"@types/react-outside-click-handler@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@types/react-outside-click-handler/-/react-outside-click-handler-1.3.1.tgz#e4772ba550e1a548468203194d2615d8f06acdf9" - integrity sha512-0BNan5zIIDyO5k9LFSG+60ZxQ/0wf+LTF9BJx3oOUdOaJlZk6RCe52jRB75mlvLLJx2YLa61+NidOwBfptWMKw== - dependencies: - "@types/react" "*" - "@types/react-router-dom@5.3.3": version "5.3.3" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" @@ -2209,10 +2106,10 @@ resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz" integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA== -"@types/sinonjs__fake-timers@^6.0.2": - version "6.0.4" - resolved "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.4.tgz" - integrity sha512-IFQTJARgMUBF+xVd2b+hIgXWrZEjND3vJtRCvIelcFB5SIXfjV4bOHbHJ0eXKh+0COrBRc8MqteKAz/j88rE0A== +"@types/sinonjs__fake-timers@8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" + integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== "@types/sizzle@^2.3.2": version "2.3.3" @@ -2257,18 +2154,6 @@ "@types/source-list-map" "*" source-map "^0.7.3" -"@types/webpack@^4.39.2": - version "4.41.28" - resolved "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.28.tgz" - integrity sha512-Nn84RAiJjKRfPFFCVR8LC4ueTtTdfWAMZ03THIzZWRJB+rX24BD3LqPSFnbMscWauEsT4segAsylPDIaZyZyLQ== - dependencies: - "@types/anymatch" "*" - "@types/node" "*" - "@types/tapable" "^1" - "@types/uglify-js" "*" - "@types/webpack-sources" "*" - source-map "^0.6.0" - "@types/webpack@^4.41.8": version "4.41.27" resolved "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.27.tgz" @@ -2559,13 +2444,6 @@ "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" -"@welldone-software/why-did-you-render@6.2.3": - version "6.2.3" - resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-6.2.3.tgz#cdd5e27cf25b7e767c1c0b0e8808f67d3f6be833" - integrity sha512-FQgi90jvC9uw2aALlonJfqaWOvU5UUBBVvdAnS2iryXwCc4YJkKsPJY5Y/LzaND3OIyk8XGUn1vTRn6hcem28Q== - dependencies: - lodash "^4" - "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz" @@ -2643,21 +2521,6 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -airbnb-prop-types@^2.15.0: - version "2.16.0" - resolved "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz" - integrity sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg== - dependencies: - array.prototype.find "^2.1.1" - function.prototype.name "^1.1.2" - is-regex "^1.1.0" - object-is "^1.1.2" - object.assign "^4.1.0" - object.entries "^1.1.2" - prop-types "^15.7.2" - prop-types-exact "^1.2.0" - react-is "^16.13.1" - ajv-errors@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz" @@ -2871,14 +2734,6 @@ array-unique@^0.3.2: resolved "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -array.prototype.find@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.1.tgz" - integrity sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.4" - array.prototype.flat@^1.2.3: version "1.2.4" resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz" @@ -3216,7 +3071,7 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.1.tgz" integrity sha512-qyTw2VPYRg31SlVU5WDdvCSyMTJ3YSP4Kz2CidWZFPFawCiHJdCyKyZeXIGMJ5ebMQYXEI56kDR8tcnDkbZstg== -base64-js@^1.0.2: +base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -3490,6 +3345,14 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" +buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + builtin-modules@^3.1.0: version "3.2.0" resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz" @@ -3831,15 +3694,14 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-table3@~0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz" - integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== +cli-table3@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.1.tgz#36ce9b7af4847f288d3cdd081fbd09bf7bd237b8" + integrity sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA== dependencies: - object-assign "^4.1.0" string-width "^4.2.0" optionalDependencies: - colors "^1.1.2" + colors "1.4.0" cli-truncate@^2.1.0: version "2.1.0" @@ -3949,9 +3811,9 @@ colorette@^1.4.0: resolved "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz" integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== -colors@^1.1.2: +colors@1.4.0: version "1.4.0" - resolved "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== combined-stream@^1.0.6, combined-stream@~1.0.6: @@ -4048,11 +3910,6 @@ console-browserify@^1.1.0: resolved "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz" integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== -"consolidated-events@^1.1.1 || ^2.0.0": - version "2.0.2" - resolved "https://registry.npmjs.org/consolidated-events/-/consolidated-events-2.0.2.tgz" - integrity sha512-2/uRVMdRypf5z/TW/ncD/66l75P5hH2vM/GR8Jf8HLc2xnfJtmina6F6du8+v4Z2vTrMo7jC+W1tmEEuuELgkQ== - constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz" @@ -4181,17 +4038,6 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" -craco@0.0.3: - version "0.0.3" - resolved "https://registry.npmjs.org/craco/-/craco-0.0.3.tgz" - integrity sha512-eeibbwJm1CTf/j3xvNgNmsRS7abegp4Cfm5qtn5nE9/0JjZRas+FHj8IlT8FMFWR0XOyZFGcWZgzaTU19DNGoQ== - dependencies: - "@babel/core" "^7.6.0" - "@craco/craco" "^5.5.0" - "@types/babel__core" "^7.1.3" - "@types/webpack" "^4.39.2" - webpack "^4.41.0" - create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz" @@ -4311,20 +4157,6 @@ css-loader@4.3.0: schema-utils "^2.7.1" semver "^7.3.2" -css-loader@6.6.0: - version "6.6.0" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.6.0.tgz#c792ad5510bd1712618b49381bd0310574fafbd3" - integrity sha512-FK7H2lisOixPT406s5gZM1S3l8GrfhEBT3ZiL2UX1Ng1XWs0y2GPllz/OTyvbaHe12VgQrIXIzuEGVlbUhodqg== - dependencies: - icss-utils "^5.1.0" - postcss "^8.4.5" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.2.0" - semver "^7.3.5" - css-prefers-color-scheme@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz" @@ -4522,24 +4354,25 @@ cyclist@^1.0.1: resolved "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -cypress@8.7.0: - version "8.7.0" - resolved "https://registry.npmjs.org/cypress/-/cypress-8.7.0.tgz" - integrity sha512-b1bMC3VQydC6sXzBMFnSqcvwc9dTZMgcaOzT0vpSD+Gq1yFc+72JDWi55sfUK5eIeNLAtWOGy1NNb6UlhMvB+Q== +cypress@9.5.1: + version "9.5.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.5.1.tgz#51162f3688cedf5ffce311b914ef49a7c1ece076" + integrity sha512-H7lUWB3Svr44gz1rNnj941xmdsCljXoJa2cDneAltjI9leKLMQLm30x6jLlpQ730tiVtIbW5HdUmBzPzwzfUQg== dependencies: - "@cypress/request" "^2.88.6" + "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" "@types/node" "^14.14.31" - "@types/sinonjs__fake-timers" "^6.0.2" + "@types/sinonjs__fake-timers" "8.1.1" "@types/sizzle" "^2.3.2" arch "^2.2.0" blob-util "^2.0.2" bluebird "^3.7.2" + buffer "^5.6.0" cachedir "^2.3.0" chalk "^4.1.0" check-more-types "^2.24.0" cli-cursor "^3.1.0" - cli-table3 "~0.6.0" + cli-table3 "~0.6.1" commander "^5.1.0" common-tags "^1.8.0" dayjs "^1.10.4" @@ -4562,12 +4395,11 @@ cypress@8.7.0: ospath "^1.2.2" pretty-bytes "^5.6.0" proxy-from-env "1.0.0" - ramda "~0.27.1" request-progress "^3.0.0" + semver "^7.3.2" supports-color "^8.1.1" tmp "~0.2.1" untildify "^4.0.0" - url "^0.11.0" yauzl "^2.10.0" d@1, d@^1.0.1: @@ -4857,18 +4689,16 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -document.contains@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/document.contains/-/document.contains-1.0.2.tgz" - integrity sha512-YcvYFs15mX8m3AO1QNQy3BlIpSMfNRj3Ujk2BEJxsZG+HZf7/hZ6jr7mDpXrF8q+ff95Vef5yjhiZxm8CGJr6Q== - dependencies: - define-properties "^1.1.3" - dom-accessibility-api@^0.5.6: version "0.5.7" resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.7.tgz" integrity sha512-ml3lJIq9YjUfM9TUnEPvEYWFSwivwIGBPKpewX7tii7fwCazA8yCioGdqQcNsItPpfFvSJ3VIdMQPj60LJhcQA== +dom-accessibility-api@^0.5.9: + version "0.5.11" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.11.tgz#79d5846c4f90eba3e617d9031e921de9324f84ed" + integrity sha512-7X6GvzjYf4yTdRKuCVScV+aA9Fvh5r8WzWrXBH9w82ZWB/eYDMGCnazoC/YAqAzUJWHzLOnZqr46K3iEyUhUvw== + dom-converter@^0.2: version "0.2.0" resolved "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz" @@ -5047,7 +4877,7 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^4.3.0, enhanced-resolve@^4.5.0: +enhanced-resolve@^4.3.0: version "4.5.0" resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz" integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== @@ -5094,7 +4924,7 @@ error-stack-parser@^2.0.6: dependencies: stackframe "^1.1.1" -es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: +es-abstract@^1.17.2, es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: version "1.18.0" resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz" integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== @@ -5824,9 +5654,9 @@ flush-write-stream@^1.0.0: readable-stream "^2.3.6" follow-redirects@^1.0.0: - version "1.13.3" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz" - integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA== + version "1.14.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" + integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== for-in@^1.0.2: version "1.0.2" @@ -5953,26 +5783,11 @@ function-bind@^1.1.1: resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function.prototype.name@^1.1.2: - version "1.1.4" - resolved "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.4.tgz" - integrity sha512-iqy1pIotY/RmhdFZygSSlW0wko2yxkSCKqsuv4pr8QESohpYyG/Z7B/XXvPRKTJS//960rgguE5mSRUsDdaJrQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.2" - functions-have-names "^1.2.2" - functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -functions-have-names@^1.2.2: - version "1.2.2" - resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.2.tgz" - integrity sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA== - gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" @@ -6466,6 +6281,15 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +http-signature@~1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" + integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw== + dependencies: + assert-plus "^1.0.0" + jsprim "^2.0.2" + sshpk "^1.14.1" + https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz" @@ -6495,11 +6319,6 @@ icss-utils@^4.0.0, icss-utils@^4.1.1: dependencies: postcss "^7.0.14" -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== - identity-obj-proxy@3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz" @@ -6507,7 +6326,7 @@ identity-obj-proxy@3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.4: +ieee754@^1.1.13, ieee754@^1.1.4: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -6974,7 +6793,7 @@ is-potential-custom-element-name@^1.0.0: resolved "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz" integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c= -is-regex@^1.0.4, is-regex@^1.1.0, is-regex@^1.1.2: +is-regex@^1.0.4, is-regex@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz" integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg== @@ -7671,6 +7490,11 @@ json-schema@0.2.3: resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz" integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" @@ -7726,6 +7550,16 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jsprim@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" + integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + jss-plugin-camel-case@^10.5.1: version "10.6.0" resolved "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.6.0.tgz" @@ -7993,11 +7827,6 @@ lodash.flatten@^4.4.0: resolved "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz" integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= -lodash.flow@3.5.0: - version "3.5.0" - resolved "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz" - integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o= - lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" @@ -8033,7 +7862,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -"lodash@>=3.5 <5", lodash@^4, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.7.0: +"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.7.0: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -8440,14 +8269,9 @@ nanocolors@^0.2.2: integrity sha512-SFNdALvzW+rVlzqexid6epYdt8H9Zol7xDoQarioEFcFN0JHo4CYNztAxmtfgGTVRCmFlEOqqhBpoFGKqSAMug== nanoid@^3.1.25: - version "3.1.28" - resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.1.28.tgz" - integrity sha512-gSu9VZ2HtmoKYe/lmyPFES5nknFrHa+/DT9muUFWFMi6Jh9E1I7bkvlQ8xxf1Kos9pi9o8lBnIOkatMhKX/YUw== - -nanoid@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" - integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA== + version "3.3.1" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" + integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== nanomatch@^1.2.9: version "1.2.13" @@ -8664,7 +8488,7 @@ object-inspect@^1.9.0: resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz" integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== -object-is@^1.0.1, object-is@^1.1.2: +object-is@^1.0.1: version "1.1.5" resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz" integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== @@ -8694,7 +8518,7 @@ object.assign@^4.1.0, object.assign@^4.1.2: has-symbols "^1.0.1" object-keys "^1.1.1" -object.entries@^1.1.0, object.entries@^1.1.2, object.entries@^1.1.3: +object.entries@^1.1.0, object.entries@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.3.tgz" integrity sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg== @@ -9094,11 +8918,6 @@ performance-now@^2.1.0: resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2: version "2.2.2" resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz" @@ -9508,11 +9327,6 @@ postcss-modules-extract-imports@^2.0.0: dependencies: postcss "^7.0.5" -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== - postcss-modules-local-by-default@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz" @@ -9523,15 +9337,6 @@ postcss-modules-local-by-default@^3.0.3: postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - postcss-modules-scope@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz" @@ -9540,13 +9345,6 @@ postcss-modules-scope@^2.2.0: postcss "^7.0.6" postcss-selector-parser "^6.0.0" -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== - dependencies: - postcss-selector-parser "^6.0.4" - postcss-modules-values@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz" @@ -9555,13 +9353,6 @@ postcss-modules-values@^3.0.0: icss-utils "^4.0.0" postcss "^7.0.6" -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - postcss-nesting@^7.0.0: version "7.0.1" resolved "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz" @@ -9811,7 +9602,7 @@ postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: indexes-of "^1.0.1" uniq "^1.0.1" -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: +postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: version "6.0.4" resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz" integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw== @@ -9850,11 +9641,6 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== -postcss-value-parser@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz" @@ -9891,15 +9677,6 @@ postcss@^8.1.0: nanoid "^3.1.25" source-map-js "^0.6.2" -postcss@^8.4.5: - version "8.4.6" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.6.tgz#c5ff3c3c457a23864f32cb45ac9b741498a09ae1" - integrity sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA== - dependencies: - nanoid "^3.2.0" - picocolors "^1.0.0" - source-map-js "^1.0.2" - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" @@ -10005,15 +9782,6 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types-exact@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz" - integrity sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA== - dependencies: - has "^1.0.3" - object.assign "^4.1.0" - reflect.ownkeys "^0.2.0" - prop-types@15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -10147,7 +9915,7 @@ querystring@^0.2.0: querystringify@^2.1.1: version "2.2.0" - resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== queue-microtask@^1.2.2: @@ -10162,11 +9930,6 @@ raf@^3.4.1: dependencies: performance-now "^2.1.0" -ramda@~0.27.1: - version "0.27.1" - resolved "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz" - integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== - randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" @@ -10281,26 +10044,15 @@ react-hooks-global-state@1.0.2: resolved "https://registry.yarnpkg.com/react-hooks-global-state/-/react-hooks-global-state-1.0.2.tgz#37bbc3203a0be9f3ac0658abfd28dd7ce7ee166c" integrity sha512-UcWz+VjcUUCQ7bXGmOhanGII3j22zyPSjwJnQWeycxFYj/etBxIbz9xziEm4sv5+OqGuS7bzvpx24XkCxgJ7Bg== -react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6: - version "16.13.1" - resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" - integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== - -"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.1: +"react-is@^16.12.0 || ^17.0.0", "react-is@^16.8.0 || ^17.0.0", react-is@^17.0.1, react-is@^17.0.2: version "17.0.2" resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-outside-click-handler@1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/react-outside-click-handler/-/react-outside-click-handler-1.3.0.tgz" - integrity sha512-Te/7zFU0oHpAnctl//pP3hEAeobfeHMyygHB8MnjP6sX5OR8KHT1G3jmLsV3U9RnIYo+Yn+peJYWu+D5tUS8qQ== - dependencies: - airbnb-prop-types "^2.15.0" - consolidated-events "^1.1.1 || ^2.0.0" - document.contains "^1.0.1" - object.values "^1.1.0" - prop-types "^15.7.2" +react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: + version "16.13.1" + resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== react-refresh@^0.8.3: version "0.8.3" @@ -10402,15 +10154,23 @@ react-scripts@4.0.3: optionalDependencies: fsevents "^2.1.3" -react-test-renderer@16.14.0: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.14.0.tgz#e98360087348e260c56d4fe2315e970480c228ae" - integrity sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg== +react-shallow-renderer@^16.13.1: + version "16.14.1" + resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz#bf0d02df8a519a558fd9b8215442efa5c840e124" + integrity sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg== dependencies: object-assign "^4.1.1" - prop-types "^15.6.2" - react-is "^16.8.6" - scheduler "^0.19.1" + react-is "^16.12.0 || ^17.0.0" + +react-test-renderer@17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c" + integrity sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ== + dependencies: + object-assign "^4.1.1" + react-is "^17.0.2" + react-shallow-renderer "^16.13.1" + scheduler "^0.20.2" react-timeago@6.2.1: version "6.2.1" @@ -10538,11 +10298,6 @@ redux@^4.1.1: dependencies: "@babel/runtime" "^7.9.2" -reflect.ownkeys@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz" - integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA= - regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz" @@ -10719,7 +10474,7 @@ require-main-filename@^2.0.0: requires-port@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= resolve-cwd@^2.0.0: @@ -10983,10 +10738,10 @@ sass-loader@^10.0.5: schema-utils "^3.0.0" semver "^7.3.2" -sass@1.49.8: - version "1.49.8" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.8.tgz#9bbbc5d43d14862db07f1c04b786c9da9b641828" - integrity sha512-NoGOjvDDOU9og9oAxhRnap71QaTjjlzrvLnKecUJ3GxhaQBrV6e7gPuSPF28u1OcVAArVojPAe4ZhOXwwC4tGw== +sass@1.49.9: + version "1.49.9" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.9.tgz#b15a189ecb0ca9e24634bae5d1ebc191809712f9" + integrity sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -11004,14 +10759,6 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -scheduler@^0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" - integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - scheduler@^0.20.2: version "0.20.2" resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz" @@ -11079,9 +10826,9 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.2.1, semver@^7.3.2, semver@^7.3.5: +semver@^7.2.1, semver@^7.3.2: version "7.3.5" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== dependencies: lru-cache "^6.0.0" @@ -11326,7 +11073,7 @@ source-list-map@^2.0.0: resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: +"source-map-js@>=0.6.2 <2.0.0": version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== @@ -11449,6 +11196,21 @@ sprintf-js@~1.0.2: resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +sshpk@^1.14.1: + version "1.17.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" + integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + sshpk@^1.7.0: version "1.16.1" resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz" @@ -12168,10 +11930,10 @@ typedarray@^0.0.6: resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@4.5.5: - version "4.5.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" - integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== +typescript@4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4" + integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg== unbox-primitive@^1.0.0: version "1.0.1" @@ -12307,9 +12069,9 @@ url-loader@4.1.1: schema-utils "^3.0.0" url-parse@^1.4.3, url-parse@^1.5.1: - version "1.5.3" - resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz" - integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" @@ -12481,11 +12243,6 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" -web-vitals@2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-2.1.4.tgz#76563175a475a5e835264d373704f9dde718290c" - integrity sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg== - webidl-conversions@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz" @@ -12564,13 +12321,6 @@ webpack-manifest-plugin@2.2.0: object.entries "^1.1.0" tapable "^1.0.0" -webpack-merge@^4.2.2: - version "4.2.2" - resolved "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz" - integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== - dependencies: - lodash "^4.17.15" - webpack-sources@^1.1.0, webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz" @@ -12608,35 +12358,6 @@ webpack@4.44.2: watchpack "^1.7.4" webpack-sources "^1.4.1" -webpack@^4.41.0: - version "4.46.0" - resolved "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz" - integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/wasm-edit" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - acorn "^6.4.1" - ajv "^6.10.2" - ajv-keywords "^3.4.1" - chrome-trace-event "^1.0.2" - enhanced-resolve "^4.5.0" - eslint-scope "^4.0.3" - json-parse-better-errors "^1.0.2" - loader-runner "^2.4.0" - loader-utils "^1.2.3" - memory-fs "^0.4.1" - micromatch "^3.1.10" - mkdirp "^0.5.3" - neo-async "^2.6.1" - node-libs-browser "^2.2.1" - schema-utils "^1.0.0" - tapable "^1.1.3" - terser-webpack-plugin "^1.4.3" - watchpack "^1.7.4" - webpack-sources "^1.4.1" - websocket-driver@>=0.5.1, websocket-driver@^0.7.4: version "0.7.4" resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz"