mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-02 01:17:58 +02:00
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
This commit is contained in:
parent
bc9ae58c20
commit
472acecdad
@ -179,9 +179,7 @@ describe('feature', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('can delete a strategy in the development environment', () => {
|
it('can delete a strategy in the development environment', () => {
|
||||||
cy.visit(
|
cy.visit(`/projects/default/features/${featureToggleName}`);
|
||||||
`/projects/default/features/${featureToggleName}/strategies/edit?environmentId=development&strategyId=${strategyId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
cy.intercept(
|
cy.intercept(
|
||||||
'DELETE',
|
'DELETE',
|
||||||
@ -193,9 +191,8 @@ describe('feature', () => {
|
|||||||
}
|
}
|
||||||
).as('deleteStrategy');
|
).as('deleteStrategy');
|
||||||
|
|
||||||
cy.get(
|
cy.get('[data-test=FEATURE_ENVIRONMENT_ACCORDION_development]').click();
|
||||||
'[data-test=SIDEBAR_MODAL_ID] [data-test=STRATEGY_FORM_REMOVE_ID]'
|
cy.get('[data-test=STRATEGY_FORM_REMOVE_ID]').click();
|
||||||
).click();
|
|
||||||
cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click();
|
cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click();
|
||||||
cy.wait('@deleteStrategy');
|
cy.wait('@deleteStrategy');
|
||||||
});
|
});
|
||||||
|
@ -17,6 +17,14 @@ button {
|
|||||||
font-family: 'Sen', sans-serif;
|
font-family: 'Sen', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.MuiInputBase-root {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MuiAccordion-root.Mui-expanded {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.MuiButton-root {
|
.MuiButton-root {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
|
@ -128,6 +128,7 @@ const EnvironmentPermissionAccordion = ({
|
|||||||
text={environment.name}
|
text={environment.name}
|
||||||
className={styles.header}
|
className={styles.header}
|
||||||
maxWidth="120"
|
maxWidth="120"
|
||||||
|
maxLength={25}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p className={styles.header}>
|
<p className={styles.header}>
|
||||||
|
@ -54,6 +54,7 @@ const BreadcrumbNav = () => {
|
|||||||
<StringTruncator
|
<StringTruncator
|
||||||
text={path}
|
text={path}
|
||||||
maxWidth="200"
|
maxWidth="200"
|
||||||
|
maxLength={25}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
@ -76,6 +77,7 @@ const BreadcrumbNav = () => {
|
|||||||
to={link}
|
to={link}
|
||||||
>
|
>
|
||||||
<StringTruncator
|
<StringTruncator
|
||||||
|
maxLength={25}
|
||||||
text={path}
|
text={path}
|
||||||
maxWidth="200"
|
maxWidth="200"
|
||||||
/>
|
/>
|
||||||
|
@ -47,7 +47,11 @@ const Constraint = ({
|
|||||||
return (
|
return (
|
||||||
<div className={classes + ' ' + className} {...rest}>
|
<div className={classes + ' ' + className} {...rest}>
|
||||||
<div className={classes + ' ' + className} {...rest}>
|
<div className={classes + ' ' + className} {...rest}>
|
||||||
<StringTruncator text={constraint.contextName} maxWidth="125" />
|
<StringTruncator
|
||||||
|
text={constraint.contextName}
|
||||||
|
maxWidth="125"
|
||||||
|
maxLength={25}
|
||||||
|
/>
|
||||||
<StrategySeparator text={constraint.operator} maxWidth="none" />
|
<StrategySeparator text={constraint.operator} maxWidth="none" />
|
||||||
<span className={styles.values}>
|
<span className={styles.values}>
|
||||||
{constraint.values?.join(', ') ?? constraint.value}
|
{constraint.values?.join(', ') ?? constraint.value}
|
||||||
|
@ -20,15 +20,31 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
width: '26px',
|
width: '26px',
|
||||||
height: '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: {
|
accordion: {
|
||||||
border: `1px solid ${theme.palette.grey[300]}`,
|
border: `1px solid ${theme.palette.grey[300]}`,
|
||||||
borderRadius: '5px',
|
borderRadius: '5px',
|
||||||
margin: '1rem 0',
|
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
|
margin: 0,
|
||||||
|
|
||||||
['&:before']: {
|
['&:before']: {
|
||||||
height: 0,
|
height: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
accordionEdit: {
|
||||||
|
backgroundColor: '#F6F6FA',
|
||||||
|
},
|
||||||
operator: {
|
operator: {
|
||||||
border: `1px solid ${theme.palette.secondary.main}`,
|
border: `1px solid ${theme.palette.secondary.main}`,
|
||||||
padding: '0.25rem 1rem',
|
padding: '0.25rem 1rem',
|
||||||
@ -53,6 +69,17 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
headerValuesContainer: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
headerValues: {
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
color: theme.palette.primary.light,
|
||||||
|
},
|
||||||
|
headerValuesExpand: {
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
},
|
||||||
headerViewValuesContainer: {
|
headerViewValuesContainer: {
|
||||||
[theme.breakpoints.down(990)]: {
|
[theme.breakpoints.down(990)]: {
|
||||||
display: 'none',
|
display: 'none',
|
||||||
@ -117,7 +144,14 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
maxHeight: '400px',
|
maxHeight: '400px',
|
||||||
overflowY: 'auto',
|
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: {
|
settingsParagraph: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
import { IConstraint } from '../../../../interfaces/strategy';
|
import { IConstraint } from '../../../../interfaces/strategy';
|
||||||
import { useStyles } from '../ConstraintAccordion.styles';
|
import { useStyles } from '../ConstraintAccordion.styles';
|
||||||
import { ConstraintAccordionEditBody } from './ConstraintAccordionEditBody/ConstraintAccordionEditBody';
|
import { ConstraintAccordionEditBody } from './ConstraintAccordionEditBody/ConstraintAccordionEditBody';
|
||||||
@ -184,8 +185,11 @@ export const ConstraintAccordionEdit = ({
|
|||||||
return (
|
return (
|
||||||
<div className={styles.form}>
|
<div className={styles.form}>
|
||||||
<Accordion
|
<Accordion
|
||||||
|
className={classnames(styles.accordion, styles.accordionEdit)}
|
||||||
|
classes={{
|
||||||
|
expanded: styles.accordionRoot,
|
||||||
|
}}
|
||||||
style={{ boxShadow: 'none' }}
|
style={{ boxShadow: 'none' }}
|
||||||
className={styles.accordion}
|
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
TransitionProps={{
|
TransitionProps={{
|
||||||
onExited: () => {
|
onExited: () => {
|
||||||
|
@ -80,7 +80,7 @@ const InvertedOperator = ({
|
|||||||
the opposite)
|
the opposite)
|
||||||
</ConstraintFormHeader>
|
</ConstraintFormHeader>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
style={{ display: 'block' }}
|
style={{ display: 'inline-block' }}
|
||||||
control={
|
control={
|
||||||
<Switch
|
<Switch
|
||||||
checked={inverted}
|
checked={inverted}
|
||||||
@ -88,7 +88,7 @@ const InvertedOperator = ({
|
|||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={'inverted'}
|
label={'negated'}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -6,6 +6,7 @@ const useStyles = makeStyles(theme => ({
|
|||||||
fontSize: theme.fontSizes.bodySize,
|
fontSize: theme.fontSizes.bodySize,
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
marginTop: '1rem',
|
marginTop: '1rem',
|
||||||
|
marginBottom: '0.25rem',
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ export const ConstraintFormHeader: React.FC<
|
|||||||
> = ({ children, ...rest }) => {
|
> = ({ children, ...rest }) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
return (
|
return (
|
||||||
<h3 className={styles.header} {...rest}>
|
<h3 {...rest} className={styles.header}>
|
||||||
{children}
|
{children}
|
||||||
</h3>
|
</h3>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Button, Chip, makeStyles } from '@material-ui/core';
|
import { Button, Chip, makeStyles } from '@material-ui/core';
|
||||||
import Input from 'component/common/Input/Input';
|
import Input from 'component/common/Input/Input';
|
||||||
|
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader';
|
import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader';
|
||||||
|
|
||||||
@ -42,6 +43,8 @@ const useStyles = makeStyles(theme => ({
|
|||||||
valuesContainer: { marginTop: '1rem' },
|
valuesContainer: { marginTop: '1rem' },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const ENTER = 'Enter';
|
||||||
|
|
||||||
export const FreeTextInput = ({
|
export const FreeTextInput = ({
|
||||||
values,
|
values,
|
||||||
removeValue,
|
removeValue,
|
||||||
@ -52,6 +55,14 @@ export const FreeTextInput = ({
|
|||||||
const [inputValues, setInputValues] = useState('');
|
const [inputValues, setInputValues] = useState('');
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
|
||||||
|
const onKeyDown = (event: React.KeyboardEvent) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if (event.key === ENTER) {
|
||||||
|
addValues();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const addValues = () => {
|
const addValues = () => {
|
||||||
if (inputValues.length === 0) {
|
if (inputValues.length === 0) {
|
||||||
setError('values can not be empty');
|
setError('values can not be empty');
|
||||||
@ -80,6 +91,7 @@ export const FreeTextInput = ({
|
|||||||
<div className={styles.inputContainer}>
|
<div className={styles.inputContainer}>
|
||||||
<div className={styles.inputInnerContainer}>
|
<div className={styles.inputInnerContainer}>
|
||||||
<Input
|
<Input
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
label="Values"
|
label="Values"
|
||||||
name="values"
|
name="values"
|
||||||
value={inputValues}
|
value={inputValues}
|
||||||
@ -129,7 +141,13 @@ const ConstraintValueChips = ({
|
|||||||
// be unique here.
|
// be unique here.
|
||||||
return (
|
return (
|
||||||
<Chip
|
<Chip
|
||||||
label={value}
|
label={
|
||||||
|
<StringTruncator
|
||||||
|
text={value}
|
||||||
|
maxLength={35}
|
||||||
|
maxWidth="100"
|
||||||
|
/>
|
||||||
|
}
|
||||||
key={`${value}-${index}`}
|
key={`${value}-${index}`}
|
||||||
onDelete={() => removeValue(index)}
|
onDelete={() => removeValue(index)}
|
||||||
className={styles.valueChip}
|
className={styles.valueChip}
|
||||||
|
@ -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');
|
||||||
|
});
|
@ -1,11 +1,13 @@
|
|||||||
import { isValid } from 'date-fns';
|
import { isValid, parseISO } from 'date-fns';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
|
|
||||||
export type ConstraintValidatorOutput = [boolean, string];
|
export type ConstraintValidatorOutput = [boolean, string];
|
||||||
|
|
||||||
export const numberValidatorGenerator = (value: unknown) => {
|
export const numberValidatorGenerator = (value: unknown) => {
|
||||||
return (): ConstraintValidatorOutput => {
|
return (): ConstraintValidatorOutput => {
|
||||||
if (!Number(value)) {
|
const converted = Number(value);
|
||||||
|
|
||||||
|
if (typeof converted !== 'number' || Number.isNaN(converted)) {
|
||||||
return [false, 'Value must be a number'];
|
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 => {
|
return (): ConstraintValidatorOutput => {
|
||||||
|
const error: ConstraintValidatorOutput = [
|
||||||
|
false,
|
||||||
|
'Values must be a list of strings',
|
||||||
|
];
|
||||||
if (!Array.isArray(values)) {
|
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, ''];
|
return [true, ''];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const semVerValidatorGenerator = (value: string) => {
|
export const semVerValidatorGenerator = (value: string) => {
|
||||||
return (): ConstraintValidatorOutput => {
|
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 [false, 'Value is not a valid semver. For example 1.2.4'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [true, ''];
|
return [true, ''];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dateValidatorGenerator = (value: string) => {
|
export const dateValidatorGenerator = (value: string) => {
|
||||||
return (): ConstraintValidatorOutput => {
|
return (): ConstraintValidatorOutput => {
|
||||||
if (isValid(value)) {
|
if (!isValid(parseISO(value))) {
|
||||||
return [false, 'Value must be a valid date matching RFC3339'];
|
return [false, 'Value must be a valid date matching RFC3339'];
|
||||||
}
|
}
|
||||||
return [true, ''];
|
return [true, ''];
|
||||||
|
@ -8,7 +8,6 @@ import { IConstraint } from '../../../../interfaces/strategy';
|
|||||||
|
|
||||||
import { ConstraintAccordionViewBody } from './ConstraintAccordionViewBody/ConstraintAccordionViewBody';
|
import { ConstraintAccordionViewBody } from './ConstraintAccordionViewBody/ConstraintAccordionViewBody';
|
||||||
import { ConstraintAccordionViewHeader } from './ConstraintAccordionViewHeader/ConstraintAccordionViewHeader';
|
import { ConstraintAccordionViewHeader } from './ConstraintAccordionViewHeader/ConstraintAccordionViewHeader';
|
||||||
import { useStyles } from '../ConstraintAccordion.styles';
|
|
||||||
import { oneOf } from '../../../../utils/one-of';
|
import { oneOf } from '../../../../utils/one-of';
|
||||||
import {
|
import {
|
||||||
dateOperators,
|
dateOperators,
|
||||||
@ -16,6 +15,7 @@ import {
|
|||||||
semVerOperators,
|
semVerOperators,
|
||||||
} from '../../../../constants/operators';
|
} from '../../../../constants/operators';
|
||||||
|
|
||||||
|
import { useStyles } from '../ConstraintAccordion.styles';
|
||||||
interface IConstraintAccordionViewProps {
|
interface IConstraintAccordionViewProps {
|
||||||
environmentId: string;
|
environmentId: string;
|
||||||
constraint: IConstraint;
|
constraint: IConstraint;
|
||||||
@ -39,7 +39,13 @@ export const ConstraintAccordionView = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion style={{ boxShadow: 'none' }} className={styles.accordion}>
|
<Accordion
|
||||||
|
className={styles.accordion}
|
||||||
|
classes={{
|
||||||
|
root: styles.accordionRoot,
|
||||||
|
}}
|
||||||
|
style={{ boxShadow: 'none' }}
|
||||||
|
>
|
||||||
<AccordionSummary
|
<AccordionSummary
|
||||||
className={styles.summary}
|
className={styles.summary}
|
||||||
expandIcon={<ExpandMore />}
|
expandIcon={<ExpandMore />}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Chip } from '@material-ui/core';
|
import { Chip } from '@material-ui/core';
|
||||||
import { ImportExportOutlined, TextFormatOutlined } from '@material-ui/icons';
|
import { ImportExportOutlined, TextFormatOutlined } from '@material-ui/icons';
|
||||||
|
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { stringOperators } from '../../../../../constants/operators';
|
import { stringOperators } from '../../../../../constants/operators';
|
||||||
import { IConstraint } from '../../../../../interfaces/strategy';
|
import { IConstraint } from '../../../../../interfaces/strategy';
|
||||||
@ -37,7 +38,7 @@ export const ConstraintAccordionViewBody = ({
|
|||||||
show={
|
show={
|
||||||
<p className={styles.settingsParagraph}>
|
<p className={styles.settingsParagraph}>
|
||||||
<ImportExportOutlined className={styles.settingsIcon} />{' '}
|
<ImportExportOutlined className={styles.settingsIcon} />{' '}
|
||||||
Operator is inverted
|
Operator is negated
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -65,7 +66,16 @@ const SingleValue = ({ value, operator }: ISingleValueProps) => {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.singleValueView}>
|
<div className={styles.singleValueView}>
|
||||||
<p className={styles.singleValueText}>Value must {operator}</p>{' '}
|
<p className={styles.singleValueText}>Value must {operator}</p>{' '}
|
||||||
<Chip label={value} className={styles.chip} />
|
<Chip
|
||||||
|
label={
|
||||||
|
<StringTruncator
|
||||||
|
maxWidth="200"
|
||||||
|
text={value}
|
||||||
|
maxLength={25}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
className={styles.chip}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -88,7 +98,13 @@ const MultipleValues = ({ values }: IMultipleValuesProps) => {
|
|||||||
.map((value, index) => (
|
.map((value, index) => (
|
||||||
<Chip
|
<Chip
|
||||||
key={`${value}-${index}`}
|
key={`${value}-${index}`}
|
||||||
label={value}
|
label={
|
||||||
|
<StringTruncator
|
||||||
|
maxWidth="200"
|
||||||
|
text={value}
|
||||||
|
maxLength={25}
|
||||||
|
/>
|
||||||
|
}
|
||||||
className={styles.chip}
|
className={styles.chip}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
@ -53,10 +53,15 @@ export const ConstraintAccordionViewHeader = ({
|
|||||||
<StringTruncator
|
<StringTruncator
|
||||||
text={constraint.contextName}
|
text={constraint.contextName}
|
||||||
maxWidth="175px"
|
maxWidth="175px"
|
||||||
|
maxLength={25}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ minWidth: '220px' }}>
|
<div style={{ minWidth: '220px', position: 'relative' }}>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(constraint.inverted)}
|
||||||
|
show={<div className={styles.negated}>NOT</div>}
|
||||||
|
/>
|
||||||
<p className={styles.operator}>{constraint.operator}</p>
|
<p className={styles.operator}>{constraint.operator}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.headerViewValuesContainer}>
|
<div className={styles.headerViewValuesContainer}>
|
||||||
@ -64,10 +69,14 @@ export const ConstraintAccordionViewHeader = ({
|
|||||||
condition={singleValue}
|
condition={singleValue}
|
||||||
show={<Chip label={constraint.value} />}
|
show={<Chip label={constraint.value} />}
|
||||||
elseShow={
|
elseShow={
|
||||||
<p>
|
<div className={styles.headerValuesContainer}>
|
||||||
{constraint?.values?.length} values. Expand to
|
<p className={styles.headerValues}>
|
||||||
view
|
{constraint?.values?.length} values
|
||||||
</p>
|
</p>
|
||||||
|
<p className={styles.headerValuesExpand}>
|
||||||
|
Expand to view
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,34 +1,44 @@
|
|||||||
import { Tooltip } from '@material-ui/core';
|
import { Tooltip } from '@material-ui/core';
|
||||||
|
import ConditionallyRender from '../ConditionallyRender';
|
||||||
|
|
||||||
interface IStringTruncatorProps {
|
interface IStringTruncatorProps {
|
||||||
text: string;
|
text: string;
|
||||||
maxWidth: string;
|
maxWidth: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
maxLength: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StringTruncator = ({
|
const StringTruncator = ({
|
||||||
text,
|
text,
|
||||||
maxWidth,
|
maxWidth,
|
||||||
|
maxLength,
|
||||||
className,
|
className,
|
||||||
...rest
|
...rest
|
||||||
}: IStringTruncatorProps) => {
|
}: IStringTruncatorProps) => {
|
||||||
return (
|
return (
|
||||||
<Tooltip title={text} arrow>
|
<ConditionallyRender
|
||||||
<span
|
condition={text.length > maxLength}
|
||||||
data-loading
|
show={
|
||||||
className={className}
|
<Tooltip title={text} arrow>
|
||||||
style={{
|
<span
|
||||||
maxWidth: `${maxWidth}px`,
|
data-loading
|
||||||
textOverflow: 'ellipsis',
|
className={className}
|
||||||
overflow: 'hidden',
|
style={{
|
||||||
whiteSpace: 'nowrap',
|
maxWidth: `${maxWidth}px`,
|
||||||
display: 'inline-block',
|
textOverflow: 'ellipsis',
|
||||||
}}
|
overflow: 'hidden',
|
||||||
{...rest}
|
whiteSpace: 'nowrap',
|
||||||
>
|
display: 'inline-block',
|
||||||
{text}
|
verticalAlign: 'middle',
|
||||||
</span>
|
}}
|
||||||
</Tooltip>
|
{...rest}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
elseShow={<>{text}</>}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,7 +19,11 @@ const EnvironmentCard = ({ name, type }: IEnvironmentProps) => {
|
|||||||
<div className={styles.infoInnerContainer}>
|
<div className={styles.infoInnerContainer}>
|
||||||
<div className={styles.infoTitle}>Id</div>
|
<div className={styles.infoTitle}>Id</div>
|
||||||
<div>
|
<div>
|
||||||
<StringTruncator text={name} maxWidth={'250'} />
|
<StringTruncator
|
||||||
|
text={name}
|
||||||
|
maxWidth={'250'}
|
||||||
|
maxLength={30}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.infoInnerContainer}>
|
<div className={styles.infoInnerContainer}>
|
||||||
|
@ -135,7 +135,11 @@ const EnvironmentListItem = ({
|
|||||||
primary={
|
primary={
|
||||||
<>
|
<>
|
||||||
<strong>
|
<strong>
|
||||||
<StringTruncator text={env.name} maxWidth={'125'} />
|
<StringTruncator
|
||||||
|
text={env.name}
|
||||||
|
maxWidth={'125'}
|
||||||
|
maxLength={25}
|
||||||
|
/>
|
||||||
</strong>
|
</strong>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={!env.enabled}
|
condition={!env.enabled}
|
||||||
|
@ -23,6 +23,7 @@ export const FeatureStrategyEmpty = ({
|
|||||||
<StringTruncator
|
<StringTruncator
|
||||||
text={environmentId}
|
text={environmentId}
|
||||||
maxWidth={'130'}
|
maxWidth={'130'}
|
||||||
|
maxLength={15}
|
||||||
className={styles.envName}
|
className={styles.envName}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
environment
|
environment
|
||||||
|
@ -5,7 +5,6 @@ import {
|
|||||||
formatStrategyName,
|
formatStrategyName,
|
||||||
} from 'utils/strategy-names';
|
} from 'utils/strategy-names';
|
||||||
import { FeatureStrategyType } from '../FeatureStrategyType/FeatureStrategyType';
|
import { FeatureStrategyType } from '../FeatureStrategyType/FeatureStrategyType';
|
||||||
import { FeatureStrategyRemove } from '../FeatureStrategyRemove/FeatureStrategyRemove';
|
|
||||||
import { FeatureStrategyEnabled } from '../FeatureStrategyEnabled/FeatureStrategyEnabled';
|
import { FeatureStrategyEnabled } from '../FeatureStrategyEnabled/FeatureStrategyEnabled';
|
||||||
import { FeatureStrategyConstraints } from '../FeatureStrategyConstraints/FeatureStrategyConstraints';
|
import { FeatureStrategyConstraints } from '../FeatureStrategyConstraints/FeatureStrategyConstraints';
|
||||||
import { Button } from '@material-ui/core';
|
import { Button } from '@material-ui/core';
|
||||||
@ -118,33 +117,6 @@ export const FeatureStrategyForm = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className={styles.buttons}>
|
<div className={styles.buttons}>
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
color="secondary"
|
|
||||||
onClick={onCancel}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={Boolean(strategy.id)}
|
|
||||||
show={
|
|
||||||
<FeatureStrategyRemove
|
|
||||||
projectId={feature.project}
|
|
||||||
featureId={feature.name}
|
|
||||||
environmentId={environmentId}
|
|
||||||
strategyId={strategy.id!}
|
|
||||||
disabled={loading}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<FeatureStrategyProdGuard
|
|
||||||
open={showProdGuard}
|
|
||||||
onClose={() => setShowProdGuard(false)}
|
|
||||||
onClick={onSubmit}
|
|
||||||
loading={loading}
|
|
||||||
label="Save strategy"
|
|
||||||
/>
|
|
||||||
<PermissionButton
|
<PermissionButton
|
||||||
permission={permission}
|
permission={permission}
|
||||||
projectId={feature.project}
|
projectId={feature.project}
|
||||||
@ -157,6 +129,22 @@ export const FeatureStrategyForm = ({
|
|||||||
>
|
>
|
||||||
Save strategy
|
Save strategy
|
||||||
</PermissionButton>
|
</PermissionButton>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
color="secondary"
|
||||||
|
onClick={onCancel}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<FeatureStrategyProdGuard
|
||||||
|
open={showProdGuard}
|
||||||
|
onClose={() => setShowProdGuard(false)}
|
||||||
|
onClick={onSubmit}
|
||||||
|
loading={loading}
|
||||||
|
label="Save strategy"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
@ -42,6 +42,7 @@ export const FeatureStrategyMenuCard = ({
|
|||||||
text={strategy.displayName || strategyName}
|
text={strategy.displayName || strategyName}
|
||||||
className={styles.name}
|
className={styles.name}
|
||||||
maxWidth="200"
|
maxWidth="200"
|
||||||
|
maxLength={25}
|
||||||
/>
|
/>
|
||||||
<div className={styles.description}>{strategy.description}</div>
|
<div className={styles.description}>{strategy.description}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -239,6 +239,7 @@ const FeatureToggleListNew = ({
|
|||||||
>
|
>
|
||||||
<StringTruncator
|
<StringTruncator
|
||||||
text={env.name}
|
text={env.name}
|
||||||
|
maxLength={15}
|
||||||
maxWidth="90"
|
maxWidth="90"
|
||||||
data-loading
|
data-loading
|
||||||
/>
|
/>
|
||||||
|
@ -86,7 +86,7 @@ const FeatureOverviewEnvSwitch = ({
|
|||||||
{' '}
|
{' '}
|
||||||
<span data-loading>{env.enabled ? 'enabled' : 'disabled'} in</span>
|
<span data-loading>{env.enabled ? 'enabled' : 'disabled'} in</span>
|
||||||
|
|
||||||
<StringTruncator text={env.name} maxWidth="120" />
|
<StringTruncator text={env.name} maxWidth="120" maxLength={15} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
},
|
},
|
||||||
headerTitle: {
|
headerTitle: {
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
headerIcon: {
|
headerIcon: {
|
||||||
marginBottom: '0.5rem',
|
marginBottom: '0.5rem',
|
||||||
|
@ -25,6 +25,7 @@ import FeatureOverviewEnvironmentBody from './FeatureOverviewEnvironmentBody/Fea
|
|||||||
import FeatureOverviewEnvironmentFooter from './FeatureOverviewEnvironmentFooter/FeatureOverviewEnvironmentFooter';
|
import FeatureOverviewEnvironmentFooter from './FeatureOverviewEnvironmentFooter/FeatureOverviewEnvironmentFooter';
|
||||||
import FeatureOverviewEnvironmentMetrics from './FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics';
|
import FeatureOverviewEnvironmentMetrics from './FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics';
|
||||||
import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu';
|
import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu';
|
||||||
|
import { FEATURE_ENVIRONMENT_ACCORDION } from 'testIds';
|
||||||
|
|
||||||
interface IStrategyIconObject {
|
interface IStrategyIconObject {
|
||||||
count: number;
|
count: number;
|
||||||
@ -86,7 +87,10 @@ const FeatureOverviewEnvironment = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.featureOverviewEnvironment}>
|
<div className={styles.featureOverviewEnvironment}>
|
||||||
<Accordion style={{ boxShadow: 'none' }}>
|
<Accordion
|
||||||
|
style={{ boxShadow: 'none' }}
|
||||||
|
data-test={`${FEATURE_ENVIRONMENT_ACCORDION}_${env.name}`}
|
||||||
|
>
|
||||||
<AccordionSummary
|
<AccordionSummary
|
||||||
className={styles.accordionHeader}
|
className={styles.accordionHeader}
|
||||||
expandIcon={<ExpandMore />}
|
expandIcon={<ExpandMore />}
|
||||||
@ -97,12 +101,15 @@ const FeatureOverviewEnvironment = ({
|
|||||||
enabled={env.enabled}
|
enabled={env.enabled}
|
||||||
className={styles.headerIcon}
|
className={styles.headerIcon}
|
||||||
/>
|
/>
|
||||||
Feature toggle execution for
|
<p>
|
||||||
<StringTruncator
|
Feature toggle execution for
|
||||||
text={env.name}
|
<StringTruncator
|
||||||
className={styles.truncator}
|
text={env.name}
|
||||||
maxWidth="100"
|
className={styles.truncator}
|
||||||
/>
|
maxWidth="100"
|
||||||
|
maxLength={15}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.strategyMenu}>
|
<div className={styles.strategyMenu}>
|
||||||
|
@ -4,7 +4,6 @@ import ConditionallyRender from 'component/common/ConditionallyRender';
|
|||||||
import FeatureOverviewEnvironmentStrategies from '../FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategies';
|
import FeatureOverviewEnvironmentStrategies from '../FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategies';
|
||||||
import { useStyles } from '../FeatureOverviewEnvironment.styles';
|
import { useStyles } from '../FeatureOverviewEnvironment.styles';
|
||||||
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||||
import { FeatureStrategyMenu } from '../../../../../FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu';
|
|
||||||
import { FeatureStrategyEmpty } from '../../../../../FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
|
import { FeatureStrategyEmpty } from '../../../../../FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
|
||||||
|
|
||||||
interface IFeatureOverviewEnvironmentBodyProps {
|
interface IFeatureOverviewEnvironmentBodyProps {
|
||||||
@ -37,14 +36,6 @@ const FeatureOverviewEnvironmentBody = ({
|
|||||||
condition={featureEnvironment?.strategies.length > 0}
|
condition={featureEnvironment?.strategies.length > 0}
|
||||||
show={
|
show={
|
||||||
<>
|
<>
|
||||||
<div className={styles.linkContainer}>
|
|
||||||
<FeatureStrategyMenu
|
|
||||||
label="Add strategy"
|
|
||||||
projectId={projectId}
|
|
||||||
featureId={featureId}
|
|
||||||
environmentId={featureEnvironment.name}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<FeatureOverviewEnvironmentStrategies
|
<FeatureOverviewEnvironmentStrategies
|
||||||
strategies={featureEnvironment?.strategies}
|
strategies={featureEnvironment?.strategies}
|
||||||
environmentName={featureEnvironment.name}
|
environmentName={featureEnvironment.name}
|
||||||
|
@ -13,6 +13,7 @@ import FeatureOverviewExecution from '../../../../FeatureOverviewExecution/Featu
|
|||||||
import { useStyles } from './FeatureOverviewEnvironmentStrategy.styles';
|
import { useStyles } from './FeatureOverviewEnvironmentStrategy.styles';
|
||||||
import { formatEditStrategyPath } from '../../../../../../FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
|
import { formatEditStrategyPath } from '../../../../../../FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
|
||||||
import { FeatureStrategyRemove } from 'component/feature/FeatureStrategy/FeatureStrategyRemove/FeatureStrategyRemove';
|
import { FeatureStrategyRemove } from 'component/feature/FeatureStrategy/FeatureStrategyRemove/FeatureStrategyRemove';
|
||||||
|
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||||
|
|
||||||
interface IFeatureOverviewEnvironmentStrategyProps {
|
interface IFeatureOverviewEnvironmentStrategyProps {
|
||||||
environmentId: string;
|
environmentId: string;
|
||||||
@ -40,15 +41,12 @@ const FeatureOverviewEnvironmentStrategy = ({
|
|||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<Icon className={styles.icon} />
|
<Icon className={styles.icon} />
|
||||||
{formatStrategyName(strategy.name)}
|
<StringTruncator
|
||||||
|
maxWidth="150"
|
||||||
|
maxLength={15}
|
||||||
|
text={formatStrategyName(strategy.name)}
|
||||||
|
/>
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
<FeatureStrategyRemove
|
|
||||||
projectId={projectId}
|
|
||||||
featureId={featureId}
|
|
||||||
environmentId={environmentId}
|
|
||||||
strategyId={strategy.id}
|
|
||||||
icon
|
|
||||||
/>
|
|
||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
permission={UPDATE_FEATURE_STRATEGY}
|
permission={UPDATE_FEATURE_STRATEGY}
|
||||||
environmentId={environmentId}
|
environmentId={environmentId}
|
||||||
@ -59,6 +57,13 @@ const FeatureOverviewEnvironmentStrategy = ({
|
|||||||
>
|
>
|
||||||
<Edit titleAccess="Edit" />
|
<Edit titleAccess="Edit" />
|
||||||
</PermissionIconButton>
|
</PermissionIconButton>
|
||||||
|
<FeatureStrategyRemove
|
||||||
|
projectId={projectId}
|
||||||
|
featureId={featureId}
|
||||||
|
environmentId={environmentId}
|
||||||
|
strategyId={strategy.id}
|
||||||
|
icon
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -29,5 +29,10 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
maxWidth: '50%',
|
maxWidth: '50%',
|
||||||
},
|
},
|
||||||
text: { textAlign: 'center', margin: '0.2rem 0 0.5rem' },
|
text: {
|
||||||
|
textAlign: 'center',
|
||||||
|
margin: '0.2rem 0 0.5rem',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -11,6 +11,7 @@ import { useStyles } from './FeatureOverviewExecution.styles';
|
|||||||
import FeatureOverviewExecutionChips from './FeatureOverviewExecutionChips/FeatureOverviewExecutionChips';
|
import FeatureOverviewExecutionChips from './FeatureOverviewExecutionChips/FeatureOverviewExecutionChips';
|
||||||
import { useStrategies } from '../../../../../hooks/api/getters/useStrategies/useStrategies';
|
import { useStrategies } from '../../../../../hooks/api/getters/useStrategies/useStrategies';
|
||||||
import Constraint from '../../../../common/Constraint/Constraint';
|
import Constraint from '../../../../common/Constraint/Constraint';
|
||||||
|
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||||
|
|
||||||
interface IFeatureOverviewExecutionProps {
|
interface IFeatureOverviewExecutionProps {
|
||||||
parameters: IParameter;
|
parameters: IParameter;
|
||||||
@ -166,7 +167,11 @@ const FeatureOverviewExecution = ({
|
|||||||
return (
|
return (
|
||||||
<Fragment key={param.name}>
|
<Fragment key={param.name}>
|
||||||
<p className={styles.text} key={param.name}>
|
<p className={styles.text} key={param.name}>
|
||||||
{param.name} must be{' '}
|
<StringTruncator
|
||||||
|
maxLength={15}
|
||||||
|
maxWidth="150"
|
||||||
|
text={param.name}
|
||||||
|
/>{' '}
|
||||||
{strategy.parameters[param.name]}
|
{strategy.parameters[param.name]}
|
||||||
</p>
|
</p>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
@ -189,7 +194,12 @@ const FeatureOverviewExecution = ({
|
|||||||
show={
|
show={
|
||||||
<>
|
<>
|
||||||
<p className={styles.text}>
|
<p className={styles.text}>
|
||||||
{param.name} is set to {numValue}
|
<StringTruncator
|
||||||
|
maxWidth="150"
|
||||||
|
maxLength={15}
|
||||||
|
text={param.name}
|
||||||
|
/>{' '}
|
||||||
|
is set to {numValue}
|
||||||
</p>
|
</p>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={notLastItem}
|
condition={notLastItem}
|
||||||
@ -208,7 +218,12 @@ const FeatureOverviewExecution = ({
|
|||||||
show={
|
show={
|
||||||
<>
|
<>
|
||||||
<p className={styles.text}>
|
<p className={styles.text}>
|
||||||
{param.name} is set to {value}
|
<StringTruncator
|
||||||
|
maxLength={15}
|
||||||
|
maxWidth="150"
|
||||||
|
text={param.name}
|
||||||
|
/>{' '}
|
||||||
|
is set to {value}
|
||||||
</p>
|
</p>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={notLastItem}
|
condition={notLastItem}
|
||||||
|
@ -20,6 +20,8 @@ import { Alert } from '@material-ui/lab';
|
|||||||
import PermissionSwitch from '../../common/PermissionSwitch/PermissionSwitch';
|
import PermissionSwitch from '../../common/PermissionSwitch/PermissionSwitch';
|
||||||
import { IProjectEnvironment } from '../../../interfaces/environments';
|
import { IProjectEnvironment } from '../../../interfaces/environments';
|
||||||
import { getEnabledEnvs } from './helpers';
|
import { getEnabledEnvs } from './helpers';
|
||||||
|
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||||
|
import { useCommonStyles } from 'common.styles';
|
||||||
|
|
||||||
interface ProjectEnvironmentListProps {
|
interface ProjectEnvironmentListProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@ -39,6 +41,7 @@ const ProjectEnvironmentList = ({ projectId }: ProjectEnvironmentListProps) => {
|
|||||||
const { project, refetch: refetchProject } = useProject(projectId);
|
const { project, refetch: refetchProject } = useProject(projectId);
|
||||||
const { removeEnvironmentFromProject, addEnvironmentToProject } =
|
const { removeEnvironmentFromProject, addEnvironmentToProject } =
|
||||||
useProjectApi();
|
useProjectApi();
|
||||||
|
const commonStyles = useCommonStyles();
|
||||||
|
|
||||||
// local state
|
// local state
|
||||||
const [selectedEnv, setSelectedEnv] = useState<IProjectEnvironment>();
|
const [selectedEnv, setSelectedEnv] = useState<IProjectEnvironment>();
|
||||||
@ -129,10 +132,20 @@ const ProjectEnvironmentList = ({ projectId }: ProjectEnvironmentListProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const genLabel = (env: IProjectEnvironment) => (
|
const genLabel = (env: IProjectEnvironment) => (
|
||||||
<>
|
<div className={commonStyles.flexRow}>
|
||||||
<code>{env.name}</code> environment is{' '}
|
<code>
|
||||||
<strong>{env.enabled ? 'enabled' : 'disabled'}</strong>
|
<StringTruncator
|
||||||
</>
|
text={env.name}
|
||||||
|
maxLength={50}
|
||||||
|
maxWidth="150"
|
||||||
|
/>
|
||||||
|
</code>
|
||||||
|
{/* This is ugly - but regular {" "} doesn't work here*/}
|
||||||
|
<p>
|
||||||
|
environment is{' '}
|
||||||
|
<strong>{env.enabled ? 'enabled' : 'disabled'}</strong>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderEnvironments = () => {
|
const renderEnvironments = () => {
|
||||||
|
@ -16,6 +16,7 @@ export const SSO_LOGIN_BUTTON = 'SSO_LOGIN_BUTTON';
|
|||||||
export const FORGOTTEN_PASSWORD_FIELD = 'FORGOTTEN_PASSWORD_FIELD';
|
export const FORGOTTEN_PASSWORD_FIELD = 'FORGOTTEN_PASSWORD_FIELD';
|
||||||
|
|
||||||
/* STRATEGY */
|
/* STRATEGY */
|
||||||
|
export const FEATURE_ENVIRONMENT_ACCORDION = 'FEATURE_ENVIRONMENT_ACCORDION';
|
||||||
export const ADD_NEW_STRATEGY_ID = 'ADD_NEW_STRATEGY_ID';
|
export const ADD_NEW_STRATEGY_ID = 'ADD_NEW_STRATEGY_ID';
|
||||||
export const ROLLOUT_SLIDER_ID = 'ROLLOUT_SLIDER_ID';
|
export const ROLLOUT_SLIDER_ID = 'ROLLOUT_SLIDER_ID';
|
||||||
export const DIALOGUE_CONFIRM_ID = 'DIALOGUE_CONFIRM_ID';
|
export const DIALOGUE_CONFIRM_ID = 'DIALOGUE_CONFIRM_ID';
|
||||||
|
@ -33,7 +33,7 @@ const mainTheme = {
|
|||||||
},
|
},
|
||||||
grey: {
|
grey: {
|
||||||
main: '#6C6C6C',
|
main: '#6C6C6C',
|
||||||
light: '#7e7e7e',
|
light: '#F6F6FA',
|
||||||
},
|
},
|
||||||
neutral: {
|
neutral: {
|
||||||
main: '#18243e',
|
main: '#18243e',
|
||||||
|
Loading…
Reference in New Issue
Block a user