mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-27 01:19:00 +02:00
try out base tabs
This commit is contained in:
parent
f169a79386
commit
abff9a9434
@ -0,0 +1,444 @@
|
||||
import type React from 'react';
|
||||
import {
|
||||
type PropsWithChildren,
|
||||
useId,
|
||||
useState,
|
||||
type FC,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
import { Box, styled, Tab, Tabs, Tooltip, Typography } from '@mui/material';
|
||||
import {
|
||||
Tab as BaseTab,
|
||||
Tabs as BaseTabs,
|
||||
TabPanel as BaseTabPanel,
|
||||
TabsList as BaseTabsList,
|
||||
} from '@mui/base';
|
||||
import BlockIcon from '@mui/icons-material/Block';
|
||||
import TrackChangesIcon from '@mui/icons-material/TrackChanges';
|
||||
import {
|
||||
StrategyDiff,
|
||||
StrategyTooltipLink,
|
||||
} from '../../StrategyTooltipLink/StrategyTooltipLink.tsx';
|
||||
import { StrategyExecution } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution';
|
||||
import type {
|
||||
ChangeRequestState,
|
||||
IChangeRequestAddStrategy,
|
||||
IChangeRequestDeleteStrategy,
|
||||
IChangeRequestUpdateStrategy,
|
||||
} from 'component/changeRequest/changeRequest.types';
|
||||
import { useCurrentStrategy } from './hooks/useCurrentStrategy.ts';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { flexRow } from 'themes/themeStyles';
|
||||
import { EnvironmentVariantsTable } from 'component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsCard/EnvironmentVariantsTable/EnvironmentVariantsTable';
|
||||
import { ChangeOverwriteWarning } from './ChangeOverwriteWarning/ChangeOverwriteWarning.tsx';
|
||||
import type { IFeatureStrategy } from 'interfaces/strategy';
|
||||
|
||||
export const ChangeItemWrapper = styled(Box)({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const ChangeItemCreateEditDeleteWrapper = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: theme.spacing(1),
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
}));
|
||||
|
||||
const ChangeItemInfo: FC<{ children?: React.ReactNode }> = styled(Box)(
|
||||
({ theme }) => ({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '150px auto',
|
||||
gridAutoFlow: 'column',
|
||||
alignItems: 'center',
|
||||
flexGrow: 1,
|
||||
gap: theme.spacing(1),
|
||||
}),
|
||||
);
|
||||
|
||||
const StyledBox: FC<{ children?: React.ReactNode }> = styled(Box)(
|
||||
({ theme }) => ({
|
||||
marginTop: theme.spacing(2),
|
||||
}),
|
||||
);
|
||||
|
||||
const StyledTypography: FC<{ children?: React.ReactNode }> = styled(Typography)(
|
||||
({ theme }) => ({
|
||||
margin: `${theme.spacing(1)} 0`,
|
||||
}),
|
||||
);
|
||||
|
||||
const DisabledEnabledState: FC<{ show?: boolean; disabled: boolean }> = ({
|
||||
show = true,
|
||||
disabled,
|
||||
}) => {
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
return (
|
||||
<Tooltip
|
||||
title='This strategy will not be taken into account when evaluating feature flag.'
|
||||
arrow
|
||||
sx={{ cursor: 'pointer' }}
|
||||
>
|
||||
<Badge color='disabled' icon={<BlockIcon />}>
|
||||
Disabled
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title='This was disabled before and with this change it will be taken into account when evaluating feature flag.'
|
||||
arrow
|
||||
sx={{ cursor: 'pointer' }}
|
||||
>
|
||||
<Badge color='success' icon={<TrackChangesIcon />}>
|
||||
Enabled
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const EditHeader: FC<{
|
||||
wasDisabled?: boolean;
|
||||
willBeDisabled?: boolean;
|
||||
}> = ({ wasDisabled = false, willBeDisabled = false }) => {
|
||||
if (wasDisabled && willBeDisabled) {
|
||||
return (
|
||||
<Typography color='action.disabled'>Editing strategy:</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
if (!wasDisabled && willBeDisabled) {
|
||||
return <Typography color='error.dark'>Editing strategy:</Typography>;
|
||||
}
|
||||
|
||||
if (wasDisabled && !willBeDisabled) {
|
||||
return <Typography color='success.dark'>Editing strategy:</Typography>;
|
||||
}
|
||||
|
||||
return <Typography>Editing strategy:</Typography>;
|
||||
};
|
||||
|
||||
const hasDiff = (object: unknown, objectToCompare: unknown) =>
|
||||
JSON.stringify(object) !== JSON.stringify(objectToCompare);
|
||||
|
||||
const DeleteStrategy: FC<{
|
||||
change: IChangeRequestDeleteStrategy;
|
||||
changeRequestState: ChangeRequestState;
|
||||
currentStrategy: IFeatureStrategy | undefined;
|
||||
actions?: ReactNode;
|
||||
}> = ({ change, changeRequestState, currentStrategy, actions }) => {
|
||||
const name =
|
||||
changeRequestState === 'Applied'
|
||||
? change.payload?.snapshot?.name
|
||||
: currentStrategy?.name;
|
||||
const title =
|
||||
changeRequestState === 'Applied'
|
||||
? change.payload?.snapshot?.title
|
||||
: currentStrategy?.title;
|
||||
const referenceStrategy =
|
||||
changeRequestState === 'Applied'
|
||||
? change.payload.snapshot
|
||||
: currentStrategy;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChangeItemCreateEditDeleteWrapper className='delete-strategy-information-wrapper'>
|
||||
<ChangeItemInfo>
|
||||
<Typography
|
||||
sx={(theme) => ({
|
||||
color: theme.palette.error.main,
|
||||
})}
|
||||
>
|
||||
- Deleting strategy:
|
||||
</Typography>
|
||||
<StrategyTooltipLink name={name || ''} title={title}>
|
||||
<StrategyDiff
|
||||
change={change}
|
||||
currentStrategy={referenceStrategy}
|
||||
/>
|
||||
</StrategyTooltipLink>
|
||||
</ChangeItemInfo>
|
||||
<div>{actions}</div>
|
||||
</ChangeItemCreateEditDeleteWrapper>
|
||||
{referenceStrategy && (
|
||||
<StrategyExecution strategy={referenceStrategy} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const tabA11yProps = (baseId: string) => (index: number) => ({
|
||||
id: `${baseId}-tab-${index}`,
|
||||
'aria-controls': `${baseId}-${index}`,
|
||||
});
|
||||
|
||||
const TabPanel: FC<
|
||||
PropsWithChildren<{
|
||||
index: number;
|
||||
value: number;
|
||||
id: string;
|
||||
'aria-labelledby': string;
|
||||
}>
|
||||
> = ({ children, index, value, id, 'aria-labelledby': ariaLabelledBy }) => {
|
||||
return (
|
||||
<div
|
||||
role='tabpanel'
|
||||
hidden={value !== index}
|
||||
id={id}
|
||||
aria-labelledby={ariaLabelledBy}
|
||||
>
|
||||
{value === index ? children : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const UpdateStrategy: FC<{
|
||||
change: IChangeRequestUpdateStrategy;
|
||||
changeRequestState: ChangeRequestState;
|
||||
currentStrategy: IFeatureStrategy | undefined;
|
||||
actions?: ReactNode;
|
||||
}> = ({ change, changeRequestState, currentStrategy, actions }) => {
|
||||
const [tabIndex, setTabIndex] = useState(0);
|
||||
const baseId = useId();
|
||||
const allyProps = tabA11yProps(baseId);
|
||||
|
||||
const previousTitle =
|
||||
changeRequestState === 'Applied'
|
||||
? change.payload.snapshot?.title
|
||||
: currentStrategy?.title;
|
||||
const referenceStrategy =
|
||||
changeRequestState === 'Applied'
|
||||
? change.payload.snapshot
|
||||
: currentStrategy;
|
||||
const hasVariantDiff = hasDiff(
|
||||
referenceStrategy?.variants || [],
|
||||
change.payload.variants || [],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseTabs>
|
||||
<ChangeOverwriteWarning
|
||||
data={{
|
||||
current: currentStrategy,
|
||||
change,
|
||||
changeType: 'strategy',
|
||||
}}
|
||||
changeRequestState={changeRequestState}
|
||||
/>
|
||||
<ChangeItemCreateEditDeleteWrapper>
|
||||
<ChangeItemInfo>
|
||||
<EditHeader
|
||||
wasDisabled={currentStrategy?.disabled}
|
||||
willBeDisabled={change.payload?.disabled}
|
||||
/>
|
||||
<StrategyTooltipLink
|
||||
name={change.payload.name}
|
||||
title={change.payload.title}
|
||||
previousTitle={previousTitle}
|
||||
>
|
||||
<StrategyDiff
|
||||
change={change}
|
||||
currentStrategy={referenceStrategy}
|
||||
/>
|
||||
</StrategyTooltipLink>
|
||||
</ChangeItemInfo>
|
||||
<div>
|
||||
<Tabs
|
||||
selectionFollowsFocus
|
||||
aria-label='Choose view'
|
||||
value={tabIndex}
|
||||
onChange={(_, newValue) => setTabIndex(newValue)}
|
||||
>
|
||||
<Tab label='Change' {...allyProps(0)} />
|
||||
<Tab label='Diff' {...allyProps(1)} />
|
||||
</Tabs>
|
||||
|
||||
{/* import {Tab as BaseTab, Tabs as BaseTabs, TabPanel as BaseTabPanel, TabsList as BaseTabsList} from '@mui/base'; */}
|
||||
|
||||
<BaseTabsList
|
||||
selectionFollowsFocus
|
||||
aria-label='Choose view'
|
||||
value={tabIndex}
|
||||
onChange={(_, newValue) => setTabIndex(newValue)}
|
||||
>
|
||||
<BaseTab {...allyProps(0)}>Change</BaseTab>
|
||||
<BaseTab {...allyProps(1)}>Diff</BaseTab>
|
||||
</BaseTabsList>
|
||||
{actions}
|
||||
</div>
|
||||
</ChangeItemCreateEditDeleteWrapper>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
change.payload?.disabled !== currentStrategy?.disabled
|
||||
}
|
||||
show={
|
||||
<Typography
|
||||
sx={{
|
||||
marginTop: (theme) => theme.spacing(2),
|
||||
marginBottom: (theme) => theme.spacing(2),
|
||||
...flexRow,
|
||||
gap: (theme) => theme.spacing(1),
|
||||
}}
|
||||
>
|
||||
This strategy will be{' '}
|
||||
<DisabledEnabledState
|
||||
disabled={change.payload?.disabled || false}
|
||||
/>
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
<TabPanel
|
||||
id={`${baseId}-${0}`}
|
||||
aria-labelledby={`${baseId}-tab-${0}`}
|
||||
value={tabIndex}
|
||||
index={0}
|
||||
>
|
||||
<StrategyExecution strategy={change.payload} />
|
||||
{hasVariantDiff ? (
|
||||
<StyledBox>
|
||||
{change.payload.variants?.length ? (
|
||||
<>
|
||||
<StyledTypography>
|
||||
{currentStrategy?.variants?.length
|
||||
? 'Updating strategy variants to:'
|
||||
: 'Adding strategy variants:'}
|
||||
</StyledTypography>
|
||||
<EnvironmentVariantsTable
|
||||
variants={change.payload.variants || []}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<StyledTypography>
|
||||
Removed all strategy variants.
|
||||
</StyledTypography>
|
||||
)}
|
||||
</StyledBox>
|
||||
) : null}
|
||||
</TabPanel>
|
||||
<TabPanel
|
||||
id={`${baseId}-${1}`}
|
||||
aria-labelledby={`${baseId}-tab-${1}`}
|
||||
value={tabIndex}
|
||||
index={1}
|
||||
>
|
||||
<StrategyDiff
|
||||
change={change}
|
||||
currentStrategy={referenceStrategy}
|
||||
/>
|
||||
</TabPanel>
|
||||
<BaseTabPanel
|
||||
id={`${baseId}-${1}`}
|
||||
aria-labelledby={`${baseId}-tab-${1}`}
|
||||
value={tabIndex}
|
||||
>
|
||||
<StrategyDiff
|
||||
change={change}
|
||||
currentStrategy={referenceStrategy}
|
||||
/>
|
||||
</BaseTabPanel>
|
||||
</BaseTabs>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const AddStrategy: FC<{
|
||||
change: IChangeRequestAddStrategy;
|
||||
actions?: ReactNode;
|
||||
}> = ({ change, actions }) => (
|
||||
<>
|
||||
<ChangeItemCreateEditDeleteWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography
|
||||
color={
|
||||
change.payload?.disabled
|
||||
? 'action.disabled'
|
||||
: 'success.dark'
|
||||
}
|
||||
>
|
||||
+ Adding strategy:
|
||||
</Typography>
|
||||
<StrategyTooltipLink
|
||||
name={change.payload.name}
|
||||
title={change.payload.title}
|
||||
/>
|
||||
<div>
|
||||
<DisabledEnabledState
|
||||
disabled
|
||||
show={change.payload?.disabled === true}
|
||||
/>
|
||||
</div>
|
||||
</ChangeItemInfo>
|
||||
<StrategyDiff change={change} currentStrategy={undefined} />
|
||||
<div>{actions}</div>
|
||||
</ChangeItemCreateEditDeleteWrapper>
|
||||
<StrategyExecution strategy={change.payload} />
|
||||
{change.payload.variants?.length ? (
|
||||
<StyledBox>
|
||||
<StyledTypography>Adding strategy variants:</StyledTypography>
|
||||
<EnvironmentVariantsTable
|
||||
variants={change.payload.variants || []}
|
||||
/>
|
||||
</StyledBox>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
||||
export const DiffableChange: FC<{
|
||||
actions?: ReactNode;
|
||||
change:
|
||||
| IChangeRequestAddStrategy
|
||||
| IChangeRequestDeleteStrategy
|
||||
| IChangeRequestUpdateStrategy;
|
||||
environmentName: string;
|
||||
featureName: string;
|
||||
projectId: string;
|
||||
changeRequestState: ChangeRequestState;
|
||||
}> = ({
|
||||
actions,
|
||||
change,
|
||||
featureName,
|
||||
environmentName,
|
||||
projectId,
|
||||
changeRequestState,
|
||||
}) => {
|
||||
const currentStrategy = useCurrentStrategy(
|
||||
change,
|
||||
projectId,
|
||||
featureName,
|
||||
environmentName,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{change.action === 'addStrategy' && (
|
||||
<AddStrategy change={change} actions={actions} />
|
||||
)}
|
||||
{change.action === 'deleteStrategy' && (
|
||||
<DeleteStrategy
|
||||
change={change}
|
||||
changeRequestState={changeRequestState}
|
||||
currentStrategy={currentStrategy}
|
||||
actions={actions}
|
||||
/>
|
||||
)}
|
||||
{change.action === 'updateStrategy' && (
|
||||
<UpdateStrategy
|
||||
change={change}
|
||||
changeRequestState={changeRequestState}
|
||||
currentStrategy={currentStrategy}
|
||||
actions={actions}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@ -15,6 +15,7 @@ import { ArchiveFeatureChange } from './ArchiveFeatureChange.tsx';
|
||||
import { DependencyChange } from './DependencyChange.tsx';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ReleasePlanChange } from './ReleasePlanChange.tsx';
|
||||
import { DiffableChange } from './DiffableChange.tsx';
|
||||
|
||||
const StyledSingleChangeBox = styled(Box, {
|
||||
shouldForwardProp: (prop: string) => !prop.startsWith('$'),
|
||||
@ -166,14 +167,25 @@ export const FeatureChange: FC<{
|
||||
{change.action === 'addStrategy' ||
|
||||
change.action === 'deleteStrategy' ||
|
||||
change.action === 'updateStrategy' ? (
|
||||
<StrategyChange
|
||||
actions={actions}
|
||||
change={change}
|
||||
featureName={feature.name}
|
||||
environmentName={changeRequest.environment}
|
||||
projectId={changeRequest.project}
|
||||
changeRequestState={changeRequest.state}
|
||||
/>
|
||||
<>
|
||||
<StrategyChange
|
||||
actions={actions}
|
||||
change={change}
|
||||
featureName={feature.name}
|
||||
environmentName={changeRequest.environment}
|
||||
projectId={changeRequest.project}
|
||||
changeRequestState={changeRequest.state}
|
||||
/>
|
||||
<hr />
|
||||
<DiffableChange
|
||||
actions={actions}
|
||||
change={change}
|
||||
featureName={feature.name}
|
||||
environmentName={changeRequest.environment}
|
||||
projectId={changeRequest.project}
|
||||
changeRequestState={changeRequest.state}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
{change.action === 'patchVariant' && (
|
||||
<VariantPatch
|
||||
|
@ -9,13 +9,14 @@ import {
|
||||
formatStrategyName,
|
||||
GetFeatureStrategyIcon,
|
||||
} from 'utils/strategyNames';
|
||||
import EventDiff from 'component/events/EventDiff/EventDiff';
|
||||
import EventDiff, { NewEventDiff } from 'component/events/EventDiff/EventDiff';
|
||||
import omit from 'lodash.omit';
|
||||
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
|
||||
import { Typography, styled } from '@mui/material';
|
||||
import type { IFeatureStrategy } from 'interfaces/strategy';
|
||||
import { textTruncated } from 'themes/themeStyles';
|
||||
import { NameWithChangeInfo } from '../NameWithChangeInfo/NameWithChangeInfo.tsx';
|
||||
import { useUiFlag } from 'hooks/useUiFlag.ts';
|
||||
|
||||
const StyledCodeSection = styled('div')(({ theme }) => ({
|
||||
overflowX: 'auto',
|
||||
@ -47,12 +48,22 @@ export const StrategyDiff: FC<{
|
||||
| IChangeRequestDeleteStrategy;
|
||||
currentStrategy?: IFeatureStrategy;
|
||||
}> = ({ change, currentStrategy }) => {
|
||||
const useNewDiff = useUiFlag('improvedJsonDiff');
|
||||
const changeRequestStrategy =
|
||||
change.action === 'deleteStrategy' ? undefined : change.payload;
|
||||
|
||||
const sortedCurrentStrategy = sortSegments(currentStrategy);
|
||||
const sortedChangeRequestStrategy = sortSegments(changeRequestStrategy);
|
||||
|
||||
if (useNewDiff) {
|
||||
return (
|
||||
<NewEventDiff
|
||||
entry={{
|
||||
preData: omit(sortedCurrentStrategy, 'sortOrder'),
|
||||
data: omit(sortedChangeRequestStrategy, 'snapshot'),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<StyledCodeSection>
|
||||
<EventDiff
|
||||
|
@ -78,7 +78,7 @@ const ButtonIcon = styled('span')(({ theme }) => ({
|
||||
marginInlineEnd: theme.spacing(0.5),
|
||||
}));
|
||||
|
||||
const NewEventDiff: FC<IEventDiffProps> = ({ entry, excludeKeys }) => {
|
||||
export const NewEventDiff: FC<IEventDiffProps> = ({ entry, excludeKeys }) => {
|
||||
const changeType = entry.preData && entry.data ? 'edit' : 'replacement';
|
||||
const showExpandButton = changeType === 'edit';
|
||||
const [full, setFull] = useState(false);
|
||||
|
Loading…
Reference in New Issue
Block a user