mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-09 13:47:13 +02:00
add tabs to milestone start (#10237)
Adds changes/view diff tabs to release plan changes that show diffs. The only instances I found where we show JSON diffs today was starting milestones and adding a new release plan if you already have one. I've moved the old file into a legacy file because we're touching two out of three internal components, so it seemed like leaving it all in one file would be a bit of a hassle. plus, this way it's consistent with segments and strategies. Start milestone: <img width="1035" alt="image" src="https://github.com/user-attachments/assets/2b4616f6-8452-4976-8101-11a94d6d5828" /> <img width="1054" alt="image" src="https://github.com/user-attachments/assets/0ba58c72-b3dc-48fa-95bf-a3980dc620fe" /> Plan replacement: <img width="1006" alt="image" src="https://github.com/user-attachments/assets/9381a48f-e23e-435e-8fa5-02fcb5050bfd" /> <img width="818" alt="image" src="https://github.com/user-attachments/assets/c5ceb9db-b095-4d05-88e8-fd8a70776479" />
This commit is contained in:
parent
fdc79e624f
commit
7c0bd12a24
@ -14,6 +14,7 @@ import { EnvironmentStrategyExecutionOrder } from './EnvironmentStrategyExecutio
|
||||
import { ArchiveFeatureChange } from './ArchiveFeatureChange.tsx';
|
||||
import { DependencyChange } from './DependencyChange.tsx';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { LegacyReleasePlanChange } from './LegacyReleasePlanChange.tsx';
|
||||
import { ReleasePlanChange } from './ReleasePlanChange.tsx';
|
||||
import { StrategyChange } from './StrategyChange.tsx';
|
||||
import { useUiFlag } from 'hooks/useUiFlag.ts';
|
||||
@ -94,6 +95,10 @@ export const FeatureChange: FC<{
|
||||
? StrategyChange
|
||||
: LegacyStrategyChange;
|
||||
|
||||
const ReleasePlanChangeComponent = useDiffableChangeComponent
|
||||
? ReleasePlanChange
|
||||
: LegacyReleasePlanChange;
|
||||
|
||||
return (
|
||||
<StyledSingleChangeBox
|
||||
key={objectId(change)}
|
||||
@ -204,7 +209,7 @@ export const FeatureChange: FC<{
|
||||
{(change.action === 'addReleasePlan' ||
|
||||
change.action === 'deleteReleasePlan' ||
|
||||
change.action === 'startMilestone') && (
|
||||
<ReleasePlanChange
|
||||
<ReleasePlanChangeComponent
|
||||
actions={actions}
|
||||
change={change}
|
||||
featureName={feature.name}
|
||||
|
@ -0,0 +1,316 @@
|
||||
import type React from 'react';
|
||||
import { useRef, useState, type FC, type ReactNode } from 'react';
|
||||
import { Box, styled, Typography } from '@mui/material';
|
||||
import type {
|
||||
ChangeRequestState,
|
||||
IChangeRequestAddReleasePlan,
|
||||
IChangeRequestDeleteReleasePlan,
|
||||
IChangeRequestStartMilestone,
|
||||
} from 'component/changeRequest/changeRequest.types';
|
||||
import { useReleasePlanPreview } from 'hooks/useReleasePlanPreview';
|
||||
import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans';
|
||||
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
|
||||
import EventDiff from 'component/events/EventDiff/EventDiff';
|
||||
import { ReleasePlan } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan';
|
||||
import { ReleasePlanMilestone } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone';
|
||||
import type { IReleasePlan } from 'interfaces/releasePlans';
|
||||
|
||||
export const ChangeItemWrapper = styled(Box)({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const ChangeItemCreateEditDeleteWrapper = styled(Box)(({ theme }) => ({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'auto auto',
|
||||
justifyContent: 'space-between',
|
||||
gap: theme.spacing(1),
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing(2),
|
||||
width: '100%',
|
||||
}));
|
||||
|
||||
const ChangeItemInfo: FC<{ children?: React.ReactNode }> = styled(Box)(
|
||||
({ theme }) => ({
|
||||
display: 'flex',
|
||||
gap: theme.spacing(1),
|
||||
}),
|
||||
);
|
||||
|
||||
const ViewDiff = styled('span')(({ theme }) => ({
|
||||
color: theme.palette.primary.main,
|
||||
marginLeft: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const StyledCodeSection = styled('div')(({ theme }) => ({
|
||||
overflowX: 'auto',
|
||||
'& code': {
|
||||
wordWrap: 'break-word',
|
||||
whiteSpace: 'pre-wrap',
|
||||
fontFamily: 'monospace',
|
||||
lineHeight: 1.5,
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
},
|
||||
}));
|
||||
|
||||
const DeleteReleasePlan: FC<{
|
||||
change: IChangeRequestDeleteReleasePlan;
|
||||
currentReleasePlan?: IReleasePlan;
|
||||
changeRequestState: ChangeRequestState;
|
||||
actions?: ReactNode;
|
||||
}> = ({ change, currentReleasePlan, changeRequestState, actions }) => {
|
||||
const releasePlan =
|
||||
changeRequestState === 'Applied' && change.payload.snapshot
|
||||
? change.payload.snapshot
|
||||
: currentReleasePlan;
|
||||
|
||||
if (!releasePlan) return;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChangeItemCreateEditDeleteWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography
|
||||
sx={(theme) => ({
|
||||
color: theme.palette.error.main,
|
||||
})}
|
||||
>
|
||||
- Deleting release plan:
|
||||
</Typography>
|
||||
<Typography>{releasePlan.name}</Typography>
|
||||
</ChangeItemInfo>
|
||||
<div>{actions}</div>
|
||||
</ChangeItemCreateEditDeleteWrapper>
|
||||
<ReleasePlan plan={releasePlan} readonly />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const StartMilestone: FC<{
|
||||
change: IChangeRequestStartMilestone;
|
||||
currentReleasePlan?: IReleasePlan;
|
||||
changeRequestState: ChangeRequestState;
|
||||
actions?: ReactNode;
|
||||
}> = ({ change, currentReleasePlan, changeRequestState, actions }) => {
|
||||
const releasePlan =
|
||||
changeRequestState === 'Applied' && change.payload.snapshot
|
||||
? change.payload.snapshot
|
||||
: currentReleasePlan;
|
||||
|
||||
if (!releasePlan) return;
|
||||
|
||||
const previousMilestone = releasePlan.milestones.find(
|
||||
(milestone) => milestone.id === releasePlan.activeMilestoneId,
|
||||
);
|
||||
|
||||
const newMilestone = releasePlan.milestones.find(
|
||||
(milestone) => milestone.id === change.payload.milestoneId,
|
||||
);
|
||||
|
||||
if (!newMilestone) return;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChangeItemCreateEditDeleteWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography color='success.dark'>
|
||||
+ Start milestone:
|
||||
</Typography>
|
||||
<Typography>{newMilestone.name}</Typography>
|
||||
<TooltipLink
|
||||
tooltip={
|
||||
<StyledCodeSection>
|
||||
<EventDiff
|
||||
entry={{
|
||||
preData: previousMilestone,
|
||||
data: newMilestone,
|
||||
}}
|
||||
/>
|
||||
</StyledCodeSection>
|
||||
}
|
||||
tooltipProps={{
|
||||
maxWidth: 500,
|
||||
maxHeight: 600,
|
||||
}}
|
||||
>
|
||||
<ViewDiff>View Diff</ViewDiff>
|
||||
</TooltipLink>
|
||||
</ChangeItemInfo>
|
||||
<div>{actions}</div>
|
||||
</ChangeItemCreateEditDeleteWrapper>
|
||||
<ReleasePlanMilestone readonly milestone={newMilestone} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const AddReleasePlan: FC<{
|
||||
change: IChangeRequestAddReleasePlan;
|
||||
currentReleasePlan?: IReleasePlan;
|
||||
environmentName: string;
|
||||
featureName: string;
|
||||
actions?: ReactNode;
|
||||
}> = ({
|
||||
change,
|
||||
currentReleasePlan,
|
||||
environmentName,
|
||||
featureName,
|
||||
actions,
|
||||
}) => {
|
||||
const [currentTooltipOpen, setCurrentTooltipOpen] = useState(false);
|
||||
const currentTooltipCloseTimeoutRef = useRef<NodeJS.Timeout>();
|
||||
const openCurrentTooltip = () => {
|
||||
if (currentTooltipCloseTimeoutRef.current) {
|
||||
clearTimeout(currentTooltipCloseTimeoutRef.current);
|
||||
}
|
||||
setCurrentTooltipOpen(true);
|
||||
};
|
||||
const closeCurrentTooltip = () => {
|
||||
currentTooltipCloseTimeoutRef.current = setTimeout(() => {
|
||||
setCurrentTooltipOpen(false);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const planPreview = useReleasePlanPreview(
|
||||
change.payload.templateId,
|
||||
featureName,
|
||||
environmentName,
|
||||
);
|
||||
|
||||
const planPreviewDiff = {
|
||||
...planPreview,
|
||||
discriminator: 'plan',
|
||||
releasePlanTemplateId: change.payload.templateId,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChangeItemCreateEditDeleteWrapper>
|
||||
<ChangeItemInfo>
|
||||
{currentReleasePlan ? (
|
||||
<Typography>
|
||||
Replacing{' '}
|
||||
<TooltipLink
|
||||
tooltip={
|
||||
<div
|
||||
onMouseEnter={() =>
|
||||
openCurrentTooltip()
|
||||
}
|
||||
onMouseLeave={() =>
|
||||
closeCurrentTooltip()
|
||||
}
|
||||
>
|
||||
<ReleasePlan
|
||||
plan={currentReleasePlan}
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
tooltipProps={{
|
||||
open: currentTooltipOpen,
|
||||
maxWidth: 500,
|
||||
maxHeight: 600,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
onMouseEnter={() => openCurrentTooltip()}
|
||||
onMouseLeave={() => closeCurrentTooltip()}
|
||||
>
|
||||
current
|
||||
</span>
|
||||
</TooltipLink>{' '}
|
||||
release plan with:
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography color='success.dark'>
|
||||
+ Adding release plan:
|
||||
</Typography>
|
||||
)}
|
||||
<Typography>{planPreview.name}</Typography>
|
||||
{currentReleasePlan && (
|
||||
<TooltipLink
|
||||
tooltip={
|
||||
<StyledCodeSection>
|
||||
<EventDiff
|
||||
entry={{
|
||||
preData: currentReleasePlan,
|
||||
data: planPreviewDiff,
|
||||
}}
|
||||
/>
|
||||
</StyledCodeSection>
|
||||
}
|
||||
tooltipProps={{
|
||||
maxWidth: 500,
|
||||
maxHeight: 600,
|
||||
}}
|
||||
>
|
||||
<ViewDiff>View Diff</ViewDiff>
|
||||
</TooltipLink>
|
||||
)}
|
||||
</ChangeItemInfo>
|
||||
<div>{actions}</div>
|
||||
</ChangeItemCreateEditDeleteWrapper>
|
||||
<ReleasePlan plan={planPreview} readonly />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deprecated: use ReleasePlanChange instead. Remove file with flag crDiffView
|
||||
* @deprecated
|
||||
*/
|
||||
export const LegacyReleasePlanChange: FC<{
|
||||
actions?: ReactNode;
|
||||
change:
|
||||
| IChangeRequestAddReleasePlan
|
||||
| IChangeRequestDeleteReleasePlan
|
||||
| IChangeRequestStartMilestone;
|
||||
environmentName: string;
|
||||
featureName: string;
|
||||
projectId: string;
|
||||
changeRequestState: ChangeRequestState;
|
||||
}> = ({
|
||||
actions,
|
||||
change,
|
||||
featureName,
|
||||
environmentName,
|
||||
projectId,
|
||||
changeRequestState,
|
||||
}) => {
|
||||
const { releasePlans } = useReleasePlans(
|
||||
projectId,
|
||||
featureName,
|
||||
environmentName,
|
||||
);
|
||||
const currentReleasePlan = releasePlans[0];
|
||||
|
||||
return (
|
||||
<>
|
||||
{change.action === 'addReleasePlan' && (
|
||||
<AddReleasePlan
|
||||
change={change}
|
||||
currentReleasePlan={currentReleasePlan}
|
||||
environmentName={environmentName}
|
||||
featureName={featureName}
|
||||
actions={actions}
|
||||
/>
|
||||
)}
|
||||
{change.action === 'deleteReleasePlan' && (
|
||||
<DeleteReleasePlan
|
||||
change={change}
|
||||
currentReleasePlan={currentReleasePlan}
|
||||
changeRequestState={changeRequestState}
|
||||
actions={actions}
|
||||
/>
|
||||
)}
|
||||
{change.action === 'startMilestone' && (
|
||||
<StartMilestone
|
||||
change={change}
|
||||
currentReleasePlan={currentReleasePlan}
|
||||
changeRequestState={changeRequestState}
|
||||
actions={actions}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@ -10,10 +10,11 @@ import type {
|
||||
import { useReleasePlanPreview } from 'hooks/useReleasePlanPreview';
|
||||
import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans';
|
||||
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
|
||||
import EventDiff from 'component/events/EventDiff/EventDiff';
|
||||
import { EventDiff } from 'component/events/EventDiff/EventDiff';
|
||||
import { ReleasePlan } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan';
|
||||
import { ReleasePlanMilestone } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone';
|
||||
import type { IReleasePlan } from 'interfaces/releasePlans';
|
||||
import { Tab, TabList, TabPanel, Tabs } from './ChangeTabComponents.tsx';
|
||||
|
||||
export const ChangeItemWrapper = styled(Box)({
|
||||
display: 'flex',
|
||||
@ -111,36 +112,34 @@ const StartMilestone: FC<{
|
||||
if (!newMilestone) return;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs>
|
||||
<ChangeItemCreateEditDeleteWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography color='success.dark'>
|
||||
+ Start milestone:
|
||||
</Typography>
|
||||
<Typography>{newMilestone.name}</Typography>
|
||||
<TooltipLink
|
||||
tooltip={
|
||||
<StyledCodeSection>
|
||||
<EventDiff
|
||||
entry={{
|
||||
preData: previousMilestone,
|
||||
data: newMilestone,
|
||||
}}
|
||||
/>
|
||||
</StyledCodeSection>
|
||||
}
|
||||
tooltipProps={{
|
||||
maxWidth: 500,
|
||||
maxHeight: 600,
|
||||
}}
|
||||
>
|
||||
<ViewDiff>View Diff</ViewDiff>
|
||||
</TooltipLink>
|
||||
</ChangeItemInfo>
|
||||
<div>{actions}</div>
|
||||
<div>
|
||||
<TabList>
|
||||
<Tab>Change</Tab>
|
||||
<Tab>View diff</Tab>
|
||||
</TabList>
|
||||
{actions}
|
||||
</div>
|
||||
</ChangeItemCreateEditDeleteWrapper>
|
||||
<ReleasePlanMilestone readonly milestone={newMilestone} />
|
||||
</>
|
||||
<TabPanel>
|
||||
<ReleasePlanMilestone readonly milestone={newMilestone} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<EventDiff
|
||||
entry={{
|
||||
preData: previousMilestone,
|
||||
data: newMilestone,
|
||||
}}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
@ -183,75 +182,78 @@ const AddReleasePlan: FC<{
|
||||
releasePlanTemplateId: change.payload.templateId,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChangeItemCreateEditDeleteWrapper>
|
||||
<ChangeItemInfo>
|
||||
{currentReleasePlan ? (
|
||||
<Typography>
|
||||
Replacing{' '}
|
||||
<TooltipLink
|
||||
tooltip={
|
||||
<div
|
||||
onMouseEnter={() =>
|
||||
openCurrentTooltip()
|
||||
}
|
||||
onMouseLeave={() =>
|
||||
closeCurrentTooltip()
|
||||
}
|
||||
>
|
||||
<ReleasePlan
|
||||
plan={currentReleasePlan}
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
tooltipProps={{
|
||||
open: currentTooltipOpen,
|
||||
maxWidth: 500,
|
||||
maxHeight: 600,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
onMouseEnter={() => openCurrentTooltip()}
|
||||
onMouseLeave={() => closeCurrentTooltip()}
|
||||
>
|
||||
current
|
||||
</span>
|
||||
</TooltipLink>{' '}
|
||||
release plan with:
|
||||
</Typography>
|
||||
) : (
|
||||
if (!currentReleasePlan) {
|
||||
return (
|
||||
<>
|
||||
<ChangeItemCreateEditDeleteWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography color='success.dark'>
|
||||
+ Adding release plan:
|
||||
</Typography>
|
||||
)}
|
||||
<Typography>{planPreview.name}</Typography>
|
||||
{currentReleasePlan && (
|
||||
<Typography>{planPreview.name}</Typography>
|
||||
</ChangeItemInfo>
|
||||
<div>{actions}</div>
|
||||
</ChangeItemCreateEditDeleteWrapper>
|
||||
<ReleasePlan plan={planPreview} readonly />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs>
|
||||
<ChangeItemCreateEditDeleteWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography>
|
||||
Replacing{' '}
|
||||
<TooltipLink
|
||||
tooltip={
|
||||
<StyledCodeSection>
|
||||
<EventDiff
|
||||
entry={{
|
||||
preData: currentReleasePlan,
|
||||
data: planPreviewDiff,
|
||||
}}
|
||||
<div
|
||||
onMouseEnter={() => openCurrentTooltip()}
|
||||
onMouseLeave={() => closeCurrentTooltip()}
|
||||
>
|
||||
<ReleasePlan
|
||||
plan={currentReleasePlan}
|
||||
readonly
|
||||
/>
|
||||
</StyledCodeSection>
|
||||
</div>
|
||||
}
|
||||
tooltipProps={{
|
||||
open: currentTooltipOpen,
|
||||
maxWidth: 500,
|
||||
maxHeight: 600,
|
||||
}}
|
||||
>
|
||||
<ViewDiff>View Diff</ViewDiff>
|
||||
</TooltipLink>
|
||||
)}
|
||||
<span
|
||||
onMouseEnter={() => openCurrentTooltip()}
|
||||
onMouseLeave={() => closeCurrentTooltip()}
|
||||
>
|
||||
current
|
||||
</span>
|
||||
</TooltipLink>{' '}
|
||||
release plan with:
|
||||
</Typography>
|
||||
<Typography>{planPreview.name}</Typography>
|
||||
</ChangeItemInfo>
|
||||
<div>{actions}</div>
|
||||
<div>
|
||||
<TabList>
|
||||
<Tab>Changes</Tab>
|
||||
<Tab>View diff</Tab>
|
||||
</TabList>
|
||||
{actions}
|
||||
</div>
|
||||
</ChangeItemCreateEditDeleteWrapper>
|
||||
<ReleasePlan plan={planPreview} readonly />
|
||||
</>
|
||||
<TabPanel>
|
||||
<ReleasePlan plan={planPreview} readonly />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<EventDiff
|
||||
entry={{
|
||||
preData: currentReleasePlan,
|
||||
data: planPreviewDiff,
|
||||
}}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user