mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-04 13:48:56 +02: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;
|
disabled?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
classes?: any;
|
classes?: any;
|
||||||
|
defaultValue?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GeneralSelect: React.FC<ISelectMenuProps> = ({
|
const GeneralSelect: React.FC<ISelectMenuProps> = ({
|
||||||
@ -29,6 +30,7 @@ const GeneralSelect: React.FC<ISelectMenuProps> = ({
|
|||||||
label = '',
|
label = '',
|
||||||
options,
|
options,
|
||||||
onChange,
|
onChange,
|
||||||
|
defaultValue,
|
||||||
id,
|
id,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
className,
|
className,
|
||||||
@ -53,6 +55,7 @@ const GeneralSelect: React.FC<ISelectMenuProps> = ({
|
|||||||
{label}
|
{label}
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
|
defaultValue={defaultValue}
|
||||||
name={name}
|
name={name}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
@ -10,6 +10,7 @@ interface IPermissionIconButtonProps extends OverridableComponent<any> {
|
|||||||
tooltip: string;
|
tooltip: string;
|
||||||
onClick?: (e: any) => void;
|
onClick?: (e: any) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
projectId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PermissionButton: React.FC<IPermissionIconButtonProps> = ({
|
const PermissionButton: React.FC<IPermissionIconButtonProps> = ({
|
||||||
@ -18,11 +19,15 @@ const PermissionButton: React.FC<IPermissionIconButtonProps> = ({
|
|||||||
onClick,
|
onClick,
|
||||||
children,
|
children,
|
||||||
disabled,
|
disabled,
|
||||||
|
projectId,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
|
|
||||||
const access = hasAccess(permission);
|
const access = projectId
|
||||||
|
? hasAccess(permission, projectId)
|
||||||
|
: hasAccess(permission);
|
||||||
|
|
||||||
const tooltipText = access
|
const tooltipText = access
|
||||||
? tooltip
|
? tooltip
|
||||||
: "You don't have access to perform this operation";
|
: "You don't have access to perform this operation";
|
||||||
|
@ -8,6 +8,7 @@ interface IPermissionIconButtonProps extends OverridableComponent<any> {
|
|||||||
Icon: React.ElementType;
|
Icon: React.ElementType;
|
||||||
tooltip: string;
|
tooltip: string;
|
||||||
onClick?: (e: any) => void;
|
onClick?: (e: any) => void;
|
||||||
|
projectId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PermissionIconButton: React.FC<IPermissionIconButtonProps> = ({
|
const PermissionIconButton: React.FC<IPermissionIconButtonProps> = ({
|
||||||
@ -15,14 +16,18 @@ const PermissionIconButton: React.FC<IPermissionIconButtonProps> = ({
|
|||||||
Icon,
|
Icon,
|
||||||
tooltip,
|
tooltip,
|
||||||
onClick,
|
onClick,
|
||||||
|
projectId,
|
||||||
children,
|
children,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
|
|
||||||
const access = hasAccess(permission);
|
const access = projectId
|
||||||
|
? hasAccess(permission, projectId)
|
||||||
|
: hasAccess(permission);
|
||||||
|
|
||||||
const tooltipText = access
|
const tooltipText = access
|
||||||
? tooltip
|
? tooltip || ''
|
||||||
: "You don't have access to perform this operation";
|
: "You don't have access to perform this operation";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -9,6 +9,7 @@ interface IResponsiveButtonProps {
|
|||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
permission?: string;
|
permission?: string;
|
||||||
|
projectId?: string;
|
||||||
maxWidth: string;
|
maxWidth: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ const ResponsiveButton: React.FC<IResponsiveButtonProps> = ({
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
children,
|
children,
|
||||||
permission,
|
permission,
|
||||||
|
projectId,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const smallScreen = useMediaQuery(`(max-width:${maxWidth})`);
|
const smallScreen = useMediaQuery(`(max-width:${maxWidth})`);
|
||||||
@ -32,6 +34,7 @@ const ResponsiveButton: React.FC<IResponsiveButtonProps> = ({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
permission={permission}
|
permission={permission}
|
||||||
|
projectId={projectId}
|
||||||
data-loading
|
data-loading
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
@ -42,6 +45,7 @@ const ResponsiveButton: React.FC<IResponsiveButtonProps> = ({
|
|||||||
<PermissionButton
|
<PermissionButton
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
permission={permission}
|
permission={permission}
|
||||||
|
projectId={projectId}
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -70,6 +70,7 @@ const EnvironmentList = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await sortOrderAPICall(sortOrder);
|
await sortOrderAPICall(sortOrder);
|
||||||
|
refetch();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setToastData({
|
setToastData({
|
||||||
show: true,
|
show: true,
|
||||||
@ -77,13 +78,11 @@ const EnvironmentList = () => {
|
|||||||
text: e.toString(),
|
text: e.toString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
mutate(ENVIRONMENT_CACHE_KEY);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortOrderAPICall = async (sortOrder: ISortOrderPayload) => {
|
const sortOrderAPICall = async (sortOrder: ISortOrderPayload) => {
|
||||||
try {
|
try {
|
||||||
changeSortOrder(sortOrder);
|
await changeSortOrder(sortOrder);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setToastData({
|
setToastData({
|
||||||
show: true,
|
show: true,
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import { useRef } from 'react';
|
import { useContext, useRef } from 'react';
|
||||||
import {
|
import { Switch, TableCell, TableRow } from '@material-ui/core';
|
||||||
Switch,
|
|
||||||
TableCell,
|
|
||||||
TableRow,
|
|
||||||
} from '@material-ui/core';
|
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
|
|
||||||
import { useStyles } from '../FeatureToggleListNew.styles';
|
import { useStyles } from '../FeatureToggleListNew.styles';
|
||||||
@ -17,6 +13,9 @@ import FeatureStatus from '../../FeatureView2/FeatureStatus/FeatureStatus';
|
|||||||
import FeatureType from '../../FeatureView2/FeatureType/FeatureType';
|
import FeatureType from '../../FeatureView2/FeatureType/FeatureType';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import CreatedAt from './CreatedAt';
|
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 {
|
interface IFeatureToggleListNewItemProps {
|
||||||
name: string;
|
name: string;
|
||||||
@ -35,14 +34,15 @@ const FeatureToggleListNewItem = ({
|
|||||||
projectId,
|
projectId,
|
||||||
createdAt,
|
createdAt,
|
||||||
}: IFeatureToggleListNewItemProps) => {
|
}: IFeatureToggleListNewItemProps) => {
|
||||||
|
const { hasAccess } = useContext(AccessContext);
|
||||||
const { toast, setToastData } = useToast();
|
const { toast, setToastData } = useToast();
|
||||||
const { toggleFeatureByEnvironment } = useToggleFeatureByEnv(
|
const { toggleFeatureByEnvironment } = useToggleFeatureByEnv(
|
||||||
projectId,
|
projectId,
|
||||||
name,
|
name
|
||||||
);
|
);
|
||||||
|
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
|
const { refetch } = useProject(projectId);
|
||||||
|
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
@ -61,6 +61,7 @@ const FeatureToggleListNewItem = ({
|
|||||||
type: 'success',
|
type: 'success',
|
||||||
text: 'Successfully updated toggle status.',
|
text: 'Successfully updated toggle status.',
|
||||||
});
|
});
|
||||||
|
refetch();
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
setToastData({
|
setToastData({
|
||||||
@ -71,43 +72,66 @@ const FeatureToggleListNewItem = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TableRow className={styles.tableRow}>
|
<TableRow className={styles.tableRow}>
|
||||||
<TableCell className={classNames(
|
<TableCell
|
||||||
styles.tableCell,
|
className={classNames(
|
||||||
styles.tableCellStatus)} align="left" onClick={onClick}>
|
styles.tableCell,
|
||||||
|
styles.tableCellStatus
|
||||||
|
)}
|
||||||
|
align="left"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
<FeatureStatus lastSeenAt={lastSeenAt} />
|
<FeatureStatus lastSeenAt={lastSeenAt} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classNames(
|
<TableCell
|
||||||
styles.tableCell,
|
className={classNames(
|
||||||
styles.tableCellType)} align="center" onClick={onClick}>
|
styles.tableCell,
|
||||||
|
styles.tableCellType
|
||||||
|
)}
|
||||||
|
align="center"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
<FeatureType type={type} />
|
<FeatureType type={type} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classNames(
|
<TableCell
|
||||||
styles.tableCell, styles.tableCellName)} align="left" onClick={onClick}>
|
className={classNames(
|
||||||
|
styles.tableCell,
|
||||||
|
styles.tableCellName
|
||||||
|
)}
|
||||||
|
align="left"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
<span data-loading>{name}</span>
|
<span data-loading>{name}</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classNames(
|
<TableCell
|
||||||
styles.tableCell, styles.tableCellCreated)} align="left" onClick={onClick}>
|
className={classNames(
|
||||||
<CreatedAt time={createdAt} />
|
styles.tableCell,
|
||||||
|
styles.tableCellCreated
|
||||||
|
)}
|
||||||
|
align="left"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<CreatedAt time={createdAt} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
|
|
||||||
{environments.map((env: IEnvironments) => {
|
{environments.map((env: IEnvironments) => {
|
||||||
return (
|
return (
|
||||||
<TableCell
|
<TableCell
|
||||||
className={classNames(
|
className={classNames(
|
||||||
styles.tableCell,
|
styles.tableCell,
|
||||||
styles.tableCellEnv)}
|
styles.tableCellEnv
|
||||||
|
)}
|
||||||
align="center"
|
align="center"
|
||||||
key={env.name}
|
key={env.name}
|
||||||
>
|
>
|
||||||
<span data-loading style={{ display: 'block' }}>
|
<span data-loading style={{ display: 'block' }}>
|
||||||
<Switch
|
<Switch
|
||||||
checked={env.enabled}
|
checked={env.enabled}
|
||||||
|
disabled={
|
||||||
|
!hasAccess(UPDATE_FEATURE, projectId)
|
||||||
|
}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onClick={handleToggle.bind(this, env)}
|
onClick={handleToggle.bind(this, env)}
|
||||||
/>
|
/>
|
||||||
|
@ -40,6 +40,7 @@ const FeatureOverviewStale = () => {
|
|||||||
<PermissionButton
|
<PermissionButton
|
||||||
onClick={() => setOpenStaleDialog(true)}
|
onClick={() => setOpenStaleDialog(true)}
|
||||||
permission={UPDATE_FEATURE}
|
permission={UPDATE_FEATURE}
|
||||||
|
projectId={projectId}
|
||||||
tooltip="Flip status"
|
tooltip="Flip status"
|
||||||
variant="text"
|
variant="text"
|
||||||
>
|
>
|
||||||
|
@ -37,6 +37,7 @@ const FeatureOverviewStrategies = () => {
|
|||||||
tooltip="Add new strategy"
|
tooltip="Add new strategy"
|
||||||
className={styles.addStrategyButton}
|
className={styles.addStrategyButton}
|
||||||
component={Link}
|
component={Link}
|
||||||
|
projectId={projectId}
|
||||||
to={`/projects/${projectId}/features2/${featureId}/strategies?addStrategy=true`}
|
to={`/projects/${projectId}/features2/${featureId}/strategies?addStrategy=true`}
|
||||||
>
|
>
|
||||||
Add new strategy
|
Add new strategy
|
||||||
|
@ -32,7 +32,7 @@ const FeatureOverviewTags = () => {
|
|||||||
type: '',
|
type: '',
|
||||||
});
|
});
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const { featureId } = useParams<IFeatureViewParams>();
|
const { featureId, projectId } = useParams<IFeatureViewParams>();
|
||||||
const { tags, refetch } = useTags(featureId);
|
const { tags, refetch } = useTags(featureId);
|
||||||
const { tagTypes } = useTagTypes();
|
const { tagTypes } = useTagTypes();
|
||||||
const { deleteTagFromFeature } = useFeatureApi();
|
const { deleteTagFromFeature } = useFeatureApi();
|
||||||
@ -131,6 +131,7 @@ const FeatureOverviewTags = () => {
|
|||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
onClick={() => setOpenTagDialog(true)}
|
onClick={() => setOpenTagDialog(true)}
|
||||||
permission={UPDATE_FEATURE}
|
permission={UPDATE_FEATURE}
|
||||||
|
projectId={projectId}
|
||||||
tooltip="Add tag"
|
tooltip="Add tag"
|
||||||
data-loading
|
data-loading
|
||||||
>
|
>
|
||||||
|
@ -89,6 +89,7 @@ const FeatureSettingsMetadata = () => {
|
|||||||
tooltip="Save changes"
|
tooltip="Save changes"
|
||||||
permission={UPDATE_FEATURE}
|
permission={UPDATE_FEATURE}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
|
projectId={projectId}
|
||||||
>
|
>
|
||||||
Save changes
|
Save changes
|
||||||
</PermissionButton>
|
</PermissionButton>
|
||||||
|
@ -78,6 +78,7 @@ const FeatureSettingsProject = () => {
|
|||||||
permission={UPDATE_FEATURE}
|
permission={UPDATE_FEATURE}
|
||||||
tooltip="Update feature"
|
tooltip="Update feature"
|
||||||
onClick={() => setShowConfirmDialog(true)}
|
onClick={() => setShowConfirmDialog(true)}
|
||||||
|
projectId={projectId}
|
||||||
>
|
>
|
||||||
Save changes
|
Save changes
|
||||||
</PermissionButton>
|
</PermissionButton>
|
||||||
|
@ -132,6 +132,7 @@ const FeatureStrategiesEnvironmentList = ({
|
|||||||
const strategiesContainerClasses = classnames({
|
const strategiesContainerClasses = classnames({
|
||||||
[styles.strategiesContainer]: !expandedSidebar,
|
[styles.strategiesContainer]: !expandedSidebar,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={!configureNewStrategy}
|
condition={!configureNewStrategy}
|
||||||
|
@ -377,6 +377,7 @@ const FeatureStrategiesEnvironments = () => {
|
|||||||
}
|
}
|
||||||
Icon={Add}
|
Icon={Add}
|
||||||
maxWidth="700px"
|
maxWidth="700px"
|
||||||
|
projectId={projectId}
|
||||||
permission={UPDATE_FEATURE}
|
permission={UPDATE_FEATURE}
|
||||||
>
|
>
|
||||||
Add new strategy
|
Add new strategy
|
||||||
@ -390,7 +391,7 @@ const FeatureStrategiesEnvironments = () => {
|
|||||||
onChange={(_, tabId) => {
|
onChange={(_, tabId) => {
|
||||||
setActiveTab(tabId);
|
setActiveTab(tabId);
|
||||||
setActiveEnvironment(
|
setActiveEnvironment(
|
||||||
featureCache?.environments[tabId]
|
feature?.environments[tabId]
|
||||||
);
|
);
|
||||||
history.replace(history.location.pathname);
|
history.replace(history.location.pathname);
|
||||||
}}
|
}}
|
||||||
|
@ -3,7 +3,6 @@ import * as jsonpatch from 'fast-json-patch';
|
|||||||
|
|
||||||
import styles from './variants.module.scss';
|
import styles from './variants.module.scss';
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableCell,
|
TableCell,
|
||||||
@ -29,6 +28,7 @@ import useToast from '../../../../../hooks/useToast';
|
|||||||
import { updateWeight } from '../../../../common/util';
|
import { updateWeight } from '../../../../common/util';
|
||||||
import cloneDeep from 'lodash.clonedeep';
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
import useDeleteVariantMarkup from './FeatureVariantsListItem/useDeleteVariantMarkup';
|
import useDeleteVariantMarkup from './FeatureVariantsListItem/useDeleteVariantMarkup';
|
||||||
|
import PermissionButton from '../../../../common/PermissionButton/PermissionButton';
|
||||||
|
|
||||||
const FeatureOverviewVariants = () => {
|
const FeatureOverviewVariants = () => {
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
@ -275,28 +275,24 @@ const FeatureOverviewVariants = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<ConditionallyRender
|
|
||||||
condition={editable}
|
<div>
|
||||||
show={
|
<PermissionButton
|
||||||
<div>
|
onClick={() => {
|
||||||
<Button
|
setEditing(false);
|
||||||
title="Add variant"
|
setEditVariant({});
|
||||||
onClick={() => {
|
setShowAddVariant(true);
|
||||||
setEditing(false);
|
}}
|
||||||
setEditVariant({});
|
className={styles.addVariantButton}
|
||||||
setShowAddVariant(true);
|
data-test={'ADD_VARIANT_BUTTON'}
|
||||||
}}
|
permission={UPDATE_FEATURE}
|
||||||
variant="contained"
|
projectId={projectId}
|
||||||
color="primary"
|
>
|
||||||
className={styles.addVariantButton}
|
Add variant
|
||||||
data-test={'ADD_VARIANT_BUTTON'}
|
</PermissionButton>
|
||||||
>
|
{renderStickiness()}
|
||||||
Add variant
|
</div>
|
||||||
</Button>
|
|
||||||
{renderStickiness()}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<AddVariant
|
<AddVariant
|
||||||
showDialog={showAddVariant}
|
showDialog={showAddVariant}
|
||||||
closeDialog={handleCloseAddVariant}
|
closeDialog={handleCloseAddVariant}
|
||||||
@ -320,187 +316,3 @@ const FeatureOverviewVariants = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default 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}>
|
<div className={styles.actions}>
|
||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
permission={UPDATE_FEATURE}
|
permission={UPDATE_FEATURE}
|
||||||
|
projectId={projectId}
|
||||||
tooltip="Copy"
|
tooltip="Copy"
|
||||||
data-loading
|
data-loading
|
||||||
component={Link}
|
component={Link}
|
||||||
@ -140,6 +141,7 @@ const FeatureView2 = () => {
|
|||||||
</PermissionIconButton>
|
</PermissionIconButton>
|
||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
permission={UPDATE_FEATURE}
|
permission={UPDATE_FEATURE}
|
||||||
|
projectId={projectId}
|
||||||
tooltip="Archive feature toggle"
|
tooltip="Archive feature toggle"
|
||||||
data-loading
|
data-loading
|
||||||
onClick={() => setShowDelDialog(true)}
|
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) {
|
if (env.enabled) {
|
||||||
handleToggleEnvironmentOff();
|
await handleToggleEnvironmentOff();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleToggleEnvironmentOn();
|
await handleToggleEnvironmentOn();
|
||||||
};
|
};
|
||||||
|
|
||||||
const iconContainerClasses = classNames(styles.iconContainer, {
|
const iconContainerClasses = classNames(styles.iconContainer, {
|
||||||
@ -100,8 +100,12 @@ const FeatureViewEnvironment: FC<IFeatureViewEnvironmentProps> = ({
|
|||||||
<div className={iconContainerClasses}>
|
<div className={iconContainerClasses}>
|
||||||
<Cloud className={iconClasses} />
|
<Cloud className={iconClasses} />
|
||||||
</div>
|
</div>
|
||||||
<Tooltip title={`${env.name} is an environment of type "${env.type}".`}>
|
<Tooltip
|
||||||
<p className={styles.environmentBadgeParagraph}>{env.name}</p>
|
title={`${env.name} is an environment of type "${env.type}".`}
|
||||||
|
>
|
||||||
|
<p className={styles.environmentBadgeParagraph}>
|
||||||
|
{env.name}
|
||||||
|
</p>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -117,11 +121,15 @@ const FeatureViewEnvironment: FC<IFeatureViewEnvironmentProps> = ({
|
|||||||
onChange={toggleEnvironment}
|
onChange={toggleEnvironment}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
<span className={styles.toggleText}>
|
<span className={styles.toggleText}>
|
||||||
{env.name}{' environment is '}
|
{env.name}
|
||||||
<strong>{env.enabled ? 'enabled' : 'disabled'}</strong>
|
{' environment is '}
|
||||||
|
<strong>
|
||||||
|
{env.enabled ? 'enabled' : 'disabled'}
|
||||||
|
</strong>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
} />
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.environmentStatus} data-loading>
|
<div className={styles.environmentStatus} data-loading>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
@ -138,7 +146,8 @@ const FeatureViewEnvironment: FC<IFeatureViewEnvironmentProps> = ({
|
|||||||
Configure strategies for {env.name}
|
Configure strategies for {env.name}
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
} />
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -87,8 +87,11 @@ const CreateFeature = ({
|
|||||||
</div>
|
</div>
|
||||||
<section className={styles.formContainer}>
|
<section className={styles.formContainer}>
|
||||||
<ProjectSelect
|
<ProjectSelect
|
||||||
value={project || input.project}
|
value={input.project}
|
||||||
onChange={v => setValue('project', v.target.value)}
|
defaultValue={project}
|
||||||
|
onChange={v => {
|
||||||
|
setValue('project', v.target.value);
|
||||||
|
}}
|
||||||
filter={projectFilterGenerator(user, CREATE_FEATURE)}
|
filter={projectFilterGenerator(user, CREATE_FEATURE)}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
@ -11,7 +11,8 @@ class ProjectSelectComponent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { value, projects, onChange, enabled, filter } = this.props;
|
const { value, projects, onChange, enabled, filter, defaultValue } =
|
||||||
|
this.props;
|
||||||
|
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return null;
|
return null;
|
||||||
@ -43,6 +44,7 @@ class ProjectSelectComponent extends Component {
|
|||||||
return (
|
return (
|
||||||
<GeneralSelect
|
<GeneralSelect
|
||||||
label="Project"
|
label="Project"
|
||||||
|
defaultValue={defaultValue}
|
||||||
options={options}
|
options={options}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
@ -55,26 +55,24 @@ const ProjectFeatureToggles = ({
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ConditionallyRender
|
|
||||||
condition={hasAccess(CREATE_FEATURE, id)}
|
<ResponsiveButton
|
||||||
show={
|
onClick={() =>
|
||||||
<ResponsiveButton
|
history.push(
|
||||||
onClick={() =>
|
getCreateTogglePath(
|
||||||
history.push(
|
id,
|
||||||
getCreateTogglePath(
|
uiConfig.flags.E
|
||||||
id,
|
)
|
||||||
uiConfig.flags.E
|
)
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
maxWidth="700px"
|
|
||||||
tooltip="New feature toggle"
|
|
||||||
Icon={Add}
|
|
||||||
>
|
|
||||||
New feature toggle
|
|
||||||
</ResponsiveButton>
|
|
||||||
}
|
}
|
||||||
/>
|
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 => ({
|
export const useStyles = makeStyles(theme => ({
|
||||||
projectInfo: {
|
projectInfo: {
|
||||||
width: '275px',
|
width: '225px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
@ -8,7 +8,9 @@ interface ProjectOverviewProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ProjectOverview = ({ projectId }: ProjectOverviewProps) => {
|
const ProjectOverview = ({ projectId }: ProjectOverviewProps) => {
|
||||||
const { project, loading } = useProject(projectId);
|
const { project, loading } = useProject(projectId, {
|
||||||
|
refreshInterval: 10000,
|
||||||
|
});
|
||||||
const { members, features, health } = project;
|
const { members, features, health } = project;
|
||||||
const styles = useStyles();
|
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 { Dispatch, SetStateAction } from 'react';
|
||||||
import { useStyles } from './ProjectCard.styles';
|
import { useStyles } from './ProjectCard.styles';
|
||||||
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
||||||
|
|
||||||
import { ReactComponent as ProjectIcon } from '../../../assets/icons/projectIcon.svg';
|
import { ReactComponent as ProjectIcon } from '../../../assets/icons/projectIcon.svg';
|
||||||
import ConditionallyRender from '../../common/ConditionallyRender';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import Dialogue from '../../common/Dialogue';
|
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 useProjects from '../../../hooks/api/getters/useProjects/useProjects';
|
||||||
import { Delete, Edit } from '@material-ui/icons';
|
import { Delete, Edit } from '@material-ui/icons';
|
||||||
import { getProjectEditPath } from '../../../utils/route-path-helpers';
|
import { getProjectEditPath } from '../../../utils/route-path-helpers';
|
||||||
|
import PermissionIconButton from '../../common/PermissionIconButton/PermissionIconButton';
|
||||||
|
import { UPDATE_PROJECT } from '../../../store/project/actions';
|
||||||
interface IProjectCardProps {
|
interface IProjectCardProps {
|
||||||
name: string;
|
name: string;
|
||||||
featureCount: number;
|
featureCount: number;
|
||||||
@ -53,18 +54,17 @@ const ProjectCard = ({
|
|||||||
<Card className={styles.projectCard} onMouseEnter={onHover}>
|
<Card className={styles.projectCard} onMouseEnter={onHover}>
|
||||||
<div className={styles.header} data-loading>
|
<div className={styles.header} data-loading>
|
||||||
<h2 className={styles.title}>{name}</h2>
|
<h2 className={styles.title}>{name}</h2>
|
||||||
<ConditionallyRender
|
|
||||||
condition={true}
|
<PermissionIconButton
|
||||||
show={
|
permission={UPDATE_PROJECT}
|
||||||
<IconButton
|
projectId={id}
|
||||||
className={styles.actionsBtn}
|
className={styles.actionsBtn}
|
||||||
data-loading
|
data-loading
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<MoreVertIcon />
|
<MoreVertIcon />
|
||||||
</IconButton>
|
</PermissionIconButton>
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Menu
|
<Menu
|
||||||
id="project-card-menu"
|
id="project-card-menu"
|
||||||
open={Boolean(anchorEl)}
|
open={Boolean(anchorEl)}
|
||||||
|
@ -135,22 +135,16 @@ const ProjectListNew = () => {
|
|||||||
<HeaderTitle
|
<HeaderTitle
|
||||||
title="Projects"
|
title="Projects"
|
||||||
actions={
|
actions={
|
||||||
<ConditionallyRender
|
<ResponsiveButton
|
||||||
condition={hasAccess(CREATE_PROJECT)}
|
Icon={Add}
|
||||||
show={
|
onClick={() => history.push('/projects/create')}
|
||||||
<ResponsiveButton
|
maxWidth="700px"
|
||||||
Icon={Add}
|
permission={CREATE_PROJECT}
|
||||||
onClick={() =>
|
tooltip={createButtonData.title}
|
||||||
history.push('/projects/create')
|
disabled={createButtonData.disabled}
|
||||||
}
|
>
|
||||||
maxWidth="700px"
|
Add new project
|
||||||
tooltip={createButtonData.title}
|
</ResponsiveButton>
|
||||||
disabled={createButtonData.disabled}
|
|
||||||
>
|
|
||||||
Add new project
|
|
||||||
</ResponsiveButton>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@ import useUiConfig from '../../hooks/api/getters/useUiConfig/useUiConfig';
|
|||||||
import { Alert } from '@material-ui/lab';
|
import { Alert } from '@material-ui/lab';
|
||||||
import { FormEvent } from 'react-router/node_modules/@types/react';
|
import { FormEvent } from 'react-router/node_modules/@types/react';
|
||||||
import useLoading from '../../hooks/useLoading';
|
import useLoading from '../../hooks/useLoading';
|
||||||
|
import PermissionButton from '../common/PermissionButton/PermissionButton';
|
||||||
|
import { UPDATE_PROJECT } from '../../store/project/actions';
|
||||||
|
|
||||||
interface ProjectFormComponentProps {
|
interface ProjectFormComponentProps {
|
||||||
editMode: boolean;
|
editMode: boolean;
|
||||||
@ -106,7 +108,7 @@ const ProjectFormComponent = (props: ProjectFormComponentProps) => {
|
|||||||
<PageContent
|
<PageContent
|
||||||
headerContent={
|
headerContent={
|
||||||
<HeaderTitle
|
<HeaderTitle
|
||||||
title={`${submitText} ${project?.name} project`}
|
title={`${submitText} ${props.project?.name} project`}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -189,7 +191,9 @@ const ProjectFormComponent = (props: ProjectFormComponentProps) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={hasAccess(CREATE_PROJECT)}
|
condition={
|
||||||
|
hasAccess(CREATE_PROJECT) && !editMode
|
||||||
|
}
|
||||||
show={
|
show={
|
||||||
<div className={styles.formButtons}>
|
<div className={styles.formButtons}>
|
||||||
<FormButtons
|
<FormButtons
|
||||||
@ -199,6 +203,20 @@ const ProjectFormComponent = (props: ProjectFormComponentProps) => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={editMode}
|
||||||
|
show={
|
||||||
|
<PermissionButton
|
||||||
|
permission={UPDATE_PROJECT}
|
||||||
|
projectId={props.project.id}
|
||||||
|
type="submit"
|
||||||
|
style={{ marginTop: '1rem' }}
|
||||||
|
>
|
||||||
|
Update project
|
||||||
|
</PermissionButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import useSWR, { mutate } from 'swr';
|
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
|
||||||
const useApiTokens = () => {
|
const useApiTokens = (options: SWRConfiguration = {}) => {
|
||||||
const fetcher = async () => {
|
const fetcher = async () => {
|
||||||
const path = formatApiPath(`api/admin/api-tokens`);
|
const path = formatApiPath(`api/admin/api-tokens`);
|
||||||
const res = await fetch(path, {
|
const res = await fetch(path, {
|
||||||
@ -14,7 +14,7 @@ const useApiTokens = () => {
|
|||||||
|
|
||||||
const KEY = `api/admin/api-tokens`;
|
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 [loading, setLoading] = useState(!error && !data);
|
||||||
|
|
||||||
const refetch = () => {
|
const refetch = () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import useSWR, { mutate } from 'swr';
|
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { IEnvironmentResponse } from '../../../../interfaces/environments';
|
import { IEnvironmentResponse } from '../../../../interfaces/environments';
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
@ -6,17 +6,20 @@ import handleErrorResponses from '../httpErrorResponseHandler';
|
|||||||
|
|
||||||
export const ENVIRONMENT_CACHE_KEY = `api/admin/environments`;
|
export const ENVIRONMENT_CACHE_KEY = `api/admin/environments`;
|
||||||
|
|
||||||
const useEnvironments = () => {
|
const useEnvironments = (options: SWRConfiguration = {}) => {
|
||||||
const fetcher = () => {
|
const fetcher = () => {
|
||||||
const path = formatApiPath(`api/admin/environments`);
|
const path = formatApiPath(`api/admin/environments`);
|
||||||
return fetch(path, {
|
return fetch(path, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
}).then(handleErrorResponses('Environments')).then(res => res.json());
|
})
|
||||||
|
.then(handleErrorResponses('Environments'))
|
||||||
|
.then(res => res.json());
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data, error } = useSWR<IEnvironmentResponse>(
|
const { data, error } = useSWR<IEnvironmentResponse>(
|
||||||
ENVIRONMENT_CACHE_KEY,
|
ENVIRONMENT_CACHE_KEY,
|
||||||
fetcher
|
fetcher,
|
||||||
|
options
|
||||||
);
|
);
|
||||||
const [loading, setLoading] = useState(!error && !data);
|
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 { useState, useEffect } from 'react';
|
||||||
|
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
@ -6,17 +6,10 @@ import { IFeatureToggle } from '../../../../interfaces/featureToggle';
|
|||||||
import { defaultFeature } from './defaultFeature';
|
import { defaultFeature } from './defaultFeature';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
|
||||||
interface IUseFeatureOptions {
|
|
||||||
refreshInterval?: number;
|
|
||||||
revalidateOnFocus?: boolean;
|
|
||||||
revalidateOnReconnect?: boolean;
|
|
||||||
revalidateIfStale?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useFeature = (
|
const useFeature = (
|
||||||
projectId: string,
|
projectId: string,
|
||||||
id: string,
|
id: string,
|
||||||
options: IUseFeatureOptions = {}
|
options: SWRConfiguration = {}
|
||||||
) => {
|
) => {
|
||||||
const fetcher = async () => {
|
const fetcher = async () => {
|
||||||
const path = formatApiPath(
|
const path = formatApiPath(
|
||||||
@ -24,7 +17,9 @@ const useFeature = (
|
|||||||
);
|
);
|
||||||
return fetch(path, {
|
return fetch(path, {
|
||||||
method: 'GET',
|
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}`;
|
const FEATURE_CACHE_KEY = `api/admin/projects/${projectId}/features/${id}`;
|
||||||
|
@ -1,24 +1,22 @@
|
|||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import useSWR, { mutate } from 'swr';
|
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
||||||
import { IFeatureMetrics } from '../../../../interfaces/featureToggle';
|
import { IFeatureMetrics } from '../../../../interfaces/featureToggle';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
|
||||||
interface IUseFeatureMetricsOptions {
|
|
||||||
refreshInterval?: number;
|
|
||||||
revalidateOnFocus?: boolean;
|
|
||||||
revalidateOnReconnect?: boolean;
|
|
||||||
revalidateIfStale?: boolean;
|
|
||||||
revalidateOnMount?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emptyMetrics = { lastHourUsage: [], seenApplications: [] };
|
const emptyMetrics = { lastHourUsage: [], seenApplications: [] };
|
||||||
|
|
||||||
const useFeatureMetrics = (projectId: string, featureId: string, options: IUseFeatureMetricsOptions = {}) => {
|
const useFeatureMetrics = (
|
||||||
|
projectId: string,
|
||||||
|
featureId: string,
|
||||||
|
options: SWRConfiguration = {}
|
||||||
|
) => {
|
||||||
const fetcher = async () => {
|
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, {
|
const res = await fetch(path, {
|
||||||
method: 'GET'
|
method: 'GET',
|
||||||
}).then(handleErrorResponses('feature metrics'));
|
}).then(handleErrorResponses('feature metrics'));
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
return res.json();
|
return res.json();
|
||||||
@ -32,7 +30,7 @@ const useFeatureMetrics = (projectId: string, featureId: string, options: IUseFe
|
|||||||
FEATURE_METRICS_CACHE_KEY,
|
FEATURE_METRICS_CACHE_KEY,
|
||||||
fetcher,
|
fetcher,
|
||||||
{
|
{
|
||||||
...options
|
...options,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -51,7 +49,7 @@ const useFeatureMetrics = (projectId: string, featureId: string, options: IUseFe
|
|||||||
error,
|
error,
|
||||||
loading,
|
loading,
|
||||||
refetch,
|
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 { useState, useEffect } from 'react';
|
||||||
|
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
import { IFeatureStrategy } from '../../../../interfaces/strategy';
|
import { IFeatureStrategy } from '../../../../interfaces/strategy';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
|
||||||
interface IUseFeatureOptions {
|
|
||||||
refreshInterval?: number;
|
|
||||||
revalidateOnFocus?: boolean;
|
|
||||||
revalidateOnReconnect?: boolean;
|
|
||||||
revalidateIfStale?: boolean;
|
|
||||||
revalidateOnMount?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useFeatureStrategy = (
|
const useFeatureStrategy = (
|
||||||
projectId: string,
|
projectId: string,
|
||||||
featureId: string,
|
featureId: string,
|
||||||
environmentId: string,
|
environmentId: string,
|
||||||
strategyId: string,
|
strategyId: string,
|
||||||
options: IUseFeatureOptions
|
options: SWRConfiguration
|
||||||
) => {
|
) => {
|
||||||
const fetcher = () => {
|
const fetcher = () => {
|
||||||
const path = formatApiPath(
|
const path = formatApiPath(
|
||||||
@ -26,7 +18,9 @@ const useFeatureStrategy = (
|
|||||||
);
|
);
|
||||||
return fetch(path, {
|
return fetch(path, {
|
||||||
method: 'GET',
|
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;
|
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 { useState, useEffect } from 'react';
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
import { IFeatureType } from '../../../../interfaces/featureTypes';
|
import { IFeatureType } from '../../../../interfaces/featureTypes';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
|
||||||
const useFeatureTypes = () => {
|
const useFeatureTypes = (options: SWRConfiguration = {}) => {
|
||||||
const fetcher = async () => {
|
const fetcher = async () => {
|
||||||
const path = formatApiPath(`api/admin/feature-types`);
|
const path = formatApiPath(`api/admin/feature-types`);
|
||||||
const res = await fetch(path, {
|
const res = await fetch(path, {
|
||||||
@ -15,7 +15,7 @@ const useFeatureTypes = () => {
|
|||||||
|
|
||||||
const KEY = `api/admin/feature-types`;
|
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 [loading, setLoading] = useState(!error && !data);
|
||||||
|
|
||||||
const refetch = () => {
|
const refetch = () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import useSWR, { mutate } from 'swr';
|
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { IProjectHealthReport } from '../../../../interfaces/project';
|
import { IProjectHealthReport } from '../../../../interfaces/project';
|
||||||
import { fallbackProject } from '../useProject/fallbackProject';
|
import { fallbackProject } from '../useProject/fallbackProject';
|
||||||
@ -6,19 +6,21 @@ import useSort from '../../../useSort';
|
|||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
|
||||||
const useHealthReport = (id: string) => {
|
const useHealthReport = (id: string, options: SWRConfiguration = {}) => {
|
||||||
const KEY = `api/admin/projects/${id}/health-report`;
|
const KEY = `api/admin/projects/${id}/health-report`;
|
||||||
|
|
||||||
const fetcher = () => {
|
const fetcher = () => {
|
||||||
const path = formatApiPath(`api/admin/projects/${id}/health-report`);
|
const path = formatApiPath(`api/admin/projects/${id}/health-report`);
|
||||||
return fetch(path, {
|
return fetch(path, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
}).then(handleErrorResponses('Health report')).then(res => res.json());
|
})
|
||||||
|
.then(handleErrorResponses('Health report'))
|
||||||
|
.then(res => res.json());
|
||||||
};
|
};
|
||||||
|
|
||||||
const [sort] = useSort();
|
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 [loading, setLoading] = useState(!error && !data);
|
||||||
|
|
||||||
const refetch = () => {
|
const refetch = () => {
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import useSWR, { mutate } from 'swr';
|
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { getProjectFetcher } from './getProjectFetcher';
|
import { getProjectFetcher } from './getProjectFetcher';
|
||||||
import { IProject } from '../../../../interfaces/project';
|
import { IProject } from '../../../../interfaces/project';
|
||||||
import { fallbackProject } from './fallbackProject';
|
import { fallbackProject } from './fallbackProject';
|
||||||
import useSort from '../../../useSort';
|
import useSort from '../../../useSort';
|
||||||
|
|
||||||
const useProject = (id: string) => {
|
const useProject = (id: string, options: SWRConfiguration = {}) => {
|
||||||
const { KEY, fetcher } = getProjectFetcher(id);
|
const { KEY, fetcher } = getProjectFetcher(id);
|
||||||
const [sort] = useSort();
|
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 [loading, setLoading] = useState(!error && !data);
|
||||||
|
|
||||||
const refetch = () => {
|
const refetch = () => {
|
||||||
|
@ -1,21 +1,27 @@
|
|||||||
import useSWR, { mutate } from 'swr';
|
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
|
|
||||||
import { IProjectCard } from '../../../../interfaces/project';
|
import { IProjectCard } from '../../../../interfaces/project';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
|
||||||
const useProjects = () => {
|
const useProjects = (options: SWRConfiguration = {}) => {
|
||||||
const fetcher = () => {
|
const fetcher = () => {
|
||||||
const path = formatApiPath(`api/admin/projects`);
|
const path = formatApiPath(`api/admin/projects`);
|
||||||
return fetch(path, {
|
return fetch(path, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
}).then(handleErrorResponses('Projects')).then(res => res.json());
|
})
|
||||||
|
.then(handleErrorResponses('Projects'))
|
||||||
|
.then(res => res.json());
|
||||||
};
|
};
|
||||||
|
|
||||||
const KEY = `api/admin/projects`;
|
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 [loading, setLoading] = useState(!error && !data);
|
||||||
|
|
||||||
const refetch = () => {
|
const refetch = () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import useSWR from 'swr';
|
import useSWR, { SWRConfiguration } from 'swr';
|
||||||
import useQueryParams from '../../../useQueryParams';
|
import useQueryParams from '../../../useQueryParams';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
@ -8,13 +8,15 @@ const getFetcher = (token: string) => () => {
|
|||||||
const path = formatApiPath(`auth/reset/validate?token=${token}`);
|
const path = formatApiPath(`auth/reset/validate?token=${token}`);
|
||||||
return fetch(path, {
|
return fetch(path, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
}).then(handleErrorResponses('Password reset')).then(res => res.json());
|
})
|
||||||
|
.then(handleErrorResponses('Password reset'))
|
||||||
|
.then(res => res.json());
|
||||||
};
|
};
|
||||||
|
|
||||||
const INVALID_TOKEN_ERROR = 'InvalidTokenError';
|
const INVALID_TOKEN_ERROR = 'InvalidTokenError';
|
||||||
const USED_TOKEN_ERROR = 'UsedTokenError';
|
const USED_TOKEN_ERROR = 'UsedTokenError';
|
||||||
|
|
||||||
const useResetPassword = () => {
|
const useResetPassword = (options: SWRConfiguration = {}) => {
|
||||||
const query = useQueryParams();
|
const query = useQueryParams();
|
||||||
const initialToken = query.get('token') || '';
|
const initialToken = query.get('token') || '';
|
||||||
const [token, setToken] = useState(initialToken);
|
const [token, setToken] = useState(initialToken);
|
||||||
@ -22,7 +24,7 @@ const useResetPassword = () => {
|
|||||||
const fetcher = getFetcher(token);
|
const fetcher = getFetcher(token);
|
||||||
|
|
||||||
const key = `auth/reset/validate?token=${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 [loading, setLoading] = useState(!error && !data);
|
||||||
|
|
||||||
const retry = () => {
|
const retry = () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import useSWR, { mutate } from 'swr';
|
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
import { IStrategy } from '../../../../interfaces/strategy';
|
import { IStrategy } from '../../../../interfaces/strategy';
|
||||||
@ -11,30 +11,41 @@ const flexibleRolloutStrategy: IStrategy = {
|
|||||||
name: 'flexibleRollout',
|
name: 'flexibleRollout',
|
||||||
displayName: 'Gradual rollout',
|
displayName: 'Gradual rollout',
|
||||||
editable: false,
|
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.',
|
description:
|
||||||
parameters: [{
|
'Roll out to a percentage of your userbase, and ensure that the experience is the same for the user on each visit.',
|
||||||
name: 'rollout', type: 'percentage', description: '', required: false
|
parameters: [
|
||||||
}, {
|
{
|
||||||
name: 'stickiness',
|
name: 'rollout',
|
||||||
type: 'string',
|
type: 'percentage',
|
||||||
description: 'Used to defined stickiness',
|
description: '',
|
||||||
required: true
|
required: false,
|
||||||
}, { name: 'groupId', type: 'string', description: '', required: true }]
|
},
|
||||||
|
{
|
||||||
|
name: 'stickiness',
|
||||||
|
type: 'string',
|
||||||
|
description: 'Used to defined stickiness',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{ name: 'groupId', type: 'string', description: '', required: true },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStrategies = () => {
|
const useStrategies = (options: SWRConfiguration = {}) => {
|
||||||
const fetcher = () => {
|
const fetcher = () => {
|
||||||
const path = formatApiPath(`api/admin/strategies`);
|
const path = formatApiPath(`api/admin/strategies`);
|
||||||
|
|
||||||
return fetch(path, {
|
return fetch(path, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
credentials: 'include'
|
credentials: 'include',
|
||||||
}).then(handleErrorResponses('Strategies')).then(res => res.json());
|
})
|
||||||
|
.then(handleErrorResponses('Strategies'))
|
||||||
|
.then(res => res.json());
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data, error } = useSWR<{ strategies: IStrategy[] }>(
|
const { data, error } = useSWR<{ strategies: IStrategy[] }>(
|
||||||
STRATEGIES_CACHE_KEY,
|
STRATEGIES_CACHE_KEY,
|
||||||
fetcher
|
fetcher,
|
||||||
|
options
|
||||||
);
|
);
|
||||||
const [loading, setLoading] = useState(!error && !data);
|
const [loading, setLoading] = useState(!error && !data);
|
||||||
|
|
||||||
@ -50,7 +61,7 @@ const useStrategies = () => {
|
|||||||
strategies: data?.strategies || [flexibleRolloutStrategy],
|
strategies: data?.strategies || [flexibleRolloutStrategy],
|
||||||
error,
|
error,
|
||||||
loading,
|
loading,
|
||||||
refetch
|
refetch,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import useSWR, { mutate } from 'swr';
|
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
import { ITagType } from '../../../../interfaces/tags';
|
import { ITagType } from '../../../../interfaces/tags';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
|
||||||
const useTagTypes = () => {
|
const useTagTypes = (options: SWRConfiguration = {}) => {
|
||||||
const fetcher = async () => {
|
const fetcher = async () => {
|
||||||
const path = formatApiPath(`api/admin/tag-types`);
|
const path = formatApiPath(`api/admin/tag-types`);
|
||||||
const res = await fetch(path, {
|
const res = await fetch(path, {
|
||||||
@ -15,7 +15,7 @@ const useTagTypes = () => {
|
|||||||
|
|
||||||
const KEY = `api/admin/tag-types`;
|
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 [loading, setLoading] = useState(!error && !data);
|
||||||
|
|
||||||
const refetch = () => {
|
const refetch = () => {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import useSWR, { mutate } from 'swr';
|
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
import { ITag } from '../../../../interfaces/tags';
|
import { ITag } from '../../../../interfaces/tags';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
|
||||||
const useTags = (featureId: string) => {
|
const useTags = (featureId: string, options: SWRConfiguration = {}) => {
|
||||||
const fetcher = async () => {
|
const fetcher = async () => {
|
||||||
const path = formatApiPath(`api/admin/features/${featureId}/tags`);
|
const path = formatApiPath(`api/admin/features/${featureId}/tags`);
|
||||||
const res = await fetch(path, {
|
const res = await fetch(path, {
|
||||||
@ -15,7 +15,7 @@ const useTags = (featureId: string) => {
|
|||||||
|
|
||||||
const KEY = `api/admin/features/${featureId}/tags`;
|
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 [loading, setLoading] = useState(!error && !data);
|
||||||
|
|
||||||
const refetch = () => {
|
const refetch = () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import useSWR, { mutate } from 'swr';
|
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
import { defaultValue } from './defaultValue';
|
import { defaultValue } from './defaultValue';
|
||||||
@ -7,17 +7,19 @@ import handleErrorResponses from '../httpErrorResponseHandler';
|
|||||||
|
|
||||||
const REQUEST_KEY = 'api/admin/ui-config';
|
const REQUEST_KEY = 'api/admin/ui-config';
|
||||||
|
|
||||||
const useUiConfig = () => {
|
const useUiConfig = (options: SWRConfiguration = {}) => {
|
||||||
const fetcher = () => {
|
const fetcher = () => {
|
||||||
const path = formatApiPath(`api/admin/ui-config`);
|
const path = formatApiPath(`api/admin/ui-config`);
|
||||||
|
|
||||||
return fetch(path, {
|
return fetch(path, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
credentials: 'include',
|
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 [loading, setLoading] = useState(!error && !data);
|
||||||
|
|
||||||
const refetch = () => {
|
const refetch = () => {
|
||||||
|
@ -1,23 +1,27 @@
|
|||||||
import useSWR, { mutate } from 'swr';
|
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
|
||||||
const useUnleashContext = (revalidate = true) => {
|
const useUnleashContext = (
|
||||||
|
options: SWRConfiguration = {
|
||||||
|
revalidateOnFocus: true,
|
||||||
|
revalidateOnReconnect: true,
|
||||||
|
revalidateIfStale: true,
|
||||||
|
}
|
||||||
|
) => {
|
||||||
const fetcher = () => {
|
const fetcher = () => {
|
||||||
const path = formatApiPath(`api/admin/context`);
|
const path = formatApiPath(`api/admin/context`);
|
||||||
return fetch(path, {
|
return fetch(path, {
|
||||||
method: 'GET',
|
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 CONTEXT_CACHE_KEY = 'api/admin/context';
|
||||||
|
|
||||||
const { data, error } = useSWR(CONTEXT_CACHE_KEY, fetcher, {
|
const { data, error } = useSWR(CONTEXT_CACHE_KEY, fetcher, options);
|
||||||
revalidateOnFocus: revalidate,
|
|
||||||
revalidateOnReconnect: revalidate,
|
|
||||||
revalidateIfStale: revalidate,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [loading, setLoading] = useState(!error && !data);
|
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 { useState, useEffect } from 'react';
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
import { IPermission } from '../../../../interfaces/user';
|
import { IPermission } from '../../../../interfaces/user';
|
||||||
@ -6,7 +6,7 @@ import handleErrorResponses from '../httpErrorResponseHandler';
|
|||||||
|
|
||||||
export const USER_CACHE_KEY = `api/admin/user`;
|
export const USER_CACHE_KEY = `api/admin/user`;
|
||||||
|
|
||||||
const useUser = () => {
|
const useUser = (options: SWRConfiguration = {}) => {
|
||||||
const fetcher = () => {
|
const fetcher = () => {
|
||||||
const path = formatApiPath(`api/admin/user`);
|
const path = formatApiPath(`api/admin/user`);
|
||||||
return fetch(path, {
|
return fetch(path, {
|
||||||
@ -16,7 +16,7 @@ const useUser = () => {
|
|||||||
.then(res => res.json());
|
.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 [loading, setLoading] = useState(!error && !data);
|
||||||
|
|
||||||
const refetch = () => {
|
const refetch = () => {
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
import useSWR, { mutate } from 'swr';
|
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { formatApiPath } from '../../../../utils/format-path';
|
import { formatApiPath } from '../../../../utils/format-path';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
|
||||||
const useUsers = () => {
|
const useUsers = (options: SWRConfiguration = {}) => {
|
||||||
const fetcher = () => {
|
const fetcher = () => {
|
||||||
const path = formatApiPath(`api/admin/user-admin`);
|
const path = formatApiPath(`api/admin/user-admin`);
|
||||||
return fetch(path, {
|
return fetch(path, {
|
||||||
method: 'GET',
|
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 [loading, setLoading] = useState(!error && !data);
|
||||||
|
|
||||||
const refetch = () => {
|
const refetch = () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user