1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-04 01:18:20 +02:00

Feat/visual enhancements (#404)

* fix: stale style

* fix: execution plan styling

* fix: paths

* fix: remove console logs

* fix: snapshots

* fix: add comma

* fix: update snapshots
This commit is contained in:
Fredrik Strand Oseberg 2021-10-08 16:19:06 +02:00 committed by GitHub
parent 6fc30d3a79
commit 7da3573edb
25 changed files with 147 additions and 60 deletions

View File

@ -0,0 +1,16 @@
<svg
id="dots"
width="50px"
height="21px"
viewBox="0 0 132 58"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<g stroke="none" fill="none">
<g id="chatbot-loader" fill="#fff">
<circle id="chatbot-loader-dot1" cx="25" cy="30" r="13"></circle>
<circle id="chatbot-loader-dot2" cx="65" cy="30" r="13"></circle>
<circle id="chatbot-loader-dot3" cx="105" cy="30" r="13"></circle>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 447 B

View File

@ -3,9 +3,14 @@ import { useTheme } from '@material-ui/core';
interface IPercentageCircleProps { interface IPercentageCircleProps {
styles?: object; styles?: object;
percentage: number; percentage: number;
secondaryPieColor?: string;
} }
const PercentageCircle = ({ styles, percentage }: IPercentageCircleProps) => { const PercentageCircle = ({
styles,
percentage,
secondaryPieColor,
}: IPercentageCircleProps) => {
const theme = useTheme(); const theme = useTheme();
let circle = { let circle = {
@ -14,7 +19,9 @@ const PercentageCircle = ({ styles, percentage }: IPercentageCircleProps) => {
borderRadius: '50%', borderRadius: '50%',
color: '#fff', color: '#fff',
backgroundColor: theme.palette.grey[200], backgroundColor: theme.palette.grey[200],
backgroundImage: `conic-gradient(${theme.palette.primary.main} ${percentage}%, ${theme.palette.grey[200]} 1%)`, backgroundImage: `conic-gradient(${
theme.palette.primary.main
} ${percentage}%, ${secondaryPieColor || theme.palette.grey[200]} 1%)`,
}; };
if (percentage === 100) { if (percentage === 100) {

View File

@ -51,18 +51,20 @@ const FeatureEnvironmentMetrics = ({
return ( return (
<div className={containerClasses}> <div className={containerClasses}>
<div className={styles.headerContainer}> <div className={styles.headerContainer}>
<h2 className={styles.title}>Traffic in {metric.name}</h2> <h2 data-loading className={styles.title}>
Traffic in {metric.name}
</h2>
</div> </div>
<div className={styles.bodyContainer}> <div className={styles.bodyContainer}>
<div className={styles.textContainer}> <div className={styles.textContainer}>
<p className={styles.paragraph}> <p className={styles.paragraph} data-loading>
No metrics available for this environment. No metrics available for this environment.
</p> </p>
</div> </div>
<div className={styles.chartContainer}> <div className={styles.chartContainer}>
<PieChartIcon className={styles.icon} /> <PieChartIcon className={styles.icon} data-loading />
</div> </div>
</div> </div>
</div> </div>
@ -72,30 +74,32 @@ const FeatureEnvironmentMetrics = ({
return ( return (
<div className={containerClasses}> <div className={containerClasses}>
<div className={styles.headerContainer}> <div className={styles.headerContainer}>
<h2 className={styles.title}>Traffic in {metric.name}</h2> <h2 data-loading className={styles.title}>
Traffic in {metric.name}
</h2>
</div> </div>
<div className={styles.bodyContainer}> <div className={styles.bodyContainer}>
<div className={styles.textContainer}> <div className={styles.textContainer}>
<div className={styles.trueCountContainer}> <div className={styles.trueCountContainer}>
<div> <div>
<div className={styles.trueCount} /> <div className={styles.trueCount} data-loading />
</div> </div>
<p className={styles.paragraph}> <p className={styles.paragraph} data-loading>
{metric.yes} users received this feature {metric.yes} users received this feature
</p> </p>
</div> </div>
<div className={styles.trueCountContainer}> <div className={styles.trueCountContainer}>
<div> <div>
<div className={styles.falseCount} /> <div className={styles.falseCount} data-loading />
</div> </div>
<p className={styles.paragraph}> <p className={styles.paragraph} data-loading>
{metric.no} users did not receive this feature {metric.no} users did not receive this feature
</p> </p>
</div> </div>
</div> </div>
<div className={styles.chartContainer}> <div className={styles.chartContainer} data-loading>
<PercentageCircle <PercentageCircle
percentage={calculatePercentage()} percentage={calculatePercentage()}
styles={{ styles={{

View File

@ -18,9 +18,9 @@ const FeatureOverview = () => {
</div> </div>
<div className={styles.mainContent}> <div className={styles.mainContent}>
<div className={styles.trafficContainer}> <div className={styles.trafficContainer}>
<FeatureOverviewMetrics /> <FeatureOverviewMetrics data-loading />
</div> </div>
<FeatureOverviewStrategies /> <FeatureOverviewStrategies data-loading />
</div> </div>
</div> </div>
); );

View File

@ -22,18 +22,20 @@ const FeatureOverviewMetaData = () => {
return ( return (
<div className={classnames(styles.container)}> <div className={classnames(styles.container)}>
<div className={styles.metaDataHeader}> <div className={styles.metaDataHeader} data-loading>
<IconComponent className={styles.headerIcon} />{' '} <IconComponent className={styles.headerIcon} />{' '}
<h3 className={styles.header}> <h3 className={styles.header}>
{capitalize(type || '')} toggle {capitalize(type || '')} toggle
</h3> </h3>
</div> </div>
<div className={styles.body}> <div className={styles.body}>
<span className={styles.bodyItem}>Project: {project}</span> <span className={styles.bodyItem} data-loading>
Project: {project}
</span>
<ConditionallyRender <ConditionallyRender
condition={description} condition={description}
show={ show={
<span className={styles.bodyItem}> <span className={styles.bodyItem} data-loading>
<div>Description:</div> <div>Description:</div>
<div className={styles.descriptionContainer}> <div className={styles.descriptionContainer}>
<p>{description}</p> <p>{description}</p>
@ -47,7 +49,7 @@ const FeatureOverviewMetaData = () => {
</span> </span>
} }
elseShow={ elseShow={
<span> <span data-loading>
<div className={styles.descriptionContainer}> <div className={styles.descriptionContainer}>
No description.{' '} No description.{' '}
<IconButton <IconButton

View File

@ -17,6 +17,16 @@ export const useStyles = makeStyles(theme => ({
maxWidth: 'none', maxWidth: 'none',
}, },
}, },
status: {
height: '12.5px',
width: '12.5px',
backgroundColor: theme.palette.success.main,
borderRadius: '50%',
marginLeft: '0.5rem',
},
statusStale: {
backgroundColor: theme.palette.error.main,
},
staleHeaderContainer: { staleHeaderContainer: {
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
@ -41,6 +51,8 @@ export const useStyles = makeStyles(theme => ({
bodyItem: { bodyItem: {
margin: '0.5rem 0', margin: '0.5rem 0',
fontSize: theme.fontSizes.bodySize, fontSize: theme.fontSizes.bodySize,
display: 'flex',
alignItems: 'center',
}, },
headerIcon: { headerIcon: {
marginRight: '1rem', marginRight: '1rem',

View File

@ -3,11 +3,11 @@ import classnames from 'classnames';
import useFeature from '../../../../../hooks/api/getters/useFeature/useFeature'; import useFeature from '../../../../../hooks/api/getters/useFeature/useFeature';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { IFeatureViewParams } from '../../../../../interfaces/params'; import { IFeatureViewParams } from '../../../../../interfaces/params';
import PermissionIconButton from '../../../../common/PermissionIconButton/PermissionIconButton';
import { UPDATE_FEATURE } from '../../../../AccessProvider/permissions'; import { UPDATE_FEATURE } from '../../../../AccessProvider/permissions';
import { Check, Close } from '@material-ui/icons';
import { useState } from 'react'; import { useState } from 'react';
import StaleDialog from './StaleDialog/StaleDialog'; import StaleDialog from './StaleDialog/StaleDialog';
import PermissionButton from '../../../../common/PermissionButton/PermissionButton';
import classNames from 'classnames';
const FeatureOverviewStale = () => { const FeatureOverviewStale = () => {
const styles = useStyles(); const styles = useStyles();
@ -15,27 +15,36 @@ const FeatureOverviewStale = () => {
const { projectId, featureId } = useParams<IFeatureViewParams>(); const { projectId, featureId } = useParams<IFeatureViewParams>();
const { feature } = useFeature(projectId, featureId); const { feature } = useFeature(projectId, featureId);
const FlipStateButton = () => (feature.stale ? <Close /> : <Check />); const flipStateButtonText = () =>
feature.stale ? 'Set to active' : 'Set to stale';
const statusClasses = classNames(styles.status, {
[styles.statusStale]: feature.stale,
});
return ( return (
<div className={classnames(styles.container)}> <div className={classnames(styles.container)}>
<div className={styles.staleHeaderContainer}> <div className={styles.staleHeaderContainer}>
<div className={styles.staleHeader}> <div className={styles.staleHeader}>
<h3 className={styles.header}>Status</h3> <h3 className={styles.header} data-loading>
Status
</h3>
</div> </div>
</div> </div>
<div className={styles.body}> <div className={styles.body}>
<span className={styles.bodyItem}> <span className={styles.bodyItem} data-loading>
Feature is {feature.stale ? 'stale' : 'active'} Feature is {feature.stale ? 'stale' : 'active'}
<div className={statusClasses} />
</span> </span>
<div className={styles.staleButton}> <div className={styles.staleButton} data-loading>
<PermissionIconButton <PermissionButton
onClick={() => setOpenStaleDialog(true)} onClick={() => setOpenStaleDialog(true)}
permission={UPDATE_FEATURE} permission={UPDATE_FEATURE}
tooltip="Flip status" tooltip="Flip status"
variant="text"
> >
<FlipStateButton /> {flipStateButtonText()}
</PermissionIconButton> </PermissionButton>
</div> </div>
</div> </div>
<StaleDialog <StaleDialog

View File

@ -31,6 +31,7 @@ const FeatureOverviewEnvironment = ({
return strategies.map(strategy => { return strategies.map(strategy => {
return ( return (
<FeatureOverviewStrategyCard <FeatureOverviewStrategyCard
data-loading
strategy={strategy} strategy={strategy}
key={strategy.id} key={strategy.id}
onClick={handleClick} onClick={handleClick}

View File

@ -24,13 +24,15 @@ const FeatureOverviewStrategyCard = ({
const { parameters } = strategy; const { parameters } = strategy;
return ( return (
<button className={styles.card} onClick={onClick}> <button className={styles.card} onClick={onClick}>
<Icon className={styles.icon} /> <Icon className={styles.icon} data-loading />
<p className={styles.cardHeader}>{strategyName}</p> <p data-loading className={styles.cardHeader}>
{strategyName}
</p>
<ConditionallyRender <ConditionallyRender
condition={Boolean(parameters?.rollout) && !smallScreen} condition={Boolean(parameters?.rollout) && !smallScreen}
show={ show={
<p className={styles.rollout}> <p className={styles.rollout} data-loading>
Rolling out to {parameters?.rollout}% Rolling out to {parameters?.rollout}%
</p> </p>
} }

View File

@ -26,7 +26,9 @@ const FeatureOverviewStrategies = () => {
<div className={styles.container}> <div className={styles.container}>
<div className={styles.headerContainer}> <div className={styles.headerContainer}>
<div className={styles.headerInnerContainer}> <div className={styles.headerInnerContainer}>
<h3 className={styles.headerTitle}>Toggle Strategies</h3> <h3 className={styles.headerTitle} data-loading>
Toggle Strategies
</h3>
<div className={styles.actions}> <div className={styles.actions}>
<ResponsiveButton <ResponsiveButton
maxWidth="700px" maxWidth="700px"

View File

@ -27,7 +27,7 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
const DEFAULT_TAG: IDefaultTag = { type: 'simple', value: '' }; const DEFAULT_TAG: IDefaultTag = { type: 'simple', value: '' };
const styles = useStyles(); const styles = useStyles();
const { featureId } = useParams<IFeatureViewParams>(); const { featureId } = useParams<IFeatureViewParams>();
const { addTagToFeature } = useFeatureApi(); const { addTagToFeature, loading } = useFeatureApi();
const { refetch } = useTags(featureId); const { refetch } = useTags(featureId);
const [errors, setErrors] = useState({ tagError: '' }); const [errors, setErrors] = useState({ tagError: '' });
const { toast, setToastData } = useToast(); const { toast, setToastData } = useToast();
@ -74,6 +74,7 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
primaryButtonText="Add tag" primaryButtonText="Add tag"
title="Add tags to feature toggle" title="Add tags to feature toggle"
onClick={onSubmit} onClick={onSubmit}
disabledPrimaryButton={loading}
onClose={onCancel} onClose={onCancel}
> >
<> <>

View File

@ -99,6 +99,7 @@ const FeatureOverviewTags = () => {
<Chip <Chip
icon={tagIcon(t.type)} icon={tagIcon(t.type)}
className={styles.tagChip} className={styles.tagChip}
data-loading
label={t.value} label={t.value}
key={`${t.type}:${t.value}`} key={`${t.type}:${t.value}`}
onDelete={() => { onDelete={() => {
@ -112,7 +113,9 @@ const FeatureOverviewTags = () => {
<div className={styles.container}> <div className={styles.container}>
<div className={styles.tagheaderContainer}> <div className={styles.tagheaderContainer}>
<div className={styles.tagHeader}> <div className={styles.tagHeader}>
<h4 className={styles.tagHeaderText}>Tags</h4> <h4 className={styles.tagHeaderText} data-loading>
Tags
</h4>
</div> </div>
<AddTagDialog open={openTagDialog} setOpen={setOpenTagDialog} /> <AddTagDialog open={openTagDialog} setOpen={setOpenTagDialog} />
@ -120,6 +123,7 @@ const FeatureOverviewTags = () => {
onClick={() => setOpenTagDialog(true)} onClick={() => setOpenTagDialog(true)}
permission={UPDATE_FEATURE} permission={UPDATE_FEATURE}
tooltip="Add tag" tooltip="Add tag"
data-loading
> >
<Add /> <Add />
</PermissionIconButton> </PermissionIconButton>
@ -143,7 +147,7 @@ const FeatureOverviewTags = () => {
<ConditionallyRender <ConditionallyRender
condition={tags.length > 0} condition={tags.length > 0}
show={tags.map(renderTag)} show={tags.map(renderTag)}
elseShow={<p>No tags to display</p>} elseShow={<p data-loading>No tags to display</p>}
/> />
</div> </div>
{toast} {toast}

View File

@ -9,9 +9,10 @@ export const useStyles = makeStyles(theme => ({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
backgroundColor: theme.palette.grey[100],
}, },
header: { header: {
color: theme.palette.primary.main, color: theme.palette.grey[700],
textAlign: 'center', textAlign: 'center',
margin: '0.5rem 0', margin: '0.5rem 0',
fontSize: theme.fontSizes.bodySize, fontSize: theme.fontSizes.bodySize,

View File

@ -48,7 +48,7 @@ const FeatureStrategiesConfigure = ({
const [strategyParams, setStrategyParams] = useState( const [strategyParams, setStrategyParams] = useState(
configureNewStrategy.parameters configureNewStrategy.parameters
); );
const { addStrategyToFeature } = useFeatureStrategyApi(); const { addStrategyToFeature, loading } = useFeatureStrategyApi();
const handleCancel = () => { const handleCancel = () => {
setConfigureNewStrategy(null); setConfigureNewStrategy(null);
@ -71,6 +71,7 @@ const FeatureStrategiesConfigure = ({
}; };
try { try {
if (loading) return;
const res = await addStrategyToFeature( const res = await addStrategyToFeature(
projectId, projectId,
featureId, featureId,
@ -163,15 +164,21 @@ const FeatureStrategiesConfigure = ({
className={styles.btn} className={styles.btn}
onClick={resolveSubmit} onClick={resolveSubmit}
data-test={ADD_NEW_STRATEGY_SAVE_ID} data-test={ADD_NEW_STRATEGY_SAVE_ID}
disabled={loading}
> >
Save Save
</Button> </Button>
<Button className={styles.btn} onClick={handleCancel}> <Button
className={styles.btn}
onClick={handleCancel}
disabled={loading}
>
Cancel Cancel
</Button> </Button>
</div> </div>
<FeatureStrategiesProductionGuard <FeatureStrategiesProductionGuard
primaryButtonText="Save changes" primaryButtonText="Save changes"
loading={loading}
show={productionGuard} show={productionGuard}
onClick={() => { onClick={() => {
handleSubmit(); handleSubmit();

View File

@ -10,9 +10,6 @@ export const useStyles = makeStyles(theme => ({
backgroundColor: theme.palette.primary.light, backgroundColor: theme.palette.primary.light,
opacity: '0.75', opacity: '0.75',
}, },
strategiesContainer: {
maxWidth: '627px',
},
dropbox: { dropbox: {
textAlign: 'center', textAlign: 'center',
fontSize: theme.fontSizes.smallBody, fontSize: theme.fontSizes.smallBody,
@ -39,5 +36,6 @@ export const useStyles = makeStyles(theme => ({
}, },
environmentList: { environmentList: {
marginTop: 0, marginTop: 0,
marginBottom: 0,
}, },
})); }));

View File

@ -59,7 +59,6 @@ const FeatureStrategiesEnvironments = () => {
if (addStrategy) { if (addStrategy) {
setExpandedSidebar(true); setExpandedSidebar(true);
} }
console.log(feature);
if (!feature) return; if (!feature) return;
if (environmentTab) { if (environmentTab) {

View File

@ -6,6 +6,7 @@ interface IFeatureStrategiesProductionGuard {
onClick: () => void; onClick: () => void;
onClose: () => void; onClose: () => void;
primaryButtonText: string; primaryButtonText: string;
loading: boolean;
} }
const FeatureStrategiesProductionGuard = ({ const FeatureStrategiesProductionGuard = ({
@ -13,6 +14,7 @@ const FeatureStrategiesProductionGuard = ({
onClick, onClick,
onClose, onClose,
primaryButtonText, primaryButtonText,
loading,
}: IFeatureStrategiesProductionGuard) => { }: IFeatureStrategiesProductionGuard) => {
return ( return (
<Dialogue <Dialogue
@ -22,6 +24,7 @@ const FeatureStrategiesProductionGuard = ({
secondaryButtonText="Cancel" secondaryButtonText="Cancel"
onClick={onClick} onClick={onClick}
onClose={onClose} onClose={onClose}
disabledPrimaryButton={loading}
> >
<Alert severity="error"> <Alert severity="error">
WARNING. You are about to make changes to a production WARNING. You are about to make changes to a production

View File

@ -23,6 +23,7 @@ import {
} from '../../../../../../testIds'; } from '../../../../../../testIds';
import AccessContext from '../../../../../../contexts/AccessContext'; import AccessContext from '../../../../../../contexts/AccessContext';
import { UPDATE_FEATURE } from '../../../../../AccessProvider/permissions'; import { UPDATE_FEATURE } from '../../../../../AccessProvider/permissions';
import useFeatureApi from '../../../../../../hooks/api/actions/useFeatureApi/useFeatureApi';
interface IFeatureStrategyEditable { interface IFeatureStrategyEditable {
currentStrategy: IFeatureStrategy; currentStrategy: IFeatureStrategy;
@ -38,6 +39,7 @@ const FeatureStrategyEditable = ({
index, index,
}: IFeatureStrategyEditable) => { }: IFeatureStrategyEditable) => {
const { hasAccess } = useContext(AccessContext); const { hasAccess } = useContext(AccessContext);
const { loading } = useFeatureApi();
const { projectId, featureId } = useParams<IFeatureViewParams>(); const { projectId, featureId } = useParams<IFeatureViewParams>();
const { activeEnvironment, featureCache, dirty, setDirty } = useContext( const { activeEnvironment, featureCache, dirty, setDirty } = useContext(
@ -171,12 +173,14 @@ const FeatureStrategyEditable = ({
className={styles.editButton} className={styles.editButton}
onClick={updateFeatureStrategy} onClick={updateFeatureStrategy}
data-test={UPDATE_STRATEGY_BUTTON_ID} data-test={UPDATE_STRATEGY_BUTTON_ID}
disabled={loading}
> >
Save changes Save changes
</Button> </Button>
<Button <Button
onClick={discardChanges} onClick={discardChanges}
className={styles.editButton} className={styles.editButton}
disabled={loading}
> >
Discard changes Discard changes
</Button> </Button>

View File

@ -10,9 +10,10 @@ export const useStyles = makeStyles(theme => ({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
backgroundColor: theme.palette.grey[100],
}, },
header: { header: {
color: theme.palette.primary.main, color: theme.palette.grey[600],
textAlign: 'center', textAlign: 'center',
margin: '0.5rem 0', margin: '0.5rem 0',
fontSize: theme.fontSizes.bodySize, fontSize: theme.fontSizes.bodySize,

View File

@ -8,7 +8,6 @@ export const useStyles = makeStyles(theme => ({
marginTop: '0.5rem', marginTop: '0.5rem',
width: '100%', width: '100%',
}, },
constraint: { constraint: {
fontSize: theme.fontSizes.smallBody, fontSize: theme.fontSizes.smallBody,
alignItems: 'center;', alignItems: 'center;',

View File

@ -70,7 +70,10 @@ const FeatureStrategyExecution = ({
are included. are included.
</p> </p>
<PercentageCircle percentage={parameters[key]} /> <PercentageCircle
percentage={parameters[key]}
secondaryPieColor={'#fff'}
/>
</Fragment> </Fragment>
); );
case 'userIds': case 'userIds':

View File

@ -6,7 +6,7 @@ export const useStyles = makeStyles(theme => ({
alignItems: 'center;', alignItems: 'center;',
margin: '0.5rem 0', margin: '0.5rem 0',
display: 'flex', display: 'flex',
border: `1px solid ${theme.palette.grey[300]}`, border: `1px solid ${theme.palette.grey[600]}`,
padding: '0.2rem', padding: '0.2rem',
borderRadius: '5px', borderRadius: '5px',
width: '100%', width: '100%',

View File

@ -17,16 +17,18 @@ import FeatureStrategies from './FeatureStrategies/FeatureStrategies';
import FeatureVariants from './FeatureVariants/FeatureVariants'; import FeatureVariants from './FeatureVariants/FeatureVariants';
import { useStyles } from './FeatureView2.styles'; import { useStyles } from './FeatureView2.styles';
import FeatureSettings from './FeatureSettings/FeatureSettings'; import FeatureSettings from './FeatureSettings/FeatureSettings';
import useLoading from '../../../hooks/useLoading';
const FeatureView2 = () => { const FeatureView2 = () => {
const { projectId, featureId } = useParams<IFeatureViewParams>(); const { projectId, featureId } = useParams<IFeatureViewParams>();
const { feature } = useFeature(projectId, featureId); const { feature, loading } = useFeature(projectId, featureId);
const { a11yProps } = useTabs(0); const { a11yProps } = useTabs(0);
const { archiveFeatureToggle } = useFeatureApi(); const { archiveFeatureToggle } = useFeatureApi();
const { toast, setToastData } = useToast(); const { toast, setToastData } = useToast();
const [showDelDialog, setShowDelDialog] = useState(false); const [showDelDialog, setShowDelDialog] = useState(false);
const styles = useStyles(); const styles = useStyles();
const history = useHistory(); const history = useHistory();
const ref = useLoading(loading);
const basePath = `/projects/${projectId}/features2/${featureId}`; const basePath = `/projects/${projectId}/features2/${featureId}`;
@ -55,7 +57,7 @@ const FeatureView2 = () => {
const tabData = [ const tabData = [
{ {
title: 'Overview', title: 'Overview',
path: `${basePath}/overview`, path: `${basePath}`,
name: 'overview', name: 'overview',
}, },
{ {
@ -81,6 +83,7 @@ const FeatureView2 = () => {
return tabData.map((tab, index) => { return tabData.map((tab, index) => {
return ( return (
<Tab <Tab
data-loading
key={tab.title} key={tab.title}
label={tab.title} label={tab.title}
value={tab.path} value={tab.path}
@ -95,14 +98,17 @@ const FeatureView2 = () => {
}; };
return ( return (
<> <div ref={ref}>
<div className={styles.header}> <div className={styles.header}>
<div className={styles.innerContainer}> <div className={styles.innerContainer}>
<h2 className={styles.featureViewHeader}>{feature.name}</h2> <h2 className={styles.featureViewHeader} data-loading>
{feature.name}
</h2>
<div className={styles.actions}> <div className={styles.actions}>
<PermissionIconButton <PermissionIconButton
permission={UPDATE_FEATURE} permission={UPDATE_FEATURE}
tooltip="Copy" tooltip="Copy"
data-loading
component={Link} component={Link}
to={`${history.location.pathname}/copy`} to={`${history.location.pathname}/copy`}
> >
@ -111,6 +117,7 @@ const FeatureView2 = () => {
<PermissionIconButton <PermissionIconButton
permission={UPDATE_FEATURE} permission={UPDATE_FEATURE}
tooltip="Archive feature toggle" tooltip="Archive feature toggle"
data-loading
onClick={() => setShowDelDialog(true)} onClick={() => setShowDelDialog(true)}
> >
<Archive /> <Archive />
@ -130,7 +137,8 @@ const FeatureView2 = () => {
</div> </div>
</div> </div>
<Route <Route
path={`/projects/:projectId/features2/:featureId/overview`} exact
path={`/projects/:projectId/features2/:featureId`}
component={FeatureOverview} component={FeatureOverview}
/> />
<Route <Route
@ -164,7 +172,7 @@ const FeatureView2 = () => {
Are you sure you want to archive this feature toggle? Are you sure you want to archive this feature toggle?
</Dialogue> </Dialogue>
{toast} {toast}
</> </div>
); );
}; };

View File

@ -96,19 +96,22 @@ const FeatureViewEnvironment: FC<IFeatureViewEnvironmentProps> = ({
return ( return (
<div className={containerClasses}> <div className={containerClasses}>
<div className={environmentIdentifierClasses}> <div className={environmentIdentifierClasses} data-loading>
<div className={iconContainerClasses}> <div className={iconContainerClasses}>
<Cloud className={iconClasses} /> <Cloud className={iconClasses} />
</div> </div>
<p className={styles.environmentBadgeParagraph}>{env.type}</p> <p className={styles.environmentBadgeParagraph}>{env.type}</p>
</div> </div>
<div className={styles.header}> <div className={styles.header}>
<div className={styles.headerInfo}> <div className={styles.headerInfo}>
<Tooltip title={env.name}> <Tooltip title={env.name}>
<p className={styles.environmentTitle}>{env.name}</p> <p data-loading className={styles.environmentTitle}>
{env.name}
</p>
</Tooltip> </Tooltip>
</div> </div>
<div className={styles.environmentStatus}> <div className={styles.environmentStatus} data-loading>
<ConditionallyRender <ConditionallyRender
condition={env?.strategies?.length > 0} condition={env?.strategies?.length > 0}
show={ show={

View File

@ -2,7 +2,7 @@ import { ITag } from '../../../../interfaces/tags';
import useAPI from '../useApi/useApi'; import useAPI from '../useApi/useApi';
const useFeatureApi = () => { const useFeatureApi = () => {
const { makeRequest, createRequest, errors } = useAPI({ const { makeRequest, createRequest, errors, loading } = useAPI({
propagateErrors: true, propagateErrors: true,
}); });
@ -146,13 +146,13 @@ const useFeatureApi = () => {
const cloneFeatureToggle = async ( const cloneFeatureToggle = async (
projectId: string, projectId: string,
featureId: string, featureId: string,
payload: {name: string, replaceGroupId: boolean} payload: { name: string; replaceGroupId: boolean }
) => { ) => {
const path = `api/admin/projects/${projectId}/features/${featureId}/clone`; const path = `api/admin/projects/${projectId}/features/${featureId}/clone`;
const req = createRequest( const req = createRequest(path, {
path, method: 'POST',
{ method: 'POST', body: JSON.stringify(payload) }, body: JSON.stringify(payload),
); });
try { try {
const res = await makeRequest(req.caller, req.id); const res = await makeRequest(req.caller, req.id);
@ -172,7 +172,8 @@ const useFeatureApi = () => {
deleteTagFromFeature, deleteTagFromFeature,
archiveFeatureToggle, archiveFeatureToggle,
patchFeatureToggle, patchFeatureToggle,
cloneFeatureToggle cloneFeatureToggle,
loading,
}; };
}; };