mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
Fix/bugfixes (#279)
* fix: add try catch to copy * fix: show constraints on default strategy * fix: require name to submit context field * fix: require name and project id to be set in order to create a project * fix: change documentation icon * fix: only validate unique names on create * Update src/component/context/form-context-component.jsx Co-authored-by: Christopher Kolstad <chriswk@getunleash.ai> Co-authored-by: Christopher Kolstad <chriswk@getunleash.ai>
This commit is contained in:
parent
0340573199
commit
f8e34d53ff
@ -3,5 +3,9 @@ import { makeStyles } from '@material-ui/styles';
|
|||||||
export const useStyles = makeStyles({
|
export const useStyles = makeStyles({
|
||||||
listItem: {
|
listItem: {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
['& a']: {
|
||||||
|
textDecoration: 'none',
|
||||||
|
color: 'inherit',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
import React, { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, Chip, TextField, Switch, Icon, Typography } from '@material-ui/core';
|
import {
|
||||||
|
Button,
|
||||||
|
Chip,
|
||||||
|
TextField,
|
||||||
|
Switch,
|
||||||
|
Icon,
|
||||||
|
Typography,
|
||||||
|
} from '@material-ui/core';
|
||||||
import styles from './Context.module.scss';
|
import styles from './Context.module.scss';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { FormButtons, styles as commonStyles } from '../common';
|
import { FormButtons, styles as commonStyles } from '../common';
|
||||||
import { trim } from '../common/util';
|
import { trim } from '../common/util';
|
||||||
import PageContent from '../common/PageContent/PageContent';
|
import PageContent from '../common/PageContent/PageContent';
|
||||||
|
import ConditionallyRender from '../common/ConditionallyRender';
|
||||||
|
import { Alert } from '@material-ui/lab';
|
||||||
|
|
||||||
const sortIgnoreCase = (a, b) => {
|
const sortIgnoreCase = (a, b) => {
|
||||||
a = a.toLowerCase();
|
a = a.toLowerCase();
|
||||||
@ -23,9 +32,26 @@ class AddContextComponent extends Component {
|
|||||||
errors: {},
|
errors: {},
|
||||||
currentLegalValue: '',
|
currentLegalValue: '',
|
||||||
dirty: false,
|
dirty: false,
|
||||||
|
focusedLegalValue: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleKeydown = e => {
|
||||||
|
if (e.key === 'Enter' && this.state.focusedLegalValue) {
|
||||||
|
this.addLegalValue(e);
|
||||||
|
} else if (e.key === 'Enter') {
|
||||||
|
this.onSubmit(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
window.addEventListener('keydown', this.handleKeydown);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
window.removeEventListener('keydown', this.handleKeydown);
|
||||||
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props, state) {
|
static getDerivedStateFromProps(props, state) {
|
||||||
if (state.contextField.initial && !props.contextField.initial) {
|
if (state.contextField.initial && !props.contextField.initial) {
|
||||||
return { contextField: props.contextField };
|
return { contextField: props.contextField };
|
||||||
@ -42,15 +68,19 @@ class AddContextComponent extends Component {
|
|||||||
|
|
||||||
validateContextName = async name => {
|
validateContextName = async name => {
|
||||||
const { errors } = this.state;
|
const { errors } = this.state;
|
||||||
const { validateName } = this.props;
|
const { validateName, editMode } = this.props;
|
||||||
|
|
||||||
|
if (editMode) return true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await validateName(name);
|
await validateName(name);
|
||||||
errors.name = undefined;
|
errors.name = undefined;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors.name = err.message;
|
errors.name = err.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ errors });
|
this.setState({ errors });
|
||||||
|
if (errors.name) return false;
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
onCancel = evt => {
|
onCancel = evt => {
|
||||||
@ -58,10 +88,23 @@ class AddContextComponent extends Component {
|
|||||||
this.props.history.push('/context');
|
this.props.history.push('/context');
|
||||||
};
|
};
|
||||||
|
|
||||||
onSubmit = evt => {
|
onSubmit = async evt => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
const { contextField } = this.state;
|
const { contextField } = this.state;
|
||||||
this.props.submit(contextField).then(() => this.props.history.push('/context'));
|
|
||||||
|
const valid = await this.validateContextName(contextField.name);
|
||||||
|
|
||||||
|
if (valid) {
|
||||||
|
this.props
|
||||||
|
.submit(contextField)
|
||||||
|
.then(() => this.props.history.push('/context'))
|
||||||
|
.catch(e =>
|
||||||
|
this.setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
errors: { api: e.toString() },
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateCurrentLegalValue = evt => {
|
updateCurrentLegalValue = evt => {
|
||||||
@ -82,7 +125,9 @@ class AddContextComponent extends Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const legalValues = contextField.legalValues.concat(trim(currentLegalValue));
|
const legalValues = contextField.legalValues.concat(
|
||||||
|
trim(currentLegalValue)
|
||||||
|
);
|
||||||
contextField.legalValues = legalValues.sort(sortIgnoreCase);
|
contextField.legalValues = legalValues.sort(sortIgnoreCase);
|
||||||
this.setState({
|
this.setState({
|
||||||
contextField,
|
contextField,
|
||||||
@ -93,7 +138,9 @@ class AddContextComponent extends Component {
|
|||||||
|
|
||||||
removeLegalValue = index => {
|
removeLegalValue = index => {
|
||||||
const { contextField } = this.state;
|
const { contextField } = this.state;
|
||||||
const legalValues = contextField.legalValues.filter((_, i) => i !== index);
|
const legalValues = contextField.legalValues.filter(
|
||||||
|
(_, i) => i !== index
|
||||||
|
);
|
||||||
contextField.legalValues = legalValues;
|
contextField.legalValues = legalValues;
|
||||||
this.setState({ contextField });
|
this.setState({ contextField });
|
||||||
};
|
};
|
||||||
@ -115,11 +162,20 @@ class AddContextComponent extends Component {
|
|||||||
return (
|
return (
|
||||||
<PageContent headerContent="Create context field">
|
<PageContent headerContent="Create context field">
|
||||||
<div className={styles.supporting}>
|
<div className={styles.supporting}>
|
||||||
Context fields are a basic building block used in Unleash to control roll-out. They can be used
|
Context fields are a basic building block used in Unleash to
|
||||||
together with strategy constraints as part of the activation strategy evaluation.
|
control roll-out. They can be used together with strategy
|
||||||
|
constraints as part of the activation strategy evaluation.
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={this.onSubmit}>
|
<form onSubmit={this.onSubmit}>
|
||||||
<section className={styles.formContainer}>
|
<section className={styles.formContainer}>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={errors.api}
|
||||||
|
show={
|
||||||
|
<Alert severity="error">
|
||||||
|
{this.state.errors.api}
|
||||||
|
</Alert>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
className={commonStyles.fullwidth}
|
className={commonStyles.fullwidth}
|
||||||
label="Name"
|
label="Name"
|
||||||
@ -130,8 +186,12 @@ class AddContextComponent extends Component {
|
|||||||
disabled={editMode}
|
disabled={editMode}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
onBlur={v => this.validateContextName(v.target.value)}
|
onBlur={v =>
|
||||||
onChange={v => this.setValue('name', trim(v.target.value))}
|
this.validateContextName(v.target.value)
|
||||||
|
}
|
||||||
|
onChange={v =>
|
||||||
|
this.setValue('name', trim(v.target.value))
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
className={commonStyles.fullwidth}
|
className={commonStyles.fullwidth}
|
||||||
@ -142,7 +202,9 @@ class AddContextComponent extends Component {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
defaultValue={contextField.description}
|
defaultValue={contextField.description}
|
||||||
onChange={v => this.setValue('description', v.target.value)}
|
onChange={v =>
|
||||||
|
this.setValue('description', v.target.value)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
@ -150,8 +212,10 @@ class AddContextComponent extends Component {
|
|||||||
<section className={styles.inset}>
|
<section className={styles.inset}>
|
||||||
<h6 className={styles.h6}>Legal values</h6>
|
<h6 className={styles.h6}>Legal values</h6>
|
||||||
<p className={styles.alpha}>
|
<p className={styles.alpha}>
|
||||||
By defining the legal values the Unleash Admin UI will validate the user input. A concrete
|
By defining the legal values the Unleash Admin UI
|
||||||
example would be that we know all values for our “environment” (local, development, stage,
|
will validate the user input. A concrete example
|
||||||
|
would be that we know all values for our
|
||||||
|
“environment” (local, development, stage,
|
||||||
production).
|
production).
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
@ -159,6 +223,18 @@ class AddContextComponent extends Component {
|
|||||||
label="Value"
|
label="Value"
|
||||||
name="value"
|
name="value"
|
||||||
className={styles.valueField}
|
className={styles.valueField}
|
||||||
|
onFocus={() =>
|
||||||
|
this.setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
focusedLegalValue: true,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
onBlur={() =>
|
||||||
|
this.setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
focusedLegalValue: false,
|
||||||
|
}))
|
||||||
|
}
|
||||||
value={this.state.currentLegalValue}
|
value={this.state.currentLegalValue}
|
||||||
error={!!errors.currentLegalValue}
|
error={!!errors.currentLegalValue}
|
||||||
helperText={errors.currentLegalValue}
|
helperText={errors.currentLegalValue}
|
||||||
@ -176,15 +252,28 @@ class AddContextComponent extends Component {
|
|||||||
Add
|
Add
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div>{contextField.legalValues.map(this.renderLegalValue)}</div>
|
<div>
|
||||||
|
{contextField.legalValues.map(
|
||||||
|
this.renderLegalValue
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<br />
|
<br />
|
||||||
<section>
|
<section>
|
||||||
<Typography variant="subtitle1">Custom stickiness (beta)</Typography>
|
<Typography variant="subtitle1">
|
||||||
<p className={classnames(styles.alpha, styles.formContainer)}>
|
Custom stickiness (beta)
|
||||||
By enabling stickiness on this context field you can use it together with the
|
</Typography>
|
||||||
flexible-rollout strategy. This will guarantee a consistent behavior for specific values of
|
<p
|
||||||
this context field. PS! Not all client SDK's support this feature yet!{' '}
|
className={classnames(
|
||||||
|
styles.alpha,
|
||||||
|
styles.formContainer
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
By enabling stickiness on this context field you can
|
||||||
|
use it together with the flexible-rollout strategy.
|
||||||
|
This will guarantee a consistent behavior for
|
||||||
|
specific values of this context field. PS! Not all
|
||||||
|
client SDK's support this feature yet!{' '}
|
||||||
<a
|
<a
|
||||||
href="https://unleash.github.io/docs/activation_strategy#flexiblerollout"
|
href="https://unleash.github.io/docs/activation_strategy#flexiblerollout"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -193,14 +282,24 @@ class AddContextComponent extends Component {
|
|||||||
Read more
|
Read more
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
{console.log(contextField.stickiness)}
|
||||||
<Switch
|
<Switch
|
||||||
label="Allow stickiness"
|
label="Allow stickiness"
|
||||||
|
checked={contextField.stickiness}
|
||||||
value={contextField.stickiness}
|
value={contextField.stickiness}
|
||||||
onChange={() => this.setValue('stickiness', !contextField.stickiness)}
|
onChange={() =>
|
||||||
|
this.setValue(
|
||||||
|
'stickiness',
|
||||||
|
!contextField.stickiness
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
<div className={styles.formButtons}>
|
<div className={styles.formButtons}>
|
||||||
<FormButtons submitText={submitText} onCancel={this.onCancel} />
|
<FormButtons
|
||||||
|
submitText={submitText}
|
||||||
|
onCancel={this.onCancel}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
|
@ -12,22 +12,45 @@ const StrategyCardContent = ({ strategy, strategyDefinition }) => {
|
|||||||
const resolveContent = () => {
|
const resolveContent = () => {
|
||||||
switch (strategy.name) {
|
switch (strategy.name) {
|
||||||
case 'default':
|
case 'default':
|
||||||
return <StrategyCardContentDefault />;
|
return <StrategyCardContentDefault strategy={strategy} />;
|
||||||
case 'flexibleRollout':
|
case 'flexibleRollout':
|
||||||
return <StrategyCardContentFlexible strategy={strategy} />;
|
return <StrategyCardContentFlexible strategy={strategy} />;
|
||||||
case 'userWithId':
|
case 'userWithId':
|
||||||
return <StrategyCardContentList parameter={'userIds'} valuesName={'userIds'} strategy={strategy} />;
|
return (
|
||||||
|
<StrategyCardContentList
|
||||||
|
parameter={'userIds'}
|
||||||
|
valuesName={'userIds'}
|
||||||
|
strategy={strategy}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case 'gradualRolloutRandom':
|
case 'gradualRolloutRandom':
|
||||||
return <StrategyCardContentGradRandom strategy={strategy} />;
|
return <StrategyCardContentGradRandom strategy={strategy} />;
|
||||||
case 'remoteAddress':
|
case 'remoteAddress':
|
||||||
return <StrategyCardContentList parameter={'IPs'} valuesName={'IPs'} strategy={strategy} />;
|
return (
|
||||||
|
<StrategyCardContentList
|
||||||
|
parameter={'IPs'}
|
||||||
|
valuesName={'IPs'}
|
||||||
|
strategy={strategy}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case 'applicationHostname':
|
case 'applicationHostname':
|
||||||
return <StrategyCardContentList parameter={'hostNames'} valuesName={'hostnames'} strategy={strategy} />;
|
return (
|
||||||
|
<StrategyCardContentList
|
||||||
|
parameter={'hostNames'}
|
||||||
|
valuesName={'hostnames'}
|
||||||
|
strategy={strategy}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case 'gradualRolloutUserId':
|
case 'gradualRolloutUserId':
|
||||||
case 'gradualRolloutSessionId':
|
case 'gradualRolloutSessionId':
|
||||||
return <StrategyCardContentRollout strategy={strategy} />;
|
return <StrategyCardContentRollout strategy={strategy} />;
|
||||||
default:
|
default:
|
||||||
return <StrategyCardContentCustom strategy={strategy} strategyDefinition={strategyDefinition} />;
|
return (
|
||||||
|
<StrategyCardContentCustom
|
||||||
|
strategy={strategy}
|
||||||
|
strategyDefinition={strategyDefinition}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,14 +3,29 @@ import React from 'react';
|
|||||||
import { Typography } from '@material-ui/core';
|
import { Typography } from '@material-ui/core';
|
||||||
|
|
||||||
import { useCommonStyles } from '../../../../../../common.styles';
|
import { useCommonStyles } from '../../../../../../common.styles';
|
||||||
|
import ConditionallyRender from '../../../../../common/ConditionallyRender';
|
||||||
|
import StrategyCardConstraints from '../common/StrategyCardConstraints/StrategyCardConstraints';
|
||||||
|
|
||||||
const StrategyCardContentDefault = () => {
|
const StrategyCardContentDefault = ({ strategy }) => {
|
||||||
const commonStyles = useCommonStyles();
|
const commonStyles = useCommonStyles();
|
||||||
|
|
||||||
|
const { constraints } = strategy;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Typography className={commonStyles.textCenter}>
|
<>
|
||||||
The default strategy is either on or off for all users.
|
<Typography className={commonStyles.textCenter}>
|
||||||
</Typography>
|
The default strategy is either on or off for all users.
|
||||||
|
</Typography>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={constraints && constraints.length > 0}
|
||||||
|
show={
|
||||||
|
<>
|
||||||
|
<div className={commonStyles.divider} />
|
||||||
|
<StrategyCardConstraints constraints={constraints} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,8 +15,7 @@ import MenuIcon from '@material-ui/icons/Menu';
|
|||||||
import Breadcrumb from '../breadcrumb';
|
import Breadcrumb from '../breadcrumb';
|
||||||
import UserProfile from '../../user/UserProfile';
|
import UserProfile from '../../user/UserProfile';
|
||||||
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
|
||||||
import HelpIcon from '@material-ui/icons/Help';
|
import MenuBookIcon from '@material-ui/icons/MenuBook';
|
||||||
|
|
||||||
import { useStyles } from './styles';
|
import { useStyles } from './styles';
|
||||||
|
|
||||||
const Header = ({ uiConfig, init }) => {
|
const Header = ({ uiConfig, init }) => {
|
||||||
@ -64,7 +63,7 @@ const Header = ({ uiConfig, init }) => {
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className={styles.docsLink}
|
className={styles.docsLink}
|
||||||
>
|
>
|
||||||
<HelpIcon className={styles.docsIcon} />
|
<MenuBookIcon className={styles.docsIcon} />
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<UserProfile />
|
<UserProfile />
|
||||||
|
@ -3,5 +3,9 @@ import { makeStyles } from '@material-ui/styles';
|
|||||||
export const useStyles = makeStyles({
|
export const useStyles = makeStyles({
|
||||||
listItem: {
|
listItem: {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
['& a']: {
|
||||||
|
textDecoration: 'none',
|
||||||
|
color: 'inherit',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -40,7 +40,9 @@ class AddContextComponent extends Component {
|
|||||||
|
|
||||||
validateId = async id => {
|
validateId = async id => {
|
||||||
const { errors } = this.state;
|
const { errors } = this.state;
|
||||||
const { validateId } = this.props;
|
const { validateId, editMode } = this.props;
|
||||||
|
if (editMode) return true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await validateId(id);
|
await validateId(id);
|
||||||
errors.id = undefined;
|
errors.id = undefined;
|
||||||
@ -49,6 +51,26 @@ class AddContextComponent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ errors });
|
this.setState({ errors });
|
||||||
|
if (errors.id) return false;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
validateName = () => {
|
||||||
|
const { project } = this.state;
|
||||||
|
if (project.name.length === 0) {
|
||||||
|
this.setState(prev => ({
|
||||||
|
errors: { ...prev.errors, name: 'Name can not be empty.' },
|
||||||
|
}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
validate = async id => {
|
||||||
|
const validId = await this.validateId(id);
|
||||||
|
const validName = this.validateName();
|
||||||
|
|
||||||
|
return validId && validName;
|
||||||
};
|
};
|
||||||
|
|
||||||
onCancel = evt => {
|
onCancel = evt => {
|
||||||
@ -59,8 +81,13 @@ class AddContextComponent extends Component {
|
|||||||
onSubmit = async evt => {
|
onSubmit = async evt => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
const { project } = this.state;
|
const { project } = this.state;
|
||||||
await this.props.submit(project);
|
|
||||||
this.props.history.push('/projects');
|
const valid = await this.validate(project.id);
|
||||||
|
|
||||||
|
if (valid) {
|
||||||
|
await this.props.submit(project);
|
||||||
|
this.props.history.push('/projects');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -71,12 +98,19 @@ class AddContextComponent extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent headerContent={`${submitText} Project`}>
|
<PageContent headerContent={`${submitText} Project`}>
|
||||||
<Typography variant="subtitle1" style={{ marginBottom: '0.5rem' }}>
|
<Typography
|
||||||
Projects allows you to group feature toggles together in the management UI.
|
variant="subtitle1"
|
||||||
|
style={{ marginBottom: '0.5rem' }}
|
||||||
|
>
|
||||||
|
Projects allows you to group feature toggles together in the
|
||||||
|
management UI.
|
||||||
</Typography>
|
</Typography>
|
||||||
<form
|
<form
|
||||||
onSubmit={this.onSubmit}
|
onSubmit={this.onSubmit}
|
||||||
className={classnames(commonStyles.contentSpacing, styles.formContainer)}
|
className={classnames(
|
||||||
|
commonStyles.contentSpacing,
|
||||||
|
styles.formContainer
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
label="Project Id"
|
label="Project Id"
|
||||||
@ -89,7 +123,9 @@ class AddContextComponent extends Component {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
onBlur={v => this.validateId(v.target.value)}
|
onBlur={v => this.validateId(v.target.value)}
|
||||||
onChange={v => this.setValue('id', trim(v.target.value))}
|
onChange={v =>
|
||||||
|
this.setValue('id', trim(v.target.value))
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<TextField
|
<TextField
|
||||||
@ -114,14 +150,21 @@ class AddContextComponent extends Component {
|
|||||||
size="small"
|
size="small"
|
||||||
multiline
|
multiline
|
||||||
value={project.description}
|
value={project.description}
|
||||||
onChange={v => this.setValue('description', v.target.value)}
|
onChange={v =>
|
||||||
|
this.setValue('description', v.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={hasAccess(CREATE_PROJECT)}
|
||||||
|
show={
|
||||||
|
<div className={styles.formButtons}>
|
||||||
|
<FormButtons
|
||||||
|
submitText={submitText}
|
||||||
|
onCancel={this.onCancel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<ConditionallyRender condition={hasAccess(CREATE_PROJECT)} show={
|
|
||||||
<div className={styles.formButtons}>
|
|
||||||
<FormButtons submitText={submitText} onCancel={this.onCancel} />
|
|
||||||
</div>
|
|
||||||
} />
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
|
@ -22,24 +22,31 @@ const UserInviteLink = ({ inviteLink }: IInviteLinkProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleCopy = () => {
|
const handleCopy = () => {
|
||||||
return navigator.clipboard
|
try {
|
||||||
.writeText(inviteLink)
|
return navigator.clipboard
|
||||||
.then(() => {
|
.writeText(inviteLink)
|
||||||
setSnackbar({
|
.then(() => {
|
||||||
show: true,
|
setSnackbar({
|
||||||
type: 'success',
|
show: true,
|
||||||
text: 'Successfully copied invite link.',
|
type: 'success',
|
||||||
|
text: 'Successfully copied invite link.',
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setError();
|
||||||
});
|
});
|
||||||
})
|
} catch (e) {
|
||||||
.catch(() => {
|
setError();
|
||||||
setSnackbar({
|
}
|
||||||
show: true,
|
|
||||||
type: 'error',
|
|
||||||
text: 'Could not copy invite link.',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setError = () =>
|
||||||
|
setSnackbar({
|
||||||
|
show: true,
|
||||||
|
type: 'error',
|
||||||
|
text: 'Could not copy invite link.',
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
@ -39,7 +39,10 @@ export function createContextField(context) {
|
|||||||
api
|
api
|
||||||
.create(context)
|
.create(context)
|
||||||
.then(() => dispatch(addContextField(context)))
|
.then(() => dispatch(addContextField(context)))
|
||||||
.catch(dispatchError(dispatch, ERROR_ADD_CONTEXT_FIELD));
|
.catch(e => {
|
||||||
|
dispatchError(dispatch, ERROR_ADD_CONTEXT_FIELD);
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateContextField(context) {
|
export function updateContextField(context) {
|
||||||
|
Loading…
Reference in New Issue
Block a user