1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

Feature overview sidepanel UI improvements (#2502)

https://linear.app/unleash/issue/2-423/update-feature-toggle-overview-sidepanel
Misc UI improvements.
This commit is contained in:
Nuno Góis 2022-11-22 15:51:41 +00:00 committed by GitHub
parent 801df6953c
commit 4d5c12dbf7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 70 additions and 39 deletions

View File

@ -1,4 +1,4 @@
import { Typography } from '@mui/material';
import { styled, Typography } from '@mui/material';
import React, { useState } from 'react';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import Input from 'component/common/Input/Input';
@ -10,6 +10,11 @@ import useTags from 'hooks/api/getters/useTags/useTags';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { ITag } from 'interfaces/tags';
const StyledInput = styled(Input)(() => ({
width: '100%',
}));
interface IAddTagDialogProps {
open: boolean;
@ -28,7 +33,7 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
const { classes: styles } = useStyles();
const featureId = useRequiredPathParam('featureId');
const { addTagToFeature, loading } = useFeatureApi();
const { refetch } = useTags(featureId);
const { tags, refetch } = useTags(featureId);
const [errors, setErrors] = useState({ tagError: '' });
const { setToastData } = useToast();
const [tag, setTag] = useState(DEFAULT_TAG);
@ -39,12 +44,6 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
setTag(DEFAULT_TAG);
};
const setValue = (field: string, value: string) => {
const newTag = { ...tag };
newTag[field] = trim(value);
setTag(newTag);
};
const onSubmit = async (evt: React.SyntheticEvent) => {
evt.preventDefault();
if (!tag.type) {
@ -68,6 +67,26 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
}
};
const isValueNotEmpty = (name: string) => name.length;
const isTagUnique = (tag: ITag) =>
!tags.some(
({ type, value }) => type === tag.type && value === tag.value
);
const isValid = isValueNotEmpty(tag.value) && isTagUnique(tag);
const onUpdateTag = (key: string, value: string) => {
setErrors({ tagError: '' });
const updatedTag = { ...tag, [key]: trim(value) };
if (!isTagUnique(updatedTag)) {
setErrors({
tagError: 'Tag already exists for this feature toggle.',
});
}
setTag(updatedTag);
};
const formId = 'add-tag-form';
return (
@ -78,7 +97,7 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
primaryButtonText="Add tag"
title="Add tags to feature toggle"
onClick={onSubmit}
disabledPrimaryButton={loading}
disabledPrimaryButton={loading || !isValid}
onClose={onCancel}
formId={formId}
>
@ -92,10 +111,10 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
autoFocus
name="type"
value={tag.type}
onChange={type => setValue('type', type)}
onChange={type => onUpdateTag('type', type)}
/>
<br />
<Input
<StyledInput
label="Value"
name="value"
placeholder="Your tag"
@ -103,8 +122,9 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
error={Boolean(errors.tagError)}
errorText={errors.tagError}
onChange={e =>
setValue('value', e.target.value)
onUpdateTag('value', e.target.value)
}
required
/>
</section>
</form>

View File

@ -7,6 +7,8 @@ import { FeatureOverviewSidePanelEnvironmentSwitches } from './FeatureOverviewSi
import { FeatureOverviewSidePanelTags } from './FeatureOverviewSidePanelTags/FeatureOverviewSidePanelTags';
const StyledContainer = styled('div')(({ theme }) => ({
position: 'sticky',
top: theme.spacing(2),
borderRadius: theme.shape.borderRadiusLarge,
backgroundColor: theme.palette.background.paper,
display: 'flex',

View File

@ -20,25 +20,27 @@ const StyledContainer = styled('div')(({ theme }) => ({
},
}));
const StyledLabel = styled('label')(({ theme }) => ({
const StyledLabel = styled('label')(() => ({
display: 'inline-flex',
alignItems: 'center',
cursor: 'pointer',
}));
interface IFeatureOverviewSidePanelEnvironmentSwitchProps {
env: IFeatureEnvironment;
environment: IFeatureEnvironment;
callback?: () => void;
showInfoBox: () => void;
children?: React.ReactNode;
}
export const FeatureOverviewSidePanelEnvironmentSwitch = ({
env,
environment,
callback,
showInfoBox,
children,
}: IFeatureOverviewSidePanelEnvironmentSwitchProps) => {
const { name, enabled } = environment;
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { toggleFeatureEnvironmentOn, toggleFeatureEnvironmentOff } =
@ -55,11 +57,11 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({
const handleToggleEnvironmentOn = async () => {
try {
await toggleFeatureEnvironmentOn(projectId, featureId, env.name);
await toggleFeatureEnvironmentOn(projectId, featureId, name);
setToastData({
type: 'success',
title: `Available in ${env.name}`,
text: `${featureId} is now available in ${env.name} based on its defined strategies.`,
title: `Available in ${name}`,
text: `${featureId} is now available in ${name} based on its defined strategies.`,
});
refetchFeature();
if (callback) {
@ -79,11 +81,11 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({
const handleToggleEnvironmentOff = async () => {
try {
await toggleFeatureEnvironmentOff(projectId, featureId, env.name);
await toggleFeatureEnvironmentOff(projectId, featureId, name);
setToastData({
type: 'success',
title: `Unavailable in ${env.name}`,
text: `${featureId} is unavailable in ${env.name} and its strategies will no longer have any effect.`,
title: `Unavailable in ${name}`,
text: `${featureId} is unavailable in ${name} and its strategies will no longer have any effect.`,
});
refetchFeature();
if (callback) {
@ -95,12 +97,12 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({
};
const toggleEnvironment = async (e: React.ChangeEvent) => {
if (isChangeRequestConfigured(env.name)) {
if (isChangeRequestConfigured(name)) {
e.preventDefault();
onChangeRequestToggle(featureId, env.name, !env.enabled);
onChangeRequestToggle(featureId, name, !enabled);
return;
}
if (env.enabled) {
if (enabled) {
await handleToggleEnvironmentOff();
return;
}
@ -110,9 +112,9 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({
const defaultContent = (
<>
{' '}
<span data-loading>{env.enabled ? 'enabled' : 'disabled'} in</span>
<span data-loading>{enabled ? 'enabled' : 'disabled'} in</span>
&nbsp;
<StringTruncator text={env.name} maxWidth="120" maxLength={15} />
<StringTruncator text={name} maxWidth="120" maxLength={15} />
</>
);
@ -120,11 +122,16 @@ export const FeatureOverviewSidePanelEnvironmentSwitch = ({
<StyledContainer>
<StyledLabel>
<PermissionSwitch
tooltip={
enabled
? `Disable feature in ${name}`
: `Enable feature in ${name}`
}
permission={UPDATE_FEATURE_ENVIRONMENT}
projectId={projectId}
checked={env.enabled}
checked={enabled}
onChange={toggleEnvironment}
environmentId={env.name}
environmentId={name}
/>
{children ?? defaultContent}
</StyledLabel>

View File

@ -2,7 +2,7 @@ import EnvironmentStrategyDialog from 'component/common/EnvironmentStrategiesDia
import { IFeatureToggle } from 'interfaces/featureToggle';
import { useState } from 'react';
import { FeatureOverviewSidePanelEnvironmentSwitch } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch';
import { Link, styled } from '@mui/material';
import { Link, styled, Tooltip } from '@mui/material';
import { Link as RouterLink } from 'react-router-dom';
const StyledContainer = styled('div')(({ theme }) => ({
@ -55,22 +55,24 @@ export const FeatureOverviewSidePanelEnvironmentSwitches = ({
const variantsLink = variants.length > 0 && (
<>
{' - '}
<StyledLink
component={RouterLink}
to={`/projects/${feature.project}/features/${feature.name}/variants`}
underline="hover"
>
{variants.length === 1
? '1 variant'
: `${variants.length} variants`}
</StyledLink>
<Tooltip title="View variants" arrow describeChild>
<StyledLink
component={RouterLink}
to={`/projects/${feature.project}/features/${feature.name}/variants`}
underline="hover"
>
{variants.length === 1
? '1 variant'
: `${variants.length} variants`}
</StyledLink>
</Tooltip>
</>
);
return (
<FeatureOverviewSidePanelEnvironmentSwitch
key={environment.name}
env={environment}
environment={environment}
showInfoBox={() => {
setEnvironmentName(environment.name);
setShowInfoBox(true);