From 472acecdad9e1ada18df396c8a6724bc3d641c59 Mon Sep 17 00:00:00 2001 From: Fredrik Strand Oseberg Date: Fri, 11 Mar 2022 13:46:00 +0100 Subject: [PATCH] Fix/constraints UI (#779) * fix: add fixed height to summary * fix: change wording to negated * fix: change header margin * fix: label click length for negated property * fix: cut values that exceed allow length while leaving others alone * fix: set edit bg color * fix: add enter to add values * fix: expand if constraint changes * fix: add string truncator to param names * fix: add validation tests * fix: string truncator * fix: accordion margins on expanded * fix: accordion expansion * fix: update e2e * fix: update parseISO * fix: review comments * fix: update spec * fix: add negated visual indicator --- .../integration/feature/feature.spec.ts | 9 +- frontend/src/app.css | 8 ++ .../EnvironmentPermissionAccordion.tsx | 1 + .../common/BreadcrumbNav/BreadcrumbNav.tsx | 2 + .../common/Constraint/Constraint.tsx | 6 +- .../ConstraintAccordion.styles.ts | 38 +++++- .../ConstraintAccordionEdit.tsx | 6 +- .../ConstraintAccordionEditBody.tsx | 4 +- .../ConstraintFormHeader.tsx | 3 +- .../FreeTextInput/FreeTextInput.tsx | 20 +++- .../constraintValidators.test.ts | 110 ++++++++++++++++++ .../constraintValidators.ts | 26 ++++- .../ConstraintAccordionView.tsx | 10 +- .../ConstraintAccordionViewBody.tsx | 22 +++- .../ConstraintAccordionViewHeader.tsx | 19 ++- .../StringTruncator/StringTruncator.tsx | 42 ++++--- .../EnvironmentCard/EnvironmentCard.tsx | 6 +- .../EnvironmentListItem.tsx | 6 +- .../FeatureStrategyEmpty.tsx | 1 + .../FeatureStrategyForm.tsx | 44 +++---- .../FeatureStrategyMenuCard.tsx | 1 + .../FeatureToggleListNew.tsx | 1 + .../FeatureOverviewEnvSwitch.tsx | 2 +- .../FeatureOverviewEnvironment.styles.ts | 1 + .../FeatureOverviewEnvironment.tsx | 21 ++-- .../FeatureOverviewEnvironmentBody.tsx | 9 -- .../FeatureOverviewEnvironmentStrategy.tsx | 21 ++-- .../FeatureOverviewExecution.styles.ts | 7 +- .../FeatureOverviewExecution.tsx | 21 +++- .../ProjectEnvironment/ProjectEnvironment.tsx | 21 +++- frontend/src/testIds.js | 1 + frontend/src/themes/main-theme.ts | 2 +- 32 files changed, 381 insertions(+), 110 deletions(-) create mode 100644 frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators.test.ts diff --git a/frontend/cypress/integration/feature/feature.spec.ts b/frontend/cypress/integration/feature/feature.spec.ts index a3854dee8d..4cd9f31c8d 100644 --- a/frontend/cypress/integration/feature/feature.spec.ts +++ b/frontend/cypress/integration/feature/feature.spec.ts @@ -179,9 +179,7 @@ describe('feature', () => { }); it('can delete a strategy in the development environment', () => { - cy.visit( - `/projects/default/features/${featureToggleName}/strategies/edit?environmentId=development&strategyId=${strategyId}` - ); + cy.visit(`/projects/default/features/${featureToggleName}`); cy.intercept( 'DELETE', @@ -193,9 +191,8 @@ describe('feature', () => { } ).as('deleteStrategy'); - cy.get( - '[data-test=SIDEBAR_MODAL_ID] [data-test=STRATEGY_FORM_REMOVE_ID]' - ).click(); + cy.get('[data-test=FEATURE_ENVIRONMENT_ACCORDION_development]').click(); + cy.get('[data-test=STRATEGY_FORM_REMOVE_ID]').click(); cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click(); cy.wait('@deleteStrategy'); }); diff --git a/frontend/src/app.css b/frontend/src/app.css index 1e9cb390a2..15658cf66e 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -17,6 +17,14 @@ button { font-family: 'Sen', sans-serif; } +.MuiInputBase-root { + background-color: #fff; +} + +.MuiAccordion-root.Mui-expanded { + margin: 0; +} + .MuiButton-root { border-radius: 3px; text-transform: none; diff --git a/frontend/src/component/admin/project-roles/ProjectRoleForm/EnvironmentPermissionAccordion/EnvironmentPermissionAccordion.tsx b/frontend/src/component/admin/project-roles/ProjectRoleForm/EnvironmentPermissionAccordion/EnvironmentPermissionAccordion.tsx index 33c4bf24c4..c666290182 100644 --- a/frontend/src/component/admin/project-roles/ProjectRoleForm/EnvironmentPermissionAccordion/EnvironmentPermissionAccordion.tsx +++ b/frontend/src/component/admin/project-roles/ProjectRoleForm/EnvironmentPermissionAccordion/EnvironmentPermissionAccordion.tsx @@ -128,6 +128,7 @@ const EnvironmentPermissionAccordion = ({ text={environment.name} className={styles.header} maxWidth="120" + maxLength={25} />  

diff --git a/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx b/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx index 67630da4b5..749e80cce4 100644 --- a/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx +++ b/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx @@ -54,6 +54,7 @@ const BreadcrumbNav = () => {

); @@ -76,6 +77,7 @@ const BreadcrumbNav = () => { to={link} > diff --git a/frontend/src/component/common/Constraint/Constraint.tsx b/frontend/src/component/common/Constraint/Constraint.tsx index fd8f6b6c3c..617a42b3ef 100644 --- a/frontend/src/component/common/Constraint/Constraint.tsx +++ b/frontend/src/component/common/Constraint/Constraint.tsx @@ -47,7 +47,11 @@ const Constraint = ({ return (
- + {constraint.values?.join(', ') ?? constraint.value} diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.styles.ts b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.styles.ts index 38d83e6284..fd272386b6 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.styles.ts +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.styles.ts @@ -20,15 +20,31 @@ export const useStyles = makeStyles(theme => ({ width: '26px', height: '26px', }, + accordionRoot: { margin: 0, boxShadow: 'none' }, + negated: { + position: 'absolute', + color: '#fff', + backgroundColor: theme.palette.primary.light, + padding: '0.1rem 0.2rem', + fontSize: '0.7rem', + fontWeight: 'bold', + top: '-15px', + left: '42px', + borderRadius: '3px', + }, accordion: { border: `1px solid ${theme.palette.grey[300]}`, borderRadius: '5px', - margin: '1rem 0', backgroundColor: '#fff', + margin: 0, + ['&:before']: { height: 0, }, }, + accordionEdit: { + backgroundColor: '#F6F6FA', + }, operator: { border: `1px solid ${theme.palette.secondary.main}`, padding: '0.25rem 1rem', @@ -53,6 +69,17 @@ export const useStyles = makeStyles(theme => ({ position: 'relative', }, }, + headerValuesContainer: { + display: 'flex', + flexDirection: 'column', + }, + headerValues: { + fontSize: theme.fontSizes.smallBody, + color: theme.palette.primary.light, + }, + headerValuesExpand: { + fontSize: theme.fontSizes.smallBody, + }, headerViewValuesContainer: { [theme.breakpoints.down(990)]: { display: 'none', @@ -117,7 +144,14 @@ export const useStyles = makeStyles(theme => ({ maxHeight: '400px', overflowY: 'auto', }, - summary: { border: 'none', padding: '0.25rem 1rem' }, + summary: { + border: 'none', + padding: '0.25rem 1rem', + height: '85px', + [theme.breakpoints.down(770)]: { + height: '175px', + }, + }, settingsParagraph: { display: 'flex', alignItems: 'center', diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEdit.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEdit.tsx index aa8638f379..3ba09e9f32 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEdit.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEdit.tsx @@ -1,4 +1,5 @@ import { useCallback, useEffect, useState } from 'react'; +import classnames from 'classnames'; import { IConstraint } from '../../../../interfaces/strategy'; import { useStyles } from '../ConstraintAccordion.styles'; import { ConstraintAccordionEditBody } from './ConstraintAccordionEditBody/ConstraintAccordionEditBody'; @@ -184,8 +185,11 @@ export const ConstraintAccordionEdit = ({ return (
{ diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.tsx index e5414fb38e..7d63f3ca8f 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.tsx @@ -80,7 +80,7 @@ const InvertedOperator = ({ the opposite) } - label={'inverted'} + label={'negated'} /> ); diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintFormHeader/ConstraintFormHeader.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintFormHeader/ConstraintFormHeader.tsx index db4b28252a..d6c5b8f10c 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintFormHeader/ConstraintFormHeader.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintFormHeader/ConstraintFormHeader.tsx @@ -6,6 +6,7 @@ const useStyles = makeStyles(theme => ({ fontSize: theme.fontSizes.bodySize, fontWeight: 'normal', marginTop: '1rem', + marginBottom: '0.25rem', }, })); @@ -14,7 +15,7 @@ export const ConstraintFormHeader: React.FC< > = ({ children, ...rest }) => { const styles = useStyles(); return ( -

+

{children}

); diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/FreeTextInput/FreeTextInput.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/FreeTextInput/FreeTextInput.tsx index 9ff443f6ff..47e39fe2c0 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/FreeTextInput/FreeTextInput.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/FreeTextInput/FreeTextInput.tsx @@ -1,5 +1,6 @@ import { Button, Chip, makeStyles } from '@material-ui/core'; import Input from 'component/common/Input/Input'; +import StringTruncator from 'component/common/StringTruncator/StringTruncator'; import React, { useState } from 'react'; import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader'; @@ -42,6 +43,8 @@ const useStyles = makeStyles(theme => ({ valuesContainer: { marginTop: '1rem' }, })); +const ENTER = 'Enter'; + export const FreeTextInput = ({ values, removeValue, @@ -52,6 +55,14 @@ export const FreeTextInput = ({ const [inputValues, setInputValues] = useState(''); const styles = useStyles(); + const onKeyDown = (event: React.KeyboardEvent) => { + event.stopPropagation(); + + if (event.key === ENTER) { + addValues(); + } + }; + const addValues = () => { if (inputValues.length === 0) { setError('values can not be empty'); @@ -80,6 +91,7 @@ export const FreeTextInput = ({
+ } key={`${value}-${index}`} onDelete={() => removeValue(index)} className={styles.valueChip} diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators.test.ts b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators.test.ts new file mode 100644 index 0000000000..0dcd5870f0 --- /dev/null +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators.test.ts @@ -0,0 +1,110 @@ +import { + numberValidatorGenerator, + semVerValidatorGenerator, + dateValidatorGenerator, + stringValidatorGenerator, +} from './constraintValidators'; + +test('numbervalidator should accept 0', () => { + const numValidator = numberValidatorGenerator(0); + const [result, err] = numValidator(); + + expect(result).toBe(true); + expect(err).toBe(''); +}); + +test('number validator should reject value that cannot be parsed to number', () => { + const numValidator = numberValidatorGenerator('testa31'); + const [result, err] = numValidator(); + + expect(result).toBe(false); + expect(err).toBe('Value must be a number'); +}); + +test('number validator should reject NaN', () => { + const numValidator = numberValidatorGenerator(NaN); + const [result, err] = numValidator(); + + expect(result).toBe(false); + expect(err).toBe('Value must be a number'); +}); + +test('number validator should accept value that can be parsed to number', () => { + const numValidator = numberValidatorGenerator('31'); + const [result, err] = numValidator(); + + expect(result).toBe(true); + expect(err).toBe(''); +}); + +test('number validator should accept float values', () => { + const numValidator = numberValidatorGenerator('31.12'); + const [result, err] = numValidator(); + + expect(result).toBe(true); + expect(err).toBe(''); +}); + +test('semver validator should reject prefixed values', () => { + const semVerValidator = semVerValidatorGenerator('v1.4.2'); + const [result, err] = semVerValidator(); + + expect(result).toBe(false); + expect(err).toBe('Value is not a valid semver. For example 1.2.4'); +}); + +test('semver validator should reject partial semver values', () => { + const semVerValidator = semVerValidatorGenerator('4.2'); + const [result, err] = semVerValidator(); + + expect(result).toBe(false); + expect(err).toBe('Value is not a valid semver. For example 1.2.4'); +}); + +test('semver validator should accept semver complient values', () => { + const semVerValidator = semVerValidatorGenerator('1.4.2'); + const [result, err] = semVerValidator(); + + expect(result).toBe(true); + expect(err).toBe(''); +}); + +test('date validator should reject invalid date', () => { + const dateValidator = dateValidatorGenerator('114mydate2005'); + const [result, err] = dateValidator(); + + expect(result).toBe(false); + expect(err).toBe('Value must be a valid date matching RFC3339'); +}); + +test('date validator should accept valid date', () => { + const dateValidator = dateValidatorGenerator('2022-03-03T10:15:23.262Z'); + const [result, err] = dateValidator(); + + expect(result).toBe(true); + expect(err).toBe(''); +}); + +test('string validator should accept a list of strings', () => { + const stringValidator = stringValidatorGenerator(['1234', '4121']); + const [result, err] = stringValidator(); + + expect(result).toBe(true); + expect(err).toBe(''); +}); + +test('string validator should reject values that are not arrays', () => { + const stringValidator = stringValidatorGenerator(4); + const [result, err] = stringValidator(); + + expect(result).toBe(false); + expect(err).toBe('Values must be a list of strings'); +}); + +test('string validator should reject arrays that are not arrays of strings', () => { + const stringValidator = stringValidatorGenerator(['test', NaN, 5]); + const [result, err] = stringValidator(); + + expect(result).toBe(false); + expect(err).toBe('Values must be a list of strings'); +}); diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators.ts b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators.ts index 8709134e5f..cf23d3e420 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators.ts +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators.ts @@ -1,11 +1,13 @@ -import { isValid } from 'date-fns'; +import { isValid, parseISO } from 'date-fns'; import semver from 'semver'; export type ConstraintValidatorOutput = [boolean, string]; export const numberValidatorGenerator = (value: unknown) => { return (): ConstraintValidatorOutput => { - if (!Number(value)) { + const converted = Number(value); + + if (typeof converted !== 'number' || Number.isNaN(converted)) { return [false, 'Value must be a number']; } @@ -13,27 +15,39 @@ export const numberValidatorGenerator = (value: unknown) => { }; }; -export const stringValidatorGenerator = (values: string[]) => { +export const stringValidatorGenerator = (values: unknown) => { return (): ConstraintValidatorOutput => { + const error: ConstraintValidatorOutput = [ + false, + 'Values must be a list of strings', + ]; if (!Array.isArray(values)) { - return [false, 'Values must be a list of strings']; + return error; } + + if (!values.every(value => typeof value === 'string')) { + return error; + } + return [true, '']; }; }; export const semVerValidatorGenerator = (value: string) => { return (): ConstraintValidatorOutput => { - if (!semver.valid(value)) { + const isCleanValue = semver.clean(value) === value; + + if (!semver.valid(value) || !isCleanValue) { return [false, 'Value is not a valid semver. For example 1.2.4']; } + return [true, '']; }; }; export const dateValidatorGenerator = (value: string) => { return (): ConstraintValidatorOutput => { - if (isValid(value)) { + if (!isValid(parseISO(value))) { return [false, 'Value must be a valid date matching RFC3339']; } return [true, '']; diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView.tsx index 2ab8e51532..07de2209f4 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView.tsx @@ -8,7 +8,6 @@ import { IConstraint } from '../../../../interfaces/strategy'; import { ConstraintAccordionViewBody } from './ConstraintAccordionViewBody/ConstraintAccordionViewBody'; import { ConstraintAccordionViewHeader } from './ConstraintAccordionViewHeader/ConstraintAccordionViewHeader'; -import { useStyles } from '../ConstraintAccordion.styles'; import { oneOf } from '../../../../utils/one-of'; import { dateOperators, @@ -16,6 +15,7 @@ import { semVerOperators, } from '../../../../constants/operators'; +import { useStyles } from '../ConstraintAccordion.styles'; interface IConstraintAccordionViewProps { environmentId: string; constraint: IConstraint; @@ -39,7 +39,13 @@ export const ConstraintAccordionView = ({ ); return ( - + } diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewBody/ConstraintAccordionViewBody.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewBody/ConstraintAccordionViewBody.tsx index 56cc188bb0..f2583698d5 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewBody/ConstraintAccordionViewBody.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewBody/ConstraintAccordionViewBody.tsx @@ -1,5 +1,6 @@ import { Chip } from '@material-ui/core'; import { ImportExportOutlined, TextFormatOutlined } from '@material-ui/icons'; +import StringTruncator from 'component/common/StringTruncator/StringTruncator'; import { useState } from 'react'; import { stringOperators } from '../../../../../constants/operators'; import { IConstraint } from '../../../../../interfaces/strategy'; @@ -37,7 +38,7 @@ export const ConstraintAccordionViewBody = ({ show={

{' '} - Operator is inverted + Operator is negated

} /> @@ -65,7 +66,16 @@ const SingleValue = ({ value, operator }: ISingleValueProps) => { return (

Value must {operator}

{' '} - + + } + className={styles.chip} + />
); }; @@ -88,7 +98,13 @@ const MultipleValues = ({ values }: IMultipleValuesProps) => { .map((value, index) => ( + } className={styles.chip} /> ))} diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx index a11ec8d193..fcbb5b5df3 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx @@ -53,10 +53,15 @@ export const ConstraintAccordionViewHeader = ({
-
+
+ NOT
} + />

{constraint.operator}

@@ -64,10 +69,14 @@ export const ConstraintAccordionViewHeader = ({ condition={singleValue} show={} elseShow={ -

- {constraint?.values?.length} values. Expand to - view -

+
+

+ {constraint?.values?.length} values +

+

+ Expand to view +

+
} />
diff --git a/frontend/src/component/common/StringTruncator/StringTruncator.tsx b/frontend/src/component/common/StringTruncator/StringTruncator.tsx index d053d96435..85ee8f1b02 100644 --- a/frontend/src/component/common/StringTruncator/StringTruncator.tsx +++ b/frontend/src/component/common/StringTruncator/StringTruncator.tsx @@ -1,34 +1,44 @@ import { Tooltip } from '@material-ui/core'; +import ConditionallyRender from '../ConditionallyRender'; interface IStringTruncatorProps { text: string; maxWidth: string; className?: string; + maxLength: number; } const StringTruncator = ({ text, maxWidth, + maxLength, className, ...rest }: IStringTruncatorProps) => { return ( - - - {text} - - + maxLength} + show={ + + + {text} + + + } + elseShow={<>{text}} + /> ); }; diff --git a/frontend/src/component/environments/EnvironmentList/EnvironmentCard/EnvironmentCard.tsx b/frontend/src/component/environments/EnvironmentList/EnvironmentCard/EnvironmentCard.tsx index d0121e7ad8..d7b6c34bf1 100644 --- a/frontend/src/component/environments/EnvironmentList/EnvironmentCard/EnvironmentCard.tsx +++ b/frontend/src/component/environments/EnvironmentList/EnvironmentCard/EnvironmentCard.tsx @@ -19,7 +19,11 @@ const EnvironmentCard = ({ name, type }: IEnvironmentProps) => {
Id
- +
diff --git a/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx b/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx index 4a82ec50f7..5bfddb0f0f 100644 --- a/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx +++ b/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx @@ -135,7 +135,11 @@ const EnvironmentListItem = ({ primary={ <> - + {' '} environment diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm.tsx index f3279b6f51..48ab30a6d8 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm.tsx @@ -5,7 +5,6 @@ import { formatStrategyName, } from 'utils/strategy-names'; import { FeatureStrategyType } from '../FeatureStrategyType/FeatureStrategyType'; -import { FeatureStrategyRemove } from '../FeatureStrategyRemove/FeatureStrategyRemove'; import { FeatureStrategyEnabled } from '../FeatureStrategyEnabled/FeatureStrategyEnabled'; import { FeatureStrategyConstraints } from '../FeatureStrategyConstraints/FeatureStrategyConstraints'; import { Button } from '@material-ui/core'; @@ -118,33 +117,6 @@ export const FeatureStrategyForm = ({ )} />
- - - } - /> - setShowProdGuard(false)} - onClick={onSubmit} - loading={loading} - label="Save strategy" - /> Save strategy + + + setShowProdGuard(false)} + onClick={onSubmit} + loading={loading} + label="Save strategy" + />
); diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenuCard/FeatureStrategyMenuCard.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenuCard/FeatureStrategyMenuCard.tsx index c8537a09b3..73b2902268 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenuCard/FeatureStrategyMenuCard.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenuCard/FeatureStrategyMenuCard.tsx @@ -42,6 +42,7 @@ export const FeatureStrategyMenuCard = ({ text={strategy.displayName || strategyName} className={styles.name} maxWidth="200" + maxLength={25} />
{strategy.description}
diff --git a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx index e25cd84df7..5da617a94d 100644 --- a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx +++ b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx @@ -239,6 +239,7 @@ const FeatureToggleListNew = ({ > diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitch/FeatureOverviewEnvSwitch.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitch/FeatureOverviewEnvSwitch.tsx index 76030e0fed..9a088f61c7 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitch/FeatureOverviewEnvSwitch.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitch/FeatureOverviewEnvSwitch.tsx @@ -86,7 +86,7 @@ const FeatureOverviewEnvSwitch = ({ {' '} {env.enabled ? 'enabled' : 'disabled'} in   - + ); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts index 0b3ff65502..694dcb8b9d 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts @@ -95,6 +95,7 @@ export const useStyles = makeStyles(theme => ({ }, headerTitle: { flexDirection: 'column', + textAlign: 'center', }, headerIcon: { marginBottom: '0.5rem', diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx index f69300aa74..7a8b83fadd 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx @@ -25,6 +25,7 @@ import FeatureOverviewEnvironmentBody from './FeatureOverviewEnvironmentBody/Fea import FeatureOverviewEnvironmentFooter from './FeatureOverviewEnvironmentFooter/FeatureOverviewEnvironmentFooter'; import FeatureOverviewEnvironmentMetrics from './FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics'; import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu'; +import { FEATURE_ENVIRONMENT_ACCORDION } from 'testIds'; interface IStrategyIconObject { count: number; @@ -86,7 +87,10 @@ const FeatureOverviewEnvironment = ({ return (
- + } @@ -97,12 +101,15 @@ const FeatureOverviewEnvironment = ({ enabled={env.enabled} className={styles.headerIcon} /> - Feature toggle execution for  - +

+ Feature toggle execution for  + +

diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentBody/FeatureOverviewEnvironmentBody.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentBody/FeatureOverviewEnvironmentBody.tsx index 35a7379ec5..8cf79e7005 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentBody/FeatureOverviewEnvironmentBody.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentBody/FeatureOverviewEnvironmentBody.tsx @@ -4,7 +4,6 @@ import ConditionallyRender from 'component/common/ConditionallyRender'; import FeatureOverviewEnvironmentStrategies from '../FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategies'; import { useStyles } from '../FeatureOverviewEnvironment.styles'; import { IFeatureEnvironment } from 'interfaces/featureToggle'; -import { FeatureStrategyMenu } from '../../../../../FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu'; import { FeatureStrategyEmpty } from '../../../../../FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty'; interface IFeatureOverviewEnvironmentBodyProps { @@ -37,14 +36,6 @@ const FeatureOverviewEnvironmentBody = ({ condition={featureEnvironment?.strategies.length > 0} show={ <> -
- -
- {formatStrategyName(strategy.name)} +
- +
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.styles.ts b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.styles.ts index 05a81fb95a..2ca2841f78 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.styles.ts +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.styles.ts @@ -29,5 +29,10 @@ export const useStyles = makeStyles(theme => ({ overflow: 'hidden', maxWidth: '50%', }, - text: { textAlign: 'center', margin: '0.2rem 0 0.5rem' }, + text: { + textAlign: 'center', + margin: '0.2rem 0 0.5rem', + display: 'flex', + alignItems: 'center', + }, })); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.tsx index 30612158bd..ea091d6d29 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.tsx @@ -11,6 +11,7 @@ import { useStyles } from './FeatureOverviewExecution.styles'; import FeatureOverviewExecutionChips from './FeatureOverviewExecutionChips/FeatureOverviewExecutionChips'; import { useStrategies } from '../../../../../hooks/api/getters/useStrategies/useStrategies'; import Constraint from '../../../../common/Constraint/Constraint'; +import StringTruncator from 'component/common/StringTruncator/StringTruncator'; interface IFeatureOverviewExecutionProps { parameters: IParameter; @@ -166,7 +167,11 @@ const FeatureOverviewExecution = ({ return (

- {param.name} must be{' '} + {' '} {strategy.parameters[param.name]}

- {param.name} is set to {numValue} + {' '} + is set to {numValue}

- {param.name} is set to {value} + {' '} + is set to {value}

{ const { project, refetch: refetchProject } = useProject(projectId); const { removeEnvironmentFromProject, addEnvironmentToProject } = useProjectApi(); + const commonStyles = useCommonStyles(); // local state const [selectedEnv, setSelectedEnv] = useState(); @@ -129,10 +132,20 @@ const ProjectEnvironmentList = ({ projectId }: ProjectEnvironmentListProps) => { }; const genLabel = (env: IProjectEnvironment) => ( - <> - {env.name} environment is{' '} - {env.enabled ? 'enabled' : 'disabled'} - +
+ + + + {/* This is ugly - but regular {" "} doesn't work here*/} +

+   environment is{' '} + {env.enabled ? 'enabled' : 'disabled'} +

+
); const renderEnvironments = () => { diff --git a/frontend/src/testIds.js b/frontend/src/testIds.js index 098e55e483..90c8642411 100644 --- a/frontend/src/testIds.js +++ b/frontend/src/testIds.js @@ -16,6 +16,7 @@ export const SSO_LOGIN_BUTTON = 'SSO_LOGIN_BUTTON'; export const FORGOTTEN_PASSWORD_FIELD = 'FORGOTTEN_PASSWORD_FIELD'; /* STRATEGY */ +export const FEATURE_ENVIRONMENT_ACCORDION = 'FEATURE_ENVIRONMENT_ACCORDION'; export const ADD_NEW_STRATEGY_ID = 'ADD_NEW_STRATEGY_ID'; export const ROLLOUT_SLIDER_ID = 'ROLLOUT_SLIDER_ID'; export const DIALOGUE_CONFIRM_ID = 'DIALOGUE_CONFIRM_ID'; diff --git a/frontend/src/themes/main-theme.ts b/frontend/src/themes/main-theme.ts index 745f5fd80f..18298b29f7 100644 --- a/frontend/src/themes/main-theme.ts +++ b/frontend/src/themes/main-theme.ts @@ -33,7 +33,7 @@ const mainTheme = { }, grey: { main: '#6C6C6C', - light: '#7e7e7e', + light: '#F6F6FA', }, neutral: { main: '#18243e',