mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-31 13:47:02 +02:00
chore: prepend changes with "change:" (#10276)
Adds "change:" to the beginning of all changes and does some work to align the use of compononents and structure across them (supersedes https://github.com/Unleash/unleash/pull/10260). In doing so, I have also added new and legacy variants for all different change components, because this has required some hierarchy restructuring every now and then. A reason for doing that was adding the correct wrapping behavior for components, such that on smaller screens, we wouldn't entirely blow out and make the kebab menu invisible and inaccessible. It also makes it so that we switch to full-width change view earlier (at breakpoint md instead of sm), because at sm, a lot of stuff got hidden before we switched to full-width. Most changes are trivial updates; I've called out bits of the code that are not in comments. Rendered, it looks like this: <img width="1203" alt="image" src="https://github.com/user-attachments/assets/36bed974-99da-4d8d-a881-ea9df7797210" /> One interesting and potentially quite useful side-effect, is that all change types now use the exact same set of components in the same fashion, as evidenced by this screenie where I've added outlines to the hierarchy: <img width="1020" alt="image" src="https://github.com/user-attachments/assets/685fefcc-af7e-4697-b8f3-8260af1e2a84" /> The one difference is that components without a diff place the "more" kebab menu one layer further inside to facilitate prettier wrapping (the kebab menu can stay on the same line as the other text when wrapping): <img width="238" alt="image" src="https://github.com/user-attachments/assets/2b8d3174-06a8-4ad4-b366-cea97720deda" />
This commit is contained in:
parent
89cff9d533
commit
2dc7cbaa31
@ -1,22 +1,38 @@
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import { Box, styled } from '@mui/material';
|
||||
import { ChangeItemWrapper } from './StrategyChange.tsx';
|
||||
import { Action, ChangeItemInfo, ChangeItemWrapper } from './Change.styles.tsx';
|
||||
import { styled } from '@mui/material';
|
||||
import { ChangeItemWrapper as LegacyChangeItemWrapper } from './LegacyStrategyChange.tsx';
|
||||
|
||||
const ArchiveBox = styled(Box)(({ theme }) => ({
|
||||
type ArchiveFeatureChange = {
|
||||
actions?: ReactNode;
|
||||
};
|
||||
|
||||
const ArchiveBox = styled('span')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
color: theme.palette.error.main,
|
||||
}));
|
||||
|
||||
interface IArchiveFeatureChange {
|
||||
actions?: ReactNode;
|
||||
}
|
||||
|
||||
export const ArchiveFeatureChange: FC<IArchiveFeatureChange> = ({
|
||||
/**
|
||||
* Deprecated: use ArchiveFeatureChange instead; remove with flag crDiffView
|
||||
* @deprecated
|
||||
*/
|
||||
export const LegacyArchiveFeatureChange: FC<ArchiveFeatureChange> = ({
|
||||
actions,
|
||||
}) => (
|
||||
<LegacyChangeItemWrapper>
|
||||
<ChangeItemInfo>
|
||||
<ArchiveBox>Archiving flag</ArchiveBox>
|
||||
{actions}
|
||||
</ChangeItemInfo>
|
||||
</LegacyChangeItemWrapper>
|
||||
);
|
||||
|
||||
export const ArchiveFeatureChange: FC<ArchiveFeatureChange> = ({ actions }) => (
|
||||
<ChangeItemWrapper>
|
||||
<ArchiveBox>Archiving flag</ArchiveBox>
|
||||
{actions}
|
||||
<ChangeItemInfo>
|
||||
<Action color='error.main'>Archiving flag</Action>
|
||||
{actions}
|
||||
</ChangeItemInfo>
|
||||
</ChangeItemWrapper>
|
||||
);
|
||||
|
@ -1,9 +1,53 @@
|
||||
import { styled, Typography } from '@mui/material';
|
||||
import { styled, Typography, type TypographyProps } from '@mui/material';
|
||||
import type { FC, PropsWithChildren } from 'react';
|
||||
|
||||
export const ChangeItemInfo = styled(Typography)(({ theme }) => ({
|
||||
export const Action: FC<TypographyProps> = ({ children, ...props }) => (
|
||||
<Typography component='span' {...props}>
|
||||
{children}
|
||||
</Typography>
|
||||
);
|
||||
|
||||
export const Deleted = styled(Action)(({ theme }) => ({
|
||||
color: theme.palette.error.main,
|
||||
'::before': { content: '"- "' },
|
||||
}));
|
||||
|
||||
export const Added = styled(Action)(({ theme }) => ({
|
||||
'::before': { content: '"+ "' },
|
||||
color: theme.palette.success.dark,
|
||||
}));
|
||||
|
||||
export const AddedStrategy = styled(Added, {
|
||||
shouldForwardProp: (prop) => prop !== 'disabled',
|
||||
})<{ disabled?: boolean }>(({ theme, disabled }) => ({
|
||||
color: disabled ? theme.palette.text.secondary : undefined,
|
||||
}));
|
||||
|
||||
const Change = styled('span')({
|
||||
fontWeight: 'bold',
|
||||
});
|
||||
|
||||
export const ChangeItemInfo = styled(
|
||||
({ children, ...props }: PropsWithChildren) => (
|
||||
<Typography {...props}>
|
||||
<Change>Change: </Change>
|
||||
{children}
|
||||
</Typography>
|
||||
),
|
||||
)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexFlow: 'row',
|
||||
justifyItems: 'flex-start',
|
||||
flexFlow: 'row wrap',
|
||||
alignItems: 'center',
|
||||
flex: 'auto',
|
||||
gap: `1ch`,
|
||||
columnGap: `1ch`,
|
||||
rowGap: theme.spacing(0.5),
|
||||
}));
|
||||
|
||||
export const ChangeItemWrapper = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexFlow: 'row wrap',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(1),
|
||||
}));
|
||||
|
@ -28,6 +28,7 @@ import Delete from '@mui/icons-material/Delete';
|
||||
import Edit from '@mui/icons-material/Edit';
|
||||
import MoreVert from '@mui/icons-material/MoreVert';
|
||||
import { EditChange } from './EditChange.tsx';
|
||||
import { useUiFlag } from 'hooks/useUiFlag.ts';
|
||||
|
||||
const useShowActions = (changeRequest: ChangeRequestType, change: IChange) => {
|
||||
const { isChangeRequestConfigured } = useChangeRequestsEnabled(
|
||||
@ -60,6 +61,10 @@ const StyledPopover = styled(Popover)(({ theme }) => ({
|
||||
padding: theme.spacing(1, 1.5),
|
||||
}));
|
||||
|
||||
const StyledIconButton = styled(IconButton)(({ theme }) => ({
|
||||
marginLeft: 'auto',
|
||||
}));
|
||||
|
||||
export const ChangeActions: FC<{
|
||||
changeRequest: ChangeRequestType;
|
||||
feature: string;
|
||||
@ -69,6 +74,9 @@ export const ChangeActions: FC<{
|
||||
const { showDiscard, showEdit } = useShowActions(changeRequest, change);
|
||||
const { discardChange } = useChangeRequestApi();
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const useNewCrView = useUiFlag('crDiffView');
|
||||
|
||||
const ButtonComponent = useNewCrView ? StyledIconButton : IconButton;
|
||||
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
|
||||
@ -113,7 +121,7 @@ export const ChangeActions: FC<{
|
||||
show={
|
||||
<>
|
||||
<Tooltip title='Change request actions' arrow describeChild>
|
||||
<IconButton
|
||||
<ButtonComponent
|
||||
id={id}
|
||||
aria-controls={open ? menuId : undefined}
|
||||
aria-haspopup='true'
|
||||
@ -122,7 +130,7 @@ export const ChangeActions: FC<{
|
||||
type='button'
|
||||
>
|
||||
<MoreVert />
|
||||
</IconButton>
|
||||
</ButtonComponent>
|
||||
</Tooltip>
|
||||
<StyledPopover
|
||||
id={menuId}
|
||||
|
@ -1,24 +0,0 @@
|
||||
import type { FC } from 'react';
|
||||
import { styled } from '@mui/material';
|
||||
import { textTruncated } from 'themes/themeStyles';
|
||||
import { NameWithChangeInfo } from './NameWithChangeInfo/NameWithChangeInfo.tsx';
|
||||
|
||||
type ChangeSegmentNameProps = {
|
||||
name?: string;
|
||||
previousName?: string;
|
||||
};
|
||||
|
||||
const Truncated = styled('div')(() => ({
|
||||
...textTruncated,
|
||||
maxWidth: 500,
|
||||
display: 'flex',
|
||||
}));
|
||||
|
||||
export const ChangeSegmentName: FC<ChangeSegmentNameProps> = ({
|
||||
name,
|
||||
previousName,
|
||||
}) => (
|
||||
<Truncated>
|
||||
<NameWithChangeInfo previousName={previousName} newName={name} />
|
||||
</Truncated>
|
||||
);
|
@ -41,11 +41,15 @@ export const Tab = styled(({ children }: ButtonProps) => (
|
||||
textTransform: 'uppercase',
|
||||
}));
|
||||
|
||||
export const Tabs = ({ children }: PropsWithChildren) => (
|
||||
export const Tabs = ({
|
||||
className,
|
||||
children,
|
||||
}: PropsWithChildren<{ className?: string }>) => (
|
||||
<MuiTabs
|
||||
aria-label='View rendered change or JSON diff'
|
||||
selectionFollowsFocus
|
||||
defaultValue={0}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</MuiTabs>
|
||||
|
@ -1,11 +1,17 @@
|
||||
import type { ReactNode, VFC } from 'react';
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import { Box, styled, Typography } from '@mui/material';
|
||||
import type {
|
||||
IChangeRequestAddDependency,
|
||||
IChangeRequestDeleteDependency,
|
||||
} from 'component/changeRequest/changeRequest.types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ChangeItemWrapper } from './StrategyChange.tsx';
|
||||
import {
|
||||
Added,
|
||||
ChangeItemInfo,
|
||||
ChangeItemWrapper,
|
||||
Deleted,
|
||||
} from './Change.styles';
|
||||
import { ChangeItemWrapper as LegacyChangeItemWrapper } from './LegacyStrategyChange.tsx';
|
||||
|
||||
const StyledLink = styled(Link)(({ theme }) => ({
|
||||
maxWidth: '100%',
|
||||
@ -15,13 +21,55 @@ const StyledLink = styled(Link)(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
export const DependencyChange: FC<{
|
||||
actions?: ReactNode;
|
||||
change: IChangeRequestAddDependency | IChangeRequestDeleteDependency;
|
||||
projectId: string;
|
||||
onNavigate?: () => void;
|
||||
}> = ({ actions, change, projectId, onNavigate }) => {
|
||||
if (change.action === 'addDependency') {
|
||||
return (
|
||||
<ChangeItemWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Added>Adding dependency</Added>
|
||||
<StyledLink
|
||||
to={`/projects/${projectId}/features/${change.payload.feature}`}
|
||||
onClick={onNavigate}
|
||||
>
|
||||
{change.payload.feature}
|
||||
</StyledLink>
|
||||
{!change.payload.enabled ? ' (disabled)' : null}
|
||||
{change.payload.variants?.length
|
||||
? `(${change.payload.variants?.join(', ')})`
|
||||
: null}
|
||||
{actions}
|
||||
</ChangeItemInfo>
|
||||
</ChangeItemWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
if (change.action === 'deleteDependency') {
|
||||
return (
|
||||
<ChangeItemWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Deleted>Deleting dependencies</Deleted>
|
||||
{actions}
|
||||
</ChangeItemInfo>
|
||||
</ChangeItemWrapper>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const AddDependencyWrapper = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
export const DependencyChange: VFC<{
|
||||
/**
|
||||
* @deprecated use DependencyChange instead; remove with flag crDiffView
|
||||
*/
|
||||
export const LegacyDependencyChange: FC<{
|
||||
actions?: ReactNode;
|
||||
change: IChangeRequestAddDependency | IChangeRequestDeleteDependency;
|
||||
projectId: string;
|
||||
@ -31,7 +79,7 @@ export const DependencyChange: VFC<{
|
||||
<>
|
||||
{change.action === 'addDependency' && (
|
||||
<>
|
||||
<ChangeItemWrapper>
|
||||
<LegacyChangeItemWrapper>
|
||||
<AddDependencyWrapper>
|
||||
<Typography color={'success.dark'}>
|
||||
+ Adding dependency
|
||||
@ -48,7 +96,7 @@ export const DependencyChange: VFC<{
|
||||
: null}
|
||||
</AddDependencyWrapper>
|
||||
{actions}
|
||||
</ChangeItemWrapper>
|
||||
</LegacyChangeItemWrapper>
|
||||
</>
|
||||
)}
|
||||
{change.action === 'deleteDependency' && (
|
||||
|
@ -9,12 +9,21 @@ import { formatStrategyName } from '../../../../../../utils/strategyNames.tsx';
|
||||
import type { IFeatureStrategy } from 'interfaces/strategy.ts';
|
||||
import { Tab, TabList, TabPanel, Tabs } from '../ChangeTabComponents.tsx';
|
||||
import { useUiFlag } from 'hooks/useUiFlag.ts';
|
||||
import {
|
||||
ChangeItemInfo as NewChangeItemInfo,
|
||||
ChangeItemWrapper,
|
||||
Action,
|
||||
} from '../Change.styles.tsx';
|
||||
|
||||
const ChangeItemInfo = styled(Box)({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
const ChangeContent = styled(ChangeItemInfo)(({ theme }) => ({
|
||||
gap: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const StyledChangeHeader = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
@ -91,9 +100,13 @@ export const EnvironmentStrategyExecutionOrder = ({
|
||||
if (useDiffableComponent) {
|
||||
return (
|
||||
<Tabs>
|
||||
<ChangeItemInfo>
|
||||
<StyledChangeHeader>
|
||||
<p>Updating strategy execution order to</p>
|
||||
<ChangeContent>
|
||||
<ChangeItemWrapper>
|
||||
<NewChangeItemInfo>
|
||||
<Action>
|
||||
Updating strategy execution order to
|
||||
</Action>
|
||||
</NewChangeItemInfo>
|
||||
<div>
|
||||
<TabList>
|
||||
<Tab>Change</Tab>
|
||||
@ -101,7 +114,7 @@ export const EnvironmentStrategyExecutionOrder = ({
|
||||
</TabList>
|
||||
{actions}
|
||||
</div>
|
||||
</StyledChangeHeader>
|
||||
</ChangeItemWrapper>
|
||||
<TabPanel>
|
||||
<StyledStrategyExecutionWrapper>
|
||||
{updatedStrategies.map((strategy, index) => (
|
||||
@ -120,7 +133,7 @@ export const EnvironmentStrategyExecutionOrder = ({
|
||||
data={data}
|
||||
/>
|
||||
</TabPanel>
|
||||
</ChangeItemInfo>
|
||||
</ChangeContent>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
@ -7,12 +7,21 @@ import type {
|
||||
import { objectId } from 'utils/objectId';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { Alert, Box, styled } from '@mui/material';
|
||||
import { ToggleStatusChange } from './ToggleStatusChange.tsx';
|
||||
import {
|
||||
LegacyToggleStatusChange,
|
||||
ToggleStatusChange,
|
||||
} from './ToggleStatusChange.tsx';
|
||||
import { LegacyStrategyChange } from './LegacyStrategyChange.tsx';
|
||||
import { VariantPatch } from './VariantPatch/VariantPatch.tsx';
|
||||
import { EnvironmentStrategyExecutionOrder } from './EnvironmentStrategyExecutionOrder/EnvironmentStrategyExecutionOrder.tsx';
|
||||
import { ArchiveFeatureChange } from './ArchiveFeatureChange.tsx';
|
||||
import { DependencyChange } from './DependencyChange.tsx';
|
||||
import {
|
||||
ArchiveFeatureChange,
|
||||
LegacyArchiveFeatureChange,
|
||||
} from './ArchiveFeatureChange.tsx';
|
||||
import {
|
||||
DependencyChange,
|
||||
LegacyDependencyChange,
|
||||
} from './DependencyChange.tsx';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { LegacyReleasePlanChange } from './LegacyReleasePlanChange.tsx';
|
||||
import { ReleasePlanChange } from './ReleasePlanChange.tsx';
|
||||
@ -100,6 +109,18 @@ export const FeatureChange: FC<{
|
||||
? ReleasePlanChange
|
||||
: LegacyReleasePlanChange;
|
||||
|
||||
const ArchiveFlagComponent = useDiffableChangeComponent
|
||||
? ArchiveFeatureChange
|
||||
: LegacyArchiveFeatureChange;
|
||||
|
||||
const DependencyChangeComponent = useDiffableChangeComponent
|
||||
? DependencyChange
|
||||
: LegacyDependencyChange;
|
||||
|
||||
const StatusChangeComponent = useDiffableChangeComponent
|
||||
? ToggleStatusChange
|
||||
: LegacyToggleStatusChange;
|
||||
|
||||
return (
|
||||
<StyledSingleChangeBox
|
||||
key={objectId(change)}
|
||||
@ -159,7 +180,7 @@ export const FeatureChange: FC<{
|
||||
<ChangeInnerBox>
|
||||
{(change.action === 'addDependency' ||
|
||||
change.action === 'deleteDependency') && (
|
||||
<DependencyChange
|
||||
<DependencyChangeComponent
|
||||
actions={actions}
|
||||
change={change}
|
||||
projectId={changeRequest.project}
|
||||
@ -167,13 +188,13 @@ export const FeatureChange: FC<{
|
||||
/>
|
||||
)}
|
||||
{change.action === 'updateEnabled' && (
|
||||
<ToggleStatusChange
|
||||
<StatusChangeComponent
|
||||
enabled={change.payload.enabled}
|
||||
actions={actions}
|
||||
/>
|
||||
)}
|
||||
{change.action === 'archiveFeature' && (
|
||||
<ArchiveFeatureChange actions={actions} />
|
||||
<ArchiveFlagComponent actions={actions} />
|
||||
)}
|
||||
|
||||
{change.action === 'addStrategy' ||
|
||||
|
@ -19,7 +19,7 @@ test.each(['', undefined])(
|
||||
).toBeNull();
|
||||
|
||||
// expect ins element with new strategy name
|
||||
await screen.findByText(newName, { selector: 'p' });
|
||||
await screen.findByText(newName, { selector: 'ins' });
|
||||
},
|
||||
);
|
||||
|
||||
@ -35,7 +35,9 @@ test.each(['', undefined])(
|
||||
);
|
||||
|
||||
// expect no ins elements
|
||||
expect(screen.queryByText(newName || '', { selector: 'p' })).toBeNull();
|
||||
expect(
|
||||
screen.queryByText(newName || '', { selector: 'ins' }),
|
||||
).toBeNull();
|
||||
|
||||
// expect del element with old strategy name
|
||||
await screen.findByText(previousName, { selector: 'del' });
|
||||
@ -53,13 +55,25 @@ test('Should render the old name as deleted and the new name as inserted if the
|
||||
await screen.findByText(previousName, { selector: 'del' });
|
||||
|
||||
// expect ins element with new strategy name
|
||||
await screen.findByText(newName, { selector: 'p' });
|
||||
await screen.findByText(newName, { selector: 'ins' });
|
||||
});
|
||||
|
||||
test('Should render the name in a span if it has not changed', async () => {
|
||||
const name = 'name';
|
||||
render(<NameWithChangeInfo newName={name} previousName={name} />);
|
||||
|
||||
// expect no del or ins elements
|
||||
expect(screen.queryByText(name, { selector: 'ins' })).toBeNull();
|
||||
expect(screen.queryByText(name, { selector: 'del' })).toBeNull();
|
||||
|
||||
// expect span element with the strategy name
|
||||
await screen.findByText(name, { selector: 'span' });
|
||||
});
|
||||
|
||||
test('Should render nothing if there was no name and there is still no name', async () => {
|
||||
render(<NameWithChangeInfo newName={undefined} previousName={undefined} />);
|
||||
|
||||
expect(screen.queryByText('', { selector: 'p' })).toBeNull();
|
||||
expect(screen.queryByText('', { selector: 'ins' })).toBeNull();
|
||||
expect(screen.queryByText('', { selector: 'del' })).toBeNull();
|
||||
expect(screen.queryByText('', { selector: 'span' })).toBeNull();
|
||||
});
|
||||
|
@ -1,18 +1,26 @@
|
||||
import type { FC } from 'react';
|
||||
import { Typography, styled } from '@mui/material';
|
||||
import { Typography, type TypographyProps, styled } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { textTruncated } from 'themes/themeStyles';
|
||||
|
||||
const Truncated = styled('div')(() => ({
|
||||
const Truncated = styled('span')(() => ({
|
||||
...textTruncated,
|
||||
maxWidth: 500,
|
||||
display: 'block',
|
||||
}));
|
||||
|
||||
const NewName = styled(Typography)<TypographyProps>({
|
||||
textDecoration: 'none',
|
||||
});
|
||||
|
||||
export const NameWithChangeInfo: FC<{
|
||||
newName: string | undefined;
|
||||
previousName: string | undefined;
|
||||
newName?: string;
|
||||
previousName?: string;
|
||||
}> = ({ newName, previousName }) => {
|
||||
const titleHasChanged = Boolean(previousName && previousName !== newName);
|
||||
const titleHasChangedOrBeenAdded = Boolean(
|
||||
titleHasChanged || (!previousName && newName),
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -30,7 +38,13 @@ export const NameWithChangeInfo: FC<{
|
||||
condition={Boolean(newName)}
|
||||
show={
|
||||
<Truncated>
|
||||
<Typography>{newName}</Typography>
|
||||
<NewName
|
||||
component={
|
||||
titleHasChangedOrBeenAdded ? 'ins' : 'span'
|
||||
}
|
||||
>
|
||||
{newName}
|
||||
</NewName>
|
||||
</Truncated>
|
||||
}
|
||||
/>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useRef, useState, type FC, type ReactNode } from 'react';
|
||||
import { Box, styled, Typography } from '@mui/material';
|
||||
import { styled, Typography } from '@mui/material';
|
||||
import type {
|
||||
ChangeRequestState,
|
||||
IChangeRequestAddReleasePlan,
|
||||
@ -14,22 +14,18 @@ import { ReleasePlan } from 'component/feature/FeatureView/FeatureOverview/Relea
|
||||
import { ReleasePlanMilestone } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone';
|
||||
import type { IReleasePlan } from 'interfaces/releasePlans';
|
||||
import { Tab, TabList, TabPanel, Tabs } from './ChangeTabComponents.tsx';
|
||||
import { ChangeItemInfo } from './Change.styles.tsx';
|
||||
import {
|
||||
Action,
|
||||
Added,
|
||||
ChangeItemInfo,
|
||||
ChangeItemWrapper,
|
||||
Deleted,
|
||||
} from './Change.styles.tsx';
|
||||
|
||||
export const ChangeItemWrapper = styled(Box)({
|
||||
const StyledTabs = styled(Tabs)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const ChangeItemCreateEditDeleteWrapper = styled(Box)(({ theme }) => ({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'auto auto',
|
||||
justifyContent: 'space-between',
|
||||
flexFlow: 'column',
|
||||
gap: theme.spacing(1),
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing(2),
|
||||
width: '100%',
|
||||
}));
|
||||
|
||||
const DeleteReleasePlan: FC<{
|
||||
@ -47,19 +43,13 @@ const DeleteReleasePlan: FC<{
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChangeItemCreateEditDeleteWrapper>
|
||||
<ChangeItemWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography
|
||||
sx={(theme) => ({
|
||||
color: theme.palette.error.main,
|
||||
})}
|
||||
>
|
||||
- Deleting release plan
|
||||
</Typography>
|
||||
<Typography>{releasePlan.name}</Typography>
|
||||
<Deleted>Deleting release plan</Deleted>
|
||||
<Typography component='span'>{releasePlan.name}</Typography>
|
||||
{actions}
|
||||
</ChangeItemInfo>
|
||||
<div>{actions}</div>
|
||||
</ChangeItemCreateEditDeleteWrapper>
|
||||
</ChangeItemWrapper>
|
||||
<ReleasePlan plan={releasePlan} readonly />
|
||||
</>
|
||||
);
|
||||
@ -89,13 +79,13 @@ const StartMilestone: FC<{
|
||||
if (!newMilestone) return;
|
||||
|
||||
return (
|
||||
<Tabs>
|
||||
<ChangeItemCreateEditDeleteWrapper>
|
||||
<StyledTabs>
|
||||
<ChangeItemWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography color='success.dark'>
|
||||
+ Start milestone
|
||||
<Added>Start milestone</Added>
|
||||
<Typography component='span'>
|
||||
{newMilestone.name}
|
||||
</Typography>
|
||||
<Typography>{newMilestone.name}</Typography>
|
||||
</ChangeItemInfo>
|
||||
<div>
|
||||
<TabList>
|
||||
@ -104,7 +94,7 @@ const StartMilestone: FC<{
|
||||
</TabList>
|
||||
{actions}
|
||||
</div>
|
||||
</ChangeItemCreateEditDeleteWrapper>
|
||||
</ChangeItemWrapper>
|
||||
<TabPanel>
|
||||
<ReleasePlanMilestone readonly milestone={newMilestone} />
|
||||
</TabPanel>
|
||||
@ -116,7 +106,7 @@ const StartMilestone: FC<{
|
||||
}}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</StyledTabs>
|
||||
);
|
||||
};
|
||||
|
||||
@ -162,25 +152,25 @@ const AddReleasePlan: FC<{
|
||||
if (!currentReleasePlan) {
|
||||
return (
|
||||
<>
|
||||
<ChangeItemCreateEditDeleteWrapper>
|
||||
<ChangeItemWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography color='success.dark'>
|
||||
+ Adding release plan
|
||||
<Added>Adding release plan</Added>
|
||||
<Typography component='span'>
|
||||
{planPreview.name}
|
||||
</Typography>
|
||||
<Typography>{planPreview.name}</Typography>
|
||||
{actions}
|
||||
</ChangeItemInfo>
|
||||
<div>{actions}</div>
|
||||
</ChangeItemCreateEditDeleteWrapper>
|
||||
</ChangeItemWrapper>
|
||||
<ReleasePlan plan={planPreview} readonly />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs>
|
||||
<ChangeItemCreateEditDeleteWrapper>
|
||||
<StyledTabs>
|
||||
<ChangeItemWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography>
|
||||
<Action>
|
||||
Replacing{' '}
|
||||
<TooltipLink
|
||||
tooltip={
|
||||
@ -207,9 +197,8 @@ const AddReleasePlan: FC<{
|
||||
current
|
||||
</span>
|
||||
</TooltipLink>{' '}
|
||||
release plan with
|
||||
</Typography>
|
||||
<Typography>{planPreview.name}</Typography>
|
||||
release plan with {planPreview.name}
|
||||
</Action>
|
||||
</ChangeItemInfo>
|
||||
<div>
|
||||
<TabList>
|
||||
@ -218,7 +207,7 @@ const AddReleasePlan: FC<{
|
||||
</TabList>
|
||||
{actions}
|
||||
</div>
|
||||
</ChangeItemCreateEditDeleteWrapper>
|
||||
</ChangeItemWrapper>
|
||||
<TabPanel>
|
||||
<ReleasePlan plan={planPreview} readonly />
|
||||
</TabPanel>
|
||||
@ -230,7 +219,7 @@ const AddReleasePlan: FC<{
|
||||
}}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</StyledTabs>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import { Box, styled, Typography } from '@mui/material';
|
||||
import { Box, styled } from '@mui/material';
|
||||
import type {
|
||||
ChangeRequestState,
|
||||
IChangeRequestDeleteSegment,
|
||||
@ -10,34 +10,31 @@ import { ViewableConstraintsList } from 'component/common/NewConstraintAccordion
|
||||
|
||||
import { ChangeOverwriteWarning } from './ChangeOverwriteWarning/ChangeOverwriteWarning.tsx';
|
||||
import { Tab, TabList, TabPanel, Tabs } from './ChangeTabComponents.tsx';
|
||||
import { ChangeItemInfo } from './Change.styles.tsx';
|
||||
import { ChangeSegmentName } from './ChangeSegmentName.tsx';
|
||||
import {
|
||||
Action,
|
||||
ChangeItemInfo,
|
||||
ChangeItemWrapper,
|
||||
Deleted,
|
||||
} from './Change.styles.tsx';
|
||||
import { SegmentDiff } from './SegmentDiff.tsx';
|
||||
import { NameWithChangeInfo } from './NameWithChangeInfo/NameWithChangeInfo.tsx';
|
||||
|
||||
const ChangeItemCreateEditWrapper = styled(Box)(({ theme }) => ({
|
||||
const ActionsContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
gap: theme.spacing(1),
|
||||
flexFlow: 'row wrap',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
margin: theme.spacing(0, 0, 1, 0),
|
||||
columnGap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
export const ChangeItemWrapper = styled(Box)({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const SegmentContainer = styled(Box, {
|
||||
shouldForwardProp: (prop) => prop !== 'conflict',
|
||||
})<{ conflict: string | undefined }>(({ theme, conflict }) => ({
|
||||
borderLeft: '1px solid',
|
||||
borderRight: '1px solid',
|
||||
borderTop: '1px solid',
|
||||
borderBottom: '1px solid',
|
||||
borderColor: conflict
|
||||
? theme.palette.warning.border
|
||||
: theme.palette.divider,
|
||||
display: 'flex',
|
||||
flexFlow: 'column',
|
||||
gap: theme.spacing(1),
|
||||
border: `1px solid ${
|
||||
conflict ? theme.palette.warning.border : theme.palette.divider
|
||||
}`,
|
||||
borderTopColor: theme.palette.divider,
|
||||
padding: theme.spacing(3),
|
||||
borderRadius: `0 0 ${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px`,
|
||||
@ -58,13 +55,13 @@ export const SegmentChangeDetails: FC<{
|
||||
changeRequestState === 'Applied' ? snapshotSegment : currentSegment;
|
||||
|
||||
const actionsWithTabs = (
|
||||
<>
|
||||
<ActionsContainer>
|
||||
<TabList>
|
||||
<Tab>Change</Tab>
|
||||
<Tab>View diff</Tab>
|
||||
</TabList>
|
||||
{actions}
|
||||
</>
|
||||
</ActionsContainer>
|
||||
);
|
||||
|
||||
return (
|
||||
@ -74,22 +71,16 @@ export const SegmentChangeDetails: FC<{
|
||||
<>
|
||||
<ChangeItemWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography
|
||||
sx={(theme) => ({
|
||||
color: theme.palette.error.main,
|
||||
})}
|
||||
>
|
||||
- Deleting segment
|
||||
</Typography>
|
||||
<ChangeSegmentName
|
||||
name={change.payload.name}
|
||||
<Deleted>Deleting segment</Deleted>
|
||||
<NameWithChangeInfo
|
||||
newName={change.payload.name}
|
||||
previousName={previousName}
|
||||
/>
|
||||
</ChangeItemInfo>
|
||||
{actionsWithTabs}
|
||||
</ChangeItemWrapper>
|
||||
|
||||
<TabPanel />
|
||||
<TabPanel sx={{ display: 'contents' }} />
|
||||
<TabPanel sx={{ mt: 1 }} variant='diff'>
|
||||
<SegmentDiff
|
||||
change={change}
|
||||
@ -108,13 +99,15 @@ export const SegmentChangeDetails: FC<{
|
||||
}}
|
||||
changeRequestState={changeRequestState}
|
||||
/>
|
||||
<ChangeItemCreateEditWrapper>
|
||||
<ChangeItemWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography>Editing segment</Typography>
|
||||
<ChangeSegmentName name={change.payload.name} />
|
||||
<Action>Editing segment</Action>
|
||||
<NameWithChangeInfo
|
||||
newName={change.payload.name}
|
||||
/>
|
||||
</ChangeItemInfo>
|
||||
{actionsWithTabs}
|
||||
</ChangeItemCreateEditWrapper>
|
||||
</ChangeItemWrapper>
|
||||
|
||||
<TabPanel>
|
||||
<ViewableConstraintsList
|
||||
|
@ -242,7 +242,7 @@ test('Deleting strategy before change request is applied diffs against current s
|
||||
{ route: `/projects/${projectId}` },
|
||||
);
|
||||
|
||||
await screen.findByText('- Deleting strategy');
|
||||
await screen.findByText('Deleting strategy');
|
||||
await screen.findByText('Gradual rollout');
|
||||
await screen.findByText('current_title');
|
||||
|
||||
@ -298,7 +298,7 @@ test('Deleting strategy after change request is applied diffs against the snapsh
|
||||
{ route: `/projects/${projectId}` },
|
||||
);
|
||||
|
||||
await screen.findByText('- Deleting strategy');
|
||||
await screen.findByText('Deleting strategy');
|
||||
await screen.findByText('Gradual rollout');
|
||||
await screen.findByText('snapshot_title');
|
||||
expect(screen.queryByText('current_title')).not.toBeInTheDocument();
|
||||
@ -352,7 +352,7 @@ test('Adding strategy always diffs against undefined strategy', async () => {
|
||||
{ route: `/projects/${projectId}` },
|
||||
);
|
||||
|
||||
await screen.findByText('+ Adding strategy');
|
||||
await screen.findByText('Adding strategy');
|
||||
await screen.findByText('change_request_title');
|
||||
|
||||
const variants = await screen.findAllByText('change_variant');
|
||||
|
@ -20,22 +20,13 @@ import type { IFeatureStrategy } from 'interfaces/strategy';
|
||||
import { Tab, TabList, TabPanel, Tabs } from './ChangeTabComponents.tsx';
|
||||
import { ChangeStrategyName } from './ChangeStrategyName.tsx';
|
||||
import { StrategyDiff } from './StrategyDiff.tsx';
|
||||
import { ChangeItemInfo } from './Change.styles.tsx';
|
||||
|
||||
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',
|
||||
marginBottom: theme.spacing(1),
|
||||
width: '100%',
|
||||
}));
|
||||
import {
|
||||
Action,
|
||||
AddedStrategy,
|
||||
ChangeItemInfo,
|
||||
ChangeItemWrapper,
|
||||
Deleted,
|
||||
} from './Change.styles.tsx';
|
||||
|
||||
const StyledBox: FC<{ children?: React.ReactNode }> = styled(Box)(
|
||||
({ theme }) => ({
|
||||
@ -60,7 +51,7 @@ const DisabledEnabledState: FC<{ show?: boolean; disabled: boolean }> = ({
|
||||
if (disabled) {
|
||||
return (
|
||||
<Tooltip
|
||||
title='This strategy will not be taken into account when evaluating feature flag.'
|
||||
title='This strategy will not be taken into account when evaluating the feature flag.'
|
||||
arrow
|
||||
sx={{ cursor: 'pointer' }}
|
||||
>
|
||||
@ -73,7 +64,7 @@ const DisabledEnabledState: FC<{ show?: boolean; disabled: boolean }> = ({
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title='This was disabled before and with this change it will be taken into account when evaluating feature flag.'
|
||||
title='This strategy was disabled before. With this change, it will be taken into account when evaluating the feature flag.'
|
||||
arrow
|
||||
sx={{ cursor: 'pointer' }}
|
||||
>
|
||||
@ -89,18 +80,18 @@ const EditHeader: FC<{
|
||||
willBeDisabled?: boolean;
|
||||
}> = ({ wasDisabled = false, willBeDisabled = false }) => {
|
||||
if (wasDisabled && willBeDisabled) {
|
||||
return <Typography color='text.secondary'>Editing strategy</Typography>;
|
||||
return <Action color='text.secondary'>Editing strategy</Action>;
|
||||
}
|
||||
|
||||
if (!wasDisabled && willBeDisabled) {
|
||||
return <Typography color='error.dark'>Editing strategy</Typography>;
|
||||
return <Action color='error.dark'>Editing strategy</Action>;
|
||||
}
|
||||
|
||||
if (wasDisabled && !willBeDisabled) {
|
||||
return <Typography color='success.dark'>Editing strategy</Typography>;
|
||||
return <Action color='success.dark'>Editing strategy</Action>;
|
||||
}
|
||||
|
||||
return <Typography>Editing strategy</Typography>;
|
||||
return <Action>Editing strategy</Action>;
|
||||
};
|
||||
|
||||
const hasDiff = (object: unknown, objectToCompare: unknown) =>
|
||||
@ -127,19 +118,13 @@ const DeleteStrategy: FC<{
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChangeItemCreateEditDeleteWrapper>
|
||||
<ChangeItemWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography
|
||||
sx={(theme) => ({
|
||||
color: theme.palette.error.main,
|
||||
})}
|
||||
>
|
||||
- Deleting strategy
|
||||
</Typography>
|
||||
<Deleted>Deleting strategy</Deleted>
|
||||
<ChangeStrategyName name={name || ''} title={title} />
|
||||
</ChangeItemInfo>
|
||||
{actions}
|
||||
</ChangeItemCreateEditDeleteWrapper>
|
||||
</ChangeItemWrapper>
|
||||
<TabPanel>
|
||||
{referenceStrategy && (
|
||||
<StrategyExecution strategy={referenceStrategy} />
|
||||
@ -184,7 +169,7 @@ const UpdateStrategy: FC<{
|
||||
}}
|
||||
changeRequestState={changeRequestState}
|
||||
/>
|
||||
<ChangeItemCreateEditDeleteWrapper>
|
||||
<ChangeItemWrapper>
|
||||
<ChangeItemInfo>
|
||||
<EditHeader
|
||||
wasDisabled={currentStrategy?.disabled}
|
||||
@ -197,7 +182,7 @@ const UpdateStrategy: FC<{
|
||||
/>
|
||||
</ChangeItemInfo>
|
||||
{actions}
|
||||
</ChangeItemCreateEditDeleteWrapper>
|
||||
</ChangeItemWrapper>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
change.payload?.disabled !== currentStrategy?.disabled
|
||||
@ -257,17 +242,11 @@ const AddStrategy: FC<{
|
||||
actions?: ReactNode;
|
||||
}> = ({ change, actions }) => (
|
||||
<>
|
||||
<ChangeItemCreateEditDeleteWrapper>
|
||||
<ChangeItemWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography
|
||||
color={
|
||||
change.payload?.disabled
|
||||
? 'text.secondary'
|
||||
: 'success.dark'
|
||||
}
|
||||
>
|
||||
+ Adding strategy
|
||||
</Typography>
|
||||
<AddedStrategy disabled={change.payload?.disabled}>
|
||||
Adding strategy
|
||||
</AddedStrategy>
|
||||
<ChangeStrategyName
|
||||
name={change.payload.name}
|
||||
title={change.payload.title}
|
||||
@ -278,7 +257,7 @@ const AddStrategy: FC<{
|
||||
/>
|
||||
</ChangeItemInfo>
|
||||
{actions}
|
||||
</ChangeItemCreateEditDeleteWrapper>
|
||||
</ChangeItemWrapper>
|
||||
<TabPanel>
|
||||
<StrategyExecution strategy={change.payload} />
|
||||
{change.payload.variants?.length ? (
|
||||
@ -298,6 +277,19 @@ const AddStrategy: FC<{
|
||||
</>
|
||||
);
|
||||
|
||||
const ActionsContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexFlow: 'row wrap',
|
||||
alignItems: 'center',
|
||||
columnGap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const StyledTabs = styled(Tabs)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexFlow: 'column',
|
||||
gap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
export const StrategyChange: FC<{
|
||||
actions?: ReactNode;
|
||||
change:
|
||||
@ -324,17 +316,17 @@ export const StrategyChange: FC<{
|
||||
);
|
||||
|
||||
const actionsWithTabs = (
|
||||
<>
|
||||
<ActionsContainer>
|
||||
<TabList>
|
||||
<Tab>Change</Tab>
|
||||
<Tab>View diff</Tab>
|
||||
</TabList>
|
||||
{actions}
|
||||
</>
|
||||
</ActionsContainer>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tabs>
|
||||
<StyledTabs>
|
||||
{change.action === 'addStrategy' && (
|
||||
<AddStrategy change={change} actions={actionsWithTabs} />
|
||||
)}
|
||||
@ -354,6 +346,6 @@ export const StrategyChange: FC<{
|
||||
actions={actionsWithTabs}
|
||||
/>
|
||||
)}
|
||||
</Tabs>
|
||||
</StyledTabs>
|
||||
);
|
||||
};
|
||||
|
@ -1,19 +1,23 @@
|
||||
import type { ReactNode, VFC } from 'react';
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
import { ChangeItemWrapper } from './StrategyChange.tsx';
|
||||
import { ChangeItemWrapper as LegacyChangeItemWrapper } from './LegacyStrategyChange.tsx';
|
||||
import { Action, ChangeItemInfo, ChangeItemWrapper } from './Change.styles';
|
||||
|
||||
interface IToggleStatusChange {
|
||||
enabled: boolean;
|
||||
actions?: ReactNode;
|
||||
}
|
||||
|
||||
export const ToggleStatusChange: VFC<IToggleStatusChange> = ({
|
||||
/**
|
||||
* @deprecated use ToggleStatusChange instead; remove with flag crDiffView
|
||||
*/
|
||||
export const LegacyToggleStatusChange: FC<IToggleStatusChange> = ({
|
||||
enabled,
|
||||
actions,
|
||||
}) => {
|
||||
return (
|
||||
<ChangeItemWrapper>
|
||||
<LegacyChangeItemWrapper>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
New status
|
||||
<Badge
|
||||
@ -24,6 +28,23 @@ export const ToggleStatusChange: VFC<IToggleStatusChange> = ({
|
||||
</Badge>
|
||||
</Box>
|
||||
{actions}
|
||||
</LegacyChangeItemWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const ToggleStatusChange: FC<IToggleStatusChange> = ({
|
||||
enabled,
|
||||
actions,
|
||||
}) => {
|
||||
return (
|
||||
<ChangeItemWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Action>New status</Action>
|
||||
<Badge color={enabled ? 'success' : 'error'}>
|
||||
{enabled ? ' Enabled' : 'Disabled'}
|
||||
</Badge>
|
||||
{actions}
|
||||
</ChangeItemInfo>
|
||||
</ChangeItemWrapper>
|
||||
);
|
||||
};
|
||||
|
@ -37,23 +37,23 @@ import { useActionableChangeRequests } from 'hooks/api/getters/useActionableChan
|
||||
import { useUiFlag } from 'hooks/useUiFlag.ts';
|
||||
import { ChangeRequestRequestedApprovers } from './ChangeRequestRequestedApprovers/ChangeRequestRequestedApprovers.tsx';
|
||||
|
||||
const breakpoint = 'md';
|
||||
|
||||
const StyledAsideBox = styled(Box)(({ theme }) => ({
|
||||
width: '30%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
[theme.breakpoints.down(breakpoint)]: {
|
||||
width: '100%',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledPaper = styled(Paper)(({ theme }) => ({
|
||||
marginTop: theme.spacing(2),
|
||||
marginLeft: theme.spacing(2),
|
||||
width: '70%',
|
||||
padding: theme.spacing(1, 2),
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
marginLeft: 0,
|
||||
[theme.breakpoints.down(breakpoint)]: {
|
||||
width: '100%',
|
||||
},
|
||||
}));
|
||||
@ -74,7 +74,8 @@ const StyledButton = styled(Button)(({ theme }) => ({
|
||||
|
||||
const ChangeRequestBody = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
columnGap: theme.spacing(2),
|
||||
[theme.breakpoints.down(breakpoint)]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
}));
|
||||
|
Loading…
Reference in New Issue
Block a user