mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Fix/environment list sorting (#447)
* fix: wait for api call before refetching * fix: set active environment from feature instead of cache * fix: remove console logs * fix: add permission icon button to project card * fix: remove project button * fix: empty tooltip if it is not passed * fix: add refresh interval * fix: permission buttons * fix: project permission buttons * fix: remove unused imports * fix: add projectId
This commit is contained in:
		
							parent
							
								
									f61a949df2
								
							
						
					
					
						commit
						57928d50c6
					
				@ -21,6 +21,7 @@ export interface ISelectMenuProps {
 | 
			
		||||
    disabled?: boolean;
 | 
			
		||||
    className?: string;
 | 
			
		||||
    classes?: any;
 | 
			
		||||
    defaultValue?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const GeneralSelect: React.FC<ISelectMenuProps> = ({
 | 
			
		||||
@ -29,6 +30,7 @@ const GeneralSelect: React.FC<ISelectMenuProps> = ({
 | 
			
		||||
    label = '',
 | 
			
		||||
    options,
 | 
			
		||||
    onChange,
 | 
			
		||||
    defaultValue,
 | 
			
		||||
    id,
 | 
			
		||||
    disabled = false,
 | 
			
		||||
    className,
 | 
			
		||||
@ -53,6 +55,7 @@ const GeneralSelect: React.FC<ISelectMenuProps> = ({
 | 
			
		||||
                {label}
 | 
			
		||||
            </InputLabel>
 | 
			
		||||
            <Select
 | 
			
		||||
                defaultValue={defaultValue}
 | 
			
		||||
                name={name}
 | 
			
		||||
                disabled={disabled}
 | 
			
		||||
                onChange={onChange}
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ interface IPermissionIconButtonProps extends OverridableComponent<any> {
 | 
			
		||||
    tooltip: string;
 | 
			
		||||
    onClick?: (e: any) => void;
 | 
			
		||||
    disabled?: boolean;
 | 
			
		||||
    projectId?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const PermissionButton: React.FC<IPermissionIconButtonProps> = ({
 | 
			
		||||
@ -18,11 +19,15 @@ const PermissionButton: React.FC<IPermissionIconButtonProps> = ({
 | 
			
		||||
    onClick,
 | 
			
		||||
    children,
 | 
			
		||||
    disabled,
 | 
			
		||||
    projectId,
 | 
			
		||||
    ...rest
 | 
			
		||||
}) => {
 | 
			
		||||
    const { hasAccess } = useContext(AccessContext);
 | 
			
		||||
 | 
			
		||||
    const access = hasAccess(permission);
 | 
			
		||||
    const access = projectId
 | 
			
		||||
        ? hasAccess(permission, projectId)
 | 
			
		||||
        : hasAccess(permission);
 | 
			
		||||
 | 
			
		||||
    const tooltipText = access
 | 
			
		||||
        ? tooltip
 | 
			
		||||
        : "You don't have access to perform this operation";
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ interface IPermissionIconButtonProps extends OverridableComponent<any> {
 | 
			
		||||
    Icon: React.ElementType;
 | 
			
		||||
    tooltip: string;
 | 
			
		||||
    onClick?: (e: any) => void;
 | 
			
		||||
    projectId?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const PermissionIconButton: React.FC<IPermissionIconButtonProps> = ({
 | 
			
		||||
@ -15,14 +16,18 @@ const PermissionIconButton: React.FC<IPermissionIconButtonProps> = ({
 | 
			
		||||
    Icon,
 | 
			
		||||
    tooltip,
 | 
			
		||||
    onClick,
 | 
			
		||||
    projectId,
 | 
			
		||||
    children,
 | 
			
		||||
    ...rest
 | 
			
		||||
}) => {
 | 
			
		||||
    const { hasAccess } = useContext(AccessContext);
 | 
			
		||||
 | 
			
		||||
    const access = hasAccess(permission);
 | 
			
		||||
    const access = projectId
 | 
			
		||||
        ? hasAccess(permission, projectId)
 | 
			
		||||
        : hasAccess(permission);
 | 
			
		||||
 | 
			
		||||
    const tooltipText = access
 | 
			
		||||
        ? tooltip
 | 
			
		||||
        ? tooltip || ''
 | 
			
		||||
        : "You don't have access to perform this operation";
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ interface IResponsiveButtonProps {
 | 
			
		||||
    tooltip?: string;
 | 
			
		||||
    disabled?: boolean;
 | 
			
		||||
    permission?: string;
 | 
			
		||||
    projectId?: string;
 | 
			
		||||
    maxWidth: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -20,6 +21,7 @@ const ResponsiveButton: React.FC<IResponsiveButtonProps> = ({
 | 
			
		||||
    disabled = false,
 | 
			
		||||
    children,
 | 
			
		||||
    permission,
 | 
			
		||||
    projectId,
 | 
			
		||||
    ...rest
 | 
			
		||||
}) => {
 | 
			
		||||
    const smallScreen = useMediaQuery(`(max-width:${maxWidth})`);
 | 
			
		||||
@ -32,6 +34,7 @@ const ResponsiveButton: React.FC<IResponsiveButtonProps> = ({
 | 
			
		||||
                    disabled={disabled}
 | 
			
		||||
                    onClick={onClick}
 | 
			
		||||
                    permission={permission}
 | 
			
		||||
                    projectId={projectId}
 | 
			
		||||
                    data-loading
 | 
			
		||||
                    {...rest}
 | 
			
		||||
                >
 | 
			
		||||
@ -42,6 +45,7 @@ const ResponsiveButton: React.FC<IResponsiveButtonProps> = ({
 | 
			
		||||
                <PermissionButton
 | 
			
		||||
                    onClick={onClick}
 | 
			
		||||
                    permission={permission}
 | 
			
		||||
                    projectId={projectId}
 | 
			
		||||
                    color="primary"
 | 
			
		||||
                    variant="contained"
 | 
			
		||||
                    disabled={disabled}
 | 
			
		||||
 | 
			
		||||
@ -70,6 +70,7 @@ const EnvironmentList = () => {
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await sortOrderAPICall(sortOrder);
 | 
			
		||||
            refetch();
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            setToastData({
 | 
			
		||||
                show: true,
 | 
			
		||||
@ -77,13 +78,11 @@ const EnvironmentList = () => {
 | 
			
		||||
                text: e.toString(),
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        mutate(ENVIRONMENT_CACHE_KEY);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const sortOrderAPICall = async (sortOrder: ISortOrderPayload) => {
 | 
			
		||||
        try {
 | 
			
		||||
            changeSortOrder(sortOrder);
 | 
			
		||||
            await changeSortOrder(sortOrder);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            setToastData({
 | 
			
		||||
                show: true,
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,5 @@
 | 
			
		||||
import { useRef } from 'react';
 | 
			
		||||
import {
 | 
			
		||||
    Switch,
 | 
			
		||||
    TableCell,
 | 
			
		||||
    TableRow,
 | 
			
		||||
} from '@material-ui/core';
 | 
			
		||||
import { useContext, useRef } from 'react';
 | 
			
		||||
import { Switch, TableCell, TableRow } from '@material-ui/core';
 | 
			
		||||
import { useHistory } from 'react-router';
 | 
			
		||||
 | 
			
		||||
import { useStyles } from '../FeatureToggleListNew.styles';
 | 
			
		||||
@ -17,6 +13,9 @@ import FeatureStatus from '../../FeatureView2/FeatureStatus/FeatureStatus';
 | 
			
		||||
import FeatureType from '../../FeatureView2/FeatureType/FeatureType';
 | 
			
		||||
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';
 | 
			
		||||
 | 
			
		||||
interface IFeatureToggleListNewItemProps {
 | 
			
		||||
    name: string;
 | 
			
		||||
@ -35,14 +34,15 @@ const FeatureToggleListNewItem = ({
 | 
			
		||||
    projectId,
 | 
			
		||||
    createdAt,
 | 
			
		||||
}: IFeatureToggleListNewItemProps) => {
 | 
			
		||||
    const { hasAccess } = useContext(AccessContext);
 | 
			
		||||
    const { toast, setToastData } = useToast();
 | 
			
		||||
    const { toggleFeatureByEnvironment } = useToggleFeatureByEnv(
 | 
			
		||||
        projectId,
 | 
			
		||||
        name,
 | 
			
		||||
        name
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const { uiConfig } = useUiConfig();
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    const { refetch } = useProject(projectId);
 | 
			
		||||
    const styles = useStyles();
 | 
			
		||||
    const history = useHistory();
 | 
			
		||||
    const ref = useRef(null);
 | 
			
		||||
@ -61,6 +61,7 @@ const FeatureToggleListNewItem = ({
 | 
			
		||||
                    type: 'success',
 | 
			
		||||
                    text: 'Successfully updated toggle status.',
 | 
			
		||||
                });
 | 
			
		||||
                refetch();
 | 
			
		||||
            })
 | 
			
		||||
            .catch(e => {
 | 
			
		||||
                setToastData({
 | 
			
		||||
@ -71,43 +72,66 @@ const FeatureToggleListNewItem = ({
 | 
			
		||||
            });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <TableRow className={styles.tableRow}>
 | 
			
		||||
                <TableCell className={classNames(
 | 
			
		||||
                <TableCell
 | 
			
		||||
                    className={classNames(
 | 
			
		||||
                        styles.tableCell,
 | 
			
		||||
                                styles.tableCellStatus)} align="left" onClick={onClick}>
 | 
			
		||||
                        styles.tableCellStatus
 | 
			
		||||
                    )}
 | 
			
		||||
                    align="left"
 | 
			
		||||
                    onClick={onClick}
 | 
			
		||||
                >
 | 
			
		||||
                    <FeatureStatus lastSeenAt={lastSeenAt} />
 | 
			
		||||
                </TableCell>
 | 
			
		||||
                <TableCell className={classNames(
 | 
			
		||||
                <TableCell
 | 
			
		||||
                    className={classNames(
 | 
			
		||||
                        styles.tableCell,
 | 
			
		||||
                                styles.tableCellType)} align="center" onClick={onClick}>
 | 
			
		||||
                        styles.tableCellType
 | 
			
		||||
                    )}
 | 
			
		||||
                    align="center"
 | 
			
		||||
                    onClick={onClick}
 | 
			
		||||
                >
 | 
			
		||||
                    <FeatureType type={type} />
 | 
			
		||||
                </TableCell>
 | 
			
		||||
                <TableCell className={classNames(
 | 
			
		||||
                                styles.tableCell, styles.tableCellName)} align="left" onClick={onClick}>
 | 
			
		||||
                <TableCell
 | 
			
		||||
                    className={classNames(
 | 
			
		||||
                        styles.tableCell,
 | 
			
		||||
                        styles.tableCellName
 | 
			
		||||
                    )}
 | 
			
		||||
                    align="left"
 | 
			
		||||
                    onClick={onClick}
 | 
			
		||||
                >
 | 
			
		||||
                    <span data-loading>{name}</span>
 | 
			
		||||
                </TableCell>
 | 
			
		||||
                <TableCell className={classNames(
 | 
			
		||||
                                styles.tableCell, styles.tableCellCreated)} align="left" onClick={onClick}>
 | 
			
		||||
                <TableCell
 | 
			
		||||
                    className={classNames(
 | 
			
		||||
                        styles.tableCell,
 | 
			
		||||
                        styles.tableCellCreated
 | 
			
		||||
                    )}
 | 
			
		||||
                    align="left"
 | 
			
		||||
                    onClick={onClick}
 | 
			
		||||
                >
 | 
			
		||||
                    <CreatedAt time={createdAt} />
 | 
			
		||||
                </TableCell>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                {environments.map((env: IEnvironments) => {
 | 
			
		||||
                    return (
 | 
			
		||||
                        <TableCell
 | 
			
		||||
                            className={classNames(
 | 
			
		||||
                                styles.tableCell,
 | 
			
		||||
                                styles.tableCellEnv)}
 | 
			
		||||
                                styles.tableCellEnv
 | 
			
		||||
                            )}
 | 
			
		||||
                            align="center"
 | 
			
		||||
                            key={env.name}
 | 
			
		||||
                        >
 | 
			
		||||
                            <span data-loading style={{ display: 'block' }}>
 | 
			
		||||
                                <Switch
 | 
			
		||||
                                    checked={env.enabled}
 | 
			
		||||
                                    disabled={
 | 
			
		||||
                                        !hasAccess(UPDATE_FEATURE, projectId)
 | 
			
		||||
                                    }
 | 
			
		||||
                                    ref={ref}
 | 
			
		||||
                                    onClick={handleToggle.bind(this, env)}
 | 
			
		||||
                                />
 | 
			
		||||
 | 
			
		||||
@ -40,6 +40,7 @@ const FeatureOverviewStale = () => {
 | 
			
		||||
                    <PermissionButton
 | 
			
		||||
                        onClick={() => setOpenStaleDialog(true)}
 | 
			
		||||
                        permission={UPDATE_FEATURE}
 | 
			
		||||
                        projectId={projectId}
 | 
			
		||||
                        tooltip="Flip status"
 | 
			
		||||
                        variant="text"
 | 
			
		||||
                    >
 | 
			
		||||
 | 
			
		||||
@ -37,6 +37,7 @@ const FeatureOverviewStrategies = () => {
 | 
			
		||||
                            tooltip="Add new strategy"
 | 
			
		||||
                            className={styles.addStrategyButton}
 | 
			
		||||
                            component={Link}
 | 
			
		||||
                            projectId={projectId}
 | 
			
		||||
                            to={`/projects/${projectId}/features2/${featureId}/strategies?addStrategy=true`}
 | 
			
		||||
                        >
 | 
			
		||||
                            Add new strategy
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ const FeatureOverviewTags = () => {
 | 
			
		||||
        type: '',
 | 
			
		||||
    });
 | 
			
		||||
    const styles = useStyles();
 | 
			
		||||
    const { featureId } = useParams<IFeatureViewParams>();
 | 
			
		||||
    const { featureId, projectId } = useParams<IFeatureViewParams>();
 | 
			
		||||
    const { tags, refetch } = useTags(featureId);
 | 
			
		||||
    const { tagTypes } = useTagTypes();
 | 
			
		||||
    const { deleteTagFromFeature } = useFeatureApi();
 | 
			
		||||
@ -131,6 +131,7 @@ const FeatureOverviewTags = () => {
 | 
			
		||||
                <PermissionIconButton
 | 
			
		||||
                    onClick={() => setOpenTagDialog(true)}
 | 
			
		||||
                    permission={UPDATE_FEATURE}
 | 
			
		||||
                    projectId={projectId}
 | 
			
		||||
                    tooltip="Add tag"
 | 
			
		||||
                    data-loading
 | 
			
		||||
                >
 | 
			
		||||
 | 
			
		||||
@ -89,6 +89,7 @@ const FeatureSettingsMetadata = () => {
 | 
			
		||||
                        tooltip="Save changes"
 | 
			
		||||
                        permission={UPDATE_FEATURE}
 | 
			
		||||
                        onClick={handleSubmit}
 | 
			
		||||
                        projectId={projectId}
 | 
			
		||||
                    >
 | 
			
		||||
                        Save changes
 | 
			
		||||
                    </PermissionButton>
 | 
			
		||||
 | 
			
		||||
@ -78,6 +78,7 @@ const FeatureSettingsProject = () => {
 | 
			
		||||
                        permission={UPDATE_FEATURE}
 | 
			
		||||
                        tooltip="Update feature"
 | 
			
		||||
                        onClick={() => setShowConfirmDialog(true)}
 | 
			
		||||
                        projectId={projectId}
 | 
			
		||||
                    >
 | 
			
		||||
                        Save changes
 | 
			
		||||
                    </PermissionButton>
 | 
			
		||||
 | 
			
		||||
@ -132,6 +132,7 @@ const FeatureStrategiesEnvironmentList = ({
 | 
			
		||||
    const strategiesContainerClasses = classnames({
 | 
			
		||||
        [styles.strategiesContainer]: !expandedSidebar,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <ConditionallyRender
 | 
			
		||||
            condition={!configureNewStrategy}
 | 
			
		||||
 | 
			
		||||
@ -377,6 +377,7 @@ const FeatureStrategiesEnvironments = () => {
 | 
			
		||||
                                        }
 | 
			
		||||
                                        Icon={Add}
 | 
			
		||||
                                        maxWidth="700px"
 | 
			
		||||
                                        projectId={projectId}
 | 
			
		||||
                                        permission={UPDATE_FEATURE}
 | 
			
		||||
                                    >
 | 
			
		||||
                                        Add new strategy
 | 
			
		||||
@ -390,7 +391,7 @@ const FeatureStrategiesEnvironments = () => {
 | 
			
		||||
                                onChange={(_, tabId) => {
 | 
			
		||||
                                    setActiveTab(tabId);
 | 
			
		||||
                                    setActiveEnvironment(
 | 
			
		||||
                                        featureCache?.environments[tabId]
 | 
			
		||||
                                        feature?.environments[tabId]
 | 
			
		||||
                                    );
 | 
			
		||||
                                    history.replace(history.location.pathname);
 | 
			
		||||
                                }}
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ import * as jsonpatch from 'fast-json-patch';
 | 
			
		||||
 | 
			
		||||
import styles from './variants.module.scss';
 | 
			
		||||
import {
 | 
			
		||||
    Button,
 | 
			
		||||
    Table,
 | 
			
		||||
    TableBody,
 | 
			
		||||
    TableCell,
 | 
			
		||||
@ -29,6 +28,7 @@ import useToast from '../../../../../hooks/useToast';
 | 
			
		||||
import { updateWeight } from '../../../../common/util';
 | 
			
		||||
import cloneDeep from 'lodash.clonedeep';
 | 
			
		||||
import useDeleteVariantMarkup from './FeatureVariantsListItem/useDeleteVariantMarkup';
 | 
			
		||||
import PermissionButton from '../../../../common/PermissionButton/PermissionButton';
 | 
			
		||||
 | 
			
		||||
const FeatureOverviewVariants = () => {
 | 
			
		||||
    const { hasAccess } = useContext(AccessContext);
 | 
			
		||||
@ -275,28 +275,24 @@ const FeatureOverviewVariants = () => {
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            <br />
 | 
			
		||||
            <ConditionallyRender
 | 
			
		||||
                condition={editable}
 | 
			
		||||
                show={
 | 
			
		||||
 | 
			
		||||
            <div>
 | 
			
		||||
                        <Button
 | 
			
		||||
                            title="Add variant"
 | 
			
		||||
                <PermissionButton
 | 
			
		||||
                    onClick={() => {
 | 
			
		||||
                        setEditing(false);
 | 
			
		||||
                        setEditVariant({});
 | 
			
		||||
                        setShowAddVariant(true);
 | 
			
		||||
                    }}
 | 
			
		||||
                            variant="contained"
 | 
			
		||||
                            color="primary"
 | 
			
		||||
                    className={styles.addVariantButton}
 | 
			
		||||
                    data-test={'ADD_VARIANT_BUTTON'}
 | 
			
		||||
                    permission={UPDATE_FEATURE}
 | 
			
		||||
                    projectId={projectId}
 | 
			
		||||
                >
 | 
			
		||||
                    Add variant
 | 
			
		||||
                        </Button>
 | 
			
		||||
                </PermissionButton>
 | 
			
		||||
                {renderStickiness()}
 | 
			
		||||
            </div>
 | 
			
		||||
                }
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            <AddVariant
 | 
			
		||||
                showDialog={showAddVariant}
 | 
			
		||||
                closeDialog={handleCloseAddVariant}
 | 
			
		||||
@ -320,187 +316,3 @@ const FeatureOverviewVariants = () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default FeatureOverviewVariants;
 | 
			
		||||
 | 
			
		||||
// class UpdateVariantComponent extends Component {
 | 
			
		||||
//     constructor(props) {
 | 
			
		||||
//         super(props);
 | 
			
		||||
//         this.state = { ...initialState };
 | 
			
		||||
//     }
 | 
			
		||||
 | 
			
		||||
//     closeDialog = () => {
 | 
			
		||||
//         this.setState({ ...initialState });
 | 
			
		||||
//     };
 | 
			
		||||
 | 
			
		||||
//     openAddVariant = e => {
 | 
			
		||||
//         e.preventDefault();
 | 
			
		||||
//         this.setState({
 | 
			
		||||
//             showDialog: true,
 | 
			
		||||
//             editVariant: undefined,
 | 
			
		||||
//             editIndex: undefined,
 | 
			
		||||
//             title: 'Add variant',
 | 
			
		||||
//         });
 | 
			
		||||
//     };
 | 
			
		||||
 | 
			
		||||
//     openEditVariant = (e, index, variant) => {
 | 
			
		||||
//         e.preventDefault();
 | 
			
		||||
//         if (this.props.editable) {
 | 
			
		||||
//             this.setState({
 | 
			
		||||
//                 showDialog: true,
 | 
			
		||||
//                 editVariant: variant,
 | 
			
		||||
//                 editIndex: index,
 | 
			
		||||
//                 title: 'Edit variant',
 | 
			
		||||
//             });
 | 
			
		||||
//         }
 | 
			
		||||
//     };
 | 
			
		||||
 | 
			
		||||
//     validateName = name => {
 | 
			
		||||
//         if (!name) {
 | 
			
		||||
//             return { name: 'Name is required' };
 | 
			
		||||
//         }
 | 
			
		||||
//     };
 | 
			
		||||
 | 
			
		||||
//     onRemoveVariant = (e, index) => {
 | 
			
		||||
//         e.preventDefault();
 | 
			
		||||
//         try {
 | 
			
		||||
//             this.props.removeVariant(index);
 | 
			
		||||
//         } catch (e) {
 | 
			
		||||
//             console.log('An exception was caught.');
 | 
			
		||||
//         }
 | 
			
		||||
//     };
 | 
			
		||||
 | 
			
		||||
//     renderVariant = (variant, index) => (
 | 
			
		||||
//         <VariantViewComponent
 | 
			
		||||
//             key={variant.name}
 | 
			
		||||
//             variant={variant}
 | 
			
		||||
//             editVariant={e => this.openEditVariant(e, index, variant)}
 | 
			
		||||
//             removeVariant={e => this.onRemoveVariant(e, index)}
 | 
			
		||||
//             editable={this.props.editable}
 | 
			
		||||
//         />
 | 
			
		||||
//     );
 | 
			
		||||
 | 
			
		||||
//     renderVariants = variants => (
 | 
			
		||||
//         <Table className={styles.variantTable}>
 | 
			
		||||
//             <TableHead>
 | 
			
		||||
//                 <TableRow>
 | 
			
		||||
//                     <TableCell>Variant name</TableCell>
 | 
			
		||||
//                     <TableCell className={styles.labels} />
 | 
			
		||||
//                     <TableCell>Weight</TableCell>
 | 
			
		||||
//                     <TableCell>Weight Type</TableCell>
 | 
			
		||||
//                     <TableCell className={styles.actions} />
 | 
			
		||||
//                 </TableRow>
 | 
			
		||||
//             </TableHead>
 | 
			
		||||
//             <TableBody>{variants.map(this.renderVariant)}</TableBody>
 | 
			
		||||
//         </Table>
 | 
			
		||||
//     );
 | 
			
		||||
 | 
			
		||||
//     renderStickiness = variants => {
 | 
			
		||||
//         const { updateStickiness, stickinessOptions } = this.props;
 | 
			
		||||
 | 
			
		||||
//         if (!variants || variants.length < 2) {
 | 
			
		||||
//             return null;
 | 
			
		||||
//         }
 | 
			
		||||
 | 
			
		||||
//         const value = variants[0].stickiness || 'default';
 | 
			
		||||
//         const options = stickinessOptions.map(c => ({ key: c, label: c }));
 | 
			
		||||
 | 
			
		||||
//         // guard on stickiness being disabled for context field.
 | 
			
		||||
//         if (!stickinessOptions.includes(value)) {
 | 
			
		||||
//             options.push({ key: value, label: value });
 | 
			
		||||
//         }
 | 
			
		||||
 | 
			
		||||
//         const onChange = event => updateStickiness(event.target.value);
 | 
			
		||||
 | 
			
		||||
//         return (
 | 
			
		||||
//             <section style={{ paddingTop: '16px' }}>
 | 
			
		||||
//                 <GeneralSelect
 | 
			
		||||
//                     label="Stickiness"
 | 
			
		||||
//                     options={options}
 | 
			
		||||
//                     value={value}
 | 
			
		||||
//                     onChange={onChange}
 | 
			
		||||
//                 />
 | 
			
		||||
//                   
 | 
			
		||||
//                 <small
 | 
			
		||||
//                     className={classnames(styles.paragraph, styles.helperText)}
 | 
			
		||||
//                     style={{ display: 'block', marginTop: '0.5rem' }}
 | 
			
		||||
//                 >
 | 
			
		||||
//                     By overriding the stickiness you can control which parameter
 | 
			
		||||
//                     you want to be used in order to ensure consistent traffic
 | 
			
		||||
//                     allocation across variants.{' '}
 | 
			
		||||
//                     <a
 | 
			
		||||
//                         href="https://docs.getunleash.io/advanced/toggle_variants"
 | 
			
		||||
//                         target="_blank"
 | 
			
		||||
//                         rel="noreferrer"
 | 
			
		||||
//                     >
 | 
			
		||||
//                         Read more
 | 
			
		||||
//                     </a>
 | 
			
		||||
//                 </small>
 | 
			
		||||
//             </section>
 | 
			
		||||
//         );
 | 
			
		||||
//     };
 | 
			
		||||
 | 
			
		||||
//     render() {
 | 
			
		||||
//         const { showDialog, editVariant, editIndex, title } = this.state;
 | 
			
		||||
//         const { variants, addVariant, updateVariant } = this.props;
 | 
			
		||||
//         const saveVariant = editVariant
 | 
			
		||||
//             ? updateVariant.bind(null, editIndex)
 | 
			
		||||
//             : addVariant;
 | 
			
		||||
 | 
			
		||||
//         return (
 | 
			
		||||
//             <section style={{ padding: '16px' }}>
 | 
			
		||||
//                 <Typography variant="body1">
 | 
			
		||||
//                     Variants allows you to return a variant object if the
 | 
			
		||||
//                     feature toggle is considered enabled for the current
 | 
			
		||||
//                     request. When using variants you should use the{' '}
 | 
			
		||||
//                     <code style={{ color: 'navy' }}>getVariant()</code> method
 | 
			
		||||
//                     in the Client SDK.
 | 
			
		||||
//                 </Typography>
 | 
			
		||||
 | 
			
		||||
//                 <ConditionallyRender
 | 
			
		||||
//                     condition={variants.length > 0}
 | 
			
		||||
//                     show={this.renderVariants(variants)}
 | 
			
		||||
//                     elseShow={<p>No variants defined.</p>}
 | 
			
		||||
//                 />
 | 
			
		||||
 | 
			
		||||
//                 <br />
 | 
			
		||||
//                 <ConditionallyRender
 | 
			
		||||
//                     condition={this.props.editable}
 | 
			
		||||
//                     show={
 | 
			
		||||
//                         <div>
 | 
			
		||||
//                             <Button
 | 
			
		||||
//                                 title="Add variant"
 | 
			
		||||
//                                 onClick={this.openAddVariant}
 | 
			
		||||
//                                 variant="contained"
 | 
			
		||||
//                                 color="primary"
 | 
			
		||||
//                                 className={styles.addVariantButton}
 | 
			
		||||
//                             >
 | 
			
		||||
//                                 Add variant
 | 
			
		||||
//                             </Button>
 | 
			
		||||
//                             {this.renderStickiness(variants)}
 | 
			
		||||
//                         </div>
 | 
			
		||||
//                     }
 | 
			
		||||
//                 />
 | 
			
		||||
 | 
			
		||||
//                 <AddVariant
 | 
			
		||||
//                     showDialog={showDialog}
 | 
			
		||||
//                     closeDialog={this.closeDialog}
 | 
			
		||||
//                     save={saveVariant}
 | 
			
		||||
//                     validateName={this.validateName}
 | 
			
		||||
//                     editVariant={editVariant}
 | 
			
		||||
//                     title={title}
 | 
			
		||||
//                 />
 | 
			
		||||
//             </section>
 | 
			
		||||
//         );
 | 
			
		||||
//     }
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// UpdateVariantComponent.propTypes = {
 | 
			
		||||
//     variants: PropTypes.array.isRequired,
 | 
			
		||||
//     addVariant: PropTypes.func.isRequired,
 | 
			
		||||
//     removeVariant: PropTypes.func.isRequired,
 | 
			
		||||
//     updateVariant: PropTypes.func.isRequired,
 | 
			
		||||
//     updateStickiness: PropTypes.func.isRequired,
 | 
			
		||||
//     editable: PropTypes.bool.isRequired,
 | 
			
		||||
//     stickinessOptions: PropTypes.array,
 | 
			
		||||
// };
 | 
			
		||||
 | 
			
		||||
// export default UpdateVariantComponent;
 | 
			
		||||
 | 
			
		||||
@ -131,6 +131,7 @@ const FeatureView2 = () => {
 | 
			
		||||
                            <div className={styles.actions}>
 | 
			
		||||
                                <PermissionIconButton
 | 
			
		||||
                                    permission={UPDATE_FEATURE}
 | 
			
		||||
                                    projectId={projectId}
 | 
			
		||||
                                    tooltip="Copy"
 | 
			
		||||
                                    data-loading
 | 
			
		||||
                                    component={Link}
 | 
			
		||||
@ -140,6 +141,7 @@ const FeatureView2 = () => {
 | 
			
		||||
                                </PermissionIconButton>
 | 
			
		||||
                                <PermissionIconButton
 | 
			
		||||
                                    permission={UPDATE_FEATURE}
 | 
			
		||||
                                    projectId={projectId}
 | 
			
		||||
                                    tooltip="Archive feature toggle"
 | 
			
		||||
                                    data-loading
 | 
			
		||||
                                    onClick={() => setShowDelDialog(true)}
 | 
			
		||||
 | 
			
		||||
@ -68,12 +68,12 @@ const FeatureViewEnvironment: FC<IFeatureViewEnvironmentProps> = ({
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const toggleEnvironment = (e: React.ChangeEvent) => {
 | 
			
		||||
    const toggleEnvironment = async (e: React.ChangeEvent) => {
 | 
			
		||||
        if (env.enabled) {
 | 
			
		||||
            handleToggleEnvironmentOff();
 | 
			
		||||
            await handleToggleEnvironmentOff();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        handleToggleEnvironmentOn();
 | 
			
		||||
        await handleToggleEnvironmentOn();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const iconContainerClasses = classNames(styles.iconContainer, {
 | 
			
		||||
@ -100,8 +100,12 @@ const FeatureViewEnvironment: FC<IFeatureViewEnvironmentProps> = ({
 | 
			
		||||
                <div className={iconContainerClasses}>
 | 
			
		||||
                    <Cloud className={iconClasses} />
 | 
			
		||||
                </div>
 | 
			
		||||
                <Tooltip title={`${env.name} is an environment of type "${env.type}".`}>
 | 
			
		||||
                    <p className={styles.environmentBadgeParagraph}>{env.name}</p>
 | 
			
		||||
                <Tooltip
 | 
			
		||||
                    title={`${env.name} is an environment of type "${env.type}".`}
 | 
			
		||||
                >
 | 
			
		||||
                    <p className={styles.environmentBadgeParagraph}>
 | 
			
		||||
                        {env.name}
 | 
			
		||||
                    </p>
 | 
			
		||||
                </Tooltip>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
@ -117,11 +121,15 @@ const FeatureViewEnvironment: FC<IFeatureViewEnvironmentProps> = ({
 | 
			
		||||
                                    onChange={toggleEnvironment}
 | 
			
		||||
                                />{' '}
 | 
			
		||||
                                <span className={styles.toggleText}>
 | 
			
		||||
                                    {env.name}{' environment is '}
 | 
			
		||||
                                    <strong>{env.enabled ? 'enabled' : 'disabled'}</strong>
 | 
			
		||||
                                    {env.name}
 | 
			
		||||
                                    {' environment is '}
 | 
			
		||||
                                    <strong>
 | 
			
		||||
                                        {env.enabled ? 'enabled' : 'disabled'}
 | 
			
		||||
                                    </strong>
 | 
			
		||||
                                </span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        } />
 | 
			
		||||
                        }
 | 
			
		||||
                    />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className={styles.environmentStatus} data-loading>
 | 
			
		||||
                    <ConditionallyRender
 | 
			
		||||
@ -138,7 +146,8 @@ const FeatureViewEnvironment: FC<IFeatureViewEnvironmentProps> = ({
 | 
			
		||||
                                    Configure strategies for {env.name}
 | 
			
		||||
                                </Link>
 | 
			
		||||
                            </>
 | 
			
		||||
                        } />
 | 
			
		||||
                        }
 | 
			
		||||
                    />
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -87,8 +87,11 @@ const CreateFeature = ({
 | 
			
		||||
                </div>
 | 
			
		||||
                <section className={styles.formContainer}>
 | 
			
		||||
                    <ProjectSelect
 | 
			
		||||
                        value={project || input.project}
 | 
			
		||||
                        onChange={v => setValue('project', v.target.value)}
 | 
			
		||||
                        value={input.project}
 | 
			
		||||
                        defaultValue={project}
 | 
			
		||||
                        onChange={v => {
 | 
			
		||||
                            setValue('project', v.target.value);
 | 
			
		||||
                        }}
 | 
			
		||||
                        filter={projectFilterGenerator(user, CREATE_FEATURE)}
 | 
			
		||||
                    />
 | 
			
		||||
                </section>
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,8 @@ class ProjectSelectComponent extends Component {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        const { value, projects, onChange, enabled, filter } = this.props;
 | 
			
		||||
        const { value, projects, onChange, enabled, filter, defaultValue } =
 | 
			
		||||
            this.props;
 | 
			
		||||
 | 
			
		||||
        if (!enabled) {
 | 
			
		||||
            return null;
 | 
			
		||||
@ -43,6 +44,7 @@ class ProjectSelectComponent extends Component {
 | 
			
		||||
        return (
 | 
			
		||||
            <GeneralSelect
 | 
			
		||||
                label="Project"
 | 
			
		||||
                defaultValue={defaultValue}
 | 
			
		||||
                options={options}
 | 
			
		||||
                value={value}
 | 
			
		||||
                onChange={onChange}
 | 
			
		||||
 | 
			
		||||
@ -55,9 +55,7 @@ const ProjectFeatureToggles = ({
 | 
			
		||||
                                    </IconButton>
 | 
			
		||||
                                }
 | 
			
		||||
                            />
 | 
			
		||||
                            <ConditionallyRender
 | 
			
		||||
                                condition={hasAccess(CREATE_FEATURE, id)}
 | 
			
		||||
                                show={
 | 
			
		||||
 | 
			
		||||
                            <ResponsiveButton
 | 
			
		||||
                                onClick={() =>
 | 
			
		||||
                                    history.push(
 | 
			
		||||
@ -70,11 +68,11 @@ const ProjectFeatureToggles = ({
 | 
			
		||||
                                maxWidth="700px"
 | 
			
		||||
                                tooltip="New feature toggle"
 | 
			
		||||
                                Icon={Add}
 | 
			
		||||
                                projectId={id}
 | 
			
		||||
                                permission={CREATE_FEATURE}
 | 
			
		||||
                            >
 | 
			
		||||
                                New feature toggle
 | 
			
		||||
                            </ResponsiveButton>
 | 
			
		||||
                                }
 | 
			
		||||
                            />
 | 
			
		||||
                        </>
 | 
			
		||||
                    }
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ import { makeStyles } from '@material-ui/core/styles';
 | 
			
		||||
 | 
			
		||||
export const useStyles = makeStyles(theme => ({
 | 
			
		||||
    projectInfo: {
 | 
			
		||||
        width: '275px',
 | 
			
		||||
        width: '225px',
 | 
			
		||||
        display: 'flex',
 | 
			
		||||
        flexDirection: 'column',
 | 
			
		||||
        alignItems: 'center',
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,9 @@ interface ProjectOverviewProps {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ProjectOverview = ({ projectId }: ProjectOverviewProps) => {
 | 
			
		||||
    const { project, loading } = useProject(projectId);
 | 
			
		||||
    const { project, loading } = useProject(projectId, {
 | 
			
		||||
        refreshInterval: 10000,
 | 
			
		||||
    });
 | 
			
		||||
    const { members, features, health } = project;
 | 
			
		||||
    const styles = useStyles();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,9 @@
 | 
			
		||||
import { Card, IconButton, Menu, MenuItem } from '@material-ui/core';
 | 
			
		||||
import { Card, Menu, MenuItem } from '@material-ui/core';
 | 
			
		||||
import { Dispatch, SetStateAction } from 'react';
 | 
			
		||||
import { useStyles } from './ProjectCard.styles';
 | 
			
		||||
import MoreVertIcon from '@material-ui/icons/MoreVert';
 | 
			
		||||
 | 
			
		||||
import { ReactComponent as ProjectIcon } from '../../../assets/icons/projectIcon.svg';
 | 
			
		||||
import ConditionallyRender from '../../common/ConditionallyRender';
 | 
			
		||||
import { useState } from 'react';
 | 
			
		||||
import { useHistory } from 'react-router-dom';
 | 
			
		||||
import Dialogue from '../../common/Dialogue';
 | 
			
		||||
@ -12,6 +11,8 @@ import useProjectApi from '../../../hooks/api/actions/useProjectApi/useProjectAp
 | 
			
		||||
import useProjects from '../../../hooks/api/getters/useProjects/useProjects';
 | 
			
		||||
import { Delete, Edit } from '@material-ui/icons';
 | 
			
		||||
import { getProjectEditPath } from '../../../utils/route-path-helpers';
 | 
			
		||||
import PermissionIconButton from '../../common/PermissionIconButton/PermissionIconButton';
 | 
			
		||||
import { UPDATE_PROJECT } from '../../../store/project/actions';
 | 
			
		||||
interface IProjectCardProps {
 | 
			
		||||
    name: string;
 | 
			
		||||
    featureCount: number;
 | 
			
		||||
@ -53,18 +54,17 @@ const ProjectCard = ({
 | 
			
		||||
        <Card className={styles.projectCard} onMouseEnter={onHover}>
 | 
			
		||||
            <div className={styles.header} data-loading>
 | 
			
		||||
                <h2 className={styles.title}>{name}</h2>
 | 
			
		||||
                <ConditionallyRender
 | 
			
		||||
                    condition={true}
 | 
			
		||||
                    show={
 | 
			
		||||
                        <IconButton
 | 
			
		||||
 | 
			
		||||
                <PermissionIconButton
 | 
			
		||||
                    permission={UPDATE_PROJECT}
 | 
			
		||||
                    projectId={id}
 | 
			
		||||
                    className={styles.actionsBtn}
 | 
			
		||||
                    data-loading
 | 
			
		||||
                    onClick={handleClick}
 | 
			
		||||
                >
 | 
			
		||||
                    <MoreVertIcon />
 | 
			
		||||
                        </IconButton>
 | 
			
		||||
                    }
 | 
			
		||||
                />
 | 
			
		||||
                </PermissionIconButton>
 | 
			
		||||
 | 
			
		||||
                <Menu
 | 
			
		||||
                    id="project-card-menu"
 | 
			
		||||
                    open={Boolean(anchorEl)}
 | 
			
		||||
 | 
			
		||||
@ -135,15 +135,11 @@ const ProjectListNew = () => {
 | 
			
		||||
                    <HeaderTitle
 | 
			
		||||
                        title="Projects"
 | 
			
		||||
                        actions={
 | 
			
		||||
                            <ConditionallyRender
 | 
			
		||||
                                condition={hasAccess(CREATE_PROJECT)}
 | 
			
		||||
                                show={
 | 
			
		||||
                            <ResponsiveButton
 | 
			
		||||
                                Icon={Add}
 | 
			
		||||
                                        onClick={() =>
 | 
			
		||||
                                            history.push('/projects/create')
 | 
			
		||||
                                        }
 | 
			
		||||
                                onClick={() => history.push('/projects/create')}
 | 
			
		||||
                                maxWidth="700px"
 | 
			
		||||
                                permission={CREATE_PROJECT}
 | 
			
		||||
                                tooltip={createButtonData.title}
 | 
			
		||||
                                disabled={createButtonData.disabled}
 | 
			
		||||
                            >
 | 
			
		||||
@ -152,8 +148,6 @@ const ProjectListNew = () => {
 | 
			
		||||
                        }
 | 
			
		||||
                    />
 | 
			
		||||
                }
 | 
			
		||||
                    />
 | 
			
		||||
                }
 | 
			
		||||
            >
 | 
			
		||||
                <ConditionallyRender condition={error} show={renderError()} />
 | 
			
		||||
                <div className={styles.container}>
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,8 @@ import useUiConfig from '../../hooks/api/getters/useUiConfig/useUiConfig';
 | 
			
		||||
import { Alert } from '@material-ui/lab';
 | 
			
		||||
import { FormEvent } from 'react-router/node_modules/@types/react';
 | 
			
		||||
import useLoading from '../../hooks/useLoading';
 | 
			
		||||
import PermissionButton from '../common/PermissionButton/PermissionButton';
 | 
			
		||||
import { UPDATE_PROJECT } from '../../store/project/actions';
 | 
			
		||||
 | 
			
		||||
interface ProjectFormComponentProps {
 | 
			
		||||
    editMode: boolean;
 | 
			
		||||
@ -106,7 +108,7 @@ const ProjectFormComponent = (props: ProjectFormComponentProps) => {
 | 
			
		||||
            <PageContent
 | 
			
		||||
                headerContent={
 | 
			
		||||
                    <HeaderTitle
 | 
			
		||||
                        title={`${submitText} ${project?.name} project`}
 | 
			
		||||
                        title={`${submitText} ${props.project?.name} project`}
 | 
			
		||||
                    />
 | 
			
		||||
                }
 | 
			
		||||
            >
 | 
			
		||||
@ -189,7 +191,9 @@ const ProjectFormComponent = (props: ProjectFormComponentProps) => {
 | 
			
		||||
                                />
 | 
			
		||||
 | 
			
		||||
                                <ConditionallyRender
 | 
			
		||||
                                    condition={hasAccess(CREATE_PROJECT)}
 | 
			
		||||
                                    condition={
 | 
			
		||||
                                        hasAccess(CREATE_PROJECT) && !editMode
 | 
			
		||||
                                    }
 | 
			
		||||
                                    show={
 | 
			
		||||
                                        <div className={styles.formButtons}>
 | 
			
		||||
                                            <FormButtons
 | 
			
		||||
@ -199,6 +203,20 @@ const ProjectFormComponent = (props: ProjectFormComponentProps) => {
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    }
 | 
			
		||||
                                />
 | 
			
		||||
 | 
			
		||||
                                <ConditionallyRender
 | 
			
		||||
                                    condition={editMode}
 | 
			
		||||
                                    show={
 | 
			
		||||
                                        <PermissionButton
 | 
			
		||||
                                            permission={UPDATE_PROJECT}
 | 
			
		||||
                                            projectId={props.project.id}
 | 
			
		||||
                                            type="submit"
 | 
			
		||||
                                            style={{ marginTop: '1rem' }}
 | 
			
		||||
                                        >
 | 
			
		||||
                                            Update project
 | 
			
		||||
                                        </PermissionButton>
 | 
			
		||||
                                    }
 | 
			
		||||
                                />
 | 
			
		||||
                            </form>
 | 
			
		||||
                        </>
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
import useSWR, { mutate } from 'swr';
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
import { formatApiPath } from '../../../../utils/format-path';
 | 
			
		||||
import handleErrorResponses from '../httpErrorResponseHandler';
 | 
			
		||||
 | 
			
		||||
const useApiTokens = () => {
 | 
			
		||||
const useApiTokens = (options: SWRConfiguration = {}) => {
 | 
			
		||||
    const fetcher = async () => {
 | 
			
		||||
        const path = formatApiPath(`api/admin/api-tokens`);
 | 
			
		||||
        const res = await fetch(path, {
 | 
			
		||||
@ -14,7 +14,7 @@ const useApiTokens = () => {
 | 
			
		||||
 | 
			
		||||
    const KEY = `api/admin/api-tokens`;
 | 
			
		||||
 | 
			
		||||
    const { data, error } = useSWR(KEY, fetcher);
 | 
			
		||||
    const { data, error } = useSWR(KEY, fetcher, options);
 | 
			
		||||
    const [loading, setLoading] = useState(!error && !data);
 | 
			
		||||
 | 
			
		||||
    const refetch = () => {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import useSWR, { mutate } from 'swr';
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
import { IEnvironmentResponse } from '../../../../interfaces/environments';
 | 
			
		||||
import { formatApiPath } from '../../../../utils/format-path';
 | 
			
		||||
@ -6,17 +6,20 @@ import handleErrorResponses from '../httpErrorResponseHandler';
 | 
			
		||||
 | 
			
		||||
export const ENVIRONMENT_CACHE_KEY = `api/admin/environments`;
 | 
			
		||||
 | 
			
		||||
const useEnvironments = () => {
 | 
			
		||||
const useEnvironments = (options: SWRConfiguration = {}) => {
 | 
			
		||||
    const fetcher = () => {
 | 
			
		||||
        const path = formatApiPath(`api/admin/environments`);
 | 
			
		||||
        return fetch(path, {
 | 
			
		||||
            method: 'GET',
 | 
			
		||||
        }).then(handleErrorResponses('Environments')).then(res => res.json());
 | 
			
		||||
        })
 | 
			
		||||
            .then(handleErrorResponses('Environments'))
 | 
			
		||||
            .then(res => res.json());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const { data, error } = useSWR<IEnvironmentResponse>(
 | 
			
		||||
        ENVIRONMENT_CACHE_KEY,
 | 
			
		||||
        fetcher
 | 
			
		||||
        fetcher,
 | 
			
		||||
        options
 | 
			
		||||
    );
 | 
			
		||||
    const [loading, setLoading] = useState(!error && !data);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import useSWR, { mutate } from 'swr';
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
 | 
			
		||||
import { formatApiPath } from '../../../../utils/format-path';
 | 
			
		||||
@ -6,17 +6,10 @@ import { IFeatureToggle } from '../../../../interfaces/featureToggle';
 | 
			
		||||
import { defaultFeature } from './defaultFeature';
 | 
			
		||||
import handleErrorResponses from '../httpErrorResponseHandler';
 | 
			
		||||
 | 
			
		||||
interface IUseFeatureOptions {
 | 
			
		||||
    refreshInterval?: number;
 | 
			
		||||
    revalidateOnFocus?: boolean;
 | 
			
		||||
    revalidateOnReconnect?: boolean;
 | 
			
		||||
    revalidateIfStale?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const useFeature = (
 | 
			
		||||
    projectId: string,
 | 
			
		||||
    id: string,
 | 
			
		||||
    options: IUseFeatureOptions = {}
 | 
			
		||||
    options: SWRConfiguration = {}
 | 
			
		||||
) => {
 | 
			
		||||
    const fetcher = async () => {
 | 
			
		||||
        const path = formatApiPath(
 | 
			
		||||
@ -24,7 +17,9 @@ const useFeature = (
 | 
			
		||||
        );
 | 
			
		||||
        return fetch(path, {
 | 
			
		||||
            method: 'GET',
 | 
			
		||||
        }).then(handleErrorResponses('Feature toggle data')).then(res => res.json());
 | 
			
		||||
        })
 | 
			
		||||
            .then(handleErrorResponses('Feature toggle data'))
 | 
			
		||||
            .then(res => res.json());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const FEATURE_CACHE_KEY = `api/admin/projects/${projectId}/features/${id}`;
 | 
			
		||||
 | 
			
		||||
@ -1,24 +1,22 @@
 | 
			
		||||
import { formatApiPath } from '../../../../utils/format-path';
 | 
			
		||||
import { useEffect, useState } from 'react';
 | 
			
		||||
import useSWR, { mutate } from 'swr';
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { IFeatureMetrics } from '../../../../interfaces/featureToggle';
 | 
			
		||||
import handleErrorResponses from '../httpErrorResponseHandler';
 | 
			
		||||
 | 
			
		||||
interface IUseFeatureMetricsOptions {
 | 
			
		||||
    refreshInterval?: number;
 | 
			
		||||
    revalidateOnFocus?: boolean;
 | 
			
		||||
    revalidateOnReconnect?: boolean;
 | 
			
		||||
    revalidateIfStale?: boolean;
 | 
			
		||||
    revalidateOnMount?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const emptyMetrics = { lastHourUsage: [], seenApplications: [] };
 | 
			
		||||
 | 
			
		||||
const useFeatureMetrics = (projectId: string, featureId: string, options: IUseFeatureMetricsOptions = {}) => {
 | 
			
		||||
const useFeatureMetrics = (
 | 
			
		||||
    projectId: string,
 | 
			
		||||
    featureId: string,
 | 
			
		||||
    options: SWRConfiguration = {}
 | 
			
		||||
) => {
 | 
			
		||||
    const fetcher = async () => {
 | 
			
		||||
        const path = formatApiPath(`api/admin/client-metrics/features/${featureId}`);
 | 
			
		||||
        const path = formatApiPath(
 | 
			
		||||
            `api/admin/client-metrics/features/${featureId}`
 | 
			
		||||
        );
 | 
			
		||||
        const res = await fetch(path, {
 | 
			
		||||
            method: 'GET'
 | 
			
		||||
            method: 'GET',
 | 
			
		||||
        }).then(handleErrorResponses('feature metrics'));
 | 
			
		||||
        if (res.ok) {
 | 
			
		||||
            return res.json();
 | 
			
		||||
@ -32,7 +30,7 @@ const useFeatureMetrics = (projectId: string, featureId: string, options: IUseFe
 | 
			
		||||
        FEATURE_METRICS_CACHE_KEY,
 | 
			
		||||
        fetcher,
 | 
			
		||||
        {
 | 
			
		||||
            ...options
 | 
			
		||||
            ...options,
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -51,7 +49,7 @@ const useFeatureMetrics = (projectId: string, featureId: string, options: IUseFe
 | 
			
		||||
        error,
 | 
			
		||||
        loading,
 | 
			
		||||
        refetch,
 | 
			
		||||
        FEATURE_METRICS_CACHE_KEY
 | 
			
		||||
        FEATURE_METRICS_CACHE_KEY,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,24 +1,16 @@
 | 
			
		||||
import useSWR, { mutate } from 'swr';
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
 | 
			
		||||
import { formatApiPath } from '../../../../utils/format-path';
 | 
			
		||||
import { IFeatureStrategy } from '../../../../interfaces/strategy';
 | 
			
		||||
import handleErrorResponses from '../httpErrorResponseHandler';
 | 
			
		||||
 | 
			
		||||
interface IUseFeatureOptions {
 | 
			
		||||
    refreshInterval?: number;
 | 
			
		||||
    revalidateOnFocus?: boolean;
 | 
			
		||||
    revalidateOnReconnect?: boolean;
 | 
			
		||||
    revalidateIfStale?: boolean;
 | 
			
		||||
    revalidateOnMount?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const useFeatureStrategy = (
 | 
			
		||||
    projectId: string,
 | 
			
		||||
    featureId: string,
 | 
			
		||||
    environmentId: string,
 | 
			
		||||
    strategyId: string,
 | 
			
		||||
    options: IUseFeatureOptions
 | 
			
		||||
    options: SWRConfiguration
 | 
			
		||||
) => {
 | 
			
		||||
    const fetcher = () => {
 | 
			
		||||
        const path = formatApiPath(
 | 
			
		||||
@ -26,7 +18,9 @@ const useFeatureStrategy = (
 | 
			
		||||
        );
 | 
			
		||||
        return fetch(path, {
 | 
			
		||||
            method: 'GET',
 | 
			
		||||
        }).then(handleErrorResponses(`Strategies for ${featureId}`)).then(res => res.json());
 | 
			
		||||
        })
 | 
			
		||||
            .then(handleErrorResponses(`Strategies for ${featureId}`))
 | 
			
		||||
            .then(res => res.json());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const FEATURE_STRATEGY_CACHE_KEY = strategyId;
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
import useSWR, { mutate } from 'swr';
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
import { formatApiPath } from '../../../../utils/format-path';
 | 
			
		||||
import { IFeatureType } from '../../../../interfaces/featureTypes';
 | 
			
		||||
import handleErrorResponses from '../httpErrorResponseHandler';
 | 
			
		||||
 | 
			
		||||
const useFeatureTypes = () => {
 | 
			
		||||
const useFeatureTypes = (options: SWRConfiguration = {}) => {
 | 
			
		||||
    const fetcher = async () => {
 | 
			
		||||
        const path = formatApiPath(`api/admin/feature-types`);
 | 
			
		||||
        const res = await fetch(path, {
 | 
			
		||||
@ -15,7 +15,7 @@ const useFeatureTypes = () => {
 | 
			
		||||
 | 
			
		||||
    const KEY = `api/admin/feature-types`;
 | 
			
		||||
 | 
			
		||||
    const { data, error } = useSWR(KEY, fetcher);
 | 
			
		||||
    const { data, error } = useSWR(KEY, fetcher, options);
 | 
			
		||||
    const [loading, setLoading] = useState(!error && !data);
 | 
			
		||||
 | 
			
		||||
    const refetch = () => {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import useSWR, { mutate } from 'swr';
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
import { IProjectHealthReport } from '../../../../interfaces/project';
 | 
			
		||||
import { fallbackProject } from '../useProject/fallbackProject';
 | 
			
		||||
@ -6,19 +6,21 @@ import useSort from '../../../useSort';
 | 
			
		||||
import { formatApiPath } from '../../../../utils/format-path';
 | 
			
		||||
import handleErrorResponses from '../httpErrorResponseHandler';
 | 
			
		||||
 | 
			
		||||
const useHealthReport = (id: string) => {
 | 
			
		||||
const useHealthReport = (id: string, options: SWRConfiguration = {}) => {
 | 
			
		||||
    const KEY = `api/admin/projects/${id}/health-report`;
 | 
			
		||||
 | 
			
		||||
    const fetcher = () => {
 | 
			
		||||
        const path = formatApiPath(`api/admin/projects/${id}/health-report`);
 | 
			
		||||
        return fetch(path, {
 | 
			
		||||
            method: 'GET',
 | 
			
		||||
        }).then(handleErrorResponses('Health report')).then(res => res.json());
 | 
			
		||||
        })
 | 
			
		||||
            .then(handleErrorResponses('Health report'))
 | 
			
		||||
            .then(res => res.json());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const [sort] = useSort();
 | 
			
		||||
 | 
			
		||||
    const { data, error } = useSWR<IProjectHealthReport>(KEY, fetcher);
 | 
			
		||||
    const { data, error } = useSWR<IProjectHealthReport>(KEY, fetcher, options);
 | 
			
		||||
    const [loading, setLoading] = useState(!error && !data);
 | 
			
		||||
 | 
			
		||||
    const refetch = () => {
 | 
			
		||||
 | 
			
		||||
@ -1,15 +1,15 @@
 | 
			
		||||
import useSWR, { mutate } from 'swr';
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
import { getProjectFetcher } from './getProjectFetcher';
 | 
			
		||||
import { IProject } from '../../../../interfaces/project';
 | 
			
		||||
import { fallbackProject } from './fallbackProject';
 | 
			
		||||
import useSort from '../../../useSort';
 | 
			
		||||
 | 
			
		||||
const useProject = (id: string) => {
 | 
			
		||||
const useProject = (id: string, options: SWRConfiguration = {}) => {
 | 
			
		||||
    const { KEY, fetcher } = getProjectFetcher(id);
 | 
			
		||||
    const [sort] = useSort();
 | 
			
		||||
 | 
			
		||||
    const { data, error } = useSWR<IProject>(KEY, fetcher);
 | 
			
		||||
    const { data, error } = useSWR<IProject>(KEY, fetcher, options);
 | 
			
		||||
    const [loading, setLoading] = useState(!error && !data);
 | 
			
		||||
 | 
			
		||||
    const refetch = () => {
 | 
			
		||||
 | 
			
		||||
@ -1,21 +1,27 @@
 | 
			
		||||
import useSWR, { mutate } from 'swr';
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
import { formatApiPath } from '../../../../utils/format-path';
 | 
			
		||||
 | 
			
		||||
import { IProjectCard } from '../../../../interfaces/project';
 | 
			
		||||
import handleErrorResponses from '../httpErrorResponseHandler';
 | 
			
		||||
 | 
			
		||||
const useProjects = () => {
 | 
			
		||||
const useProjects = (options: SWRConfiguration = {}) => {
 | 
			
		||||
    const fetcher = () => {
 | 
			
		||||
        const path = formatApiPath(`api/admin/projects`);
 | 
			
		||||
        return fetch(path, {
 | 
			
		||||
            method: 'GET',
 | 
			
		||||
        }).then(handleErrorResponses('Projects')).then(res => res.json());
 | 
			
		||||
        })
 | 
			
		||||
            .then(handleErrorResponses('Projects'))
 | 
			
		||||
            .then(res => res.json());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const KEY = `api/admin/projects`;
 | 
			
		||||
 | 
			
		||||
    const { data, error } = useSWR<{ projects: IProjectCard[] }>(KEY, fetcher);
 | 
			
		||||
    const { data, error } = useSWR<{ projects: IProjectCard[] }>(
 | 
			
		||||
        KEY,
 | 
			
		||||
        fetcher,
 | 
			
		||||
        options
 | 
			
		||||
    );
 | 
			
		||||
    const [loading, setLoading] = useState(!error && !data);
 | 
			
		||||
 | 
			
		||||
    const refetch = () => {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import useSWR from 'swr';
 | 
			
		||||
import useSWR, { SWRConfiguration } from 'swr';
 | 
			
		||||
import useQueryParams from '../../../useQueryParams';
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
import { formatApiPath } from '../../../../utils/format-path';
 | 
			
		||||
@ -8,13 +8,15 @@ const getFetcher = (token: string) => () => {
 | 
			
		||||
    const path = formatApiPath(`auth/reset/validate?token=${token}`);
 | 
			
		||||
    return fetch(path, {
 | 
			
		||||
        method: 'GET',
 | 
			
		||||
    }).then(handleErrorResponses('Password reset')).then(res => res.json());
 | 
			
		||||
    })
 | 
			
		||||
        .then(handleErrorResponses('Password reset'))
 | 
			
		||||
        .then(res => res.json());
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const INVALID_TOKEN_ERROR = 'InvalidTokenError';
 | 
			
		||||
const USED_TOKEN_ERROR = 'UsedTokenError';
 | 
			
		||||
 | 
			
		||||
const useResetPassword = () => {
 | 
			
		||||
const useResetPassword = (options: SWRConfiguration = {}) => {
 | 
			
		||||
    const query = useQueryParams();
 | 
			
		||||
    const initialToken = query.get('token') || '';
 | 
			
		||||
    const [token, setToken] = useState(initialToken);
 | 
			
		||||
@ -22,7 +24,7 @@ const useResetPassword = () => {
 | 
			
		||||
    const fetcher = getFetcher(token);
 | 
			
		||||
 | 
			
		||||
    const key = `auth/reset/validate?token=${token}`;
 | 
			
		||||
    const { data, error } = useSWR(key, fetcher);
 | 
			
		||||
    const { data, error } = useSWR(key, fetcher, options);
 | 
			
		||||
    const [loading, setLoading] = useState(!error && !data);
 | 
			
		||||
 | 
			
		||||
    const retry = () => {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import useSWR, { mutate } from 'swr';
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { useEffect, useState } from 'react';
 | 
			
		||||
import { formatApiPath } from '../../../../utils/format-path';
 | 
			
		||||
import { IStrategy } from '../../../../interfaces/strategy';
 | 
			
		||||
@ -11,30 +11,41 @@ const flexibleRolloutStrategy: IStrategy = {
 | 
			
		||||
    name: 'flexibleRollout',
 | 
			
		||||
    displayName: 'Gradual rollout',
 | 
			
		||||
    editable: false,
 | 
			
		||||
    description: 'Roll out to a percentage of your userbase, and ensure that the experience is the same for the user on each visit.',
 | 
			
		||||
    parameters: [{
 | 
			
		||||
        name: 'rollout', type: 'percentage', description: '', required: false
 | 
			
		||||
    }, {
 | 
			
		||||
    description:
 | 
			
		||||
        'Roll out to a percentage of your userbase, and ensure that the experience is the same for the user on each visit.',
 | 
			
		||||
    parameters: [
 | 
			
		||||
        {
 | 
			
		||||
            name: 'rollout',
 | 
			
		||||
            type: 'percentage',
 | 
			
		||||
            description: '',
 | 
			
		||||
            required: false,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            name: 'stickiness',
 | 
			
		||||
            type: 'string',
 | 
			
		||||
            description: 'Used to defined stickiness',
 | 
			
		||||
        required: true
 | 
			
		||||
    }, { name: 'groupId', type: 'string', description: '', required: true }]
 | 
			
		||||
            required: true,
 | 
			
		||||
        },
 | 
			
		||||
        { name: 'groupId', type: 'string', description: '', required: true },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const useStrategies = () => {
 | 
			
		||||
const useStrategies = (options: SWRConfiguration = {}) => {
 | 
			
		||||
    const fetcher = () => {
 | 
			
		||||
        const path = formatApiPath(`api/admin/strategies`);
 | 
			
		||||
 | 
			
		||||
        return fetch(path, {
 | 
			
		||||
            method: 'GET',
 | 
			
		||||
            credentials: 'include'
 | 
			
		||||
        }).then(handleErrorResponses('Strategies')).then(res => res.json());
 | 
			
		||||
            credentials: 'include',
 | 
			
		||||
        })
 | 
			
		||||
            .then(handleErrorResponses('Strategies'))
 | 
			
		||||
            .then(res => res.json());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const { data, error } = useSWR<{ strategies: IStrategy[] }>(
 | 
			
		||||
        STRATEGIES_CACHE_KEY,
 | 
			
		||||
        fetcher
 | 
			
		||||
        fetcher,
 | 
			
		||||
        options
 | 
			
		||||
    );
 | 
			
		||||
    const [loading, setLoading] = useState(!error && !data);
 | 
			
		||||
 | 
			
		||||
@ -50,7 +61,7 @@ const useStrategies = () => {
 | 
			
		||||
        strategies: data?.strategies || [flexibleRolloutStrategy],
 | 
			
		||||
        error,
 | 
			
		||||
        loading,
 | 
			
		||||
        refetch
 | 
			
		||||
        refetch,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
import useSWR, { mutate } from 'swr';
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
import { formatApiPath } from '../../../../utils/format-path';
 | 
			
		||||
import { ITagType } from '../../../../interfaces/tags';
 | 
			
		||||
import handleErrorResponses from '../httpErrorResponseHandler';
 | 
			
		||||
 | 
			
		||||
const useTagTypes = () => {
 | 
			
		||||
const useTagTypes = (options: SWRConfiguration = {}) => {
 | 
			
		||||
    const fetcher = async () => {
 | 
			
		||||
        const path = formatApiPath(`api/admin/tag-types`);
 | 
			
		||||
        const res = await fetch(path, {
 | 
			
		||||
@ -15,7 +15,7 @@ const useTagTypes = () => {
 | 
			
		||||
 | 
			
		||||
    const KEY = `api/admin/tag-types`;
 | 
			
		||||
 | 
			
		||||
    const { data, error } = useSWR(KEY, fetcher);
 | 
			
		||||
    const { data, error } = useSWR(KEY, fetcher, options);
 | 
			
		||||
    const [loading, setLoading] = useState(!error && !data);
 | 
			
		||||
 | 
			
		||||
    const refetch = () => {
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
import useSWR, { mutate } from 'swr';
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
import { formatApiPath } from '../../../../utils/format-path';
 | 
			
		||||
import { ITag } from '../../../../interfaces/tags';
 | 
			
		||||
import handleErrorResponses from '../httpErrorResponseHandler';
 | 
			
		||||
 | 
			
		||||
const useTags = (featureId: string) => {
 | 
			
		||||
const useTags = (featureId: string, options: SWRConfiguration = {}) => {
 | 
			
		||||
    const fetcher = async () => {
 | 
			
		||||
        const path = formatApiPath(`api/admin/features/${featureId}/tags`);
 | 
			
		||||
        const res = await fetch(path, {
 | 
			
		||||
@ -15,7 +15,7 @@ const useTags = (featureId: string) => {
 | 
			
		||||
 | 
			
		||||
    const KEY = `api/admin/features/${featureId}/tags`;
 | 
			
		||||
 | 
			
		||||
    const { data, error } = useSWR(KEY, fetcher);
 | 
			
		||||
    const { data, error } = useSWR(KEY, fetcher, options);
 | 
			
		||||
    const [loading, setLoading] = useState(!error && !data);
 | 
			
		||||
 | 
			
		||||
    const refetch = () => {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import useSWR, { mutate } from 'swr';
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
import { formatApiPath } from '../../../../utils/format-path';
 | 
			
		||||
import { defaultValue } from './defaultValue';
 | 
			
		||||
@ -7,17 +7,19 @@ import handleErrorResponses from '../httpErrorResponseHandler';
 | 
			
		||||
 | 
			
		||||
const REQUEST_KEY = 'api/admin/ui-config';
 | 
			
		||||
 | 
			
		||||
const useUiConfig = () => {
 | 
			
		||||
const useUiConfig = (options: SWRConfiguration = {}) => {
 | 
			
		||||
    const fetcher = () => {
 | 
			
		||||
        const path = formatApiPath(`api/admin/ui-config`);
 | 
			
		||||
 | 
			
		||||
        return fetch(path, {
 | 
			
		||||
            method: 'GET',
 | 
			
		||||
            credentials: 'include',
 | 
			
		||||
        }).then(handleErrorResponses('configuration')).then(res => res.json());
 | 
			
		||||
        })
 | 
			
		||||
            .then(handleErrorResponses('configuration'))
 | 
			
		||||
            .then(res => res.json());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const { data, error } = useSWR<IUiConfig>(REQUEST_KEY, fetcher);
 | 
			
		||||
    const { data, error } = useSWR<IUiConfig>(REQUEST_KEY, fetcher, options);
 | 
			
		||||
    const [loading, setLoading] = useState(!error && !data);
 | 
			
		||||
 | 
			
		||||
    const refetch = () => {
 | 
			
		||||
 | 
			
		||||
@ -1,23 +1,27 @@
 | 
			
		||||
import useSWR, { mutate } from 'swr';
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
import { formatApiPath } from '../../../../utils/format-path';
 | 
			
		||||
import handleErrorResponses from '../httpErrorResponseHandler';
 | 
			
		||||
 | 
			
		||||
const useUnleashContext = (revalidate = true) => {
 | 
			
		||||
const useUnleashContext = (
 | 
			
		||||
    options: SWRConfiguration = {
 | 
			
		||||
        revalidateOnFocus: true,
 | 
			
		||||
        revalidateOnReconnect: true,
 | 
			
		||||
        revalidateIfStale: true,
 | 
			
		||||
    }
 | 
			
		||||
) => {
 | 
			
		||||
    const fetcher = () => {
 | 
			
		||||
        const path = formatApiPath(`api/admin/context`);
 | 
			
		||||
        return fetch(path, {
 | 
			
		||||
            method: 'GET',
 | 
			
		||||
        }).then(handleErrorResponses('Context variables')).then(res => res.json());
 | 
			
		||||
        })
 | 
			
		||||
            .then(handleErrorResponses('Context variables'))
 | 
			
		||||
            .then(res => res.json());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const CONTEXT_CACHE_KEY = 'api/admin/context';
 | 
			
		||||
 | 
			
		||||
    const { data, error } = useSWR(CONTEXT_CACHE_KEY, fetcher, {
 | 
			
		||||
        revalidateOnFocus: revalidate,
 | 
			
		||||
        revalidateOnReconnect: revalidate,
 | 
			
		||||
        revalidateIfStale: revalidate,
 | 
			
		||||
    });
 | 
			
		||||
    const { data, error } = useSWR(CONTEXT_CACHE_KEY, fetcher, options);
 | 
			
		||||
 | 
			
		||||
    const [loading, setLoading] = useState(!error && !data);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import useSWR, { mutate } from 'swr';
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
import { formatApiPath } from '../../../../utils/format-path';
 | 
			
		||||
import { IPermission } from '../../../../interfaces/user';
 | 
			
		||||
@ -6,7 +6,7 @@ import handleErrorResponses from '../httpErrorResponseHandler';
 | 
			
		||||
 | 
			
		||||
export const USER_CACHE_KEY = `api/admin/user`;
 | 
			
		||||
 | 
			
		||||
const useUser = () => {
 | 
			
		||||
const useUser = (options: SWRConfiguration = {}) => {
 | 
			
		||||
    const fetcher = () => {
 | 
			
		||||
        const path = formatApiPath(`api/admin/user`);
 | 
			
		||||
        return fetch(path, {
 | 
			
		||||
@ -16,7 +16,7 @@ const useUser = () => {
 | 
			
		||||
            .then(res => res.json());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const { data, error } = useSWR(USER_CACHE_KEY, fetcher);
 | 
			
		||||
    const { data, error } = useSWR(USER_CACHE_KEY, fetcher, options);
 | 
			
		||||
    const [loading, setLoading] = useState(!error && !data);
 | 
			
		||||
 | 
			
		||||
    const refetch = () => {
 | 
			
		||||
 | 
			
		||||
@ -1,17 +1,19 @@
 | 
			
		||||
import useSWR, { mutate } from 'swr';
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
import { formatApiPath } from '../../../../utils/format-path';
 | 
			
		||||
import handleErrorResponses from '../httpErrorResponseHandler';
 | 
			
		||||
 | 
			
		||||
const useUsers = () => {
 | 
			
		||||
const useUsers = (options: SWRConfiguration = {}) => {
 | 
			
		||||
    const fetcher = () => {
 | 
			
		||||
        const path = formatApiPath(`api/admin/user-admin`);
 | 
			
		||||
        return fetch(path, {
 | 
			
		||||
            method: 'GET',
 | 
			
		||||
        }).then(handleErrorResponses('Users')).then(res => res.json());
 | 
			
		||||
        })
 | 
			
		||||
            .then(handleErrorResponses('Users'))
 | 
			
		||||
            .then(res => res.json());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const { data, error } = useSWR(`api/admin/user-admin`, fetcher);
 | 
			
		||||
    const { data, error } = useSWR(`api/admin/user-admin`, fetcher, options);
 | 
			
		||||
    const [loading, setLoading] = useState(!error && !data);
 | 
			
		||||
 | 
			
		||||
    const refetch = () => {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user