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:
parent
6fc30d3a79
commit
7da3573edb
16
frontend/src/assets/icons/dots.svg
Normal file
16
frontend/src/assets/icons/dots.svg
Normal 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 |
@ -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) {
|
||||||
|
@ -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={{
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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
|
||||||
|
@ -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',
|
||||||
|
@ -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
|
||||||
|
@ -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}
|
||||||
|
@ -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>
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
|
@ -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}
|
||||||
|
@ -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,
|
||||||
|
@ -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();
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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,
|
||||||
|
@ -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;',
|
||||||
|
@ -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':
|
||||||
|
@ -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%',
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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={
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user