mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-03 01:18:43 +02:00
fix: merge conflicts
This commit is contained in:
commit
b58cccc7d0
8
frontend/.github/workflows/e2e.auth.yml
vendored
8
frontend/.github/workflows/e2e.auth.yml
vendored
@ -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 }}
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
|
8
frontend/.github/workflows/e2e.feature.yml
vendored
8
frontend/.github/workflows/e2e.feature.yml
vendored
@ -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 }}
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
|
@ -1,4 +1,6 @@
|
||||
{
|
||||
"projectId": "tc2qff",
|
||||
"defaultCommandTimeout": 12000
|
||||
"defaultCommandTimeout": 12000,
|
||||
"screenshotOnRunFailure": false,
|
||||
"video": false
|
||||
}
|
||||
|
@ -1,15 +1,4 @@
|
||||
/* eslint-disable jest/no-conditional-expect */
|
||||
/// <reference types="cypress" />
|
||||
// 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';
|
@ -1,82 +1,48 @@
|
||||
/* eslint-disable jest/no-conditional-expect */
|
||||
/// <reference types="cypress" />
|
||||
// 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');
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "unleash-frontend",
|
||||
"description": "unleash your features",
|
||||
"version": "4.8.0-beta.10",
|
||||
"version": "4.8.0",
|
||||
"keywords": [
|
||||
"unleash",
|
||||
"feature toggle",
|
||||
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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<HTMLInputElement>) => void;
|
||||
error: Record<string, string>;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TextField, FormControlLabel, Switch } from '@material-ui/core';
|
||||
import { FormButtons, styles as commonStyles } from '../../common';
|
||||
import { TextField, FormControlLabel, Switch, Button } from '@material-ui/core';
|
||||
import { styles as commonStyles } from '../../common';
|
||||
import { trim } from '../../common/util';
|
||||
import { AddonParameters } from './AddonParameters/AddonParameters';
|
||||
import { AddonEvents } from './AddonEvents/AddonEvents';
|
||||
@ -79,7 +79,7 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => {
|
||||
setErrors({ ...errors, events: undefined });
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
const onCancel = () => {
|
||||
history.goBack();
|
||||
};
|
||||
|
||||
@ -203,10 +203,12 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => {
|
||||
/>
|
||||
</section>
|
||||
<section className={styles.formSection}>
|
||||
<FormButtons
|
||||
submitText={submitText}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
<Button type="submit" color="primary" variant="contained">
|
||||
{submitText}
|
||||
</Button>
|
||||
<Button type="button" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
</section>
|
||||
</form>
|
||||
</PageContent>
|
||||
|
@ -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',
|
||||
|
@ -44,7 +44,6 @@ export const AvailableAddons = ({
|
||||
onClick={() =>
|
||||
history.push(`/addons/create/${provider.name}`)
|
||||
}
|
||||
tooltip={`Configure ${provider.name} Addon`}
|
||||
>
|
||||
Configure
|
||||
</PermissionButton>
|
||||
|
@ -21,6 +21,7 @@ import AccessContext from '../../../../contexts/AccessContext';
|
||||
import { IAddon } from '../../../../interfaces/addons';
|
||||
import PermissionIconButton from '../../../common/PermissionIconButton/PermissionIconButton';
|
||||
import Dialogue from '../../../common/Dialogue';
|
||||
import { formatUnknownError } from '../../../../utils/format-unknown-error';
|
||||
|
||||
interface IConfigureAddonsProps {
|
||||
getAddonIcon: (name: string) => ReactElement;
|
||||
@ -59,8 +60,8 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
|
||||
title: 'Success',
|
||||
text: 'Addon state switched successfully',
|
||||
});
|
||||
} catch (e: any) {
|
||||
setToastApiError(e.toString());
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
@ -122,7 +123,6 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
permission={UPDATE_ADDON}
|
||||
tooltip={'Edit Addon'}
|
||||
onClick={() => {
|
||||
history.push(`/addons/edit/${addon.id}`);
|
||||
}}
|
||||
@ -131,7 +131,6 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
permission={DELETE_ADDON}
|
||||
tooltip={'Remove Addon'}
|
||||
onClick={() => {
|
||||
setDeletedAddon(addon);
|
||||
setShowDelete(true);
|
||||
|
@ -24,7 +24,6 @@ import {
|
||||
DELETE_API_TOKEN,
|
||||
} from '../../../providers/AccessProvider/permissions';
|
||||
import { useStyles } from './ApiTokenList.styles';
|
||||
import { formatDateWithLocale } from '../../../common/util';
|
||||
import Secret from './secret';
|
||||
import { Delete, FileCopy } from '@material-ui/icons';
|
||||
import Dialogue from '../../../common/Dialogue';
|
||||
@ -32,6 +31,7 @@ import { CREATE_API_TOKEN_BUTTON } from '../../../../testIds';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { useLocationSettings } from '../../../../hooks/useLocationSettings';
|
||||
import { formatDateYMD } from '../../../../utils/format-date';
|
||||
|
||||
interface IApiToken {
|
||||
createdAt: Date;
|
||||
@ -146,7 +146,7 @@ export const ApiTokenList = () => {
|
||||
align="left"
|
||||
className={styles.hideSM}
|
||||
>
|
||||
{formatDateWithLocale(
|
||||
{formatDateYMD(
|
||||
item.createdAt,
|
||||
locationSettings.locale
|
||||
)}
|
||||
@ -249,7 +249,7 @@ export const ApiTokenList = () => {
|
||||
}
|
||||
data-test={CREATE_API_TOKEN_BUTTON}
|
||||
>
|
||||
Create API token
|
||||
New API token
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
@ -10,6 +10,7 @@ import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||
import { ConfirmToken } from '../ConfirmToken/ConfirmToken';
|
||||
import { useState } from 'react';
|
||||
import { scrollToTop } from '../../../common/util';
|
||||
import { formatUnknownError } from '../../../../utils/format-unknown-error';
|
||||
|
||||
export const CreateApiToken = () => {
|
||||
const { setToastApiError } = useToast();
|
||||
@ -49,8 +50,8 @@ export const CreateApiToken = () => {
|
||||
setToken(api.secret);
|
||||
setShowConfirm(true);
|
||||
});
|
||||
} catch (e: any) {
|
||||
setToastApiError(e.toString());
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -68,8 +68,8 @@ export const GoogleAuth = () => {
|
||||
title: 'Settings stored',
|
||||
type: 'success',
|
||||
});
|
||||
} catch (err) {
|
||||
setToastApiError(formatUnknownError(err));
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -79,8 +79,8 @@ export const OidcAuth = () => {
|
||||
title: 'Settings stored',
|
||||
type: 'success',
|
||||
});
|
||||
} catch (err) {
|
||||
setToastApiError(formatUnknownError(err));
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
@ -75,8 +75,8 @@ export const SamlAuth = () => {
|
||||
title: 'Settings stored',
|
||||
type: 'success',
|
||||
});
|
||||
} catch (err) {
|
||||
setToastApiError(formatUnknownError(err));
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
)}
|
||||
|
@ -7,6 +7,7 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { CreateButton } from 'component/common/CreateButton/CreateButton';
|
||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||
import { formatUnknownError } from 'utils/format-unknown-error';
|
||||
|
||||
const CreateProjectRole = () => {
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
@ -49,8 +50,8 @@ const CreateProjectRole = () => {
|
||||
confetti: true,
|
||||
type: 'success',
|
||||
});
|
||||
} catch (e: any) {
|
||||
setToastApiError(e.toString());
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -10,6 +10,7 @@ import { IPermission } from 'interfaces/user';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import useProjectRoleForm from '../hooks/useProjectRoleForm';
|
||||
import ProjectRoleForm from '../ProjectRoleForm/ProjectRoleForm';
|
||||
import { formatUnknownError } from 'utils/format-unknown-error';
|
||||
|
||||
const EditProjectRole = () => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
@ -85,8 +86,8 @@ const EditProjectRole = () => {
|
||||
text: 'Your role changes will automatically be applied to the users with this role.',
|
||||
confetti: true,
|
||||
});
|
||||
} catch (e: any) {
|
||||
setToastApiError(e.toString());
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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<IProjectRoleForm> = ({
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { makeStyles } from '@material-ui/styles';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
export const useStyles = makeStyles(theme => ({
|
||||
deleteParagraph: {
|
||||
|
@ -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('');
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { useStyles } from './ProjectRoleListItem.styles';
|
||||
import { TableRow, TableCell, Typography } from '@material-ui/core';
|
||||
import { Edit, Delete } from '@material-ui/icons';
|
||||
import { TableCell, TableRow, Typography } from '@material-ui/core';
|
||||
import { Delete, Edit } from '@material-ui/icons';
|
||||
import { ADMIN } from '../../../../../providers/AccessProvider/permissions';
|
||||
import SupervisedUserCircleIcon from '@material-ui/icons/SupervisedUserCircle';
|
||||
import PermissionIconButton from '../../../../../common/PermissionIconButton/PermissionIconButton';
|
||||
import { IProjectRole } from '../../../../../../interfaces/role';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
|
||||
interface IRoleListItemProps {
|
||||
id: number;
|
||||
@ -50,7 +51,6 @@ const RoleListItem = ({
|
||||
<PermissionIconButton
|
||||
data-loading
|
||||
aria-label="Edit"
|
||||
tooltip="Edit"
|
||||
disabled={type === BUILTIN_ROLE_TYPE}
|
||||
onClick={() => {
|
||||
history.push(`/admin/roles/${id}/edit`);
|
||||
@ -62,7 +62,6 @@ const RoleListItem = ({
|
||||
<PermissionIconButton
|
||||
data-loading
|
||||
aria-label="Remove role"
|
||||
tooltip="Remove role"
|
||||
disabled={type === BUILTIN_ROLE_TYPE}
|
||||
onClick={() => {
|
||||
setCurrentRole({ id, name, description });
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { makeStyles } from '@material-ui/styles';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
export const useStyles = makeStyles(theme => ({
|
||||
rolesListBody: {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { makeStyles } from '@material-ui/styles';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
export const useStyles = makeStyles({
|
||||
iconContainer: {
|
||||
|
@ -10,6 +10,7 @@ import { useState } from 'react';
|
||||
import { scrollToTop } from '../../../common/util';
|
||||
import { CreateButton } from 'component/common/CreateButton/CreateButton';
|
||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||
import { formatUnknownError } from '../../../../utils/format-unknown-error';
|
||||
|
||||
const CreateUser = () => {
|
||||
const { setToastApiError } = useToast();
|
||||
@ -51,8 +52,8 @@ const CreateUser = () => {
|
||||
setInviteLink(user.inviteLink);
|
||||
setShowConfirm(true);
|
||||
});
|
||||
} catch (e: any) {
|
||||
setToastApiError(e.toString());
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -11,6 +11,7 @@ import useAdminUsersApi from 'hooks/api/actions/useAdminUsersApi/useAdminUsersAp
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import useUserInfo from 'hooks/api/getters/useUserInfo/useUserInfo';
|
||||
import useToast from 'hooks/useToast';
|
||||
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));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { makeStyles } from '@material-ui/styles';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
export const useStyles = makeStyles(theme => ({
|
||||
userListBody: {
|
||||
|
@ -35,7 +35,7 @@ const UsersAdmin = () => {
|
||||
history.push('/admin/create-user')
|
||||
}
|
||||
>
|
||||
Add new user
|
||||
New user
|
||||
</Button>
|
||||
}
|
||||
elseShow={
|
||||
|
@ -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<Response>;
|
||||
user: Partial<IUser>;
|
||||
}
|
||||
|
||||
const ChangePassword = ({
|
||||
@ -25,7 +25,7 @@ const ChangePassword = ({
|
||||
user = {},
|
||||
}: IChangePasswordProps) => {
|
||||
const [data, setData] = useState({});
|
||||
const [error, setError] = useState({});
|
||||
const [error, setError] = useState<Record<string, string>>({});
|
||||
const [validPassword, setValidPassword] = useState(false);
|
||||
const commonStyles = useCommonStyles();
|
||||
|
||||
@ -88,7 +88,7 @@ const ChangePassword = ({
|
||||
)}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={error.general}
|
||||
condition={Boolean(error.general)}
|
||||
show={<Alert severity="error">{error.general}</Alert>}
|
||||
/>
|
||||
<Typography variant="subtitle1">
|
||||
|
@ -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<string, string>;
|
||||
}
|
||||
|
||||
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"
|
||||
>
|
||||
<div ref={ref}>
|
||||
<ConditionallyRender
|
||||
condition={userApiErrors[REMOVE_USER_ERROR]}
|
||||
condition={Boolean(userApiErrors[REMOVE_USER_ERROR])}
|
||||
show={
|
||||
<Alert
|
||||
data-loading
|
||||
|
@ -1,25 +1,24 @@
|
||||
import {
|
||||
TableRow,
|
||||
TableCell,
|
||||
Avatar,
|
||||
IconButton,
|
||||
TableCell,
|
||||
TableRow,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import { Edit, Lock, Delete } from '@material-ui/icons';
|
||||
import { Delete, Edit, Lock } from '@material-ui/icons';
|
||||
import { SyntheticEvent, useContext } from 'react';
|
||||
import { ADMIN } from '../../../../providers/AccessProvider/permissions';
|
||||
import ConditionallyRender from '../../../../common/ConditionallyRender';
|
||||
import { formatDateWithLocale } from '../../../../common/util';
|
||||
import AccessContext from '../../../../../contexts/AccessContext';
|
||||
import { IUser } from '../../../../../interfaces/user';
|
||||
import { useStyles } from './UserListItem.styles';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { ILocationSettings } from '../../../../../hooks/useLocationSettings';
|
||||
import { formatDateYMD } from '../../../../../utils/format-date';
|
||||
|
||||
interface IUserListItemProps {
|
||||
user: IUser;
|
||||
renderRole: (roleId: number) => 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 = ({
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span data-loading>
|
||||
{formatDateWithLocale(
|
||||
user.createdAt,
|
||||
locationSettings.locale
|
||||
)}
|
||||
{formatDateYMD(user.createdAt, locationSettings.locale)}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className={styles.leftTableCell}>
|
||||
|
@ -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 = () => {
|
||||
<DeleteUser
|
||||
showDialog={delDialog}
|
||||
closeDialog={closeDelDialog}
|
||||
user={delUser}
|
||||
user={delUser!}
|
||||
removeUser={onDeleteUser}
|
||||
userLoading={userLoading}
|
||||
userApiErrors={userApiErrors}
|
||||
|
@ -1,16 +1,15 @@
|
||||
/* eslint react/no-multi-comp:off */
|
||||
import { useContext, useState } from 'react';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Link,
|
||||
Icon,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
Link,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import { Link as LinkIcon } from '@material-ui/icons';
|
||||
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
|
||||
import { formatDateWithLocale } from '../../common/util';
|
||||
import { UPDATE_APPLICATION } from '../../providers/AccessProvider/permissions';
|
||||
import { ApplicationView } from '../ApplicationView/ApplicationView';
|
||||
import { ApplicationUpdate } from '../ApplicationUpdate/ApplicationUpdate';
|
||||
@ -25,6 +24,8 @@ import { useHistory, useParams } from 'react-router-dom';
|
||||
import { useLocationSettings } from '../../../hooks/useLocationSettings';
|
||||
import useToast from '../../../hooks/useToast';
|
||||
import PermissionButton from '../../common/PermissionButton/PermissionButton';
|
||||
import { formatDateYMD } from '../../../utils/format-date';
|
||||
import { formatUnknownError } from '../../../utils/format-unknown-error';
|
||||
|
||||
export const ApplicationEdit = () => {
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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 = () => {
|
||||
<span>
|
||||
{clientIp} last seen at{' '}
|
||||
<small>
|
||||
{formatFullDateTimeWithLocale(
|
||||
lastSeen
|
||||
{formatDateYMDHMS(
|
||||
lastSeen,
|
||||
locationSettings.locale
|
||||
)}
|
||||
</small>
|
||||
</span>
|
||||
|
@ -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<IAnimateOnMountProps> = ({
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Button } from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import React from 'react';
|
||||
|
||||
interface IApiErrorProps {
|
||||
className?: string;
|
||||
|
@ -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)) {
|
||||
|
@ -52,7 +52,6 @@ const Constraint = ({
|
||||
<div className={styles.btnContainer}>
|
||||
<PermissionIconButton
|
||||
onClick={editCallback}
|
||||
tooltip="Edit strategy"
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={projectId}
|
||||
>
|
||||
@ -61,7 +60,6 @@ const Constraint = ({
|
||||
|
||||
<PermissionIconButton
|
||||
onClick={deleteCallback}
|
||||
tooltip="Delete strategy"
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={projectId}
|
||||
>
|
||||
|
@ -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<IDialogue> = ({
|
||||
@ -69,7 +69,7 @@ const Dialogue: React.FC<IDialogue> = ({
|
||||
<DialogActions>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(permissionButton)}
|
||||
show={permissionButton}
|
||||
show={permissionButton!}
|
||||
elseShow={
|
||||
<ConditionallyRender
|
||||
condition={Boolean(onClick)}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
interface EnvironmentSplashPageProps {
|
||||
title: React.ReactNode;
|
||||
topDescription: React.ReactNode;
|
||||
|
@ -7,6 +7,7 @@ import ConditionallyRender from '../ConditionallyRender';
|
||||
import Loader from '../Loader/Loader';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import useToast from '../../../hooks/useToast';
|
||||
import React from 'react';
|
||||
|
||||
interface ICreateProps {
|
||||
title: string;
|
||||
|
@ -1,3 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
interface IGradientProps {
|
||||
from: string;
|
||||
to: string;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { TextField } from '@material-ui/core';
|
||||
import { INPUT_ERROR_TEXT } from '../../../testIds';
|
||||
import { useStyles } from './Input.styles.ts';
|
||||
import { useStyles } from './Input.styles';
|
||||
import React from 'react';
|
||||
|
||||
interface IInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
label: string;
|
||||
|
@ -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();
|
||||
|
@ -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';
|
||||
|
@ -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);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Button, Tooltip } from '@material-ui/core';
|
||||
import { Lock } from '@material-ui/icons';
|
||||
import AccessContext from 'contexts/AccessContext';
|
||||
import { useContext } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import ConditionallyRender from '../ConditionallyRender';
|
||||
|
||||
export interface IPermissionButtonProps
|
||||
@ -16,7 +16,7 @@ export interface IPermissionButtonProps
|
||||
|
||||
const PermissionButton: React.FC<IPermissionButtonProps> = ({
|
||||
permission,
|
||||
tooltip = 'Click to perform action',
|
||||
tooltip,
|
||||
onClick,
|
||||
children,
|
||||
disabled,
|
||||
@ -54,9 +54,9 @@ const PermissionButton: React.FC<IPermissionButtonProps> = ({
|
||||
|
||||
access = handleAccess();
|
||||
|
||||
const tooltipText = access
|
||||
? tooltip
|
||||
: "You don't have access to perform this operation";
|
||||
const tooltipText = !access
|
||||
? "You don't have access to perform this operation"
|
||||
: '';
|
||||
|
||||
return (
|
||||
<Tooltip title={tooltipText} arrow>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { IconButton, Tooltip } from '@material-ui/core';
|
||||
import { useContext } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import AccessContext from '../../../contexts/AccessContext';
|
||||
|
||||
interface IPermissionIconButtonProps
|
||||
@ -39,9 +39,9 @@ const PermissionIconButton: React.FC<IPermissionIconButtonProps> = ({
|
||||
access = hasAccess(permission);
|
||||
}
|
||||
|
||||
const tooltipText = access
|
||||
? tooltip || ''
|
||||
: "You don't have access to perform this operation";
|
||||
const tooltipText = !access
|
||||
? "You don't have access to perform this operation"
|
||||
: '';
|
||||
|
||||
return (
|
||||
<Tooltip title={tooltipText} arrow>
|
||||
|
@ -19,7 +19,7 @@ const PermissionSwitch = React.forwardRef<
|
||||
>((props, ref) => {
|
||||
const {
|
||||
permission,
|
||||
tooltip = '',
|
||||
tooltip,
|
||||
disabled,
|
||||
projectId,
|
||||
environmentId,
|
||||
@ -39,9 +39,9 @@ const PermissionSwitch = React.forwardRef<
|
||||
access = hasAccess(permission);
|
||||
}
|
||||
|
||||
const tooltipText = access
|
||||
? tooltip
|
||||
: "You don't have access to perform this operation";
|
||||
const tooltipText = !access
|
||||
? "You don't have access to perform this operation"
|
||||
: '';
|
||||
|
||||
return (
|
||||
<Tooltip title={tooltipText} arrow>
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
|
||||
|
@ -7,7 +7,7 @@ interface ITagSelect extends React.SelectHTMLAttributes<HTMLSelectElement> {
|
||||
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 => ({
|
||||
|
@ -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 (
|
||||
|
@ -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 (
|
||||
<Portal>
|
||||
<AnimateOnMount
|
||||
mounted={toastData?.show}
|
||||
mounted={Boolean(toastData?.show)}
|
||||
start={commonStyles.fadeInBottomStartWithoutFixed}
|
||||
enter={commonStyles.fadeInBottomEnter}
|
||||
leave={commonStyles.fadeInBottomLeave}
|
||||
|
@ -1,35 +0,0 @@
|
||||
import { formatFullDateTimeWithLocale } from '../util';
|
||||
|
||||
test.skip('formats dates correctly', () => {
|
||||
expect(formatFullDateTimeWithLocale(1487861809466, 'nb-NO', 'UTC')).toEqual(
|
||||
'2017-02-23 14:56:49'
|
||||
);
|
||||
expect(
|
||||
formatFullDateTimeWithLocale(1487861809466, 'nb-NO', 'Europe/Paris')
|
||||
).toEqual('2017-02-23 15:56:49');
|
||||
expect(
|
||||
formatFullDateTimeWithLocale(1487861809466, 'nb-NO', 'Europe/Oslo')
|
||||
).toEqual('2017-02-23 15:56:49');
|
||||
expect(
|
||||
formatFullDateTimeWithLocale(1487861809466, 'nb-NO', 'Europe/London')
|
||||
).toEqual('2017-02-23 14:56:49');
|
||||
expect(
|
||||
formatFullDateTimeWithLocale(1487861809466, 'en-GB', 'Europe/Paris')
|
||||
).toEqual('02/23/2017, 3:56:49 PM');
|
||||
expect(
|
||||
formatFullDateTimeWithLocale(1487861809466, 'en-GB', 'Europe/Oslo')
|
||||
).toEqual('02/23/2017, 3:56:49 PM');
|
||||
expect(
|
||||
formatFullDateTimeWithLocale(1487861809466, 'en-GB', 'Europe/London')
|
||||
).toEqual('02/23/2017, 2:56:49 PM');
|
||||
|
||||
expect(formatFullDateTimeWithLocale(1487861809466, 'nb-NO')).toEqual(
|
||||
expect.stringMatching(/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/)
|
||||
);
|
||||
expect(formatFullDateTimeWithLocale(1487861809466, 'en-GB')).toEqual(
|
||||
expect.stringContaining('02/23/2017')
|
||||
);
|
||||
expect(formatFullDateTimeWithLocale(1487861809466, 'en-US')).toEqual(
|
||||
expect.stringContaining('02/23/2017')
|
||||
);
|
||||
});
|
@ -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();
|
||||
|
@ -1,9 +1,9 @@
|
||||
import Input from '../../common/Input/Input';
|
||||
import Input from 'component/common/Input/Input';
|
||||
import { TextField, Button, Switch, Chip, Typography } from '@material-ui/core';
|
||||
import { useStyles } from './ContextForm.styles';
|
||||
import React, { useState } from 'react';
|
||||
import { Add } from '@material-ui/icons';
|
||||
import { trim } from '../../common/util';
|
||||
import { trim } from 'component/common/util';
|
||||
|
||||
interface IContextForm {
|
||||
contextName: string;
|
||||
@ -15,20 +15,20 @@ interface IContextForm {
|
||||
setStickiness: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setLegalValues: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
handleSubmit: (e: any) => void;
|
||||
handleCancel: () => void;
|
||||
onCancel: () => void;
|
||||
errors: { [key: string]: string };
|
||||
mode: string;
|
||||
clearErrors: () => void;
|
||||
validateNameUniqueness: () => void;
|
||||
validateContext?: () => void;
|
||||
setErrors: React.Dispatch<React.SetStateAction<Object>>;
|
||||
}
|
||||
|
||||
const ENTER = 'Enter';
|
||||
|
||||
const ContextForm: React.FC<IContextForm> = ({
|
||||
export const ContextForm: React.FC<IContextForm> = ({
|
||||
children,
|
||||
handleSubmit,
|
||||
handleCancel,
|
||||
onCancel,
|
||||
contextName,
|
||||
contextDesc,
|
||||
legalValues,
|
||||
@ -39,7 +39,7 @@ const ContextForm: React.FC<IContextForm> = ({
|
||||
setStickiness,
|
||||
errors,
|
||||
mode,
|
||||
validateNameUniqueness,
|
||||
validateContext,
|
||||
setErrors,
|
||||
clearErrors,
|
||||
}) => {
|
||||
@ -108,7 +108,7 @@ const ContextForm: React.FC<IContextForm> = ({
|
||||
error={Boolean(errors.name)}
|
||||
errorText={errors.name}
|
||||
onFocus={() => clearErrors()}
|
||||
onBlur={validateNameUniqueness}
|
||||
onBlur={validateContext}
|
||||
autoFocus
|
||||
/>
|
||||
<p className={styles.inputDescription}>
|
||||
@ -187,12 +187,10 @@ const ContextForm: React.FC<IContextForm> = ({
|
||||
</div>
|
||||
<div className={styles.buttonContainer}>
|
||||
{children}
|
||||
<Button onClick={handleCancel} className={styles.cancelButton}>
|
||||
<Button onClick={onCancel} className={styles.cancelButton}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContextForm;
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
UPDATE_CONTEXT_FIELD,
|
||||
} from '../../providers/AccessProvider/permissions';
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
List,
|
||||
ListItem,
|
||||
@ -14,7 +15,6 @@ import {
|
||||
ListItemText,
|
||||
Tooltip,
|
||||
useMediaQuery,
|
||||
Button,
|
||||
} from '@material-ui/core';
|
||||
import { Add, Album, Delete, Edit } from '@material-ui/icons';
|
||||
import { useContext, useState } from 'react';
|
||||
@ -25,6 +25,7 @@ import AccessContext from '../../../contexts/AccessContext';
|
||||
import useUnleashContext from '../../../hooks/api/getters/useUnleashContext/useUnleashContext';
|
||||
import useContextsApi from '../../../hooks/api/actions/useContextsApi/useContextsApi';
|
||||
import useToast from '../../../hooks/useToast';
|
||||
import { formatUnknownError } from '../../../utils/format-unknown-error';
|
||||
|
||||
const ContextList = () => {
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
@ -46,8 +47,8 @@ const ContextList = () => {
|
||||
title: 'Successfully deleted context',
|
||||
text: 'Your context is now deleted',
|
||||
});
|
||||
} catch (e) {
|
||||
setToastApiError(e.toString());
|
||||
} catch (error) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
setName(undefined);
|
||||
setShowDelDialogue(false);
|
||||
@ -127,7 +128,7 @@ const ContextList = () => {
|
||||
color="primary"
|
||||
variant="contained"
|
||||
>
|
||||
Add new context field
|
||||
New context field
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import useContextForm from '../hooks/useContextForm';
|
||||
import ContextForm from '../ContextForm/ContextForm';
|
||||
import { CreateButton } from 'component/common/CreateButton/CreateButton';
|
||||
import useToast from 'hooks/useToast';
|
||||
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||
import { useContextForm } from '../hooks/useContextForm';
|
||||
import { ContextForm } from '../ContextForm/ContextForm';
|
||||
import { CREATE_CONTEXT_FIELD } from 'component/providers/AccessProvider/permissions';
|
||||
import useContextsApi from 'hooks/api/actions/useContextsApi/useContextsApi';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { formatUnknownError } from 'utils/format-unknown-error';
|
||||
|
||||
const CreateContext = () => {
|
||||
export const CreateContext = () => {
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const { uiConfig } = useUiConfig();
|
||||
const history = useHistory();
|
||||
@ -23,8 +24,7 @@ const CreateContext = () => {
|
||||
setLegalValues,
|
||||
setStickiness,
|
||||
getContextPayload,
|
||||
validateNameUniqueness,
|
||||
validateName,
|
||||
validateContext,
|
||||
clearErrors,
|
||||
setErrors,
|
||||
errors,
|
||||
@ -34,7 +34,8 @@ const CreateContext = () => {
|
||||
|
||||
const handleSubmit = async (e: Event) => {
|
||||
e.preventDefault();
|
||||
const validName = validateName();
|
||||
const validName = await validateContext();
|
||||
|
||||
if (validName) {
|
||||
const payload = getContextPayload();
|
||||
try {
|
||||
@ -46,8 +47,8 @@ const CreateContext = () => {
|
||||
confetti: true,
|
||||
type: 'success',
|
||||
});
|
||||
} catch (e: any) {
|
||||
setToastApiError(e.toString());
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -61,7 +62,7 @@ const CreateContext = () => {
|
||||
--data-raw '${JSON.stringify(getContextPayload(), undefined, 2)}'`;
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
const onCancel = () => {
|
||||
history.goBack();
|
||||
};
|
||||
|
||||
@ -69,7 +70,7 @@ const CreateContext = () => {
|
||||
<FormTemplate
|
||||
loading={loading}
|
||||
title="Create context"
|
||||
description="Context fields are a basic building block used in Unleash to control roll-out.
|
||||
description="Context fields are a basic building block used in Unleash to control roll-out.
|
||||
They can be used together with strategy constraints as part of the activation strategy evaluation."
|
||||
documentationLink="https://docs.getunleash.io/how-to/how-to-define-custom-context-fields"
|
||||
formatApiCode={formatApiCode}
|
||||
@ -77,7 +78,7 @@ const CreateContext = () => {
|
||||
<ContextForm
|
||||
errors={errors}
|
||||
handleSubmit={handleSubmit}
|
||||
handleCancel={handleCancel}
|
||||
onCancel={onCancel}
|
||||
contextName={contextName}
|
||||
setContextName={setContextName}
|
||||
contextDesc={contextDesc}
|
||||
@ -87,7 +88,7 @@ const CreateContext = () => {
|
||||
stickiness={stickiness}
|
||||
setStickiness={setStickiness}
|
||||
mode="Create"
|
||||
validateNameUniqueness={validateNameUniqueness}
|
||||
validateContext={validateContext}
|
||||
setErrors={setErrors}
|
||||
clearErrors={clearErrors}
|
||||
>
|
||||
@ -99,5 +100,3 @@ const CreateContext = () => {
|
||||
</FormTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateContext;
|
||||
|
@ -8,10 +8,11 @@ import useToast from 'hooks/useToast';
|
||||
import { useEffect } from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { scrollToTop } from '../../common/util';
|
||||
import ContextForm from '../ContextForm/ContextForm';
|
||||
import useContextForm from '../hooks/useContextForm';
|
||||
import { formatUnknownError } from 'utils/format-unknown-error';
|
||||
import { ContextForm } from '../ContextForm/ContextForm';
|
||||
import { useContextForm } from '../hooks/useContextForm';
|
||||
|
||||
const EditContext = () => {
|
||||
export const EditContext = () => {
|
||||
useEffect(() => {
|
||||
scrollToTop();
|
||||
}, []);
|
||||
@ -32,8 +33,6 @@ const EditContext = () => {
|
||||
setLegalValues,
|
||||
setStickiness,
|
||||
getContextPayload,
|
||||
validateNameUniqueness,
|
||||
validateName,
|
||||
clearErrors,
|
||||
setErrors,
|
||||
errors,
|
||||
@ -56,24 +55,21 @@ const EditContext = () => {
|
||||
const handleSubmit = async (e: Event) => {
|
||||
e.preventDefault();
|
||||
const payload = getContextPayload();
|
||||
const validName = validateName();
|
||||
|
||||
if (validName) {
|
||||
try {
|
||||
await updateContext(payload);
|
||||
refetch();
|
||||
history.push('/context');
|
||||
setToastData({
|
||||
title: 'Context information updated',
|
||||
type: 'success',
|
||||
});
|
||||
} catch (e: any) {
|
||||
setToastApiError(e.toString());
|
||||
}
|
||||
try {
|
||||
await updateContext(payload);
|
||||
refetch();
|
||||
history.push('/context');
|
||||
setToastData({
|
||||
title: 'Context information updated',
|
||||
type: 'success',
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
setToastApiError(formatUnknownError(e));
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
const onCancel = () => {
|
||||
history.goBack();
|
||||
};
|
||||
|
||||
@ -81,7 +77,7 @@ const EditContext = () => {
|
||||
<FormTemplate
|
||||
loading={loading}
|
||||
title="Edit context"
|
||||
description="Context fields are a basic building block used in Unleash to control roll-out.
|
||||
description="Context fields are a basic building block used in Unleash to control roll-out.
|
||||
They can be used together with strategy constraints as part of the activation strategy evaluation."
|
||||
documentationLink="https://docs.getunleash.io/how-to/how-to-define-custom-context-fields"
|
||||
formatApiCode={formatApiCode}
|
||||
@ -89,7 +85,7 @@ const EditContext = () => {
|
||||
<ContextForm
|
||||
errors={errors}
|
||||
handleSubmit={handleSubmit}
|
||||
handleCancel={handleCancel}
|
||||
onCancel={onCancel}
|
||||
contextName={contextName}
|
||||
setContextName={setContextName}
|
||||
contextDesc={contextDesc}
|
||||
@ -99,7 +95,6 @@ const EditContext = () => {
|
||||
stickiness={stickiness}
|
||||
setStickiness={setStickiness}
|
||||
mode="Edit"
|
||||
validateNameUniqueness={validateNameUniqueness}
|
||||
setErrors={setErrors}
|
||||
clearErrors={clearErrors}
|
||||
>
|
||||
@ -108,5 +103,3 @@ const EditContext = () => {
|
||||
</FormTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditContext;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import useContextsApi from '../../../hooks/api/actions/useContextsApi/useContextsApi';
|
||||
|
||||
const useContextForm = (
|
||||
export const useContextForm = (
|
||||
initialcontextName = '',
|
||||
initialcontextDesc = '',
|
||||
initialLegalValues = [] as string[],
|
||||
@ -42,25 +42,28 @@ const useContextForm = (
|
||||
|
||||
const NAME_EXISTS_ERROR = 'A context field with that name already exist';
|
||||
|
||||
const validateNameUniqueness = async () => {
|
||||
const validateContext = async () => {
|
||||
if (contextName.length === 0) {
|
||||
setErrors(prev => ({ ...prev, name: 'Name can not be empty.' }));
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
await validateContextName(contextName);
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
if (e.toString().includes(NAME_EXISTS_ERROR)) {
|
||||
setErrors(prev => ({
|
||||
...prev,
|
||||
name: 'A context field with that name already exist',
|
||||
}));
|
||||
} else {
|
||||
setErrors(prev => ({
|
||||
...prev,
|
||||
name: e.toString(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const validateName = () => {
|
||||
if (contextName.length === 0) {
|
||||
setErrors(prev => ({ ...prev, name: 'Name can not be empty.' }));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const clearErrors = () => {
|
||||
@ -77,12 +80,9 @@ const useContextForm = (
|
||||
setLegalValues,
|
||||
setStickiness,
|
||||
getContextPayload,
|
||||
validateNameUniqueness,
|
||||
validateName,
|
||||
validateContext,
|
||||
setErrors,
|
||||
clearErrors,
|
||||
errors,
|
||||
};
|
||||
};
|
||||
|
||||
export default useContextForm;
|
||||
|
@ -14,6 +14,7 @@ import ConditionallyRender from 'component/common/ConditionallyRender';
|
||||
import PageContent from 'component/common/PageContent/PageContent';
|
||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||
import HeaderTitle from 'component/common/HeaderTitle/HeaderTitle';
|
||||
import { formatUnknownError } from 'utils/format-unknown-error';
|
||||
|
||||
const CreateEnvironment = () => {
|
||||
const { setToastApiError, setToastData } = useToast();
|
||||
@ -49,8 +50,8 @@ const CreateEnvironment = () => {
|
||||
confetti: true,
|
||||
});
|
||||
history.push('/environments');
|
||||
} catch (e: any) {
|
||||
setToastApiError(e.toString());
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ import { useHistory, useParams } from 'react-router-dom';
|
||||
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 (
|
||||
<FormTemplate
|
||||
title="Edit environment"
|
||||
description="Environments allow you to manage your
|
||||
description="Environments allow you to manage your
|
||||
product lifecycle from local development
|
||||
through production. Your projects and
|
||||
feature toggles are accessible in all your
|
||||
|
@ -1,10 +1,11 @@
|
||||
import {
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
RadioGroup,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
} from '@material-ui/core';
|
||||
import { useStyles } from './EnvironmentTypeSelector.styles';
|
||||
import React from 'react';
|
||||
|
||||
interface IEnvironmentTypeSelectorProps {
|
||||
onChange: (event: React.FormEvent<HTMLInputElement>) => void;
|
||||
|
@ -11,7 +11,7 @@ interface IEnviromentDeleteConfirmProps {
|
||||
open: boolean;
|
||||
setSelectedEnv: React.Dispatch<React.SetStateAction<IEnvironment>>;
|
||||
setDeldialogue: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
handleDeleteEnvironment: (name: string) => Promise<void>;
|
||||
handleDeleteEnvironment: () => Promise<void>;
|
||||
confirmName: string;
|
||||
setConfirmName: React.Dispatch<React.SetStateAction<string>>;
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import EnvironmentToggleConfirm from './EnvironmentToggleConfirm/EnvironmentTogg
|
||||
import useProjectRolePermissions from '../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
|
||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { formatUnknownError } from '../../../utils/format-unknown-error';
|
||||
|
||||
const EnvironmentList = () => {
|
||||
const defaultEnv = {
|
||||
@ -75,16 +76,16 @@ const EnvironmentList = () => {
|
||||
try {
|
||||
await sortOrderAPICall(sortOrder);
|
||||
refetch();
|
||||
} catch (e) {
|
||||
setToastApiError(e.toString());
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
const sortOrderAPICall = async (sortOrder: ISortOrderPayload) => {
|
||||
try {
|
||||
await changeSortOrder(sortOrder);
|
||||
} catch (e) {
|
||||
setToastApiError(e.toString());
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
@ -97,8 +98,8 @@ const EnvironmentList = () => {
|
||||
title: 'Project environment deleted',
|
||||
text: 'You have successfully deleted the project environment.',
|
||||
});
|
||||
} catch (e) {
|
||||
setToastApiError(e.toString());
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
} finally {
|
||||
setDeldialogue(false);
|
||||
setSelectedEnv(defaultEnv);
|
||||
@ -124,8 +125,8 @@ const EnvironmentList = () => {
|
||||
title: 'Project environment enabled',
|
||||
text: 'Your environment is enabled',
|
||||
});
|
||||
} catch (e) {
|
||||
setToastApiError(e.toString());
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
} finally {
|
||||
refetch();
|
||||
}
|
||||
@ -140,8 +141,8 @@ const EnvironmentList = () => {
|
||||
title: 'Project environment disabled',
|
||||
text: 'Your environment is disabled.',
|
||||
});
|
||||
} catch (e) {
|
||||
setToastApiError(e.toString());
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
} finally {
|
||||
refetch();
|
||||
}
|
||||
@ -174,12 +175,11 @@ const EnvironmentList = () => {
|
||||
<ResponsiveButton
|
||||
onClick={navigateToCreateEnvironment}
|
||||
maxWidth="700px"
|
||||
tooltip="Add environment"
|
||||
Icon={Add}
|
||||
permission={ADMIN}
|
||||
disabled={!Boolean(uiConfig.flags.EEA)}
|
||||
>
|
||||
Add Environment
|
||||
New Environment
|
||||
</ResponsiveButton>
|
||||
</>
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import { useContext } from 'react';
|
||||
import { CreateButton } from 'component/common/CreateButton/CreateButton';
|
||||
import UIContext from 'contexts/UIContext';
|
||||
import { CF_CREATE_BTN_ID } from 'testIds';
|
||||
import { formatUnknownError } from '../../../utils/format-unknown-error';
|
||||
|
||||
const CreateFeature = () => {
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
@ -53,8 +54,8 @@ const CreateFeature = () => {
|
||||
type: 'success',
|
||||
});
|
||||
setShowFeedback(true);
|
||||
} catch (e: any) {
|
||||
setToastApiError(e.toString());
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -10,6 +10,7 @@ import useFeature from 'hooks/api/getters/useFeature/useFeature';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { IFeatureViewParams } from 'interfaces/params';
|
||||
import { formatUnknownError } from 'utils/format-unknown-error';
|
||||
|
||||
const EditFeature = () => {
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
@ -57,8 +58,8 @@ const EditFeature = () => {
|
||||
title: 'Toggle updated successfully',
|
||||
type: 'success',
|
||||
});
|
||||
} catch (e: any) {
|
||||
setToastApiError(e.toString());
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -183,7 +183,7 @@ const FeatureToggleList = ({
|
||||
skeleton: loading,
|
||||
})}
|
||||
>
|
||||
Create feature toggle
|
||||
New feature toggle
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
@ -165,7 +165,7 @@ exports[`renders correctly with one feature 1`] = `
|
||||
<span
|
||||
className="MuiButton-label"
|
||||
>
|
||||
Create feature toggle
|
||||
New feature toggle
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
@ -362,7 +362,7 @@ exports[`renders correctly with one feature without permissions 1`] = `
|
||||
<span
|
||||
className="MuiButton-label"
|
||||
>
|
||||
Create feature toggle
|
||||
New feature toggle
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -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 (
|
||||
<Tooltip
|
||||
title={`Created at ${formatFullDateTimeWithLocale(
|
||||
title={`Created at ${formatDateYMDHMS(
|
||||
time,
|
||||
locationSettings.locale
|
||||
)}`}
|
||||
>
|
||||
<span>{formatDateWithLocale(time, locationSettings.locale)}</span>
|
||||
<span>{formatDateYMD(time, locationSettings.locale)}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
@ -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 = ({
|
||||
|
@ -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),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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 => (
|
||||
<TableRow key={metric.timestamp}>
|
||||
<TableCell>
|
||||
{formatFullDateTimeWithLocale(
|
||||
{formatDateYMDHMS(
|
||||
metric.timestamp,
|
||||
locationSettings.locale
|
||||
)}
|
||||
|
@ -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 });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -8,6 +8,8 @@ import { IFeatureViewParams } from '../../../../../../interfaces/params';
|
||||
import PermissionSwitch from '../../../../../common/PermissionSwitch/PermissionSwitch';
|
||||
import StringTruncator from '../../../../../common/StringTruncator/StringTruncator';
|
||||
import { UPDATE_FEATURE_ENVIRONMENT } from '../../../../../providers/AccessProvider/permissions';
|
||||
import React from 'react';
|
||||
import { formatUnknownError } from '../../../../../../utils/format-unknown-error';
|
||||
|
||||
interface IFeatureOverviewEnvSwitchProps {
|
||||
env: IFeatureEnvironment;
|
||||
@ -40,7 +42,7 @@ const FeatureOverviewEnvSwitch = ({
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e) {
|
||||
if (e.message === ENVIRONMENT_STRATEGY_ERROR) {
|
||||
showInfoBox(true);
|
||||
} else {
|
||||
@ -61,8 +63,8 @@ const FeatureOverviewEnvSwitch = ({
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
} catch (e: any) {
|
||||
setToastApiError(e.message);
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
@ -93,7 +95,6 @@ const FeatureOverviewEnvSwitch = ({
|
||||
checked={env.enabled}
|
||||
onChange={toggleEnvironment}
|
||||
environmentId={env.name}
|
||||
tooltip={''}
|
||||
/>
|
||||
{content}
|
||||
</div>
|
||||
|
@ -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';
|
||||
|
@ -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<HTMLButtonElement> {
|
||||
projectId: string;
|
||||
@ -53,8 +54,8 @@ const FeatureOverviewTags: React.FC<IFeatureOverviewTagsProps> = ({
|
||||
title: 'Tag deleted',
|
||||
text: 'Successfully deleted tag',
|
||||
});
|
||||
} catch (e) {
|
||||
setToastApiError(e.message);
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useContext } from 'react';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { useHistory, useParams } from 'react-router';
|
||||
import AccessContext from '../../../../../contexts/AccessContext';
|
||||
import useFeatureApi from '../../../../../hooks/api/actions/useFeatureApi/useFeatureApi';
|
||||
@ -12,6 +12,7 @@ import FeatureProjectSelect from './FeatureProjectSelect/FeatureProjectSelect';
|
||||
import FeatureSettingsProjectConfirm from './FeatureSettingsProjectConfirm/FeatureSettingsProjectConfirm';
|
||||
import { IPermission } from '../../../../../interfaces/user';
|
||||
import { useAuthPermissions } from '../../../../../hooks/api/getters/useAuth/useAuthPermissions';
|
||||
import { formatUnknownError } from '../../../../../utils/format-unknown-error';
|
||||
|
||||
const FeatureSettingsProject = () => {
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
@ -61,16 +62,16 @@ const FeatureSettingsProject = () => {
|
||||
history.replace(
|
||||
`/projects/${newProject}/features/${featureId}/settings`
|
||||
);
|
||||
} catch (e) {
|
||||
setToastApiError(e.message);
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
const createMoveTargets = () => {
|
||||
return permissions.reduce(
|
||||
(acc: { [key: string]: boolean }, permission: IPermission) => {
|
||||
if (permission.permission === MOVE_FEATURE_TOGGLE) {
|
||||
acc[permission.project] = true;
|
||||
(acc: { [key: string]: boolean }, p: IPermission) => {
|
||||
if (p.project && p.permission === MOVE_FEATURE_TOGGLE) {
|
||||
acc[p.project] = true;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
@ -101,7 +102,6 @@ const FeatureSettingsProject = () => {
|
||||
show={
|
||||
<PermissionButton
|
||||
permission={MOVE_FEATURE_TOGGLE}
|
||||
tooltip="Update feature"
|
||||
onClick={() => setShowConfirmDialog(true)}
|
||||
projectId={projectId}
|
||||
>
|
||||
|
@ -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={
|
||||
<TimeAgo
|
||||
date={lastSeenAt}
|
||||
date={lastSeenAt!}
|
||||
title=""
|
||||
live={false}
|
||||
formatter={(
|
||||
|
@ -19,6 +19,7 @@ import { ADD_NEW_STRATEGY_SAVE_ID } from '../../../../../../testIds';
|
||||
import useFeature from '../../../../../../hooks/api/getters/useFeature/useFeature';
|
||||
import { scrollToTop } from '../../../../../common/util';
|
||||
import useToast from '../../../../../../hooks/useToast';
|
||||
import { formatUnknownError } from '../../../../../../utils/format-unknown-error';
|
||||
|
||||
const FeatureStrategiesConfigure = () => {
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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}
|
||||
/>
|
||||
</div>
|
||||
|
@ -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<IFeatureViewParams>();
|
||||
@ -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,
|
||||
|
@ -282,7 +282,7 @@ const FeatureStrategiesEnvironments = () => {
|
||||
environmentId={activeEnvironment.name}
|
||||
permission={CREATE_FEATURE_STRATEGY}
|
||||
>
|
||||
Add new strategy
|
||||
New strategy
|
||||
</ResponsiveButton>
|
||||
}
|
||||
/>
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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 = ({
|
||||
</div>
|
||||
);
|
||||
} else if (type === 'list') {
|
||||
let list = [];
|
||||
let list: string[] = [];
|
||||
if (typeof value === 'string') {
|
||||
list = value.trim().split(',').filter(Boolean);
|
||||
}
|
||||
|
@ -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: {
|
||||
|
@ -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<IStrategyConstraintProps> = ({
|
||||
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<IStrategyConstraintProps> = ({
|
||||
};
|
||||
};
|
||||
|
||||
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;
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
interface IStrategyInputList {
|
||||
name: string;
|
||||
list: string[];
|
||||
setConfig: () => void;
|
||||
setConfig: (field: string, value: string) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user