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:
parent
801df6953c
commit
4d5c12dbf7
@ -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>
|
||||
|
@ -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',
|
||||
|
@ -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>
|
||||
|
||||
<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>
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user