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/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..e002d93ddc 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,7 +50,7 @@
"@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",
@@ -63,7 +64,7 @@
"copy-to-clipboard": "3.3.1",
"craco": "0.0.3",
"css-loader": "6.6.0",
- "cypress": "8.7.0",
+ "cypress": "9.5.0",
"date-fns": "2.28.0",
"debounce": "1.2.1",
"deep-diff": "1.0.2",
@@ -82,7 +83,7 @@
"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",
"swr": "1.2.2",
@@ -117,6 +118,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/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/ConfiguredAddons/ConfiguredAddons.tsx b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx
index 78ef5188cc..7defb1f187 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));
}
};
diff --git a/frontend/src/component/admin/api-token/ApiTokenList/ApiTokenList.tsx b/frontend/src/component/admin/api-token/ApiTokenList/ApiTokenList.tsx
index 7e97a831b1..7f6058b383 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
)}
diff --git a/frontend/src/component/admin/api-token/CreateApiToken/CreateApiToken.tsx b/frontend/src/component/admin/api-token/CreateApiToken/CreateApiToken.tsx
index 85f2ff0e0e..a1a31d6035 100644
--- a/frontend/src/component/admin/api-token/CreateApiToken/CreateApiToken.tsx
+++ b/frontend/src/component/admin/api-token/CreateApiToken/CreateApiToken.tsx
@@ -10,6 +10,7 @@ import { ADMIN } from '../../../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));
}
};
diff --git a/frontend/src/component/admin/auth/GoogleAuth/GoogleAuth.tsx b/frontend/src/component/admin/auth/GoogleAuth/GoogleAuth.tsx
index f9860a1fe0..43c9712e6b 100644
--- a/frontend/src/component/admin/auth/GoogleAuth/GoogleAuth.tsx
+++ b/frontend/src/component/admin/auth/GoogleAuth/GoogleAuth.tsx
@@ -68,8 +68,8 @@ export const GoogleAuth = () => {
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..97835a132f 100644
--- a/frontend/src/component/admin/project-roles/CreateProjectRole/CreateProjectRole.tsx
+++ b/frontend/src/component/admin/project-roles/CreateProjectRole/CreateProjectRole.tsx
@@ -7,6 +7,7 @@ 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 { 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));
}
}
};
diff --git a/frontend/src/component/admin/project-roles/EditProjectRole/EditProjectRole.tsx b/frontend/src/component/admin/project-roles/EditProjectRole/EditProjectRole.tsx
index eb79af949c..6b9b752bea 100644
--- a/frontend/src/component/admin/project-roles/EditProjectRole/EditProjectRole.tsx
+++ b/frontend/src/component/admin/project-roles/EditProjectRole/EditProjectRole.tsx
@@ -13,6 +13,7 @@ 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 { formatUnknownError } from '../../../../utils/format-unknown-error';
const EditProjectRole = () => {
const { uiConfig } = useUiConfig();
@@ -88,8 +89,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));
}
}
};
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..386363abfd 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;
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..8c4e1ffefe 100644
--- a/frontend/src/component/admin/users/CreateUser/CreateUser.tsx
+++ b/frontend/src/component/admin/users/CreateUser/CreateUser.tsx
@@ -10,6 +10,7 @@ import { useState } from 'react';
import { scrollToTop } from '../../../common/util';
import PermissionButton from '../../../common/PermissionButton/PermissionButton';
import { ADMIN } from '../../../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));
}
}
};
diff --git a/frontend/src/component/admin/users/EditUser/EditUser.tsx b/frontend/src/component/admin/users/EditUser/EditUser.tsx
index 07e212bf56..c82b818ccd 100644
--- a/frontend/src/component/admin/users/EditUser/EditUser.tsx
+++ b/frontend/src/component/admin/users/EditUser/EditUser.tsx
@@ -11,6 +11,7 @@ import { useEffect } from 'react';
import PermissionButton from '../../../common/PermissionButton/PermissionButton';
import { ADMIN } from '../../../providers/AccessProvider/permissions';
import { EDIT } from '../../../../constants/misc';
+import { formatUnknownError } from '../../../../utils/format-unknown-error';
const EditUser = () => {
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));
}
}
};
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/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/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..3e6dae15b6 100644
--- a/frontend/src/component/common/PermissionButton/PermissionButton.tsx
+++ b/frontend/src/component/common/PermissionButton/PermissionButton.tsx
@@ -1,6 +1,6 @@
import { Button, Tooltip } from '@material-ui/core';
import { Lock } from '@material-ui/icons';
-import { useContext } from 'react';
+import React, { useContext } from 'react';
import AccessContext from '../../../contexts/AccessContext';
import ConditionallyRender from '../ConditionallyRender';
diff --git a/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx b/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx
index d987e05a94..708f5b5af5 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
diff --git a/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx b/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx
index 0edb2cb94f..8e3f545293 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 (
{
- 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/ContextList/ContextList.jsx b/frontend/src/component/context/ContextList/ContextList.jsx
index c9379b0110..f751349da3 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);
diff --git a/frontend/src/component/context/EditContext/EditContext.tsx b/frontend/src/component/context/EditContext/EditContext.tsx
index 56ab77b099..411d207281 100644
--- a/frontend/src/component/context/EditContext/EditContext.tsx
+++ b/frontend/src/component/context/EditContext/EditContext.tsx
@@ -55,7 +55,7 @@ export const EditContext = () => {
const handleSubmit = async (e: Event) => {
e.preventDefault();
const payload = getContextPayload();
-
+
try {
await updateContext(payload);
refetch();
diff --git a/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx b/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx
index 567a09d9fb..a9f8391512 100644
--- a/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx
+++ b/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx
@@ -14,6 +14,7 @@ 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 { 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));
}
}
};
diff --git a/frontend/src/component/environments/EditEnvironment/EditEnvironment.tsx b/frontend/src/component/environments/EditEnvironment/EditEnvironment.tsx
index ecfc11ec5d..88efef7748 100644
--- a/frontend/src/component/environments/EditEnvironment/EditEnvironment.tsx
+++ b/frontend/src/component/environments/EditEnvironment/EditEnvironment.tsx
@@ -9,6 +9,7 @@ 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 (
) => 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..44136ed7ee 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();
}
diff --git a/frontend/src/component/feature/CreateFeature/CreateFeature.tsx b/frontend/src/component/feature/CreateFeature/CreateFeature.tsx
index 2fe8477254..7872f0bc90 100644
--- a/frontend/src/component/feature/CreateFeature/CreateFeature.tsx
+++ b/frontend/src/component/feature/CreateFeature/CreateFeature.tsx
@@ -10,6 +10,7 @@ import PermissionButton from '../../common/PermissionButton/PermissionButton';
import { CF_CREATE_BTN_ID } from '../../../testIds';
import { useContext } from 'react';
import UIContext from '../../../contexts/UIContext';
+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));
}
}
};
diff --git a/frontend/src/component/feature/EditFeature/EditFeature.tsx b/frontend/src/component/feature/EditFeature/EditFeature.tsx
index 239dac8030..d23fb042d2 100644
--- a/frontend/src/component/feature/EditFeature/EditFeature.tsx
+++ b/frontend/src/component/feature/EditFeature/EditFeature.tsx
@@ -10,6 +10,7 @@ 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 { 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));
}
};
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..819900aae3 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));
}
};
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..663e627289 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;
},
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/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}
/>
>;
+ 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..2f98a16ae0 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"
@@ -2209,10 +2222,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"
@@ -3216,7 +3229,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 +3503,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 +3852,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 +3969,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:
@@ -4522,24 +4542,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.0:
+ version "9.5.0"
+ resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.5.0.tgz#704a79f0d3d4e775f433334eb8f5ae065e3bea31"
+ integrity sha512-rC5QPolKsVjJ8QJZ7IeZ6HlKM4gswBGZc0XvoAJNL8urQCSL8zTX0A/ai/h35WfF47NQ0iSZnwIXBlHX3MOUIQ==
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 +4583,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:
@@ -4869,6 +4889,11 @@ dom-accessibility-api@^0.5.6:
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"
@@ -6466,6 +6491,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 +6541,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==
@@ -7671,6 +7705,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 +7765,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 +8488,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"
@@ -10147,7 +10191,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 +10206,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,16 +10320,16 @@ 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-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-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"
@@ -10402,15 +10441,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"
@@ -10719,7 +10766,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:
@@ -11004,14 +11051,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 +11488,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"
@@ -12307,9 +12361,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"