1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-14 00:19:16 +01:00

Merge branch 'master' into fix/envs-drag-permission

This commit is contained in:
Youssef Khedher 2021-10-21 23:44:20 +01:00 committed by GitHub
commit c3e063ed98
12 changed files with 250 additions and 194 deletions

View File

@ -1,7 +1,7 @@
{
"name": "unleash-frontend",
"description": "unleash your features",
"version": "4.2.8",
"version": "4.2.9",
"keywords": [
"unleash",
"feature toggle",
@ -53,7 +53,7 @@
"@welldone-software/why-did-you-render": "6.2.1",
"array-move": "3.0.1",
"classnames": "2.3.1",
"copy-to-clipboard": "^3.3.1",
"copy-to-clipboard": "3.3.1",
"craco": "0.0.3",
"css-loader": "6.4.0",
"cypress": "8.6.0",

View File

@ -5,6 +5,8 @@ import { styles as commonStyles } from '../../../component/common';
import { IApiTokenCreate } from '../../../hooks/api/actions/useApiTokensApi/useApiTokensApi';
import useEnvironments from '../../../hooks/api/getters/useEnvironments/useEnvironments';
import useProjects from '../../../hooks/api/getters/useProjects/useProjects';
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
import ConditionallyRender from '../../common/ConditionallyRender';
import Dialogue from '../../common/Dialogue';
import GeneralSelect from '../../common/GeneralSelect/GeneralSelect';
@ -41,6 +43,7 @@ const ApiTokenCreate = ({
const [error, setError] = useState<IDataError>({});
const { projects } = useProjects();
const { environments } = useEnvironments();
const { uiConfig } = useUiConfig();
useEffect(() => {
if (
@ -192,6 +195,8 @@ const ApiTokenCreate = ({
className={undefined}
classes={undefined}
/>
<ConditionallyRender condition={uiConfig?.flags.E} show={
<>
<GeneralSelect
disabled={data.type === TYPE_ADMIN}
options={selectableEnvs}
@ -204,6 +209,8 @@ const ApiTokenCreate = ({
className={undefined}
classes={undefined}
/>
</>
} />
</form>
</Dialogue>
);

View File

@ -0,0 +1,40 @@
import { Switch, Tooltip } from '@material-ui/core';
import { OverridableComponent } from '@material-ui/core/OverridableComponent';
import AccessContext from '../../../contexts/AccessContext';
import React, { useContext } from 'react';
interface IPermissionSwitchProps extends OverridableComponent<any> {
permission: string;
tooltip: string;
onChange?: (e: any) => void;
disabled?: boolean;
projectId?: string;
}
const PermissionSwitch: React.FC<IPermissionSwitchProps> = ({
permission,
tooltip = '',
disabled,
projectId,
onChange,
...rest
}) => {
const { hasAccess } = useContext(AccessContext);
const access = projectId
? hasAccess(permission, projectId)
: hasAccess(permission);
const tooltipText = access
? tooltip
: "You don't have access to perform this operation";
return (
<Tooltip title={tooltipText} arrow>
<span>
<Switch onChange={onChange} disabled={disabled || !access} {...rest} />
</span>
</Tooltip>
)
}
export default PermissionSwitch;

View File

@ -4,7 +4,7 @@ import HeaderTitle from '../../common/HeaderTitle';
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
import {
CREATE_CONTEXT_FIELD,
DELETE_CONTEXT_FIELD,
DELETE_CONTEXT_FIELD, UPDATE_CONTEXT_FIELD,
} from '../../providers/AccessProvider/permissions';
import {
IconButton,
@ -39,10 +39,10 @@ const ContextList = ({ removeContextField, history, contextFields }) => {
</ListItemIcon>
<ListItemText
primary={
<Link to={`/context/edit/${field.name}`}>
<ConditionallyRender condition={hasAccess(UPDATE_CONTEXT_FIELD)} show={<Link to={`/context/edit/${field.name}`}>
<strong>{field.name}</strong>
</Link>
}
} elseShow={<strong>{field.name}</strong>} />}
secondary={field.description}
/>
<ConditionallyRender

View File

@ -1,5 +1,5 @@
import { useContext, useRef } from 'react';
import { Switch, TableCell, TableRow } from '@material-ui/core';
import { useRef } from 'react';
import { TableCell, TableRow } from '@material-ui/core';
import { useHistory } from 'react-router';
import { useStyles } from '../FeatureToggleListNew.styles';
@ -15,7 +15,7 @@ import classNames from 'classnames';
import CreatedAt from './CreatedAt';
import useProject from '../../../../hooks/api/getters/useProject/useProject';
import { UPDATE_FEATURE } from '../../../providers/AccessProvider/permissions';
import AccessContext from '../../../../contexts/AccessContext';
import PermissionSwitch from '../../../common/PermissionSwitch/PermissionSwitch';
interface IFeatureToggleListNewItemProps {
name: string;
@ -34,7 +34,6 @@ const FeatureToggleListNewItem = ({
projectId,
createdAt,
}: IFeatureToggleListNewItemProps) => {
const { hasAccess } = useContext(AccessContext);
const { toast, setToastData } = useToast();
const { toggleFeatureByEnvironment } = useToggleFeatureByEnv(
projectId,
@ -127,11 +126,10 @@ const FeatureToggleListNewItem = ({
key={env.name}
>
<span data-loading style={{ display: 'block' }}>
<Switch
<PermissionSwitch
checked={env.enabled}
disabled={
!hasAccess(UPDATE_FEATURE, projectId)
}
projectId={projectId}
permission={UPDATE_FEATURE}
ref={ref}
onClick={handleToggle.bind(this, env)}
/>

View File

@ -9,6 +9,8 @@ import { useStyles } from './FeatureOverviewMetadata.styles';
import { Edit } from '@material-ui/icons';
import { IFeatureViewParams } from '../../../../../interfaces/params';
import PermissionIconButton from '../../../../common/PermissionIconButton/PermissionIconButton';
import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions';
const FeatureOverviewMetaData = () => {
const styles = useStyles();
@ -39,12 +41,14 @@ const FeatureOverviewMetaData = () => {
<div>Description:</div>
<div className={styles.descriptionContainer}>
<p>{description}</p>
<IconButton
<PermissionIconButton
projectId={projectId}
permission={UPDATE_FEATURE}
component={Link}
to={`/projects/${projectId}/features2/${featureId}/settings`}
>
<Edit />
</IconButton>
</PermissionIconButton>
</div>
</span>
}

View File

@ -4,7 +4,6 @@ import {
FormControl,
FormControlLabel,
Grid,
Switch,
TextField,
InputAdornment,
Button,
@ -20,6 +19,8 @@ import GeneralSelect from '../../../../../common/GeneralSelect/GeneralSelect';
import { useCommonStyles } from '../../../../../../common.styles';
import Dialogue from '../../../../../common/Dialogue';
import { trim, modalStyles } from '../../../../../common/util';
import PermissionSwitch from '../../../../../common/PermissionSwitch/PermissionSwitch';
import { UPDATE_FEATURE } from '../../../../../providers/AccessProvider/permissions';
const payloadOptions = [
{ key: 'string', label: 'string' },
@ -242,7 +243,8 @@ const AddVariant = ({
<FormControl>
<FormControlLabel
control={
<Switch
<PermissionSwitch
permission={UPDATE_FEATURE}
name="weightType"
checked={isFixWeight}
data-test={'VARIANT_WEIGHT_TYPE'}

View File

@ -1,17 +1,18 @@
import React from 'react';
import React, { FC } from 'react';
import { Cloud } from '@material-ui/icons';
import { useParams, Link } from 'react-router-dom';
import { Switch, Tooltip } from '@material-ui/core';
import { Link, useParams } from 'react-router-dom';
import { Tooltip } from '@material-ui/core';
import classNames from 'classnames';
import ConditionallyRender from '../../../common/ConditionallyRender';
import useFeatureApi from '../../../../hooks/api/actions/useFeatureApi/useFeatureApi';
import useToast from '../../../../hooks/useToast';
import { FC } from 'react';
import { IFeatureEnvironment } from '../../../../interfaces/featureToggle';
import { IFeatureViewParams } from '../../../../interfaces/params';
import { useStyles } from './FeatureViewEnvironment.styles';
import useFeature from '../../../../hooks/api/getters/useFeature/useFeature';
import PermissionSwitch from '../../../common/PermissionSwitch/PermissionSwitch';
import { UPDATE_FEATURE } from '../../../providers/AccessProvider/permissions';
interface IFeatureViewEnvironmentProps {
env: IFeatureEnvironment;
@ -115,7 +116,9 @@ const FeatureViewEnvironment: FC<IFeatureViewEnvironmentProps> = ({
condition={env?.strategies?.length > 0}
show={
<div className={styles.textContainer}>
<Switch
<PermissionSwitch
projectId={projectId}
permission={UPDATE_FEATURE}
value={env.enabled}
checked={env.enabled}
onChange={toggleEnvironment}

View File

@ -1,10 +1,9 @@
import { useContext, useState } from 'react';
import { useState } from 'react';
import ConditionallyRender from '../../common/ConditionallyRender';
import { useStyles } from './ProjectEnvironment.styles';
import useLoading from '../../../hooks/useLoading';
import PageContent from '../../common/PageContent';
import AccessContext from '../../../contexts/AccessContext';
import HeaderTitle from '../../common/HeaderTitle';
import { UPDATE_PROJECT } from '../../providers/AccessProvider/permissions';
@ -13,11 +12,12 @@ import useToast from '../../../hooks/useToast';
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
import useEnvironments from '../../../hooks/api/getters/useEnvironments/useEnvironments';
import useProject from '../../../hooks/api/getters/useProject/useProject';
import { FormControlLabel, FormGroup, Switch } from '@material-ui/core';
import { FormControlLabel, FormGroup } from '@material-ui/core';
import useProjectApi from '../../../hooks/api/actions/useProjectApi/useProjectApi';
import EnvironmentDisableConfirm from './EnvironmentDisableConfirm/EnvironmentDisableConfirm';
import { Link } from 'react-router-dom';
import { Alert } from '@material-ui/lab';
import PermissionSwitch from '../../common/PermissionSwitch/PermissionSwitch';
export interface ProjectEnvironment {
name: string;
@ -29,8 +29,6 @@ interface ProjectEnvironmentListProps {
}
const ProjectEnvironmentList = ({ projectId }: ProjectEnvironmentListProps) => {
const { hasAccess } = useContext(AccessContext);
// api state
const { toast, setToastData } = useToast();
const { uiConfig } = useUiConfig();
@ -126,8 +124,6 @@ const ProjectEnvironmentList = ({ projectId }: ProjectEnvironmentListProps) => {
enabled: project?.environments.includes(e.name),
}));
const hasPermission = hasAccess(UPDATE_PROJECT, projectId);
const genLabel = (env: ProjectEnvironment) => (
<>
<code>{env.name}</code> environment is{' '}
@ -143,9 +139,11 @@ const ProjectEnvironmentList = ({ projectId }: ProjectEnvironmentListProps) => {
key={env.name}
label={genLabel(env)}
control={
<Switch
<PermissionSwitch
tooltip={`${env.enabled ? 'Disable' : 'Enable'} environment`}
size="medium"
disabled={!hasPermission}
projectId={projectId}
permission={UPDATE_PROJECT}
checked={env.enabled}
onChange={toggleEnv.bind(this, env)}
/>

View File

@ -4,27 +4,10 @@ import classnames from 'classnames';
import { Link, useHistory } from 'react-router-dom';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import {
List,
ListItem,
ListItemAvatar,
IconButton,
ListItemText,
Button,
Tooltip,
} from '@material-ui/core';
import {
Add,
Visibility,
VisibilityOff,
Delete,
Extension,
} from '@material-ui/icons';
import { IconButton, List, ListItem, ListItemAvatar, ListItemText, Tooltip } from '@material-ui/core';
import { Add, Delete, Extension, Visibility, VisibilityOff } from '@material-ui/icons';
import {
CREATE_STRATEGY,
DELETE_STRATEGY,
} from '../../providers/AccessProvider/permissions';
import { CREATE_STRATEGY, DELETE_STRATEGY, UPDATE_STRATEGY } from '../../providers/AccessProvider/permissions';
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
import PageContent from '../../common/PageContent/PageContent';
@ -34,6 +17,8 @@ import { useStyles } from './styles';
import AccessContext from '../../../contexts/AccessContext';
import Dialogue from '../../common/Dialogue';
import { ADD_NEW_STRATEGY_ID } from '../../../testIds';
import PermissionIconButton from '../../common/PermissionIconButton/PermissionIconButton';
import PermissionButton from '../../common/PermissionButton/PermissionButton';
const StrategiesList = ({
strategies,
@ -60,26 +45,29 @@ const StrategiesList = ({
<ConditionallyRender
condition={smallScreen}
show={
<Tooltip title="Add new strategy">
<IconButton
<PermissionIconButton
data-test={ADD_NEW_STRATEGY_ID}
onClick={() =>
history.push('/strategies/create')
}
permission={CREATE_STRATEGY}
tooltip={'Add new strategy'}
>
<Add />
</IconButton>
</Tooltip>
</PermissionIconButton>
}
elseShow={
<Button
<PermissionButton
onClick={() => history.push('/strategies/create')}
color="primary"
permission={CREATE_STRATEGY}
variant="contained"
data-test={ADD_NEW_STRATEGY_ID}
tooltip={'Add new strategy'}
>
Add new strategy
</Button>
</PermissionButton>
}
/>
}
@ -98,7 +86,7 @@ const StrategiesList = ({
const reactivateButton = strategy => (
<Tooltip title="Reactivate activation strategy">
<IconButton
<PermissionIconButton
onClick={() =>
setDialogueMetaData({
show: true,
@ -106,9 +94,9 @@ const StrategiesList = ({
onConfirm: () => reactivateStrategy(strategy),
})
}
>
<Visibility />
</IconButton>
permission={UPDATE_STRATEGY}
tooltip={'Reactivate activation strategy'}
><VisibilityOff /></PermissionIconButton>
</Tooltip>
);
@ -119,15 +107,14 @@ const StrategiesList = ({
<Tooltip title="You cannot deprecate the default strategy">
<div>
<IconButton disabled>
<VisibilityOff />
<Visibility />
</IconButton>
</div>
</Tooltip>
}
elseShow={
<Tooltip title="Deprecate activation strategy">
<div>
<IconButton
<PermissionIconButton
onClick={() =>
setDialogueMetaData({
show: true,
@ -136,11 +123,12 @@ const StrategiesList = ({
deprecateStrategy(strategy),
})
}
permission={UPDATE_STRATEGY}
tooltip={'Deprecate activation strategy'}
>
<VisibilityOff />
</IconButton>
<Visibility />
</PermissionIconButton>
</div>
</Tooltip>
}
/>
);
@ -149,8 +137,7 @@ const StrategiesList = ({
<ConditionallyRender
condition={strategy.editable}
show={
<Tooltip title="Delete strategy">
<IconButton
<PermissionIconButton
onClick={() =>
setDialogueMetaData({
show: true,
@ -158,10 +145,11 @@ const StrategiesList = ({
onConfirm: () => removeStrategy(strategy),
})
}
permission={DELETE_STRATEGY}
tooltip={'Delete strategy'}
>
<Delete />
</IconButton>
</Tooltip>
</PermissionIconButton>
}
elseShow={
<Tooltip title="You cannot delete a built-in strategy">

View File

@ -81,7 +81,8 @@ exports[`renders correctly with one strategy 1`] = `
another's description
</p>
</div>
<div
<div>
<span
aria-describedby={null}
className=""
onBlur={[Function]}
@ -90,11 +91,11 @@ exports[`renders correctly with one strategy 1`] = `
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title="Deprecate activation strategy"
title="You don't have access to perform this operation"
>
<button
className="MuiButtonBase-root MuiIconButton-root"
disabled={false}
className="MuiButtonBase-root MuiIconButton-root Mui-disabled Mui-disabled"
disabled={true}
onBlur={[Function]}
onClick={[Function]}
onDragLeave={[Function]}
@ -107,7 +108,7 @@ exports[`renders correctly with one strategy 1`] = `
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={0}
tabIndex={-1}
type="button"
>
<span
@ -120,14 +121,12 @@ exports[`renders correctly with one strategy 1`] = `
viewBox="0 0 24 24"
>
<path
d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"
d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"
/>
</svg>
</span>
<span
className="MuiTouchRipple-root"
/>
</button>
</span>
</div>
</li>
</ul>
@ -163,6 +162,17 @@ exports[`renders correctly with one strategy without permissions 1`] = `
</div>
<div
className="makeStyles-headerActions-9"
>
<span
aria-describedby={null}
className=""
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title="Add new strategy"
>
<button
className="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary"
@ -187,11 +197,15 @@ exports[`renders correctly with one strategy without permissions 1`] = `
className="MuiButton-label"
>
Add new strategy
<span
className="MuiButton-endIcon MuiButton-iconSizeMedium"
/>
</span>
<span
className="MuiTouchRipple-root"
/>
</button>
</span>
</div>
</div>
</div>
@ -245,7 +259,8 @@ exports[`renders correctly with one strategy without permissions 1`] = `
another's description
</p>
</div>
<div
<div>
<span
aria-describedby={null}
className=""
onBlur={[Function]}
@ -284,7 +299,7 @@ exports[`renders correctly with one strategy without permissions 1`] = `
viewBox="0 0 24 24"
>
<path
d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"
d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"
/>
</svg>
</span>
@ -292,6 +307,7 @@ exports[`renders correctly with one strategy without permissions 1`] = `
className="MuiTouchRipple-root"
/>
</button>
</span>
</div>
<div
aria-describedby={null}

View File

@ -4144,7 +4144,7 @@ copy-descriptor@^0.1.0:
resolved "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
copy-to-clipboard@^3.3.1:
copy-to-clipboard@3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae"
integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==