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/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 58380489b0..5f99cb7c76 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -42,6 +42,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,10 +50,9 @@ "@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", @@ -63,7 +63,7 @@ "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", @@ -79,14 +79,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", + "typescript": "4.6.2", "web-vitals": "2.1.4" }, "jest": { @@ -117,6 +116,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..a9aa89e609 100644 --- a/frontend/src/component/common/PermissionSwitch/PermissionSwitch.tsx +++ b/frontend/src/component/common/PermissionSwitch/PermissionSwitch.tsx @@ -19,7 +19,7 @@ const PermissionSwitch = React.forwardRef< >((props, ref) => { const { permission, - tooltip = '', + tooltip, disabled, projectId, environmentId, @@ -39,9 +39,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 71602501e8..f6b07dae8b 100644 --- a/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx +++ b/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx @@ -2,6 +2,7 @@ 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; 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/FeatureToggleListNewItem/CreatedAt.tsx b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/CreatedAt.tsx index d20eb7aa44..f7ba855579 100644 --- a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/CreatedAt.tsx +++ b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/CreatedAt.tsx @@ -1,9 +1,6 @@ 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; @@ -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..39ffefbd8c 100644 --- a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/FeatureToggleListNewItem.tsx +++ b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/FeatureToggleListNewItem.tsx @@ -1,10 +1,12 @@ import { 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 { + IEnvironments, + IFeatureEnvironment, +} from '../../../../interfaces/featureToggle'; import useToast from '../../../../hooks/useToast'; import { getTogglePath } from '../../../../utils/route-path-helpers'; import { SyntheticEvent } from 'react-router/node_modules/@types/react'; @@ -25,8 +27,8 @@ interface IFeatureToggleListNewItemProps { type: string; environments: IFeatureEnvironment[]; projectId: string; - lastSeenAt?: Date; - createdAt: Date; + lastSeenAt?: string; + createdAt: string; } const FeatureToggleListNewItem = ({ 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/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..cde71a704d 100644 --- a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/AddFeatureVariant.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/AddFeatureVariant.tsx @@ -1,12 +1,11 @@ -import { useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; +import React, { useEffect, useState } from 'react'; import { + Button, FormControl, FormControlLabel, Grid, - TextField, InputAdornment, - Button, + TextField, Tooltip, } from '@material-ui/core'; import { Info } from '@material-ui/icons'; @@ -18,13 +17,16 @@ import ConditionallyRender from '../../../../../common/ConditionallyRender'; import GeneralSelect from '../../../../../common/GeneralSelect/GeneralSelect'; import { useCommonStyles } from '../../../../../../common.styles'; import Dialogue from '../../../../../common/Dialogue'; -import { trim, modalStyles } from '../../../../../common/util'; +import { modalStyles, trim } from '../../../../../common/util'; import PermissionSwitch from '../../../../../common/PermissionSwitch/PermissionSwitch'; import { UPDATE_FEATURE_VARIANTS } from '../../../../../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 { + IFeatureVariant, + IOverride, +} from '../../../../../../interfaces/featureToggle'; import cloneDeep from 'lodash.clonedeep'; const payloadOptions = [ @@ -35,6 +37,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 +57,11 @@ const AddVariant = ({ validateWeight, title, editing, -}) => { +}: 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); @@ -80,7 +93,7 @@ const AddVariant = ({ setError({}); }; - const setClonedVariants = clonedVariants => + const setClonedVariants = (clonedVariants: IFeatureVariant[]) => setVariants(cloneDeep(clonedVariants)); useEffect(() => { @@ -159,7 +172,7 @@ const AddVariant = ({ } }; - const onPayload = e => { + const onPayload = (e: React.SyntheticEvent) => { e.preventDefault(); setPayload({ ...payload, @@ -167,13 +180,13 @@ const AddVariant = ({ }); }; - const onCancel = e => { + const onCancel = (e: React.SyntheticEvent) => { e.preventDefault(); clear(); closeDialog(); }; - const updateOverrideType = index => e => { + const updateOverrideType = (index: number) => (e: React.SyntheticEvent) => { e.preventDefault(); setOverrides( overrides.map((o, i) => { @@ -186,7 +199,7 @@ const AddVariant = ({ ); }; - const updateOverrideValues = (index, values) => { + const updateOverrideValues = (index: number, values: string[]) => { setOverrides( overrides.map((o, i) => { if (i === index) { @@ -197,12 +210,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, @@ -388,7 +401,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/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/interfaces/featureToggle.ts b/frontend/src/interfaces/featureToggle.ts index eac5c72ed6..3c60d74a9a 100644 --- a/frontend/src/interfaces/featureToggle.ts +++ b/frontend/src/interfaces/featureToggle.ts @@ -69,8 +69,8 @@ export interface IFeatureEnvironmentMetrics { } 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/yarn.lock b/frontend/yarn.lock index 9fd9a61447..b22657be95 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1298,10 +1298,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 +1310,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 +1871,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" @@ -2092,10 +2105,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 +2147,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 +2215,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" @@ -2643,21 +2649,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 +2862,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 +3199,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 +3473,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 +3822,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 +3939,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 +4038,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" @@ -4522,24 +4507,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 +4548,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 +4842,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" @@ -5094,7 +5077,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 +5807,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 +5936,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 +6434,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" @@ -6507,7 +6484,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 +6951,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 +7648,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 +7708,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" @@ -8439,15 +8431,10 @@ nanocolors@^0.2.2: resolved "https://registry.npmjs.org/nanocolors/-/nanocolors-0.2.12.tgz" 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== +nanoid@^3.1.25, nanoid@^3.2.0: + 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 +8651,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 +8681,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== @@ -10005,15 +9992,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 +10125,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 +10140,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 +10254,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 +10364,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 +10508,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 +10684,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 +10948,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 +10969,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" @@ -11449,6 +11406,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 +12140,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 +12279,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"