mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-21 13:47:39 +02:00
Merge branch 'main' into fix/empty-name-admin
This commit is contained in:
commit
c1e6860b8f
4
frontend/.github/workflows/e2e.auth.yml
vendored
4
frontend/.github/workflows/e2e.auth.yml
vendored
@ -17,9 +17,9 @@ jobs:
|
|||||||
- name: Run Cypress
|
- name: Run Cypress
|
||||||
uses: cypress-io/github-action@v2
|
uses: cypress-io/github-action@v2
|
||||||
with:
|
with:
|
||||||
env: AUTH_TOKEN=${{ secrets.UNLEASH_TOKEN }},DEFAULT_ENV="development"
|
env: AUTH_TOKEN=${{ secrets.UNLEASH_TOKEN }}
|
||||||
config: baseUrl=${{ github.event.deployment_status.target_url }}
|
config: baseUrl=${{ github.event.deployment_status.target_url }}
|
||||||
record: true
|
record: true
|
||||||
spec: cypress/integration/auth/auth.spec.js
|
spec: cypress/integration/auth/auth.spec.ts
|
||||||
env:
|
env:
|
||||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
4
frontend/.github/workflows/e2e.feature.yml
vendored
4
frontend/.github/workflows/e2e.feature.yml
vendored
@ -17,9 +17,9 @@ jobs:
|
|||||||
- name: Run Cypress
|
- name: Run Cypress
|
||||||
uses: cypress-io/github-action@v2
|
uses: cypress-io/github-action@v2
|
||||||
with:
|
with:
|
||||||
env: AUTH_TOKEN=${{ secrets.UNLEASH_TOKEN }},DEFAULT_ENV="development"
|
env: AUTH_TOKEN=${{ secrets.UNLEASH_TOKEN }}
|
||||||
config: baseUrl=${{ github.event.deployment_status.target_url }}
|
config: baseUrl=${{ github.event.deployment_status.target_url }}
|
||||||
record: true
|
record: true
|
||||||
spec: cypress/integration/feature-toggle/feature.spec.js
|
spec: cypress/integration/feature/feature.spec.ts
|
||||||
env:
|
env:
|
||||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
@ -14,7 +14,7 @@ jobs:
|
|||||||
node-version: [14.x]
|
node-version: [14.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v2
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
|
@ -1,21 +1,4 @@
|
|||||||
# Developing
|
# unleash-frontend
|
||||||
|
|
||||||
## Why did you render
|
|
||||||
|
|
||||||
This application is set up with [WDYR](https://github.com/welldone-software/why-did-you-render) and [craco](https://github.com/gsoft-inc/craco) in order to find, debug and remove uneccesary re-renders. This configuration can be found in /src/wdyr.ts.
|
|
||||||
|
|
||||||
In order to turn it on, change the configuration accordingly:
|
|
||||||
|
|
||||||
```
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
const whyDidYouRender = require('@welldone-software/why-did-you-render');
|
|
||||||
whyDidYouRender(React, {
|
|
||||||
trackAllPureComponents: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Now you should be able to review rendering information in the console. If you do utilise this functionality, please remember to set the configuration back to spare other developers the noise in the console.
|
|
||||||
|
|
||||||
## Run with a local instance of the unleash-api:
|
## Run with a local instance of the unleash-api:
|
||||||
|
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
const presetReact = require('@babel/preset-react').default;
|
|
||||||
const presetCRA = require('babel-preset-react-app');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
babel: {
|
|
||||||
loaderOptions: (babelLoaderOptions, { env, paths }) => {
|
|
||||||
const origBabelPresetReactAppIndex = babelLoaderOptions.presets.findIndex(
|
|
||||||
preset => {
|
|
||||||
return preset[0].includes('babel-preset-react-app');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (origBabelPresetReactAppIndex === -1) {
|
|
||||||
return babelLoaderOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
const overridenBabelPresetReactApp = (...args) => {
|
|
||||||
const babelPresetReactAppResult = presetCRA(...args);
|
|
||||||
const origPresetReact = babelPresetReactAppResult.presets.find(
|
|
||||||
preset => {
|
|
||||||
return preset[0] === presetReact;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
Object.assign(origPresetReact[1], {
|
|
||||||
importSource: '@welldone-software/why-did-you-render',
|
|
||||||
});
|
|
||||||
return babelPresetReactAppResult;
|
|
||||||
};
|
|
||||||
|
|
||||||
babelLoaderOptions.presets[
|
|
||||||
origBabelPresetReactAppIndex
|
|
||||||
] = overridenBabelPresetReactApp;
|
|
||||||
|
|
||||||
return babelLoaderOptions;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,4 +1,6 @@
|
|||||||
{
|
{
|
||||||
"projectId": "tc2qff",
|
"projectId": "tc2qff",
|
||||||
"defaultCommandTimeout": 12000
|
"defaultCommandTimeout": 12000,
|
||||||
|
"screenshotOnRunFailure": false,
|
||||||
|
"video": false
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,4 @@
|
|||||||
/* eslint-disable jest/no-conditional-expect */
|
|
||||||
/// <reference types="cypress" />
|
/// <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 username = 'test@test.com';
|
||||||
const password = 'qY70$NDcJNXA';
|
const password = 'qY70$NDcJNXA';
|
@ -1,82 +1,48 @@
|
|||||||
/* eslint-disable jest/no-conditional-expect */
|
|
||||||
/// <reference types="cypress" />
|
/// <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 = '';
|
import { disableFeatureStrategiesProductionGuard } from '../../../src/component/feature/FeatureView/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesProductionGuard/FeatureStrategiesProductionGuard';
|
||||||
let enterprise = false;
|
|
||||||
|
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 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(() => {
|
after(() => {
|
||||||
const authToken = Cypress.env('AUTH_TOKEN');
|
|
||||||
|
|
||||||
cy.request({
|
cy.request({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: `${
|
url: `${baseUrl}/api/admin/features/${featureToggleName}`,
|
||||||
Cypress.config().baseUrl
|
headers: { Authorization: authToken },
|
||||||
}/api/admin/features/${featureToggleName}`,
|
|
||||||
headers: {
|
|
||||||
Authorization: authToken,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.request({
|
cy.request({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: `${
|
url: `${baseUrl}/api/admin/archive/${featureToggleName}`,
|
||||||
Cypress.config().baseUrl
|
headers: { Authorization: authToken },
|
||||||
}/api/admin/archive/${featureToggleName}`,
|
|
||||||
headers: {
|
|
||||||
Authorization: authToken,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Cypress starts out with a blank slate for each test
|
disableFeatureStrategiesProductionGuard();
|
||||||
// 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'));
|
|
||||||
|
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
|
||||||
if (passwordAuth) {
|
if (passwordAuth) {
|
||||||
cy.get('[data-test="LOGIN_EMAIL_ID"]').type('test@test.com');
|
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_PASSWORD_ID"]').type('qY70$NDcJNXA');
|
||||||
|
|
||||||
cy.get("[data-test='LOGIN_BUTTON']").click();
|
cy.get("[data-test='LOGIN_BUTTON']").click();
|
||||||
} else {
|
} else {
|
||||||
cy.get('[data-test=LOGIN_EMAIL_ID]').type('test@unleash-e2e.com');
|
cy.get('[data-test=LOGIN_EMAIL_ID]').type('test@unleash-e2e.com');
|
||||||
cy.get('[data-test=LOGIN_BUTTON]').click();
|
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', () => {
|
it('can create a feature toggle', () => {
|
||||||
if (
|
if (document.querySelector("[data-test='CLOSE_SPLASH']")) {
|
||||||
document.querySelectorAll("[data-test='CLOSE_SPLASH']").length > 0
|
|
||||||
) {
|
|
||||||
cy.get("[data-test='CLOSE_SPLASH']").click();
|
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_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='CF_CREATE_BTN_ID']").click();
|
||||||
cy.wait('@createFeature');
|
cy.wait('@createFeature');
|
||||||
cy.url().should('include', featureToggleName);
|
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.get('[data-test=NAVIGATE_TO_CREATE_FEATURE').click();
|
||||||
|
|
||||||
cy.intercept('POST', '/api/admin/projects/default/features').as(
|
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_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='CF_CREATE_BTN_ID']").click();
|
||||||
|
|
||||||
cy.get("[data-test='INPUT_ERROR_TEXT']").contains(
|
cy.get("[data-test='INPUT_ERROR_TEXT']").contains(
|
||||||
'A feature with this name already exists'
|
'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.get('[data-test=NAVIGATE_TO_CREATE_FEATURE').click();
|
||||||
|
|
||||||
cy.intercept('POST', '/api/admin/projects/default/features').as(
|
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_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='CF_CREATE_BTN_ID']").click();
|
||||||
|
|
||||||
cy.get("[data-test='INPUT_ERROR_TEXT']").contains(
|
cy.get("[data-test='INPUT_ERROR_TEXT']").contains(
|
||||||
`"name" must be URL friendly`
|
`"name" must be URL friendly`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Can add a gradual rollout strategy to the development environment', () => {
|
it('can add a gradual rollout strategy to the development environment', () => {
|
||||||
cy.wait(1000);
|
|
||||||
cy.visit(`/projects/default/features/${featureToggleName}/strategies`);
|
cy.visit(`/projects/default/features/${featureToggleName}/strategies`);
|
||||||
cy.get('[data-test=ADD_NEW_STRATEGY_ID]').click();
|
cy.get('[data-test=ADD_NEW_STRATEGY_ID]').click();
|
||||||
cy.get('[data-test=ADD_NEW_STRATEGY_CARD_BUTTON_ID-2').click();
|
cy.get('[data-test=ADD_NEW_STRATEGY_CARD_BUTTON_ID-2').click();
|
||||||
@ -147,10 +107,9 @@ describe('feature toggle', () => {
|
|||||||
|
|
||||||
cy.intercept(
|
cy.intercept(
|
||||||
'POST',
|
'POST',
|
||||||
`/api/admin/projects/default/features/${featureToggleName}/environments/${defaultEnv}/strategies`,
|
`/api/admin/projects/default/features/${featureToggleName}/environments/*/strategies`,
|
||||||
req => {
|
req => {
|
||||||
expect(req.body.name).to.equal('flexibleRollout');
|
expect(req.body.name).to.equal('flexibleRollout');
|
||||||
|
|
||||||
expect(req.body.parameters.groupId).to.equal(featureToggleName);
|
expect(req.body.parameters.groupId).to.equal(featureToggleName);
|
||||||
expect(req.body.parameters.stickiness).to.equal('default');
|
expect(req.body.parameters.stickiness).to.equal('default');
|
||||||
expect(req.body.parameters.rollout).to.equal(30);
|
expect(req.body.parameters.rollout).to.equal(30);
|
||||||
@ -172,7 +131,6 @@ describe('feature toggle', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('can update a strategy in the development environment', () => {
|
it('can update a strategy in the development environment', () => {
|
||||||
cy.wait(1000);
|
|
||||||
cy.visit(`/projects/default/features/${featureToggleName}/strategies`);
|
cy.visit(`/projects/default/features/${featureToggleName}/strategies`);
|
||||||
cy.get('[data-test=STRATEGY_ACCORDION_ID-flexibleRollout').click();
|
cy.get('[data-test=STRATEGY_ACCORDION_ID-flexibleRollout').click();
|
||||||
|
|
||||||
@ -188,7 +146,6 @@ describe('feature toggle', () => {
|
|||||||
.first()
|
.first()
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
let newGroupId = 'new-group-id';
|
|
||||||
cy.get('[data-test=FLEXIBLE_STRATEGY_GROUP_ID]')
|
cy.get('[data-test=FLEXIBLE_STRATEGY_GROUP_ID]')
|
||||||
.first()
|
.first()
|
||||||
.clear()
|
.clear()
|
||||||
@ -196,9 +153,9 @@ describe('feature toggle', () => {
|
|||||||
|
|
||||||
cy.intercept(
|
cy.intercept(
|
||||||
'PUT',
|
'PUT',
|
||||||
`/api/admin/projects/default/features/${featureToggleName}/environments/${defaultEnv}/strategies/${strategyId}`,
|
`/api/admin/projects/default/features/${featureToggleName}/environments/*/strategies/${strategyId}`,
|
||||||
req => {
|
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.stickiness).to.equal('sessionId');
|
||||||
expect(req.body.parameters.rollout).to.equal(60);
|
expect(req.body.parameters.rollout).to.equal(60);
|
||||||
|
|
||||||
@ -219,12 +176,11 @@ describe('feature toggle', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('can delete a strategy in the development environment', () => {
|
it('can delete a strategy in the development environment', () => {
|
||||||
cy.wait(1000);
|
|
||||||
cy.visit(`/projects/default/features/${featureToggleName}/strategies`);
|
cy.visit(`/projects/default/features/${featureToggleName}/strategies`);
|
||||||
|
|
||||||
cy.intercept(
|
cy.intercept(
|
||||||
'DELETE',
|
'DELETE',
|
||||||
`/api/admin/projects/default/features/${featureToggleName}/environments/${defaultEnv}/strategies/${strategyId}`,
|
`/api/admin/projects/default/features/${featureToggleName}/environments/*/strategies/${strategyId}`,
|
||||||
req => {
|
req => {
|
||||||
req.continue(res => {
|
req.continue(res => {
|
||||||
expect(res.statusCode).to.equal(200);
|
expect(res.statusCode).to.equal(200);
|
||||||
@ -237,8 +193,7 @@ describe('feature toggle', () => {
|
|||||||
cy.wait('@deleteStrategy');
|
cy.wait('@deleteStrategy');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Can add a userid strategy to the development environment', () => {
|
it('can add a userid strategy to the development environment', () => {
|
||||||
cy.wait(1000);
|
|
||||||
cy.visit(`/projects/default/features/${featureToggleName}/strategies`);
|
cy.visit(`/projects/default/features/${featureToggleName}/strategies`);
|
||||||
cy.get('[data-test=ADD_NEW_STRATEGY_ID]').click();
|
cy.get('[data-test=ADD_NEW_STRATEGY_ID]').click();
|
||||||
cy.get('[data-test=ADD_NEW_STRATEGY_CARD_BUTTON_ID-3').click();
|
cy.get('[data-test=ADD_NEW_STRATEGY_CARD_BUTTON_ID-3').click();
|
||||||
@ -260,7 +215,7 @@ describe('feature toggle', () => {
|
|||||||
|
|
||||||
cy.intercept(
|
cy.intercept(
|
||||||
'POST',
|
'POST',
|
||||||
`/api/admin/projects/default/features/${featureToggleName}/environments/${defaultEnv}/strategies`,
|
`/api/admin/projects/default/features/${featureToggleName}/environments/*/strategies`,
|
||||||
req => {
|
req => {
|
||||||
expect(req.body.name).to.equal('userWithId');
|
expect(req.body.name).to.equal('userWithId');
|
||||||
|
|
||||||
@ -282,11 +237,12 @@ describe('feature toggle', () => {
|
|||||||
cy.wait('@addStrategyToFeature');
|
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 variantName = 'my-new-variant';
|
||||||
const secondVariantName = 'my-second-variant';
|
const secondVariantName = 'my-second-variant';
|
||||||
cy.wait(1000);
|
|
||||||
cy.visit(`/projects/default/features/${featureToggleName}/variants`);
|
cy.visit(`/projects/default/features/${featureToggleName}/variants`);
|
||||||
|
|
||||||
cy.intercept(
|
cy.intercept(
|
||||||
'PATCH',
|
'PATCH',
|
||||||
`/api/admin/projects/default/features/${featureToggleName}/variants`,
|
`/api/admin/projects/default/features/${featureToggleName}/variants`,
|
||||||
@ -304,20 +260,23 @@ describe('feature toggle', () => {
|
|||||||
expect(req.body[1].value.name).to.equal(secondVariantName);
|
expect(req.body[1].value.name).to.equal(secondVariantName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).as('variantcreation');
|
).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);
|
|
||||||
|
|
||||||
|
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.visit(`/projects/default/features/${featureToggleName}/variants`);
|
||||||
|
|
||||||
cy.get('[data-test=VARIANT_EDIT_BUTTON]').first().click();
|
cy.get('[data-test=VARIANT_EDIT_BUTTON]').first().click();
|
||||||
cy.get('[data-test=VARIANT_NAME_INPUT]')
|
cy.get('[data-test=VARIANT_NAME_INPUT]')
|
||||||
.children()
|
.children()
|
||||||
@ -328,6 +287,7 @@ describe('feature toggle', () => {
|
|||||||
.find('input')
|
.find('input')
|
||||||
.check();
|
.check();
|
||||||
cy.get('[data-test=VARIANT_WEIGHT_INPUT]').clear().type('15');
|
cy.get('[data-test=VARIANT_WEIGHT_INPUT]').clear().type('15');
|
||||||
|
|
||||||
cy.intercept(
|
cy.intercept(
|
||||||
'PATCH',
|
'PATCH',
|
||||||
`/api/admin/projects/default/features/${featureToggleName}/variants`,
|
`/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].path).to.match(/weight/);
|
||||||
expect(req.body[2].value).to.equal(150);
|
expect(req.body[2].value).to.equal(150);
|
||||||
}
|
}
|
||||||
).as('variantupdate');
|
).as('variantUpdate');
|
||||||
|
|
||||||
cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click();
|
cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click();
|
||||||
cy.wait('@variantupdate');
|
cy.wait('@variantUpdate');
|
||||||
cy.get('[data-test=VARIANT_WEIGHT]')
|
cy.get('[data-test=VARIANT_WEIGHT]')
|
||||||
.first()
|
.first()
|
||||||
.should('have.text', '15 %');
|
.should('have.text', '15 %');
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`can delete variant`, () => {
|
it('can delete variant', () => {
|
||||||
const variantName = 'to-be-deleted';
|
const variantName = 'to-be-deleted';
|
||||||
cy.wait(1000);
|
|
||||||
cy.visit(`/projects/default/features/${featureToggleName}/variants`);
|
cy.visit(`/projects/default/features/${featureToggleName}/variants`);
|
||||||
cy.get('[data-test=ADD_VARIANT_BUTTON]').click();
|
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.get('[data-test=DIALOGUE_CONFIRM_ID]').click();
|
||||||
|
|
||||||
cy.intercept(
|
cy.intercept(
|
||||||
'PATCH',
|
'PATCH',
|
||||||
`/api/admin/projects/default/features/${featureToggleName}/variants`,
|
`/api/admin/projects/default/features/${featureToggleName}/variants`,
|
||||||
@ -365,6 +327,7 @@ describe('feature toggle', () => {
|
|||||||
expect(e.path).to.match(/\//);
|
expect(e.path).to.match(/\//);
|
||||||
}
|
}
|
||||||
).as('delete');
|
).as('delete');
|
||||||
|
|
||||||
cy.get(`[data-test=VARIANT_DELETE_BUTTON_${variantName}]`).click();
|
cy.get(`[data-test=VARIANT_DELETE_BUTTON_${variantName}]`).click();
|
||||||
cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click();
|
cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click();
|
||||||
cy.wait('@delete');
|
cy.wait('@delete');
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "unleash-frontend",
|
"name": "unleash-frontend",
|
||||||
"description": "unleash your features",
|
"description": "unleash your features",
|
||||||
"version": "4.8.0-beta.10",
|
"version": "4.8.0",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"unleash",
|
"unleash",
|
||||||
"feature toggle",
|
"feature toggle",
|
||||||
@ -29,7 +29,6 @@
|
|||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"start:heroku": "UNLEASH_API=https://unleash.herokuapp.com yarn run start",
|
"start:heroku": "UNLEASH_API=https://unleash.herokuapp.com yarn run start",
|
||||||
"start:ea": "UNLEASH_API=https://unleash4.herokuapp.com yarn run start",
|
"start:ea": "UNLEASH_API=https://unleash4.herokuapp.com yarn run start",
|
||||||
"start:demo": "UNLEASH_API=http://unleash.herokuapp.com yarn run start",
|
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"prepare": "yarn run build",
|
"prepare": "yarn run build",
|
||||||
"fmt": "prettier src --write --loglevel warn",
|
"fmt": "prettier src --write --loglevel warn",
|
||||||
@ -42,6 +41,7 @@
|
|||||||
"@material-ui/core": "4.12.3",
|
"@material-ui/core": "4.12.3",
|
||||||
"@material-ui/icons": "4.11.2",
|
"@material-ui/icons": "4.11.2",
|
||||||
"@material-ui/lab": "4.0.0-alpha.60",
|
"@material-ui/lab": "4.0.0-alpha.60",
|
||||||
|
"@testing-library/dom": "8.11.3",
|
||||||
"@testing-library/jest-dom": "5.16.2",
|
"@testing-library/jest-dom": "5.16.2",
|
||||||
"@testing-library/react": "12.1.3",
|
"@testing-library/react": "12.1.3",
|
||||||
"@testing-library/user-event": "13.5.0",
|
"@testing-library/user-event": "13.5.0",
|
||||||
@ -49,28 +49,23 @@
|
|||||||
"@types/deep-diff": "1.0.1",
|
"@types/deep-diff": "1.0.1",
|
||||||
"@types/jest": "27.4.1",
|
"@types/jest": "27.4.1",
|
||||||
"@types/lodash.clonedeep": "4.5.6",
|
"@types/lodash.clonedeep": "4.5.6",
|
||||||
"@types/node": "14.18.12",
|
"@types/node": "17.0.18",
|
||||||
"@types/react": "17.0.39",
|
"@types/react": "17.0.39",
|
||||||
"@types/react-dom": "17.0.11",
|
"@types/react-dom": "17.0.11",
|
||||||
"@types/react-outside-click-handler": "1.3.1",
|
|
||||||
"@types/react-router-dom": "5.3.3",
|
"@types/react-router-dom": "5.3.3",
|
||||||
"@types/react-test-renderer": "17.0.1",
|
"@types/react-test-renderer": "17.0.1",
|
||||||
"@types/react-timeago": "4.1.3",
|
"@types/react-timeago": "4.1.3",
|
||||||
"@welldone-software/why-did-you-render": "6.2.3",
|
|
||||||
"chart.js": "3.7.1",
|
"chart.js": "3.7.1",
|
||||||
"chartjs-adapter-date-fns": "2.0.0",
|
"chartjs-adapter-date-fns": "2.0.0",
|
||||||
"classnames": "2.3.1",
|
"classnames": "2.3.1",
|
||||||
"copy-to-clipboard": "3.3.1",
|
"copy-to-clipboard": "3.3.1",
|
||||||
"craco": "0.0.3",
|
"cypress": "9.5.1",
|
||||||
"css-loader": "6.6.0",
|
|
||||||
"cypress": "8.7.0",
|
|
||||||
"date-fns": "2.28.0",
|
"date-fns": "2.28.0",
|
||||||
"debounce": "1.2.1",
|
"debounce": "1.2.1",
|
||||||
"deep-diff": "1.0.2",
|
"deep-diff": "1.0.2",
|
||||||
"fast-json-patch": "3.1.0",
|
"fast-json-patch": "3.1.0",
|
||||||
"http-proxy-middleware": "2.0.3",
|
"http-proxy-middleware": "2.0.3",
|
||||||
"lodash.clonedeep": "4.5.0",
|
"lodash.clonedeep": "4.5.0",
|
||||||
"lodash.flow": "3.5.0",
|
|
||||||
"prettier": "2.5.1",
|
"prettier": "2.5.1",
|
||||||
"prop-types": "15.8.1",
|
"prop-types": "15.8.1",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
@ -79,15 +74,13 @@
|
|||||||
"react-dnd-html5-backend": "14.1.0",
|
"react-dnd-html5-backend": "14.1.0",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"react-hooks-global-state": "1.0.2",
|
"react-hooks-global-state": "1.0.2",
|
||||||
"react-outside-click-handler": "1.3.0",
|
|
||||||
"react-router-dom": "5.3.0",
|
"react-router-dom": "5.3.0",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
"react-test-renderer": "16.14.0",
|
"react-test-renderer": "17.0.2",
|
||||||
"react-timeago": "6.2.1",
|
"react-timeago": "6.2.1",
|
||||||
"sass": "1.49.8",
|
"sass": "1.49.9",
|
||||||
"swr": "1.2.2",
|
"swr": "1.2.2",
|
||||||
"typescript": "4.5.5",
|
"typescript": "4.6.2"
|
||||||
"web-vitals": "2.1.4"
|
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleNameMapper": {
|
"moduleNameMapper": {
|
||||||
@ -117,6 +110,9 @@
|
|||||||
"no-restricted-globals": "off",
|
"no-restricted-globals": "off",
|
||||||
"no-useless-computed-key": "off",
|
"no-useless-computed-key": "off",
|
||||||
"import/no-anonymous-default-export": "off"
|
"import/no-anonymous-default-export": "off"
|
||||||
}
|
},
|
||||||
|
"ignorePatterns": [
|
||||||
|
"cypress"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,9 @@ import { IAddonProvider } from '../../../../interfaces/addons';
|
|||||||
interface IAddonProps {
|
interface IAddonProps {
|
||||||
provider: IAddonProvider;
|
provider: IAddonProvider;
|
||||||
checkedEvents: string[];
|
checkedEvents: string[];
|
||||||
setEventValue: (name: string) => void;
|
setEventValue: (
|
||||||
|
name: string
|
||||||
|
) => (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
error: Record<string, string>;
|
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 PropTypes from 'prop-types';
|
||||||
import { TextField, FormControlLabel, Switch } from '@material-ui/core';
|
import { TextField, FormControlLabel, Switch, Button } from '@material-ui/core';
|
||||||
import { FormButtons, styles as commonStyles } from '../../common';
|
import { styles as commonStyles } from '../../common';
|
||||||
import { trim } from '../../common/util';
|
import { trim } from '../../common/util';
|
||||||
import { AddonParameters } from './AddonParameters/AddonParameters';
|
import { AddonParameters } from './AddonParameters/AddonParameters';
|
||||||
import { AddonEvents } from './AddonEvents/AddonEvents';
|
import { AddonEvents } from './AddonEvents/AddonEvents';
|
||||||
@ -79,7 +79,7 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => {
|
|||||||
setErrors({ ...errors, events: undefined });
|
setErrors({ ...errors, events: undefined });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const onCancel = () => {
|
||||||
history.goBack();
|
history.goBack();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -203,10 +203,12 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => {
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
<section className={styles.formSection}>
|
<section className={styles.formSection}>
|
||||||
<FormButtons
|
<Button type="submit" color="primary" variant="contained">
|
||||||
submitText={submitText}
|
{submitText}
|
||||||
onCancel={handleCancel}
|
</Button>
|
||||||
/>
|
<Button type="button" onClick={onCancel}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ReactElement } from 'react';
|
import React, { ReactElement } from 'react';
|
||||||
import { ConfiguredAddons } from './ConfiguredAddons/ConfiguredAddons';
|
import { ConfiguredAddons } from './ConfiguredAddons/ConfiguredAddons';
|
||||||
import { AvailableAddons } from './AvailableAddons/AvailableAddons';
|
import { AvailableAddons } from './AvailableAddons/AvailableAddons';
|
||||||
import { Avatar } from '@material-ui/core';
|
import { Avatar } from '@material-ui/core';
|
||||||
@ -12,7 +12,7 @@ import dataDogIcon from '../../../assets/icons/datadog.svg';
|
|||||||
import { formatAssetPath } from '../../../utils/format-path';
|
import { formatAssetPath } from '../../../utils/format-path';
|
||||||
import useAddons from '../../../hooks/api/getters/useAddons/useAddons';
|
import useAddons from '../../../hooks/api/getters/useAddons/useAddons';
|
||||||
|
|
||||||
const style = {
|
const style: React.CSSProperties = {
|
||||||
width: '40px',
|
width: '40px',
|
||||||
height: '40px',
|
height: '40px',
|
||||||
marginRight: '16px',
|
marginRight: '16px',
|
||||||
|
@ -44,7 +44,6 @@ export const AvailableAddons = ({
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
history.push(`/addons/create/${provider.name}`)
|
history.push(`/addons/create/${provider.name}`)
|
||||||
}
|
}
|
||||||
tooltip={`Configure ${provider.name} Addon`}
|
|
||||||
>
|
>
|
||||||
Configure
|
Configure
|
||||||
</PermissionButton>
|
</PermissionButton>
|
||||||
|
@ -21,6 +21,7 @@ import AccessContext from '../../../../contexts/AccessContext';
|
|||||||
import { IAddon } from '../../../../interfaces/addons';
|
import { IAddon } from '../../../../interfaces/addons';
|
||||||
import PermissionIconButton from '../../../common/PermissionIconButton/PermissionIconButton';
|
import PermissionIconButton from '../../../common/PermissionIconButton/PermissionIconButton';
|
||||||
import Dialogue from '../../../common/Dialogue';
|
import Dialogue from '../../../common/Dialogue';
|
||||||
|
import { formatUnknownError } from '../../../../utils/format-unknown-error';
|
||||||
|
|
||||||
interface IConfigureAddonsProps {
|
interface IConfigureAddonsProps {
|
||||||
getAddonIcon: (name: string) => ReactElement;
|
getAddonIcon: (name: string) => ReactElement;
|
||||||
@ -59,8 +60,8 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
|
|||||||
title: 'Success',
|
title: 'Success',
|
||||||
text: 'Addon state switched successfully',
|
text: 'Addon state switched successfully',
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -122,7 +123,6 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
|
|||||||
</PermissionIconButton>
|
</PermissionIconButton>
|
||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
permission={UPDATE_ADDON}
|
permission={UPDATE_ADDON}
|
||||||
tooltip={'Edit Addon'}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
history.push(`/addons/edit/${addon.id}`);
|
history.push(`/addons/edit/${addon.id}`);
|
||||||
}}
|
}}
|
||||||
@ -131,7 +131,6 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
|
|||||||
</PermissionIconButton>
|
</PermissionIconButton>
|
||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
permission={DELETE_ADDON}
|
permission={DELETE_ADDON}
|
||||||
tooltip={'Remove Addon'}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDeletedAddon(addon);
|
setDeletedAddon(addon);
|
||||||
setShowDelete(true);
|
setShowDelete(true);
|
||||||
|
@ -24,7 +24,6 @@ import {
|
|||||||
DELETE_API_TOKEN,
|
DELETE_API_TOKEN,
|
||||||
} from '../../../providers/AccessProvider/permissions';
|
} from '../../../providers/AccessProvider/permissions';
|
||||||
import { useStyles } from './ApiTokenList.styles';
|
import { useStyles } from './ApiTokenList.styles';
|
||||||
import { formatDateWithLocale } from '../../../common/util';
|
|
||||||
import Secret from './secret';
|
import Secret from './secret';
|
||||||
import { Delete, FileCopy } from '@material-ui/icons';
|
import { Delete, FileCopy } from '@material-ui/icons';
|
||||||
import Dialogue from '../../../common/Dialogue';
|
import Dialogue from '../../../common/Dialogue';
|
||||||
@ -32,6 +31,7 @@ import { CREATE_API_TOKEN_BUTTON } from '../../../../testIds';
|
|||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from '@material-ui/lab';
|
||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
import { useLocationSettings } from '../../../../hooks/useLocationSettings';
|
import { useLocationSettings } from '../../../../hooks/useLocationSettings';
|
||||||
|
import { formatDateYMD } from '../../../../utils/format-date';
|
||||||
|
|
||||||
interface IApiToken {
|
interface IApiToken {
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
@ -146,7 +146,7 @@ export const ApiTokenList = () => {
|
|||||||
align="left"
|
align="left"
|
||||||
className={styles.hideSM}
|
className={styles.hideSM}
|
||||||
>
|
>
|
||||||
{formatDateWithLocale(
|
{formatDateYMD(
|
||||||
item.createdAt,
|
item.createdAt,
|
||||||
locationSettings.locale
|
locationSettings.locale
|
||||||
)}
|
)}
|
||||||
@ -249,7 +249,7 @@ export const ApiTokenList = () => {
|
|||||||
}
|
}
|
||||||
data-test={CREATE_API_TOKEN_BUTTON}
|
data-test={CREATE_API_TOKEN_BUTTON}
|
||||||
>
|
>
|
||||||
Create API token
|
New API token
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import FormTemplate from '../../../common/FormTemplate/FormTemplate';
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import ApiTokenForm from '../ApiTokenForm/ApiTokenForm';
|
import ApiTokenForm from '../ApiTokenForm/ApiTokenForm';
|
||||||
|
import { CreateButton } from 'component/common/CreateButton/CreateButton';
|
||||||
|
import useApiTokensApi from 'hooks/api/actions/useApiTokensApi/useApiTokensApi';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
import useApiTokenForm from '../hooks/useApiTokenForm';
|
import useApiTokenForm from '../hooks/useApiTokenForm';
|
||||||
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
import useToast from '../../../../hooks/useToast';
|
|
||||||
import useApiTokensApi from '../../../../hooks/api/actions/useApiTokensApi/useApiTokensApi';
|
|
||||||
import PermissionButton from '../../../common/PermissionButton/PermissionButton';
|
|
||||||
import { ADMIN } from '../../../providers/AccessProvider/permissions';
|
|
||||||
import { ConfirmToken } from '../ConfirmToken/ConfirmToken';
|
import { ConfirmToken } from '../ConfirmToken/ConfirmToken';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { scrollToTop } from '../../../common/util';
|
import { scrollToTop } from '../../../common/util';
|
||||||
|
import { formatUnknownError } from '../../../../utils/format-unknown-error';
|
||||||
|
|
||||||
export const CreateApiToken = () => {
|
export const CreateApiToken = () => {
|
||||||
const { setToastApiError } = useToast();
|
const { setToastApiError } = useToast();
|
||||||
@ -49,8 +50,8 @@ export const CreateApiToken = () => {
|
|||||||
setToken(api.secret);
|
setToken(api.secret);
|
||||||
setShowConfirm(true);
|
setShowConfirm(true);
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -95,9 +96,7 @@ export const CreateApiToken = () => {
|
|||||||
mode="Create"
|
mode="Create"
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
>
|
>
|
||||||
<PermissionButton permission={ADMIN} type="submit">
|
<CreateButton name="token" permission={ADMIN} />
|
||||||
Create token
|
|
||||||
</PermissionButton>
|
|
||||||
</ApiTokenForm>
|
</ApiTokenForm>
|
||||||
<ConfirmToken
|
<ConfirmToken
|
||||||
open={showConfirm}
|
open={showConfirm}
|
||||||
|
@ -68,8 +68,8 @@ export const GoogleAuth = () => {
|
|||||||
title: 'Settings stored',
|
title: 'Settings stored',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(formatUnknownError(err));
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -79,8 +79,8 @@ export const OidcAuth = () => {
|
|||||||
title: 'Settings stored',
|
title: 'Settings stored',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(formatUnknownError(err));
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,8 +51,8 @@ export const PasswordAuth = () => {
|
|||||||
type: 'success',
|
type: 'success',
|
||||||
show: true,
|
show: true,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(formatUnknownError(err));
|
setToastApiError(formatUnknownError(error));
|
||||||
setDisablePasswordAuth(config.disabled);
|
setDisablePasswordAuth(config.disabled);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -75,8 +75,8 @@ export const SamlAuth = () => {
|
|||||||
title: 'Settings stored',
|
title: 'Settings stored',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(formatUnknownError(err));
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
import OpenInNew from '@material-ui/icons/OpenInNew';
|
import OpenInNew from '@material-ui/icons/OpenInNew';
|
||||||
import { formatDateWithLocale } from '../../common/util';
|
|
||||||
import PageContent from '../../common/PageContent';
|
import PageContent from '../../common/PageContent';
|
||||||
import HeaderTitle from '../../common/HeaderTitle';
|
import HeaderTitle from '../../common/HeaderTitle';
|
||||||
import ConditionallyRender from '../../common/ConditionallyRender';
|
import ConditionallyRender from '../../common/ConditionallyRender';
|
||||||
@ -16,6 +15,7 @@ import { formatApiPath } from '../../../utils/format-path';
|
|||||||
import useInvoices from '../../../hooks/api/getters/useInvoices/useInvoices';
|
import useInvoices from '../../../hooks/api/getters/useInvoices/useInvoices';
|
||||||
import { IInvoice } from '../../../interfaces/invoice';
|
import { IInvoice } from '../../../interfaces/invoice';
|
||||||
import { useLocationSettings } from '../../../hooks/useLocationSettings';
|
import { useLocationSettings } from '../../../hooks/useLocationSettings';
|
||||||
|
import { formatDateYMD } from '../../../utils/format-date';
|
||||||
|
|
||||||
const PORTAL_URL = formatApiPath('api/admin/invoices/portal');
|
const PORTAL_URL = formatApiPath('api/admin/invoices/portal');
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ const InvoiceList = () => {
|
|||||||
style={{ textAlign: 'left' }}
|
style={{ textAlign: 'left' }}
|
||||||
>
|
>
|
||||||
{item.dueDate &&
|
{item.dueDate &&
|
||||||
formatDateWithLocale(
|
formatDateYMD(
|
||||||
item.dueDate,
|
item.dueDate,
|
||||||
locationSettings.locale
|
locationSettings.locale
|
||||||
)}
|
)}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import FormTemplate from '../../../common/FormTemplate/FormTemplate';
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
import useProjectRolesApi from '../../../../hooks/api/actions/useProjectRolesApi/useProjectRolesApi';
|
import useProjectRolesApi from 'hooks/api/actions/useProjectRolesApi/useProjectRolesApi';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import ProjectRoleForm from '../ProjectRoleForm/ProjectRoleForm';
|
import ProjectRoleForm from '../ProjectRoleForm/ProjectRoleForm';
|
||||||
import useProjectRoleForm from '../hooks/useProjectRoleForm';
|
import useProjectRoleForm from '../hooks/useProjectRoleForm';
|
||||||
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import useToast from '../../../../hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import PermissionButton from '../../../common/PermissionButton/PermissionButton';
|
import { CreateButton } from 'component/common/CreateButton/CreateButton';
|
||||||
import { ADMIN } from '../../../providers/AccessProvider/permissions';
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import { formatUnknownError } from 'utils/format-unknown-error';
|
||||||
|
|
||||||
const CreateProjectRole = () => {
|
const CreateProjectRole = () => {
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
@ -49,8 +50,8 @@ const CreateProjectRole = () => {
|
|||||||
confetti: true,
|
confetti: true,
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -95,9 +96,7 @@ const CreateProjectRole = () => {
|
|||||||
validateNameUniqueness={validateNameUniqueness}
|
validateNameUniqueness={validateNameUniqueness}
|
||||||
getRoleKey={getRoleKey}
|
getRoleKey={getRoleKey}
|
||||||
>
|
>
|
||||||
<PermissionButton permission={ADMIN} type="submit">
|
<CreateButton name="role" permission={ADMIN} />
|
||||||
Create role
|
|
||||||
</PermissionButton>
|
|
||||||
</ProjectRoleForm>
|
</ProjectRoleForm>
|
||||||
</FormTemplate>
|
</FormTemplate>
|
||||||
);
|
);
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
import FormTemplate from '../../../common/FormTemplate/FormTemplate';
|
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton';
|
||||||
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
import useProjectRolesApi from '../../../../hooks/api/actions/useProjectRolesApi/useProjectRolesApi';
|
import useProjectRolesApi from 'hooks/api/actions/useProjectRolesApi/useProjectRolesApi';
|
||||||
|
import useProjectRole from 'hooks/api/getters/useProjectRole/useProjectRole';
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import ProjectRoleForm from '../ProjectRoleForm/ProjectRoleForm';
|
import useToast from 'hooks/useToast';
|
||||||
|
import { IPermission } from 'interfaces/user';
|
||||||
|
import { useParams, useHistory } from 'react-router-dom';
|
||||||
import useProjectRoleForm from '../hooks/useProjectRoleForm';
|
import useProjectRoleForm from '../hooks/useProjectRoleForm';
|
||||||
import useProjectRole from '../../../../hooks/api/getters/useProjectRole/useProjectRole';
|
import ProjectRoleForm from '../ProjectRoleForm/ProjectRoleForm';
|
||||||
import { IPermission } from '../../../../interfaces/project';
|
import { formatUnknownError } from 'utils/format-unknown-error';
|
||||||
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import useToast from '../../../../hooks/useToast';
|
|
||||||
import PermissionButton from '../../../common/PermissionButton/PermissionButton';
|
|
||||||
import { ADMIN } from '../../../providers/AccessProvider/permissions';
|
|
||||||
|
|
||||||
const EditProjectRole = () => {
|
const EditProjectRole = () => {
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
@ -88,8 +86,8 @@ const EditProjectRole = () => {
|
|||||||
text: 'Your role changes will automatically be applied to the users with this role.',
|
text: 'Your role changes will automatically be applied to the users with this role.',
|
||||||
confetti: true,
|
confetti: true,
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -124,9 +122,7 @@ to resources within a project"
|
|||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
getRoleKey={getRoleKey}
|
getRoleKey={getRoleKey}
|
||||||
>
|
>
|
||||||
<PermissionButton permission={ADMIN} type="submit">
|
<UpdateButton permission={ADMIN} />
|
||||||
Edit role
|
|
||||||
</PermissionButton>
|
|
||||||
</ProjectRoleForm>
|
</ProjectRoleForm>
|
||||||
</FormTemplate>
|
</FormTemplate>
|
||||||
);
|
);
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import Input from '../../../common/Input/Input';
|
import Input from '../../../common/Input/Input';
|
||||||
import EnvironmentPermissionAccordion from './EnvironmentPermissionAccordion/EnvironmentPermissionAccordion';
|
import EnvironmentPermissionAccordion from './EnvironmentPermissionAccordion/EnvironmentPermissionAccordion';
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
TextField,
|
TextField,
|
||||||
Button,
|
|
||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
import useProjectRolePermissions from '../../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
|
import useProjectRolePermissions from '../../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
|
||||||
|
|
||||||
import { useStyles } from './ProjectRoleForm.styles';
|
import { useStyles } from './ProjectRoleForm.styles';
|
||||||
import ConditionallyRender from '../../../common/ConditionallyRender';
|
import ConditionallyRender from '../../../common/ConditionallyRender';
|
||||||
import React from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import { IPermission } from '../../../../interfaces/project';
|
import { IPermission } from '../../../../interfaces/project';
|
||||||
import {
|
import {
|
||||||
ICheckedPermission,
|
ICheckedPermission,
|
||||||
@ -33,6 +33,7 @@ interface IProjectRoleForm {
|
|||||||
clearErrors: () => void;
|
clearErrors: () => void;
|
||||||
validateNameUniqueness?: () => void;
|
validateNameUniqueness?: () => void;
|
||||||
getRoleKey: (permission: { id: number; environment?: string }) => string;
|
getRoleKey: (permission: { id: number; environment?: string }) => string;
|
||||||
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProjectRoleForm: React.FC<IProjectRoleForm> = ({
|
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 => ({
|
export const useStyles = makeStyles(theme => ({
|
||||||
deleteParagraph: {
|
deleteParagraph: {
|
||||||
|
@ -16,6 +16,7 @@ import IRole, { IProjectRole } from '../../../../../interfaces/role';
|
|||||||
import useProjectRolesApi from '../../../../../hooks/api/actions/useProjectRolesApi/useProjectRolesApi';
|
import useProjectRolesApi from '../../../../../hooks/api/actions/useProjectRolesApi/useProjectRolesApi';
|
||||||
import useToast from '../../../../../hooks/useToast';
|
import useToast from '../../../../../hooks/useToast';
|
||||||
import ProjectRoleDeleteConfirm from '../ProjectRoleDeleteConfirm/ProjectRoleDeleteConfirm';
|
import ProjectRoleDeleteConfirm from '../ProjectRoleDeleteConfirm/ProjectRoleDeleteConfirm';
|
||||||
|
import { formatUnknownError } from '../../../../../utils/format-unknown-error';
|
||||||
|
|
||||||
const ROOTROLE = 'root';
|
const ROOTROLE = 'root';
|
||||||
|
|
||||||
@ -44,8 +45,8 @@ const ProjectRoleList = () => {
|
|||||||
title: 'Successfully deleted role',
|
title: 'Successfully deleted role',
|
||||||
text: 'Your role is now deleted',
|
text: 'Your role is now deleted',
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
setDelDialog(false);
|
setDelDialog(false);
|
||||||
setConfirmName('');
|
setConfirmName('');
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { useStyles } from './ProjectRoleListItem.styles';
|
import { useStyles } from './ProjectRoleListItem.styles';
|
||||||
import { TableRow, TableCell, Typography } from '@material-ui/core';
|
import { TableCell, TableRow, Typography } from '@material-ui/core';
|
||||||
import { Edit, Delete } from '@material-ui/icons';
|
import { Delete, Edit } from '@material-ui/icons';
|
||||||
import { ADMIN } from '../../../../../providers/AccessProvider/permissions';
|
import { ADMIN } from '../../../../../providers/AccessProvider/permissions';
|
||||||
import SupervisedUserCircleIcon from '@material-ui/icons/SupervisedUserCircle';
|
import SupervisedUserCircleIcon from '@material-ui/icons/SupervisedUserCircle';
|
||||||
import PermissionIconButton from '../../../../../common/PermissionIconButton/PermissionIconButton';
|
import PermissionIconButton from '../../../../../common/PermissionIconButton/PermissionIconButton';
|
||||||
import { IProjectRole } from '../../../../../../interfaces/role';
|
import { IProjectRole } from '../../../../../../interfaces/role';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
interface IRoleListItemProps {
|
interface IRoleListItemProps {
|
||||||
id: number;
|
id: number;
|
||||||
@ -50,7 +51,6 @@ const RoleListItem = ({
|
|||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
data-loading
|
data-loading
|
||||||
aria-label="Edit"
|
aria-label="Edit"
|
||||||
tooltip="Edit"
|
|
||||||
disabled={type === BUILTIN_ROLE_TYPE}
|
disabled={type === BUILTIN_ROLE_TYPE}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
history.push(`/admin/roles/${id}/edit`);
|
history.push(`/admin/roles/${id}/edit`);
|
||||||
@ -62,7 +62,6 @@ const RoleListItem = ({
|
|||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
data-loading
|
data-loading
|
||||||
aria-label="Remove role"
|
aria-label="Remove role"
|
||||||
tooltip="Remove role"
|
|
||||||
disabled={type === BUILTIN_ROLE_TYPE}
|
disabled={type === BUILTIN_ROLE_TYPE}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentRole({ id, name, description });
|
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 => ({
|
export const useStyles = makeStyles(theme => ({
|
||||||
rolesListBody: {
|
rolesListBody: {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { makeStyles } from '@material-ui/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
export const useStyles = makeStyles({
|
export const useStyles = makeStyles({
|
||||||
iconContainer: {
|
iconContainer: {
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import FormTemplate from '../../../common/FormTemplate/FormTemplate';
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import UserForm from '../UserForm/UserForm';
|
import UserForm from '../UserForm/UserForm';
|
||||||
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import useToast from '../../../../hooks/useToast';
|
import useAdminUsersApi from 'hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
import useAddUserForm from '../hooks/useAddUserForm';
|
import useAddUserForm from '../hooks/useAddUserForm';
|
||||||
import useAdminUsersApi from '../../../../hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
|
|
||||||
import ConfirmUserAdded from '../ConfirmUserAdded/ConfirmUserAdded';
|
import ConfirmUserAdded from '../ConfirmUserAdded/ConfirmUserAdded';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { scrollToTop } from '../../../common/util';
|
import { scrollToTop } from '../../../common/util';
|
||||||
import PermissionButton from '../../../common/PermissionButton/PermissionButton';
|
import { CreateButton } from 'component/common/CreateButton/CreateButton';
|
||||||
import { ADMIN } from '../../../providers/AccessProvider/permissions';
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import { formatUnknownError } from '../../../../utils/format-unknown-error';
|
||||||
|
|
||||||
const CreateUser = () => {
|
const CreateUser = () => {
|
||||||
const { setToastApiError } = useToast();
|
const { setToastApiError } = useToast();
|
||||||
@ -51,8 +52,8 @@ const CreateUser = () => {
|
|||||||
setInviteLink(user.inviteLink);
|
setInviteLink(user.inviteLink);
|
||||||
setShowConfirm(true);
|
setShowConfirm(true);
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -97,9 +98,7 @@ const CreateUser = () => {
|
|||||||
setRootRole={setRootRole}
|
setRootRole={setRootRole}
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
>
|
>
|
||||||
<PermissionButton permission={ADMIN} type="submit">
|
<CreateButton name="user" permission={ADMIN} />
|
||||||
Create user
|
|
||||||
</PermissionButton>
|
|
||||||
</UserForm>
|
</UserForm>
|
||||||
<ConfirmUserAdded
|
<ConfirmUserAdded
|
||||||
open={showConfirm}
|
open={showConfirm}
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import FormTemplate from '../../../common/FormTemplate/FormTemplate';
|
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
import UserForm from '../UserForm/UserForm';
|
import UserForm from '../UserForm/UserForm';
|
||||||
import useAddUserForm from '../hooks/useAddUserForm';
|
import useAddUserForm from '../hooks/useAddUserForm';
|
||||||
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import useToast from '../../../../hooks/useToast';
|
|
||||||
import useAdminUsersApi from '../../../../hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
|
|
||||||
import useUserInfo from '../../../../hooks/api/getters/useUserInfo/useUserInfo';
|
|
||||||
import { scrollToTop } from '../../../common/util';
|
import { scrollToTop } from '../../../common/util';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import PermissionButton from '../../../common/PermissionButton/PermissionButton';
|
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton';
|
||||||
import { ADMIN } from '../../../providers/AccessProvider/permissions';
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
import { EDIT } from '../../../../constants/misc';
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import { EDIT } from 'constants/misc';
|
||||||
|
import useAdminUsersApi from 'hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
|
||||||
|
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 = () => {
|
const EditUser = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -60,8 +61,8 @@ const EditUser = () => {
|
|||||||
title: 'User information updated',
|
title: 'User information updated',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -94,9 +95,7 @@ const EditUser = () => {
|
|||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
mode={EDIT}
|
mode={EDIT}
|
||||||
>
|
>
|
||||||
<PermissionButton permission={ADMIN} type="submit">
|
<UpdateButton permission={ADMIN} />
|
||||||
Edit user
|
|
||||||
</PermissionButton>
|
|
||||||
</UserForm>
|
</UserForm>
|
||||||
</FormTemplate>
|
</FormTemplate>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { makeStyles } from '@material-ui/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
export const useStyles = makeStyles(theme => ({
|
export const useStyles = makeStyles(theme => ({
|
||||||
userListBody: {
|
userListBody: {
|
||||||
|
@ -35,7 +35,7 @@ const UsersAdmin = () => {
|
|||||||
history.push('/admin/create-user')
|
history.push('/admin/create-user')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Add new user
|
New user
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
elseShow={
|
elseShow={
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import classnames from 'classnames';
|
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 { trim } from '../../../../common/util';
|
||||||
import { modalStyles } from '../../util';
|
import { modalStyles } from '../../util';
|
||||||
import Dialogue from '../../../../common/Dialogue/Dialogue';
|
import Dialogue from '../../../../common/Dialogue/Dialogue';
|
||||||
@ -12,10 +12,10 @@ import { Alert } from '@material-ui/lab';
|
|||||||
import { IUser } from '../../../../../interfaces/user';
|
import { IUser } from '../../../../../interfaces/user';
|
||||||
|
|
||||||
interface IChangePasswordProps {
|
interface IChangePasswordProps {
|
||||||
showDialog: () => void;
|
showDialog: boolean;
|
||||||
closeDialog: () => void;
|
closeDialog: () => void;
|
||||||
changePassword: () => void;
|
changePassword: (user: IUser, password: string) => Promise<Response>;
|
||||||
user: IUser;
|
user: Partial<IUser>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChangePassword = ({
|
const ChangePassword = ({
|
||||||
@ -25,7 +25,7 @@ const ChangePassword = ({
|
|||||||
user = {},
|
user = {},
|
||||||
}: IChangePasswordProps) => {
|
}: IChangePasswordProps) => {
|
||||||
const [data, setData] = useState({});
|
const [data, setData] = useState({});
|
||||||
const [error, setError] = useState({});
|
const [error, setError] = useState<Record<string, string>>({});
|
||||||
const [validPassword, setValidPassword] = useState(false);
|
const [validPassword, setValidPassword] = useState(false);
|
||||||
const commonStyles = useCommonStyles();
|
const commonStyles = useCommonStyles();
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ const ChangePassword = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={error.general}
|
condition={Boolean(error.general)}
|
||||||
show={<Alert severity="error">{error.general}</Alert>}
|
show={<Alert severity="error">{error.general}</Alert>}
|
||||||
/>
|
/>
|
||||||
<Typography variant="subtitle1">
|
<Typography variant="subtitle1">
|
||||||
|
@ -9,12 +9,12 @@ import { useCommonStyles } from '../../../../../common.styles';
|
|||||||
import { IUser } from '../../../../../interfaces/user';
|
import { IUser } from '../../../../../interfaces/user';
|
||||||
|
|
||||||
interface IDeleteUserProps {
|
interface IDeleteUserProps {
|
||||||
showDialog: () => void;
|
showDialog: boolean;
|
||||||
closeDialog: () => void;
|
closeDialog: () => void;
|
||||||
user: IUser;
|
user: IUser;
|
||||||
userLoading: boolean;
|
userLoading: boolean;
|
||||||
removeUser: () => void;
|
removeUser: () => void;
|
||||||
userApiErrors: Object;
|
userApiErrors: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeleteUser = ({
|
const DeleteUser = ({
|
||||||
@ -33,13 +33,13 @@ const DeleteUser = ({
|
|||||||
open={showDialog}
|
open={showDialog}
|
||||||
title="Really delete user?"
|
title="Really delete user?"
|
||||||
onClose={closeDialog}
|
onClose={closeDialog}
|
||||||
onClick={() => removeUser(user)}
|
onClick={removeUser}
|
||||||
primaryButtonText="Delete user"
|
primaryButtonText="Delete user"
|
||||||
secondaryButtonText="Cancel"
|
secondaryButtonText="Cancel"
|
||||||
>
|
>
|
||||||
<div ref={ref}>
|
<div ref={ref}>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={userApiErrors[REMOVE_USER_ERROR]}
|
condition={Boolean(userApiErrors[REMOVE_USER_ERROR])}
|
||||||
show={
|
show={
|
||||||
<Alert
|
<Alert
|
||||||
data-loading
|
data-loading
|
||||||
|
@ -1,25 +1,24 @@
|
|||||||
import {
|
import {
|
||||||
TableRow,
|
|
||||||
TableCell,
|
|
||||||
Avatar,
|
Avatar,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
TableCell,
|
||||||
|
TableRow,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@material-ui/core';
|
} 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 { SyntheticEvent, useContext } from 'react';
|
||||||
import { ADMIN } from '../../../../providers/AccessProvider/permissions';
|
import { ADMIN } from '../../../../providers/AccessProvider/permissions';
|
||||||
import ConditionallyRender from '../../../../common/ConditionallyRender';
|
import ConditionallyRender from '../../../../common/ConditionallyRender';
|
||||||
import { formatDateWithLocale } from '../../../../common/util';
|
|
||||||
import AccessContext from '../../../../../contexts/AccessContext';
|
import AccessContext from '../../../../../contexts/AccessContext';
|
||||||
import { IUser } from '../../../../../interfaces/user';
|
import { IUser } from '../../../../../interfaces/user';
|
||||||
import { useStyles } from './UserListItem.styles';
|
import { useStyles } from './UserListItem.styles';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { ILocationSettings } from '../../../../../hooks/useLocationSettings';
|
import { ILocationSettings } from '../../../../../hooks/useLocationSettings';
|
||||||
|
import { formatDateYMD } from '../../../../../utils/format-date';
|
||||||
|
|
||||||
interface IUserListItemProps {
|
interface IUserListItemProps {
|
||||||
user: IUser;
|
user: IUser;
|
||||||
renderRole: (roleId: number) => string;
|
renderRole: (roleId: number) => string;
|
||||||
openUpdateDialog: (user: IUser) => (e: SyntheticEvent) => void;
|
|
||||||
openPwDialog: (user: IUser) => (e: SyntheticEvent) => void;
|
openPwDialog: (user: IUser) => (e: SyntheticEvent) => void;
|
||||||
openDelDialog: (user: IUser) => (e: SyntheticEvent) => void;
|
openDelDialog: (user: IUser) => (e: SyntheticEvent) => void;
|
||||||
locationSettings: ILocationSettings;
|
locationSettings: ILocationSettings;
|
||||||
@ -30,7 +29,6 @@ const UserListItem = ({
|
|||||||
renderRole,
|
renderRole,
|
||||||
openDelDialog,
|
openDelDialog,
|
||||||
openPwDialog,
|
openPwDialog,
|
||||||
openUpdateDialog,
|
|
||||||
locationSettings,
|
locationSettings,
|
||||||
}: IUserListItemProps) => {
|
}: IUserListItemProps) => {
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
@ -51,10 +49,7 @@ const UserListItem = ({
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<span data-loading>
|
<span data-loading>
|
||||||
{formatDateWithLocale(
|
{formatDateYMD(user.createdAt, locationSettings.locale)}
|
||||||
user.createdAt,
|
|
||||||
locationSettings.locale
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={styles.leftTableCell}>
|
<TableCell className={styles.leftTableCell}>
|
||||||
|
@ -24,6 +24,7 @@ import { IUser } from '../../../../interfaces/user';
|
|||||||
import IRole from '../../../../interfaces/role';
|
import IRole from '../../../../interfaces/role';
|
||||||
import useToast from '../../../../hooks/useToast';
|
import useToast from '../../../../hooks/useToast';
|
||||||
import { useLocationSettings } from '../../../../hooks/useLocationSettings';
|
import { useLocationSettings } from '../../../../hooks/useLocationSettings';
|
||||||
|
import { formatUnknownError } from '../../../../utils/format-unknown-error';
|
||||||
|
|
||||||
const UsersList = () => {
|
const UsersList = () => {
|
||||||
const { users, roles, refetch, loading } = useUsers();
|
const { users, roles, refetch, loading } = useUsers();
|
||||||
@ -79,8 +80,8 @@ const UsersList = () => {
|
|||||||
});
|
});
|
||||||
refetch();
|
refetch();
|
||||||
closeDelDialog();
|
closeDelDialog();
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -172,7 +173,7 @@ const UsersList = () => {
|
|||||||
<DeleteUser
|
<DeleteUser
|
||||||
showDialog={delDialog}
|
showDialog={delDialog}
|
||||||
closeDialog={closeDelDialog}
|
closeDialog={closeDelDialog}
|
||||||
user={delUser}
|
user={delUser!}
|
||||||
removeUser={onDeleteUser}
|
removeUser={onDeleteUser}
|
||||||
userLoading={userLoading}
|
userLoading={userLoading}
|
||||||
userApiErrors={userApiErrors}
|
userApiErrors={userApiErrors}
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
/* eslint react/no-multi-comp:off */
|
/* eslint react/no-multi-comp:off */
|
||||||
import { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Link,
|
|
||||||
Icon,
|
Icon,
|
||||||
IconButton,
|
IconButton,
|
||||||
LinearProgress,
|
LinearProgress,
|
||||||
|
Link,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
import { Link as LinkIcon } from '@material-ui/icons';
|
import { Link as LinkIcon } from '@material-ui/icons';
|
||||||
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
|
||||||
import { formatDateWithLocale } from '../../common/util';
|
|
||||||
import { UPDATE_APPLICATION } from '../../providers/AccessProvider/permissions';
|
import { UPDATE_APPLICATION } from '../../providers/AccessProvider/permissions';
|
||||||
import { ApplicationView } from '../ApplicationView/ApplicationView';
|
import { ApplicationView } from '../ApplicationView/ApplicationView';
|
||||||
import { ApplicationUpdate } from '../ApplicationUpdate/ApplicationUpdate';
|
import { ApplicationUpdate } from '../ApplicationUpdate/ApplicationUpdate';
|
||||||
@ -25,6 +24,8 @@ import { useHistory, useParams } from 'react-router-dom';
|
|||||||
import { useLocationSettings } from '../../../hooks/useLocationSettings';
|
import { useLocationSettings } from '../../../hooks/useLocationSettings';
|
||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
import PermissionButton from '../../common/PermissionButton/PermissionButton';
|
import PermissionButton from '../../common/PermissionButton/PermissionButton';
|
||||||
|
import { formatDateYMD } from '../../../utils/format-date';
|
||||||
|
import { formatUnknownError } from '../../../utils/format-unknown-error';
|
||||||
|
|
||||||
export const ApplicationEdit = () => {
|
export const ApplicationEdit = () => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@ -42,10 +43,9 @@ export const ApplicationEdit = () => {
|
|||||||
setShowDialog(!showDialog);
|
setShowDialog(!showDialog);
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (v: string) =>
|
const formatDate = (v: string) => formatDateYMD(v, locationSettings.locale);
|
||||||
formatDateWithLocale(v, locationSettings.locale);
|
|
||||||
|
|
||||||
const onDeleteApplication = async (evt: Event) => {
|
const onDeleteApplication = async (evt: React.SyntheticEvent) => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
try {
|
try {
|
||||||
await deleteApplication(appName);
|
await deleteApplication(appName);
|
||||||
@ -55,8 +55,8 @@ export const ApplicationEdit = () => {
|
|||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
history.push('/applications');
|
history.push('/applications');
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ChangeEvent, useState } from 'react';
|
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 { useCommonStyles } from '../../../common.styles';
|
||||||
import icons from '../icon-names';
|
import icons from '../icon-names';
|
||||||
import GeneralSelect from '../../common/GeneralSelect/GeneralSelect';
|
import GeneralSelect from '../../common/GeneralSelect/GeneralSelect';
|
||||||
@ -7,6 +7,7 @@ import useApplicationsApi from '../../../hooks/api/actions/useApplicationsApi/us
|
|||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
import { IApplication } from '../../../interfaces/application';
|
import { IApplication } from '../../../interfaces/application';
|
||||||
import useApplication from '../../../hooks/api/getters/useApplication/useApplication';
|
import useApplication from '../../../hooks/api/getters/useApplication/useApplication';
|
||||||
|
import { formatUnknownError } from '../../../utils/format-unknown-error';
|
||||||
|
|
||||||
interface IApplicationUpdateProps {
|
interface IApplicationUpdateProps {
|
||||||
application: IApplication;
|
application: IApplication;
|
||||||
@ -35,8 +36,8 @@ export const ApplicationUpdate = ({ application }: IApplicationUpdateProps) => {
|
|||||||
title: 'Updated Successfully',
|
title: 'Updated Successfully',
|
||||||
text: `${field} successfully updated`,
|
text: `${field} successfully updated`,
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,16 +4,16 @@ import {
|
|||||||
Grid,
|
Grid,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemText,
|
|
||||||
ListItemAvatar,
|
ListItemAvatar,
|
||||||
|
ListItemText,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
import {
|
import {
|
||||||
Report,
|
|
||||||
Extension,
|
Extension,
|
||||||
Timeline,
|
|
||||||
FlagRounded,
|
FlagRounded,
|
||||||
|
Report,
|
||||||
SvgIconComponent,
|
SvgIconComponent,
|
||||||
|
Timeline,
|
||||||
} from '@material-ui/icons';
|
} from '@material-ui/icons';
|
||||||
import {
|
import {
|
||||||
CREATE_FEATURE,
|
CREATE_FEATURE,
|
||||||
@ -23,13 +23,16 @@ import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyR
|
|||||||
import { getTogglePath } from '../../../utils/route-path-helpers';
|
import { getTogglePath } from '../../../utils/route-path-helpers';
|
||||||
import useApplication from '../../../hooks/api/getters/useApplication/useApplication';
|
import useApplication from '../../../hooks/api/getters/useApplication/useApplication';
|
||||||
import AccessContext from '../../../contexts/AccessContext';
|
import AccessContext from '../../../contexts/AccessContext';
|
||||||
import { formatFullDateTimeWithLocale } from '../../common/util';
|
import { formatDateYMDHMS } from '../../../utils/format-date';
|
||||||
|
import { useLocationSettings } from '../../../hooks/useLocationSettings';
|
||||||
|
|
||||||
export const ApplicationView = () => {
|
export const ApplicationView = () => {
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
const { name } = useParams<{ name: string }>();
|
const { name } = useParams<{ name: string }>();
|
||||||
const { application } = useApplication(name);
|
const { application } = useApplication(name);
|
||||||
|
const { locationSettings } = useLocationSettings();
|
||||||
const { instances, strategies, seenToggles } = application;
|
const { instances, strategies, seenToggles } = application;
|
||||||
|
|
||||||
const notFoundListItem = ({
|
const notFoundListItem = ({
|
||||||
createUrl,
|
createUrl,
|
||||||
name,
|
name,
|
||||||
@ -114,10 +117,9 @@ export const ApplicationView = () => {
|
|||||||
createUrl: `/projects/default/create-toggle?name=${name}`,
|
createUrl: `/projects/default/create-toggle?name=${name}`,
|
||||||
name,
|
name,
|
||||||
permission: CREATE_FEATURE,
|
permission: CREATE_FEATURE,
|
||||||
i,
|
|
||||||
})}
|
})}
|
||||||
elseShow={foundListItem({
|
elseShow={foundListItem({
|
||||||
viewUrl: getTogglePath(project, name, true),
|
viewUrl: getTogglePath(project, name),
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
Icon: FlagRounded,
|
Icon: FlagRounded,
|
||||||
@ -195,8 +197,9 @@ export const ApplicationView = () => {
|
|||||||
<span>
|
<span>
|
||||||
{clientIp} last seen at{' '}
|
{clientIp} last seen at{' '}
|
||||||
<small>
|
<small>
|
||||||
{formatFullDateTimeWithLocale(
|
{formatDateYMDHMS(
|
||||||
lastSeen
|
lastSeen,
|
||||||
|
locationSettings.locale
|
||||||
)}
|
)}
|
||||||
</small>
|
</small>
|
||||||
</span>
|
</span>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState, useRef, FC } from 'react';
|
import React, { useEffect, useState, useRef, FC } from 'react';
|
||||||
import ConditionallyRender from '../ConditionallyRender';
|
import ConditionallyRender from '../ConditionallyRender';
|
||||||
|
|
||||||
interface IAnimateOnMountProps {
|
interface IAnimateOnMountProps {
|
||||||
@ -7,7 +7,7 @@ interface IAnimateOnMountProps {
|
|||||||
start: string;
|
start: string;
|
||||||
leave: string;
|
leave: string;
|
||||||
container?: string;
|
container?: string;
|
||||||
style?: Object;
|
style?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AnimateOnMount: FC<IAnimateOnMountProps> = ({
|
const AnimateOnMount: FC<IAnimateOnMountProps> = ({
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Button } from '@material-ui/core';
|
import { Button } from '@material-ui/core';
|
||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from '@material-ui/lab';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
interface IApiErrorProps {
|
interface IApiErrorProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
interface IConditionallyRenderProps {
|
interface IConditionallyRenderProps {
|
||||||
condition: boolean;
|
condition: boolean;
|
||||||
show: JSX.Element | RenderFunc;
|
show: TargetElement;
|
||||||
elseShow?: JSX.Element | RenderFunc;
|
elseShow?: TargetElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TargetElement = JSX.Element | JSX.Element[] | RenderFunc | null;
|
||||||
type RenderFunc = () => JSX.Element;
|
type RenderFunc = () => JSX.Element;
|
||||||
|
|
||||||
const ConditionallyRender = ({
|
const ConditionallyRender = ({
|
||||||
@ -23,8 +24,9 @@ const ConditionallyRender = ({
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isFunc = (param: JSX.Element | RenderFunc) =>
|
const isFunc = (param: TargetElement): boolean => {
|
||||||
typeof param === 'function';
|
return typeof param === 'function';
|
||||||
|
};
|
||||||
|
|
||||||
if (condition) {
|
if (condition) {
|
||||||
if (isFunc(show)) {
|
if (isFunc(show)) {
|
||||||
|
@ -52,7 +52,6 @@ const Constraint = ({
|
|||||||
<div className={styles.btnContainer}>
|
<div className={styles.btnContainer}>
|
||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
onClick={editCallback}
|
onClick={editCallback}
|
||||||
tooltip="Edit strategy"
|
|
||||||
permission={UPDATE_FEATURE}
|
permission={UPDATE_FEATURE}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
>
|
>
|
||||||
@ -61,7 +60,6 @@ const Constraint = ({
|
|||||||
|
|
||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
onClick={deleteCallback}
|
onClick={deleteCallback}
|
||||||
tooltip="Delete strategy"
|
|
||||||
permission={UPDATE_FEATURE}
|
permission={UPDATE_FEATURE}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
>
|
>
|
||||||
|
15
frontend/src/component/common/CreateButton/CreateButton.tsx
Normal file
15
frontend/src/component/common/CreateButton/CreateButton.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import PermissionButton, {
|
||||||
|
IPermissionButtonProps,
|
||||||
|
} from '../PermissionButton/PermissionButton';
|
||||||
|
|
||||||
|
interface ICreateButtonProps extends IPermissionButtonProps {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CreateButton = ({ name, ...rest }: ICreateButtonProps) => {
|
||||||
|
return (
|
||||||
|
<PermissionButton type="submit" {...rest}>
|
||||||
|
Create {name}
|
||||||
|
</PermissionButton>
|
||||||
|
);
|
||||||
|
};
|
@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogTitle,
|
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
Button,
|
DialogTitle,
|
||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
|
|
||||||
import ConditionallyRender from '../ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../ConditionallyRender/ConditionallyRender';
|
||||||
@ -15,15 +15,15 @@ interface IDialogue {
|
|||||||
primaryButtonText?: string;
|
primaryButtonText?: string;
|
||||||
secondaryButtonText?: string;
|
secondaryButtonText?: string;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClick: (e: any) => void;
|
onClick: (e: React.SyntheticEvent) => void;
|
||||||
onClose: () => void;
|
onClose?: (e: React.SyntheticEvent) => void;
|
||||||
style?: object;
|
style?: object;
|
||||||
title: string;
|
title: string;
|
||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
maxWidth?: 'lg' | 'sm' | 'xs' | 'md' | 'xl';
|
maxWidth?: 'lg' | 'sm' | 'xs' | 'md' | 'xl';
|
||||||
disabledPrimaryButton?: boolean;
|
disabledPrimaryButton?: boolean;
|
||||||
formId?: string;
|
formId?: string;
|
||||||
permissionButton?: React.ReactNode;
|
permissionButton?: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dialogue: React.FC<IDialogue> = ({
|
const Dialogue: React.FC<IDialogue> = ({
|
||||||
@ -69,7 +69,7 @@ const Dialogue: React.FC<IDialogue> = ({
|
|||||||
<DialogActions>
|
<DialogActions>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(permissionButton)}
|
condition={Boolean(permissionButton)}
|
||||||
show={permissionButton}
|
show={permissionButton!}
|
||||||
elseShow={
|
elseShow={
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(onClick)}
|
condition={Boolean(onClick)}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
interface EnvironmentSplashPageProps {
|
interface EnvironmentSplashPageProps {
|
||||||
title: React.ReactNode;
|
title: React.ReactNode;
|
||||||
topDescription: React.ReactNode;
|
topDescription: React.ReactNode;
|
||||||
|
@ -7,6 +7,7 @@ import ConditionallyRender from '../ConditionallyRender';
|
|||||||
import Loader from '../Loader/Loader';
|
import Loader from '../Loader/Loader';
|
||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
interface ICreateProps {
|
interface ICreateProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
interface IGradientProps {
|
interface IGradientProps {
|
||||||
from: string;
|
from: string;
|
||||||
to: string;
|
to: string;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { TextField } from '@material-ui/core';
|
import { TextField } from '@material-ui/core';
|
||||||
import { INPUT_ERROR_TEXT } from '../../../testIds';
|
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> {
|
interface IInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ReactComponent as NoItemsIcon } from '../../../assets/icons/addfiles.svg';
|
import { ReactComponent as NoItemsIcon } from '../../../assets/icons/addfiles.svg';
|
||||||
import { useStyles } from './NoItems.styles';
|
import { useStyles } from './NoItems.styles';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
const NoItems: React.FC = ({ children }) => {
|
const NoItems: React.FC = ({ children }) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import ConditionallyRender from '../ConditionallyRender';
|
import ConditionallyRender from '../ConditionallyRender';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { useStyles } from './PaginationUI.styles';
|
import { useStyles } from './PaginationUI.styles';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { IconButton, InputAdornment, TextField } from '@material-ui/core';
|
import { IconButton, InputAdornment, TextField } from '@material-ui/core';
|
||||||
import { Visibility, VisibilityOff } from '@material-ui/icons';
|
import { Visibility, VisibilityOff } from '@material-ui/icons';
|
||||||
import { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
const PasswordField = ({ ...rest }) => {
|
const PasswordField = ({ ...rest }) => {
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Button, Tooltip } from '@material-ui/core';
|
import { Button, Tooltip } from '@material-ui/core';
|
||||||
import { Lock } from '@material-ui/icons';
|
import { Lock } from '@material-ui/icons';
|
||||||
import { useContext } from 'react';
|
import AccessContext from 'contexts/AccessContext';
|
||||||
import AccessContext from '../../../contexts/AccessContext';
|
import React, { useContext } from 'react';
|
||||||
import ConditionallyRender from '../ConditionallyRender';
|
import ConditionallyRender from '../ConditionallyRender';
|
||||||
|
|
||||||
export interface IPermissionIconButtonProps
|
export interface IPermissionButtonProps
|
||||||
extends React.HTMLProps<HTMLButtonElement> {
|
extends React.HTMLProps<HTMLButtonElement> {
|
||||||
permission: string | string[];
|
permission: string | string[];
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
@ -14,9 +14,9 @@ export interface IPermissionIconButtonProps
|
|||||||
environmentId?: string;
|
environmentId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PermissionButton: React.FC<IPermissionIconButtonProps> = ({
|
const PermissionButton: React.FC<IPermissionButtonProps> = ({
|
||||||
permission,
|
permission,
|
||||||
tooltip = 'Click to perform action',
|
tooltip,
|
||||||
onClick,
|
onClick,
|
||||||
children,
|
children,
|
||||||
disabled,
|
disabled,
|
||||||
@ -54,9 +54,9 @@ const PermissionButton: React.FC<IPermissionIconButtonProps> = ({
|
|||||||
|
|
||||||
access = handleAccess();
|
access = handleAccess();
|
||||||
|
|
||||||
const tooltipText = access
|
const tooltipText = !access
|
||||||
? tooltip
|
? "You don't have access to perform this operation"
|
||||||
: "You don't have access to perform this operation";
|
: '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={tooltipText} arrow>
|
<Tooltip title={tooltipText} arrow>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { IconButton, Tooltip } from '@material-ui/core';
|
import { IconButton, Tooltip } from '@material-ui/core';
|
||||||
import { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import AccessContext from '../../../contexts/AccessContext';
|
import AccessContext from '../../../contexts/AccessContext';
|
||||||
|
|
||||||
interface IPermissionIconButtonProps
|
interface IPermissionIconButtonProps
|
||||||
@ -39,9 +39,9 @@ const PermissionIconButton: React.FC<IPermissionIconButtonProps> = ({
|
|||||||
access = hasAccess(permission);
|
access = hasAccess(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tooltipText = access
|
const tooltipText = !access
|
||||||
? tooltip || ''
|
? "You don't have access to perform this operation"
|
||||||
: "You don't have access to perform this operation";
|
: '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={tooltipText} arrow>
|
<Tooltip title={tooltipText} arrow>
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { Switch, Tooltip } from '@material-ui/core';
|
import { Switch, Tooltip, SwitchProps } from '@material-ui/core';
|
||||||
import { OverridableComponent } from '@material-ui/core/OverridableComponent';
|
|
||||||
import AccessContext from '../../../contexts/AccessContext';
|
import AccessContext from '../../../contexts/AccessContext';
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
|
|
||||||
interface IPermissionSwitchProps extends OverridableComponent<any> {
|
interface IPermissionSwitchProps extends SwitchProps {
|
||||||
permission: string;
|
permission: string;
|
||||||
tooltip: string;
|
tooltip?: string;
|
||||||
onChange?: (e: any) => void;
|
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
environmentId?: string;
|
environmentId?: string;
|
||||||
@ -19,7 +18,7 @@ const PermissionSwitch = React.forwardRef<
|
|||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const {
|
const {
|
||||||
permission,
|
permission,
|
||||||
tooltip = '',
|
tooltip,
|
||||||
disabled,
|
disabled,
|
||||||
projectId,
|
projectId,
|
||||||
environmentId,
|
environmentId,
|
||||||
@ -39,9 +38,9 @@ const PermissionSwitch = React.forwardRef<
|
|||||||
access = hasAccess(permission);
|
access = hasAccess(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tooltipText = access
|
const tooltipText = !access
|
||||||
? tooltip
|
? "You don't have access to perform this operation"
|
||||||
: "You don't have access to perform this operation";
|
: '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={tooltipText} arrow>
|
<Tooltip title={tooltipText} arrow>
|
||||||
|
@ -2,16 +2,18 @@ import { useMediaQuery } from '@material-ui/core';
|
|||||||
import ConditionallyRender from '../ConditionallyRender';
|
import ConditionallyRender from '../ConditionallyRender';
|
||||||
import PermissionButton from '../PermissionButton/PermissionButton';
|
import PermissionButton from '../PermissionButton/PermissionButton';
|
||||||
import PermissionIconButton from '../PermissionIconButton/PermissionIconButton';
|
import PermissionIconButton from '../PermissionIconButton/PermissionIconButton';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
interface IResponsiveButtonProps {
|
interface IResponsiveButtonProps {
|
||||||
Icon: React.ElementType;
|
Icon: React.ElementType;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
permission?: string;
|
permission: string;
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
environmentId?: string;
|
environmentId?: string;
|
||||||
maxWidth: string;
|
maxWidth: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResponsiveButton: React.FC<IResponsiveButtonProps> = ({
|
const ResponsiveButton: React.FC<IResponsiveButtonProps> = ({
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { Fragment } from 'react';
|
import React, { Fragment, useState } from 'react';
|
||||||
import { Button, IconButton } from '@material-ui/core';
|
import { Button, IconButton } from '@material-ui/core';
|
||||||
import { useStyles } from './Splash.styles';
|
import { useStyles } from './Splash.styles';
|
||||||
import {
|
import {
|
||||||
|
CloseOutlined,
|
||||||
FiberManualRecord,
|
FiberManualRecord,
|
||||||
FiberManualRecordOutlined,
|
FiberManualRecordOutlined,
|
||||||
CloseOutlined,
|
|
||||||
} from '@material-ui/icons';
|
} from '@material-ui/icons';
|
||||||
import { useState } from 'react';
|
|
||||||
import ConditionallyRender from '../ConditionallyRender';
|
import ConditionallyRender from '../ConditionallyRender';
|
||||||
import { CLOSE_SPLASH } from '../../../testIds';
|
import { CLOSE_SPLASH } from '../../../testIds';
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ interface ITagSelect extends React.SelectHTMLAttributes<HTMLSelectElement> {
|
|||||||
onChange: (val: any) => void;
|
onChange: (val: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TagSelect = ({ value, types, onChange, ...rest }: ITagSelect) => {
|
const TagSelect = ({ value, onChange, ...rest }: ITagSelect) => {
|
||||||
const { tagTypes } = useTagTypes();
|
const { tagTypes } = useTagTypes();
|
||||||
|
|
||||||
const options = tagTypes.map(tagType => ({
|
const options = tagTypes.map(tagType => ({
|
||||||
|
@ -3,12 +3,12 @@ import classnames from 'classnames';
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { IconButton } from '@material-ui/core';
|
import { IconButton } from '@material-ui/core';
|
||||||
import CheckMarkBadge from '../../CheckmarkBadge/CheckMarkBadge';
|
import CheckMarkBadge from '../../CheckmarkBadge/CheckMarkBadge';
|
||||||
import UIContext, { IToastData } from '../../../../contexts/UIContext';
|
import UIContext from '../../../../contexts/UIContext';
|
||||||
import ConditionallyRender from '../../ConditionallyRender';
|
import ConditionallyRender from '../../ConditionallyRender';
|
||||||
import Close from '@material-ui/icons/Close';
|
import Close from '@material-ui/icons/Close';
|
||||||
|
import { IToast } from '../../../../interfaces/toast';
|
||||||
|
|
||||||
const Toast = ({ title, text, type, confetti }: IToastData) => {
|
const Toast = ({ title, text, type, confetti }: IToast) => {
|
||||||
// @ts-expect-error
|
|
||||||
const { setToast } = useContext(UIContext);
|
const { setToast } = useContext(UIContext);
|
||||||
|
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
@ -51,7 +51,7 @@ const Toast = ({ title, text, type, confetti }: IToastData) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const hide = () => {
|
const hide = () => {
|
||||||
setToast((prev: IToastData) => ({ ...prev, show: false }));
|
setToast((prev: IToast) => ({ ...prev, show: false }));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import { Portal } from '@material-ui/core';
|
import { Portal } from '@material-ui/core';
|
||||||
import { useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
import { useCommonStyles } from '../../../common.styles';
|
import { useCommonStyles } from '../../../common.styles';
|
||||||
import UIContext, { IToastData } from '../../../contexts/UIContext';
|
import UIContext from '../../../contexts/UIContext';
|
||||||
import { useStyles } from './ToastRenderer.styles';
|
import { useStyles } from './ToastRenderer.styles';
|
||||||
import AnimateOnMount from '../AnimateOnMount/AnimateOnMount';
|
import AnimateOnMount from '../AnimateOnMount/AnimateOnMount';
|
||||||
import Toast from './Toast/Toast';
|
import Toast from './Toast/Toast';
|
||||||
|
import { IToast } from '../../../interfaces/toast';
|
||||||
|
|
||||||
const ToastRenderer = () => {
|
const ToastRenderer = () => {
|
||||||
// @ts-expect-error
|
|
||||||
const { toastData, setToast } = useContext(UIContext);
|
const { toastData, setToast } = useContext(UIContext);
|
||||||
const commonStyles = useCommonStyles();
|
const commonStyles = useCommonStyles();
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
|
||||||
const hide = () => {
|
const hide = () => {
|
||||||
setToast((prev: IToastData) => ({ ...prev, show: false }));
|
setToast((prev: IToast) => ({ ...prev, show: false }));
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -31,7 +31,7 @@ const ToastRenderer = () => {
|
|||||||
return (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
<AnimateOnMount
|
<AnimateOnMount
|
||||||
mounted={toastData?.show}
|
mounted={Boolean(toastData?.show)}
|
||||||
start={commonStyles.fadeInBottomStartWithoutFixed}
|
start={commonStyles.fadeInBottomStartWithoutFixed}
|
||||||
enter={commonStyles.fadeInBottomEnter}
|
enter={commonStyles.fadeInBottomEnter}
|
||||||
leave={commonStyles.fadeInBottomLeave}
|
leave={commonStyles.fadeInBottomLeave}
|
||||||
|
11
frontend/src/component/common/UpdateButton/UpdateButton.tsx
Normal file
11
frontend/src/component/common/UpdateButton/UpdateButton.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import PermissionButton, {
|
||||||
|
IPermissionButtonProps,
|
||||||
|
} from '../PermissionButton/PermissionButton';
|
||||||
|
|
||||||
|
export const UpdateButton = ({ ...rest }: IPermissionButtonProps) => {
|
||||||
|
return (
|
||||||
|
<PermissionButton type="submit" {...rest}>
|
||||||
|
Save
|
||||||
|
</PermissionButton>
|
||||||
|
);
|
||||||
|
};
|
@ -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 { weightTypes } from '../feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/enums';
|
||||||
import differenceInDays from 'date-fns/differenceInDays';
|
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 => {
|
export const filterByFlags = flags => r => {
|
||||||
if (r.flag && !flags[r.flag]) {
|
if (r.flag && !flags[r.flag]) {
|
||||||
return false;
|
return false;
|
||||||
@ -32,27 +12,6 @@ export const scrollToTop = () => {
|
|||||||
window.scrollTo(0, 0);
|
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 => {
|
export const trim = value => {
|
||||||
if (value && value.trim) {
|
if (value && value.trim) {
|
||||||
return 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 { TextField, Button, Switch, Chip, Typography } from '@material-ui/core';
|
||||||
import { useStyles } from './ContextForm.styles';
|
import { useStyles } from './ContextForm.styles';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Add } from '@material-ui/icons';
|
import { Add } from '@material-ui/icons';
|
||||||
import { trim } from '../../common/util';
|
import { trim } from 'component/common/util';
|
||||||
|
|
||||||
interface IContextForm {
|
interface IContextForm {
|
||||||
contextName: string;
|
contextName: string;
|
||||||
@ -15,20 +15,20 @@ interface IContextForm {
|
|||||||
setStickiness: React.Dispatch<React.SetStateAction<boolean>>;
|
setStickiness: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
setLegalValues: React.Dispatch<React.SetStateAction<string[]>>;
|
setLegalValues: React.Dispatch<React.SetStateAction<string[]>>;
|
||||||
handleSubmit: (e: any) => void;
|
handleSubmit: (e: any) => void;
|
||||||
handleCancel: () => void;
|
onCancel: () => void;
|
||||||
errors: { [key: string]: string };
|
errors: { [key: string]: string };
|
||||||
mode: string;
|
mode: string;
|
||||||
clearErrors: () => void;
|
clearErrors: () => void;
|
||||||
validateNameUniqueness: () => void;
|
validateContext?: () => void;
|
||||||
setErrors: React.Dispatch<React.SetStateAction<Object>>;
|
setErrors: React.Dispatch<React.SetStateAction<Object>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ENTER = 'Enter';
|
const ENTER = 'Enter';
|
||||||
|
|
||||||
const ContextForm: React.FC<IContextForm> = ({
|
export const ContextForm: React.FC<IContextForm> = ({
|
||||||
children,
|
children,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
handleCancel,
|
onCancel,
|
||||||
contextName,
|
contextName,
|
||||||
contextDesc,
|
contextDesc,
|
||||||
legalValues,
|
legalValues,
|
||||||
@ -39,7 +39,7 @@ const ContextForm: React.FC<IContextForm> = ({
|
|||||||
setStickiness,
|
setStickiness,
|
||||||
errors,
|
errors,
|
||||||
mode,
|
mode,
|
||||||
validateNameUniqueness,
|
validateContext,
|
||||||
setErrors,
|
setErrors,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
}) => {
|
}) => {
|
||||||
@ -108,7 +108,7 @@ const ContextForm: React.FC<IContextForm> = ({
|
|||||||
error={Boolean(errors.name)}
|
error={Boolean(errors.name)}
|
||||||
errorText={errors.name}
|
errorText={errors.name}
|
||||||
onFocus={() => clearErrors()}
|
onFocus={() => clearErrors()}
|
||||||
onBlur={validateNameUniqueness}
|
onBlur={validateContext}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<p className={styles.inputDescription}>
|
<p className={styles.inputDescription}>
|
||||||
@ -187,12 +187,10 @@ const ContextForm: React.FC<IContextForm> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.buttonContainer}>
|
<div className={styles.buttonContainer}>
|
||||||
{children}
|
{children}
|
||||||
<Button onClick={handleCancel} className={styles.cancelButton}>
|
<Button onClick={onCancel} className={styles.cancelButton}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ContextForm;
|
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
UPDATE_CONTEXT_FIELD,
|
UPDATE_CONTEXT_FIELD,
|
||||||
} from '../../providers/AccessProvider/permissions';
|
} from '../../providers/AccessProvider/permissions';
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
IconButton,
|
IconButton,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
@ -14,7 +15,6 @@ import {
|
|||||||
ListItemText,
|
ListItemText,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
Button,
|
|
||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
import { Add, Album, Delete, Edit } from '@material-ui/icons';
|
import { Add, Album, Delete, Edit } from '@material-ui/icons';
|
||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
@ -25,6 +25,7 @@ import AccessContext from '../../../contexts/AccessContext';
|
|||||||
import useUnleashContext from '../../../hooks/api/getters/useUnleashContext/useUnleashContext';
|
import useUnleashContext from '../../../hooks/api/getters/useUnleashContext/useUnleashContext';
|
||||||
import useContextsApi from '../../../hooks/api/actions/useContextsApi/useContextsApi';
|
import useContextsApi from '../../../hooks/api/actions/useContextsApi/useContextsApi';
|
||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
|
import { formatUnknownError } from '../../../utils/format-unknown-error';
|
||||||
|
|
||||||
const ContextList = () => {
|
const ContextList = () => {
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
@ -46,8 +47,8 @@ const ContextList = () => {
|
|||||||
title: 'Successfully deleted context',
|
title: 'Successfully deleted context',
|
||||||
text: 'Your context is now deleted',
|
text: 'Your context is now deleted',
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
setName(undefined);
|
setName(undefined);
|
||||||
setShowDelDialogue(false);
|
setShowDelDialogue(false);
|
||||||
@ -127,7 +128,7 @@ const ContextList = () => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
>
|
>
|
||||||
Add new context field
|
New context field
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
|
import { CreateButton } from 'component/common/CreateButton/CreateButton';
|
||||||
import useToast from '../../../hooks/useToast';
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
import FormTemplate from '../../common/FormTemplate/FormTemplate';
|
import { useContextForm } from '../hooks/useContextForm';
|
||||||
import useContextForm from '../hooks/useContextForm';
|
import { ContextForm } from '../ContextForm/ContextForm';
|
||||||
import ContextForm from '../ContextForm/ContextForm';
|
import { CREATE_CONTEXT_FIELD } from 'component/providers/AccessProvider/permissions';
|
||||||
import PermissionButton from '../../common/PermissionButton/PermissionButton';
|
import useContextsApi from 'hooks/api/actions/useContextsApi/useContextsApi';
|
||||||
import { CREATE_CONTEXT_FIELD } from '../../providers/AccessProvider/permissions';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import useContextsApi from '../../../hooks/api/actions/useContextsApi/useContextsApi';
|
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
|
||||||
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 { setToastData, setToastApiError } = useToast();
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@ -23,8 +24,7 @@ const CreateContext = () => {
|
|||||||
setLegalValues,
|
setLegalValues,
|
||||||
setStickiness,
|
setStickiness,
|
||||||
getContextPayload,
|
getContextPayload,
|
||||||
validateNameUniqueness,
|
validateContext,
|
||||||
validateName,
|
|
||||||
clearErrors,
|
clearErrors,
|
||||||
setErrors,
|
setErrors,
|
||||||
errors,
|
errors,
|
||||||
@ -34,7 +34,8 @@ const CreateContext = () => {
|
|||||||
|
|
||||||
const handleSubmit = async (e: Event) => {
|
const handleSubmit = async (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const validName = validateName();
|
const validName = await validateContext();
|
||||||
|
|
||||||
if (validName) {
|
if (validName) {
|
||||||
const payload = getContextPayload();
|
const payload = getContextPayload();
|
||||||
try {
|
try {
|
||||||
@ -46,8 +47,8 @@ const CreateContext = () => {
|
|||||||
confetti: true,
|
confetti: true,
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -61,7 +62,7 @@ const CreateContext = () => {
|
|||||||
--data-raw '${JSON.stringify(getContextPayload(), undefined, 2)}'`;
|
--data-raw '${JSON.stringify(getContextPayload(), undefined, 2)}'`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const onCancel = () => {
|
||||||
history.goBack();
|
history.goBack();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -77,7 +78,7 @@ const CreateContext = () => {
|
|||||||
<ContextForm
|
<ContextForm
|
||||||
errors={errors}
|
errors={errors}
|
||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
handleCancel={handleCancel}
|
onCancel={onCancel}
|
||||||
contextName={contextName}
|
contextName={contextName}
|
||||||
setContextName={setContextName}
|
setContextName={setContextName}
|
||||||
contextDesc={contextDesc}
|
contextDesc={contextDesc}
|
||||||
@ -87,19 +88,15 @@ const CreateContext = () => {
|
|||||||
stickiness={stickiness}
|
stickiness={stickiness}
|
||||||
setStickiness={setStickiness}
|
setStickiness={setStickiness}
|
||||||
mode="Create"
|
mode="Create"
|
||||||
validateNameUniqueness={validateNameUniqueness}
|
validateContext={validateContext}
|
||||||
setErrors={setErrors}
|
setErrors={setErrors}
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
>
|
>
|
||||||
<PermissionButton
|
<CreateButton
|
||||||
|
name="context"
|
||||||
permission={CREATE_CONTEXT_FIELD}
|
permission={CREATE_CONTEXT_FIELD}
|
||||||
type="submit"
|
/>
|
||||||
>
|
|
||||||
Create context
|
|
||||||
</PermissionButton>
|
|
||||||
</ContextForm>
|
</ContextForm>
|
||||||
</FormTemplate>
|
</FormTemplate>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CreateContext;
|
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
|
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton';
|
||||||
|
import { UPDATE_CONTEXT_FIELD } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import useContextsApi from 'hooks/api/actions/useContextsApi/useContextsApi';
|
||||||
|
import useContext from 'hooks/api/getters/useContext/useContext';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
import useContextsApi from '../../../hooks/api/actions/useContextsApi/useContextsApi';
|
|
||||||
import useContext from '../../../hooks/api/getters/useContext/useContext';
|
|
||||||
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import useToast from '../../../hooks/useToast';
|
|
||||||
import FormTemplate from '../../common/FormTemplate/FormTemplate';
|
|
||||||
import PermissionButton from '../../common/PermissionButton/PermissionButton';
|
|
||||||
import { scrollToTop } from '../../common/util';
|
import { scrollToTop } from '../../common/util';
|
||||||
import { UPDATE_CONTEXT_FIELD } from '../../providers/AccessProvider/permissions';
|
import { formatUnknownError } from 'utils/format-unknown-error';
|
||||||
import ContextForm from '../ContextForm/ContextForm';
|
import { ContextForm } from '../ContextForm/ContextForm';
|
||||||
import useContextForm from '../hooks/useContextForm';
|
import { useContextForm } from '../hooks/useContextForm';
|
||||||
|
|
||||||
const EditContext = () => {
|
export const EditContext = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollToTop();
|
scrollToTop();
|
||||||
}, []);
|
}, []);
|
||||||
@ -32,8 +33,6 @@ const EditContext = () => {
|
|||||||
setLegalValues,
|
setLegalValues,
|
||||||
setStickiness,
|
setStickiness,
|
||||||
getContextPayload,
|
getContextPayload,
|
||||||
validateNameUniqueness,
|
|
||||||
validateName,
|
|
||||||
clearErrors,
|
clearErrors,
|
||||||
setErrors,
|
setErrors,
|
||||||
errors,
|
errors,
|
||||||
@ -56,24 +55,21 @@ const EditContext = () => {
|
|||||||
const handleSubmit = async (e: Event) => {
|
const handleSubmit = async (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const payload = getContextPayload();
|
const payload = getContextPayload();
|
||||||
const validName = validateName();
|
|
||||||
|
|
||||||
if (validName) {
|
try {
|
||||||
try {
|
await updateContext(payload);
|
||||||
await updateContext(payload);
|
refetch();
|
||||||
refetch();
|
history.push('/context');
|
||||||
history.push('/context');
|
setToastData({
|
||||||
setToastData({
|
title: 'Context information updated',
|
||||||
title: 'Context information updated',
|
type: 'success',
|
||||||
type: 'success',
|
});
|
||||||
});
|
} catch (e: unknown) {
|
||||||
} catch (e: any) {
|
setToastApiError(formatUnknownError(e));
|
||||||
setToastApiError(e.toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const onCancel = () => {
|
||||||
history.goBack();
|
history.goBack();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,7 +85,7 @@ const EditContext = () => {
|
|||||||
<ContextForm
|
<ContextForm
|
||||||
errors={errors}
|
errors={errors}
|
||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
handleCancel={handleCancel}
|
onCancel={onCancel}
|
||||||
contextName={contextName}
|
contextName={contextName}
|
||||||
setContextName={setContextName}
|
setContextName={setContextName}
|
||||||
contextDesc={contextDesc}
|
contextDesc={contextDesc}
|
||||||
@ -99,19 +95,11 @@ const EditContext = () => {
|
|||||||
stickiness={stickiness}
|
stickiness={stickiness}
|
||||||
setStickiness={setStickiness}
|
setStickiness={setStickiness}
|
||||||
mode="Edit"
|
mode="Edit"
|
||||||
validateNameUniqueness={validateNameUniqueness}
|
|
||||||
setErrors={setErrors}
|
setErrors={setErrors}
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
>
|
>
|
||||||
<PermissionButton
|
<UpdateButton permission={UPDATE_CONTEXT_FIELD} />
|
||||||
permission={UPDATE_CONTEXT_FIELD}
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Edit context
|
|
||||||
</PermissionButton>
|
|
||||||
</ContextForm>
|
</ContextForm>
|
||||||
</FormTemplate>
|
</FormTemplate>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EditContext;
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import useContextsApi from '../../../hooks/api/actions/useContextsApi/useContextsApi';
|
import useContextsApi from '../../../hooks/api/actions/useContextsApi/useContextsApi';
|
||||||
|
|
||||||
const useContextForm = (
|
export const useContextForm = (
|
||||||
initialcontextName = '',
|
initialcontextName = '',
|
||||||
initialcontextDesc = '',
|
initialcontextDesc = '',
|
||||||
initialLegalValues = [] as string[],
|
initialLegalValues = [] as string[],
|
||||||
@ -42,25 +42,28 @@ const useContextForm = (
|
|||||||
|
|
||||||
const NAME_EXISTS_ERROR = 'A context field with that name already exist';
|
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 {
|
try {
|
||||||
await validateContextName(contextName);
|
await validateContextName(contextName);
|
||||||
|
return true;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.toString().includes(NAME_EXISTS_ERROR)) {
|
if (e.toString().includes(NAME_EXISTS_ERROR)) {
|
||||||
setErrors(prev => ({
|
setErrors(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
name: 'A context field with that name already exist',
|
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 false;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearErrors = () => {
|
const clearErrors = () => {
|
||||||
@ -77,12 +80,9 @@ const useContextForm = (
|
|||||||
setLegalValues,
|
setLegalValues,
|
||||||
setStickiness,
|
setStickiness,
|
||||||
getContextPayload,
|
getContextPayload,
|
||||||
validateNameUniqueness,
|
validateContext,
|
||||||
validateName,
|
|
||||||
setErrors,
|
setErrors,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
errors,
|
errors,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useContextForm;
|
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import useEnvironmentForm from '../hooks/useEnvironmentForm';
|
import useEnvironmentForm from '../hooks/useEnvironmentForm';
|
||||||
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import useToast from '../../../hooks/useToast';
|
|
||||||
import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
|
|
||||||
import EnvironmentForm from '../EnvironmentForm/EnvironmentForm';
|
import EnvironmentForm from '../EnvironmentForm/EnvironmentForm';
|
||||||
import FormTemplate from '../../common/FormTemplate/FormTemplate';
|
import FormTemplate from '../../common/FormTemplate/FormTemplate';
|
||||||
import useEnvironments from '../../../hooks/api/getters/useEnvironments/useEnvironments';
|
|
||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from '@material-ui/lab';
|
||||||
import { Button } from '@material-ui/core';
|
import { Button } from '@material-ui/core';
|
||||||
import ConditionallyRender from '../../common/ConditionallyRender';
|
import { CreateButton } from 'component/common/CreateButton/CreateButton';
|
||||||
import PageContent from '../../common/PageContent';
|
import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
|
||||||
import HeaderTitle from '../../common/HeaderTitle';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import PermissionButton from '../../common/PermissionButton/PermissionButton';
|
import useToast from 'hooks/useToast';
|
||||||
import { ADMIN } from '../../providers/AccessProvider/permissions';
|
import useEnvironments from 'hooks/api/getters/useEnvironments/useEnvironments';
|
||||||
import useProjectRolePermissions from '../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
|
import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
|
||||||
|
import ConditionallyRender from 'component/common/ConditionallyRender';
|
||||||
|
import PageContent from 'component/common/PageContent/PageContent';
|
||||||
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import HeaderTitle from 'component/common/HeaderTitle/HeaderTitle';
|
||||||
|
import { formatUnknownError } from 'utils/format-unknown-error';
|
||||||
|
|
||||||
const CreateEnvironment = () => {
|
const CreateEnvironment = () => {
|
||||||
const { setToastApiError, setToastData } = useToast();
|
const { setToastApiError, setToastData } = useToast();
|
||||||
@ -49,8 +50,8 @@ const CreateEnvironment = () => {
|
|||||||
confetti: true,
|
confetti: true,
|
||||||
});
|
});
|
||||||
history.push('/environments');
|
history.push('/environments');
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -100,9 +101,7 @@ const CreateEnvironment = () => {
|
|||||||
mode="Create"
|
mode="Create"
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
>
|
>
|
||||||
<PermissionButton permission={ADMIN} type="submit">
|
<CreateButton name="environment" permission={ADMIN} />
|
||||||
Create environment
|
|
||||||
</PermissionButton>
|
|
||||||
</EnvironmentForm>
|
</EnvironmentForm>
|
||||||
</FormTemplate>
|
</FormTemplate>
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
|
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton';
|
||||||
|
import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
|
||||||
|
import useEnvironment from 'hooks/api/getters/useEnvironment/useEnvironment';
|
||||||
|
import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
|
|
||||||
import useEnvironment from '../../../hooks/api/getters/useEnvironment/useEnvironment';
|
|
||||||
import useProjectRolePermissions from '../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
|
|
||||||
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import useToast from '../../../hooks/useToast';
|
|
||||||
import FormTemplate from '../../common/FormTemplate/FormTemplate';
|
|
||||||
import PermissionButton from '../../common/PermissionButton/PermissionButton';
|
|
||||||
import { ADMIN } from '../../providers/AccessProvider/permissions';
|
import { ADMIN } from '../../providers/AccessProvider/permissions';
|
||||||
import EnvironmentForm from '../EnvironmentForm/EnvironmentForm';
|
import EnvironmentForm from '../EnvironmentForm/EnvironmentForm';
|
||||||
import useEnvironmentForm from '../hooks/useEnvironmentForm';
|
import useEnvironmentForm from '../hooks/useEnvironmentForm';
|
||||||
|
import { formatUnknownError } from '../../../utils/format-unknown-error';
|
||||||
|
|
||||||
const EditEnvironment = () => {
|
const EditEnvironment = () => {
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
@ -49,8 +50,8 @@ const EditEnvironment = () => {
|
|||||||
type: 'success',
|
type: 'success',
|
||||||
title: 'Successfully updated environment.',
|
title: 'Successfully updated environment.',
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -85,9 +86,7 @@ const EditEnvironment = () => {
|
|||||||
errors={errors}
|
errors={errors}
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
>
|
>
|
||||||
<PermissionButton permission={ADMIN} type="submit">
|
<UpdateButton permission={ADMIN} />
|
||||||
Edit environment
|
|
||||||
</PermissionButton>
|
|
||||||
</EnvironmentForm>
|
</EnvironmentForm>
|
||||||
</FormTemplate>
|
</FormTemplate>
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
RadioGroup,
|
|
||||||
Radio,
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
import { useStyles } from './EnvironmentTypeSelector.styles';
|
import { useStyles } from './EnvironmentTypeSelector.styles';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
interface IEnvironmentTypeSelectorProps {
|
interface IEnvironmentTypeSelectorProps {
|
||||||
onChange: (event: React.FormEvent<HTMLInputElement>) => void;
|
onChange: (event: React.FormEvent<HTMLInputElement>) => void;
|
||||||
|
@ -11,7 +11,7 @@ interface IEnviromentDeleteConfirmProps {
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
setSelectedEnv: React.Dispatch<React.SetStateAction<IEnvironment>>;
|
setSelectedEnv: React.Dispatch<React.SetStateAction<IEnvironment>>;
|
||||||
setDeldialogue: React.Dispatch<React.SetStateAction<boolean>>;
|
setDeldialogue: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
handleDeleteEnvironment: (name: string) => Promise<void>;
|
handleDeleteEnvironment: () => Promise<void>;
|
||||||
confirmName: string;
|
confirmName: string;
|
||||||
setConfirmName: React.Dispatch<React.SetStateAction<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 useProjectRolePermissions from '../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
|
||||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import { formatUnknownError } from '../../../utils/format-unknown-error';
|
||||||
|
|
||||||
const EnvironmentList = () => {
|
const EnvironmentList = () => {
|
||||||
const defaultEnv = {
|
const defaultEnv = {
|
||||||
@ -75,16 +76,16 @@ const EnvironmentList = () => {
|
|||||||
try {
|
try {
|
||||||
await sortOrderAPICall(sortOrder);
|
await sortOrderAPICall(sortOrder);
|
||||||
refetch();
|
refetch();
|
||||||
} catch (e) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortOrderAPICall = async (sortOrder: ISortOrderPayload) => {
|
const sortOrderAPICall = async (sortOrder: ISortOrderPayload) => {
|
||||||
try {
|
try {
|
||||||
await changeSortOrder(sortOrder);
|
await changeSortOrder(sortOrder);
|
||||||
} catch (e) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -97,8 +98,8 @@ const EnvironmentList = () => {
|
|||||||
title: 'Project environment deleted',
|
title: 'Project environment deleted',
|
||||||
text: 'You have successfully deleted the project environment.',
|
text: 'You have successfully deleted the project environment.',
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
} finally {
|
} finally {
|
||||||
setDeldialogue(false);
|
setDeldialogue(false);
|
||||||
setSelectedEnv(defaultEnv);
|
setSelectedEnv(defaultEnv);
|
||||||
@ -124,8 +125,8 @@ const EnvironmentList = () => {
|
|||||||
title: 'Project environment enabled',
|
title: 'Project environment enabled',
|
||||||
text: 'Your environment is enabled',
|
text: 'Your environment is enabled',
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
} finally {
|
} finally {
|
||||||
refetch();
|
refetch();
|
||||||
}
|
}
|
||||||
@ -140,8 +141,8 @@ const EnvironmentList = () => {
|
|||||||
title: 'Project environment disabled',
|
title: 'Project environment disabled',
|
||||||
text: 'Your environment is disabled.',
|
text: 'Your environment is disabled.',
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
} finally {
|
} finally {
|
||||||
refetch();
|
refetch();
|
||||||
}
|
}
|
||||||
@ -174,12 +175,11 @@ const EnvironmentList = () => {
|
|||||||
<ResponsiveButton
|
<ResponsiveButton
|
||||||
onClick={navigateToCreateEnvironment}
|
onClick={navigateToCreateEnvironment}
|
||||||
maxWidth="700px"
|
maxWidth="700px"
|
||||||
tooltip="Add environment"
|
|
||||||
Icon={Add}
|
Icon={Add}
|
||||||
permission={ADMIN}
|
permission={ADMIN}
|
||||||
disabled={!Boolean(uiConfig.flags.EEA)}
|
disabled={!Boolean(uiConfig.flags.EEA)}
|
||||||
>
|
>
|
||||||
Add Environment
|
New Environment
|
||||||
</ResponsiveButton>
|
</ResponsiveButton>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import FormTemplate from '../../common/FormTemplate/FormTemplate';
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import FeatureForm from '../FeatureForm/FeatureForm';
|
import FeatureForm from '../FeatureForm/FeatureForm';
|
||||||
import useFeatureForm from '../hooks/useFeatureForm';
|
import useFeatureForm from '../hooks/useFeatureForm';
|
||||||
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import useToast from '../../../hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import useFeatureApi from '../../../hooks/api/actions/useFeatureApi/useFeatureApi';
|
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
|
||||||
import { CREATE_FEATURE } from '../../providers/AccessProvider/permissions';
|
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
||||||
import PermissionButton from '../../common/PermissionButton/PermissionButton';
|
|
||||||
import { CF_CREATE_BTN_ID } from '../../../testIds';
|
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import UIContext from '../../../contexts/UIContext';
|
import { CreateButton } from 'component/common/CreateButton/CreateButton';
|
||||||
|
import UIContext from 'contexts/UIContext';
|
||||||
|
import { CF_CREATE_BTN_ID } from 'testIds';
|
||||||
|
import { formatUnknownError } from '../../../utils/format-unknown-error';
|
||||||
|
|
||||||
const CreateFeature = () => {
|
const CreateFeature = () => {
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
@ -53,8 +54,8 @@ const CreateFeature = () => {
|
|||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
setShowFeedback(true);
|
setShowFeedback(true);
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -99,15 +100,12 @@ const CreateFeature = () => {
|
|||||||
mode="Create"
|
mode="Create"
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
>
|
>
|
||||||
<PermissionButton
|
<CreateButton
|
||||||
onClick={handleSubmit}
|
name="Feature"
|
||||||
permission={CREATE_FEATURE}
|
permission={CREATE_FEATURE}
|
||||||
projectId={project}
|
projectId={project}
|
||||||
type="submit"
|
|
||||||
data-test={CF_CREATE_BTN_ID}
|
data-test={CF_CREATE_BTN_ID}
|
||||||
>
|
/>
|
||||||
Create toggle
|
|
||||||
</PermissionButton>
|
|
||||||
</FeatureForm>
|
</FeatureForm>
|
||||||
</FormTemplate>
|
</FormTemplate>
|
||||||
);
|
);
|
||||||
|
@ -2,14 +2,15 @@ import FormTemplate from '../../common/FormTemplate/FormTemplate';
|
|||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
import FeatureForm from '../FeatureForm/FeatureForm';
|
import FeatureForm from '../FeatureForm/FeatureForm';
|
||||||
import useFeatureForm from '../hooks/useFeatureForm';
|
import useFeatureForm from '../hooks/useFeatureForm';
|
||||||
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import useToast from '../../../hooks/useToast';
|
|
||||||
import useFeatureApi from '../../../hooks/api/actions/useFeatureApi/useFeatureApi';
|
|
||||||
import useFeature from '../../../hooks/api/getters/useFeature/useFeature';
|
|
||||||
import { IFeatureViewParams } from '../../../interfaces/params';
|
|
||||||
import * as jsonpatch from 'fast-json-patch';
|
import * as jsonpatch from 'fast-json-patch';
|
||||||
import PermissionButton from '../../common/PermissionButton/PermissionButton';
|
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton';
|
||||||
import { UPDATE_FEATURE } from '../../providers/AccessProvider/permissions';
|
import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
|
||||||
|
import useFeature from 'hooks/api/getters/useFeature/useFeature';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
|
import { IFeatureViewParams } from 'interfaces/params';
|
||||||
|
import { formatUnknownError } from 'utils/format-unknown-error';
|
||||||
|
|
||||||
const EditFeature = () => {
|
const EditFeature = () => {
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
@ -57,8 +58,8 @@ const EditFeature = () => {
|
|||||||
title: 'Toggle updated successfully',
|
title: 'Toggle updated successfully',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -101,13 +102,7 @@ const EditFeature = () => {
|
|||||||
mode="Edit"
|
mode="Edit"
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
>
|
>
|
||||||
<PermissionButton
|
<UpdateButton permission={UPDATE_FEATURE} projectId={project} />
|
||||||
permission={UPDATE_FEATURE}
|
|
||||||
projectId={project}
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Edit toggle
|
|
||||||
</PermissionButton>
|
|
||||||
</FeatureForm>
|
</FeatureForm>
|
||||||
</FormTemplate>
|
</FormTemplate>
|
||||||
);
|
);
|
||||||
|
@ -183,7 +183,7 @@ const FeatureToggleList = ({
|
|||||||
skeleton: loading,
|
skeleton: loading,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
Create feature toggle
|
New feature toggle
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -165,7 +165,7 @@ exports[`renders correctly with one feature 1`] = `
|
|||||||
<span
|
<span
|
||||||
className="MuiButton-label"
|
className="MuiButton-label"
|
||||||
>
|
>
|
||||||
Create feature toggle
|
New feature toggle
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -362,7 +362,7 @@ exports[`renders correctly with one feature without permissions 1`] = `
|
|||||||
<span
|
<span
|
||||||
className="MuiButton-label"
|
className="MuiButton-label"
|
||||||
>
|
>
|
||||||
Create feature toggle
|
New feature toggle
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -135,7 +135,7 @@ const FeatureToggleListNew = ({
|
|||||||
type={feature.type}
|
type={feature.type}
|
||||||
environments={feature.environments}
|
environments={feature.environments}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
createdAt={new Date()}
|
createdAt={new Date().toISOString()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import { Tooltip } from '@material-ui/core';
|
import { Tooltip } from '@material-ui/core';
|
||||||
import {
|
|
||||||
formatDateWithLocale,
|
|
||||||
formatFullDateTimeWithLocale,
|
|
||||||
} from '../../../common/util';
|
|
||||||
import { useLocationSettings } from '../../../../hooks/useLocationSettings';
|
import { useLocationSettings } from '../../../../hooks/useLocationSettings';
|
||||||
|
import { formatDateYMD, formatDateYMDHMS } from '../../../../utils/format-date';
|
||||||
|
|
||||||
interface CreatedAtProps {
|
interface CreatedAtProps {
|
||||||
time: Date;
|
time: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreatedAt = ({ time }: CreatedAtProps) => {
|
const CreatedAt = ({ time }: CreatedAtProps) => {
|
||||||
@ -14,12 +11,12 @@ const CreatedAt = ({ time }: CreatedAtProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={`Created at ${formatFullDateTimeWithLocale(
|
title={`Created at ${formatDateYMDHMS(
|
||||||
time,
|
time,
|
||||||
locationSettings.locale
|
locationSettings.locale
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
<span>{formatDateWithLocale(time, locationSettings.locale)}</span>
|
<span>{formatDateYMD(time, locationSettings.locale)}</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
import { TableCell, TableRow } from '@material-ui/core';
|
import { TableCell, TableRow } from '@material-ui/core';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
|
|
||||||
import { useStyles } from '../FeatureToggleListNew.styles';
|
import { useStyles } from '../FeatureToggleListNew.styles';
|
||||||
import useToggleFeatureByEnv from '../../../../hooks/api/actions/useToggleFeatureByEnv/useToggleFeatureByEnv';
|
import useToggleFeatureByEnv from '../../../../hooks/api/actions/useToggleFeatureByEnv/useToggleFeatureByEnv';
|
||||||
import { IEnvironments } from '../../../../interfaces/featureToggle';
|
import { IEnvironments } from '../../../../interfaces/featureToggle';
|
||||||
import useToast from '../../../../hooks/useToast';
|
import useToast from '../../../../hooks/useToast';
|
||||||
import { getTogglePath } from '../../../../utils/route-path-helpers';
|
import { getTogglePath } from '../../../../utils/route-path-helpers';
|
||||||
import { SyntheticEvent } from 'react-router/node_modules/@types/react';
|
|
||||||
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import FeatureStatus from '../../FeatureView/FeatureStatus/FeatureStatus';
|
import FeatureStatus from '../../FeatureView/FeatureStatus/FeatureStatus';
|
||||||
import FeatureType from '../../FeatureView/FeatureType/FeatureType';
|
import FeatureType from '../../FeatureView/FeatureType/FeatureType';
|
||||||
@ -23,10 +21,10 @@ import EnvironmentStrategyDialog from '../../../common/EnvironmentStrategiesDial
|
|||||||
interface IFeatureToggleListNewItemProps {
|
interface IFeatureToggleListNewItemProps {
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
environments: IFeatureEnvironment[];
|
environments: IEnvironments[];
|
||||||
projectId: string;
|
projectId: string;
|
||||||
lastSeenAt?: Date;
|
lastSeenAt?: string;
|
||||||
createdAt: Date;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FeatureToggleListNewItem = ({
|
const FeatureToggleListNewItem = ({
|
||||||
@ -47,7 +45,7 @@ const FeatureToggleListNewItem = ({
|
|||||||
const { refetch } = useProject(projectId);
|
const { refetch } = useProject(projectId);
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const ref = useRef(null);
|
const ref = useRef<HTMLButtonElement>(null);
|
||||||
const [showInfoBox, setShowInfoBox] = useState(false);
|
const [showInfoBox, setShowInfoBox] = useState(false);
|
||||||
const [environmentName, setEnvironmentName] = useState('');
|
const [environmentName, setEnvironmentName] = useState('');
|
||||||
|
|
||||||
@ -55,8 +53,8 @@ const FeatureToggleListNewItem = ({
|
|||||||
setShowInfoBox(false);
|
setShowInfoBox(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClick = (e: SyntheticEvent) => {
|
const onClick = (e: React.MouseEvent) => {
|
||||||
if (!ref.current?.contains(e.target)) {
|
if (!ref.current?.contains(e.target as Node)) {
|
||||||
history.push(getTogglePath(projectId, name));
|
history.push(getTogglePath(projectId, name));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { ILocationSettings } from '../../../../../hooks/useLocationSettings';
|
import { ILocationSettings } from '../../../../../hooks/useLocationSettings';
|
||||||
import 'chartjs-adapter-date-fns';
|
import 'chartjs-adapter-date-fns';
|
||||||
import { ChartOptions, defaults } from 'chart.js';
|
import { ChartOptions, defaults } from 'chart.js';
|
||||||
import { formatTimeWithLocale } from '../../../../common/util';
|
|
||||||
import { IFeatureMetricsRaw } from '../../../../../interfaces/featureToggle';
|
import { IFeatureMetricsRaw } from '../../../../../interfaces/featureToggle';
|
||||||
import theme from '../../../../../themes/main-theme';
|
import theme from '../../../../../themes/main-theme';
|
||||||
|
import { formatDateHM } from '../../../../../utils/format-date';
|
||||||
|
|
||||||
export const createChartOptions = (
|
export const createChartOptions = (
|
||||||
metrics: IFeatureMetricsRaw[],
|
metrics: IFeatureMetricsRaw[],
|
||||||
@ -30,7 +30,7 @@ export const createChartOptions = (
|
|||||||
usePointStyle: true,
|
usePointStyle: true,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
title: items =>
|
title: items =>
|
||||||
formatTimeWithLocale(
|
formatDateHM(
|
||||||
items[0].parsed.x,
|
items[0].parsed.x,
|
||||||
locationSettings.locale
|
locationSettings.locale
|
||||||
),
|
),
|
||||||
@ -73,10 +73,7 @@ export const createChartOptions = (
|
|||||||
grid: { display: false },
|
grid: { display: false },
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: (_, i, data) =>
|
callback: (_, i, data) =>
|
||||||
formatTimeWithLocale(
|
formatDateHM(data[i].value, locationSettings.locale),
|
||||||
data[i].value,
|
|
||||||
locationSettings.locale
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -9,8 +9,8 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
import { useLocationSettings } from '../../../../../hooks/useLocationSettings';
|
import { useLocationSettings } from '../../../../../hooks/useLocationSettings';
|
||||||
import { formatFullDateTimeWithLocale } from '../../../../common/util';
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { formatDateYMDHMS } from 'utils/format-date';
|
||||||
|
|
||||||
export const FEATURE_METRICS_TABLE_ID = 'feature-metrics-table-id';
|
export const FEATURE_METRICS_TABLE_ID = 'feature-metrics-table-id';
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ export const FeatureMetricsTable = ({ metrics }: IFeatureMetricsTableProps) => {
|
|||||||
{sortedMetrics.map(metric => (
|
{sortedMetrics.map(metric => (
|
||||||
<TableRow key={metric.timestamp}>
|
<TableRow key={metric.timestamp}>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{formatFullDateTimeWithLocale(
|
{formatDateYMDHMS(
|
||||||
metric.timestamp,
|
metric.timestamp,
|
||||||
locationSettings.locale
|
locationSettings.locale
|
||||||
)}
|
)}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { DialogContentText } from '@material-ui/core';
|
import { DialogContentText } from '@material-ui/core';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
import { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { IFeatureViewParams } from '../../../../../interfaces/params';
|
import { IFeatureViewParams } from '../../../../../interfaces/params';
|
||||||
import Dialogue from '../../../../common/Dialogue';
|
import Dialogue from '../../../../common/Dialogue';
|
||||||
import Input from '../../../../common/Input/Input';
|
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 useFeatureApi from '../../../../../hooks/api/actions/useFeatureApi/useFeatureApi';
|
||||||
import useTags from '../../../../../hooks/api/getters/useTags/useTags';
|
import useTags from '../../../../../hooks/api/getters/useTags/useTags';
|
||||||
import useToast from '../../../../../hooks/useToast';
|
import useToast from '../../../../../hooks/useToast';
|
||||||
|
import { formatUnknownError } from '../../../../../utils/format-unknown-error';
|
||||||
|
|
||||||
interface IAddTagDialogProps {
|
interface IAddTagDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -20,6 +21,7 @@ interface IAddTagDialogProps {
|
|||||||
interface IDefaultTag {
|
interface IDefaultTag {
|
||||||
type: string;
|
type: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
|
||||||
[index: string]: string;
|
[index: string]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,9 +64,10 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
|||||||
text: 'We successfully added a tag to your toggle',
|
text: 'We successfully added a tag to your toggle',
|
||||||
confetti: true,
|
confetti: true,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.message);
|
const message = formatUnknownError(error);
|
||||||
setErrors({ tagError: e.message });
|
setToastApiError(message);
|
||||||
|
setErrors({ tagError: message });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ import { IFeatureViewParams } from '../../../../../../interfaces/params';
|
|||||||
import PermissionSwitch from '../../../../../common/PermissionSwitch/PermissionSwitch';
|
import PermissionSwitch from '../../../../../common/PermissionSwitch/PermissionSwitch';
|
||||||
import StringTruncator from '../../../../../common/StringTruncator/StringTruncator';
|
import StringTruncator from '../../../../../common/StringTruncator/StringTruncator';
|
||||||
import { UPDATE_FEATURE_ENVIRONMENT } from '../../../../../providers/AccessProvider/permissions';
|
import { UPDATE_FEATURE_ENVIRONMENT } from '../../../../../providers/AccessProvider/permissions';
|
||||||
|
import React from 'react';
|
||||||
|
import { formatUnknownError } from '../../../../../../utils/format-unknown-error';
|
||||||
|
|
||||||
interface IFeatureOverviewEnvSwitchProps {
|
interface IFeatureOverviewEnvSwitchProps {
|
||||||
env: IFeatureEnvironment;
|
env: IFeatureEnvironment;
|
||||||
@ -40,7 +42,7 @@ const FeatureOverviewEnvSwitch = ({
|
|||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
if (e.message === ENVIRONMENT_STRATEGY_ERROR) {
|
if (e.message === ENVIRONMENT_STRATEGY_ERROR) {
|
||||||
showInfoBox(true);
|
showInfoBox(true);
|
||||||
} else {
|
} else {
|
||||||
@ -61,8 +63,8 @@ const FeatureOverviewEnvSwitch = ({
|
|||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.message);
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -93,7 +95,6 @@ const FeatureOverviewEnvSwitch = ({
|
|||||||
checked={env.enabled}
|
checked={env.enabled}
|
||||||
onChange={toggleEnvironment}
|
onChange={toggleEnvironment}
|
||||||
environmentId={env.name}
|
environmentId={env.name}
|
||||||
tooltip={''}
|
|
||||||
/>
|
/>
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Settings } from '@material-ui/icons';
|
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 { Link, useParams } from 'react-router-dom';
|
||||||
import { IFeatureViewParams } from '../../../../../../../../interfaces/params';
|
import { IFeatureViewParams } from '../../../../../../../../interfaces/params';
|
||||||
import { IFeatureStrategy } from '../../../../../../../../interfaces/strategy';
|
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 { 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 { useParams } from 'react-router-dom';
|
||||||
import useTags from '../../../../../../hooks/api/getters/useTags/useTags';
|
import useTags from '../../../../../../hooks/api/getters/useTags/useTags';
|
||||||
import { IFeatureViewParams } from '../../../../../../interfaces/params';
|
import { IFeatureViewParams } from '../../../../../../interfaces/params';
|
||||||
@ -17,6 +17,7 @@ import useToast from '../../../../../../hooks/useToast';
|
|||||||
import { UPDATE_FEATURE } from '../../../../../providers/AccessProvider/permissions';
|
import { UPDATE_FEATURE } from '../../../../../providers/AccessProvider/permissions';
|
||||||
import ConditionallyRender from '../../../../../common/ConditionallyRender';
|
import ConditionallyRender from '../../../../../common/ConditionallyRender';
|
||||||
import AccessContext from '../../../../../../contexts/AccessContext';
|
import AccessContext from '../../../../../../contexts/AccessContext';
|
||||||
|
import { formatUnknownError } from '../../../../../../utils/format-unknown-error';
|
||||||
|
|
||||||
interface IFeatureOverviewTagsProps extends React.HTMLProps<HTMLButtonElement> {
|
interface IFeatureOverviewTagsProps extends React.HTMLProps<HTMLButtonElement> {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@ -53,8 +54,8 @@ const FeatureOverviewTags: React.FC<IFeatureOverviewTagsProps> = ({
|
|||||||
title: 'Tag deleted',
|
title: 'Tag deleted',
|
||||||
text: 'Successfully deleted tag',
|
text: 'Successfully deleted tag',
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.message);
|
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 * as jsonpatch from 'fast-json-patch';
|
||||||
import { TextField } from '@material-ui/core';
|
import { TextField } from '@material-ui/core';
|
||||||
import PermissionButton from '../../../../common/PermissionButton/PermissionButton';
|
import PermissionButton from '../../../../common/PermissionButton/PermissionButton';
|
||||||
@ -11,6 +11,7 @@ import { IFeatureViewParams } from '../../../../../interfaces/params';
|
|||||||
import useToast from '../../../../../hooks/useToast';
|
import useToast from '../../../../../hooks/useToast';
|
||||||
import useFeatureApi from '../../../../../hooks/api/actions/useFeatureApi/useFeatureApi';
|
import useFeatureApi from '../../../../../hooks/api/actions/useFeatureApi/useFeatureApi';
|
||||||
import ConditionallyRender from '../../../../common/ConditionallyRender';
|
import ConditionallyRender from '../../../../common/ConditionallyRender';
|
||||||
|
import { formatUnknownError } from '../../../../../utils/format-unknown-error';
|
||||||
|
|
||||||
const FeatureSettingsMetadata = () => {
|
const FeatureSettingsMetadata = () => {
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
@ -54,8 +55,8 @@ const FeatureSettingsMetadata = () => {
|
|||||||
});
|
});
|
||||||
setDirty(false);
|
setDirty(false);
|
||||||
refetch();
|
refetch();
|
||||||
} catch (e) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import useFeatureTypes from '../../../../../../hooks/api/getters/useFeatureTypes/useFeatureTypes';
|
import useFeatureTypes from '../../../../../../hooks/api/getters/useFeatureTypes/useFeatureTypes';
|
||||||
import GeneralSelect from '../../../../../common/GeneralSelect/GeneralSelect';
|
import GeneralSelect, {
|
||||||
|
ISelectOption,
|
||||||
|
} from '../../../../../common/GeneralSelect/GeneralSelect';
|
||||||
|
|
||||||
const FeatureTypeSelect = ({
|
const FeatureTypeSelect = ({
|
||||||
editable,
|
editable,
|
||||||
@ -11,7 +13,7 @@ const FeatureTypeSelect = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { featureTypes } = useFeatureTypes();
|
const { featureTypes } = useFeatureTypes();
|
||||||
|
|
||||||
const options = featureTypes.map(t => ({
|
const options: ISelectOption[] = featureTypes.map(t => ({
|
||||||
key: t.id,
|
key: t.id,
|
||||||
label: t.name,
|
label: t.name,
|
||||||
title: t.description,
|
title: t.description,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useContext } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import { useHistory, useParams } from 'react-router';
|
import { useHistory, useParams } from 'react-router';
|
||||||
import AccessContext from '../../../../../contexts/AccessContext';
|
import AccessContext from '../../../../../contexts/AccessContext';
|
||||||
import useFeatureApi from '../../../../../hooks/api/actions/useFeatureApi/useFeatureApi';
|
import useFeatureApi from '../../../../../hooks/api/actions/useFeatureApi/useFeatureApi';
|
||||||
@ -12,6 +12,7 @@ import FeatureProjectSelect from './FeatureProjectSelect/FeatureProjectSelect';
|
|||||||
import FeatureSettingsProjectConfirm from './FeatureSettingsProjectConfirm/FeatureSettingsProjectConfirm';
|
import FeatureSettingsProjectConfirm from './FeatureSettingsProjectConfirm/FeatureSettingsProjectConfirm';
|
||||||
import { IPermission } from '../../../../../interfaces/user';
|
import { IPermission } from '../../../../../interfaces/user';
|
||||||
import { useAuthPermissions } from '../../../../../hooks/api/getters/useAuth/useAuthPermissions';
|
import { useAuthPermissions } from '../../../../../hooks/api/getters/useAuth/useAuthPermissions';
|
||||||
|
import { formatUnknownError } from '../../../../../utils/format-unknown-error';
|
||||||
|
|
||||||
const FeatureSettingsProject = () => {
|
const FeatureSettingsProject = () => {
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
@ -61,16 +62,16 @@ const FeatureSettingsProject = () => {
|
|||||||
history.replace(
|
history.replace(
|
||||||
`/projects/${newProject}/features/${featureId}/settings`
|
`/projects/${newProject}/features/${featureId}/settings`
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.message);
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createMoveTargets = () => {
|
const createMoveTargets = () => {
|
||||||
return permissions.reduce(
|
return permissions.reduce(
|
||||||
(acc: { [key: string]: boolean }, permission: IPermission) => {
|
(acc: { [key: string]: boolean }, p: IPermission) => {
|
||||||
if (permission.permission === MOVE_FEATURE_TOGGLE) {
|
if (p.project && p.permission === MOVE_FEATURE_TOGGLE) {
|
||||||
acc[permission.project] = true;
|
acc[p.project] = true;
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
@ -101,7 +102,6 @@ const FeatureSettingsProject = () => {
|
|||||||
show={
|
show={
|
||||||
<PermissionButton
|
<PermissionButton
|
||||||
permission={MOVE_FEATURE_TOGGLE}
|
permission={MOVE_FEATURE_TOGGLE}
|
||||||
tooltip="Update feature"
|
|
||||||
onClick={() => setShowConfirmDialog(true)}
|
onClick={() => setShowConfirmDialog(true)}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
>
|
>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { useStyles } from './FeatureStatus.styles';
|
import { useStyles } from './FeatureStatus.styles';
|
||||||
import TimeAgo from 'react-timeago';
|
import TimeAgo from 'react-timeago';
|
||||||
import ConditionallyRender from '../../../common/ConditionallyRender';
|
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 {
|
function generateUnit(unit?: string): string {
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
@ -46,8 +47,8 @@ function getColor(unit?: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface FeatureStatusProps {
|
interface FeatureStatusProps {
|
||||||
lastSeenAt?: Date;
|
lastSeenAt?: string;
|
||||||
tooltipPlacement?: string;
|
tooltipPlacement?: TooltipProps['placement'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const FeatureStatus = ({
|
const FeatureStatus = ({
|
||||||
@ -76,7 +77,7 @@ const FeatureStatus = ({
|
|||||||
condition={!!lastSeenAt}
|
condition={!!lastSeenAt}
|
||||||
show={
|
show={
|
||||||
<TimeAgo
|
<TimeAgo
|
||||||
date={lastSeenAt}
|
date={lastSeenAt!}
|
||||||
title=""
|
title=""
|
||||||
live={false}
|
live={false}
|
||||||
formatter={(
|
formatter={(
|
||||||
|
@ -19,6 +19,7 @@ import { ADD_NEW_STRATEGY_SAVE_ID } from '../../../../../../testIds';
|
|||||||
import useFeature from '../../../../../../hooks/api/getters/useFeature/useFeature';
|
import useFeature from '../../../../../../hooks/api/getters/useFeature/useFeature';
|
||||||
import { scrollToTop } from '../../../../../common/util';
|
import { scrollToTop } from '../../../../../common/util';
|
||||||
import useToast from '../../../../../../hooks/useToast';
|
import useToast from '../../../../../../hooks/useToast';
|
||||||
|
import { formatUnknownError } from '../../../../../../utils/format-unknown-error';
|
||||||
|
|
||||||
const FeatureStrategiesConfigure = () => {
|
const FeatureStrategiesConfigure = () => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@ -99,8 +100,8 @@ const FeatureStrategiesConfigure = () => {
|
|||||||
history.replace(history.location.pathname);
|
history.replace(history.location.pathname);
|
||||||
refetch();
|
refetch();
|
||||||
scrollToTop();
|
scrollToTop();
|
||||||
} catch (e) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.message);
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -39,7 +39,6 @@ const FeatureStrategiesEnvironmentList = ({
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
activeEnvironmentsRef,
|
activeEnvironmentsRef,
|
||||||
setToastData,
|
|
||||||
deleteStrategy,
|
deleteStrategy,
|
||||||
updateStrategy,
|
updateStrategy,
|
||||||
delDialog,
|
delDialog,
|
||||||
@ -162,7 +161,6 @@ const FeatureStrategiesEnvironmentList = ({
|
|||||||
: 'Toggle is disabled and no strategies are executing'
|
: 'Toggle is disabled and no strategies are executing'
|
||||||
}
|
}
|
||||||
env={activeEnvironment}
|
env={activeEnvironment}
|
||||||
setToastData={setToastData}
|
|
||||||
callback={updateFeatureEnvironmentCache}
|
callback={updateFeatureEnvironmentCache}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,9 +4,13 @@ import FeatureStrategiesUIContext from '../../../../../../contexts/FeatureStrate
|
|||||||
import useFeatureStrategyApi from '../../../../../../hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
|
import useFeatureStrategyApi from '../../../../../../hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
|
||||||
import useToast from '../../../../../../hooks/useToast';
|
import useToast from '../../../../../../hooks/useToast';
|
||||||
import { IFeatureViewParams } from '../../../../../../interfaces/params';
|
import { IFeatureViewParams } from '../../../../../../interfaces/params';
|
||||||
import { IFeatureStrategy } from '../../../../../../interfaces/strategy';
|
import {
|
||||||
|
IFeatureStrategy,
|
||||||
|
IStrategyPayload,
|
||||||
|
} from '../../../../../../interfaces/strategy';
|
||||||
import cloneDeep from 'lodash.clonedeep';
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
import { IFeatureEnvironment } from '../../../../../../interfaces/featureToggle';
|
import { IFeatureEnvironment } from '../../../../../../interfaces/featureToggle';
|
||||||
|
import { formatUnknownError } from '../../../../../../utils/format-unknown-error';
|
||||||
|
|
||||||
const useFeatureStrategiesEnvironmentList = () => {
|
const useFeatureStrategiesEnvironmentList = () => {
|
||||||
const { projectId, featureId } = useParams<IFeatureViewParams>();
|
const { projectId, featureId } = useParams<IFeatureViewParams>();
|
||||||
@ -85,8 +89,8 @@ const useFeatureStrategiesEnvironmentList = () => {
|
|||||||
strategy.constraints = updateStrategyPayload.constraints;
|
strategy.constraints = updateStrategyPayload.constraints;
|
||||||
history.replace(history.location.pathname);
|
history.replace(history.location.pathname);
|
||||||
setFeatureCache(feature);
|
setFeatureCache(feature);
|
||||||
} catch (e) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.message);
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -118,14 +122,13 @@ const useFeatureStrategiesEnvironmentList = () => {
|
|||||||
text: `Successfully deleted strategy from ${featureId}`,
|
text: `Successfully deleted strategy from ${featureId}`,
|
||||||
});
|
});
|
||||||
history.replace(history.location.pathname);
|
history.replace(history.location.pathname);
|
||||||
} catch (e) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(e.message);
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activeEnvironmentsRef,
|
activeEnvironmentsRef,
|
||||||
setToastData,
|
|
||||||
deleteStrategy,
|
deleteStrategy,
|
||||||
updateStrategy,
|
updateStrategy,
|
||||||
delDialog,
|
delDialog,
|
||||||
|
@ -282,7 +282,7 @@ const FeatureStrategiesEnvironments = () => {
|
|||||||
environmentId={activeEnvironment.name}
|
environmentId={activeEnvironment.name}
|
||||||
permission={CREATE_FEATURE_STRATEGY}
|
permission={CREATE_FEATURE_STRATEGY}
|
||||||
>
|
>
|
||||||
Add new strategy
|
New strategy
|
||||||
</ResponsiveButton>
|
</ResponsiveButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -11,7 +11,7 @@ interface IFeatureStrategiesProductionGuard {
|
|||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
primaryButtonText: string;
|
primaryButtonText: string;
|
||||||
loading: boolean;
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FeatureStrategiesProductionGuard = ({
|
const FeatureStrategiesProductionGuard = ({
|
||||||
@ -61,4 +61,11 @@ const FeatureStrategiesProductionGuard = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const disableFeatureStrategiesProductionGuard = () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
FEATURE_STRATEGY_PRODUCTION_GUARD_SETTING,
|
||||||
|
String(true)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default FeatureStrategiesProductionGuard;
|
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 { useParams } from 'react-router-dom';
|
||||||
import { mutate } from 'swr';
|
import { mutate } from 'swr';
|
||||||
import FeatureStrategiesUIContext from '../../../../../../contexts/FeatureStrategiesUIContext';
|
import FeatureStrategiesUIContext from '../../../../../../contexts/FeatureStrategiesUIContext';
|
||||||
@ -6,8 +6,8 @@ import useFeatureStrategy from '../../../../../../hooks/api/getters/useFeatureSt
|
|||||||
import { IFeatureViewParams } from '../../../../../../interfaces/params';
|
import { IFeatureViewParams } from '../../../../../../interfaces/params';
|
||||||
import {
|
import {
|
||||||
IConstraint,
|
IConstraint,
|
||||||
IParameter,
|
|
||||||
IFeatureStrategy,
|
IFeatureStrategy,
|
||||||
|
IParameter,
|
||||||
} from '../../../../../../interfaces/strategy';
|
} from '../../../../../../interfaces/strategy';
|
||||||
import FeatureStrategyAccordion from '../../FeatureStrategyAccordion/FeatureStrategyAccordion';
|
import FeatureStrategyAccordion from '../../FeatureStrategyAccordion/FeatureStrategyAccordion';
|
||||||
import cloneDeep from 'lodash.clonedeep';
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user