mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: visualize variants diff in CR (#2979)
https://linear.app/unleash/issue/2-582/display-the-change-request-created-with-variants-in-the-ui ![image](https://user-images.githubusercontent.com/14320932/214341314-c4f1aefb-fada-4d59-9d40-86f8dce98b76.png) Includes a basic diff visualisation on variants change requests. It seems like components like `CodeSnippetPopover` and `PopoverDiff` are currently very tightly coupled together with strategies, so I preferred to follow my own approach and leave those alone for now instead of trying to refactor them. `patchVariant` could also be renamed to a more fitting name in the future as well, since we're now doing more of an override than applying a patch. `Diff` is a generic diff component that uses `EventDiff` internally and simply takes into account a "before" and "after" state, as `preData` and `data`. I made some changes to `EventDiff` that made some sense to me: - Cover edge cases where `path` is undefined and `.join` crashes, also fallback to the diff index (or undefined); - Leverage the key to correctly sort the change items in the diff;
This commit is contained in:
parent
80c444aa99
commit
c5fced89fb
@ -1,4 +1,4 @@
|
||||
import React, { FC, ReactNode } from 'react';
|
||||
import { FC, ReactNode } from 'react';
|
||||
import {
|
||||
hasNameField,
|
||||
IChange,
|
||||
@ -20,6 +20,7 @@ import {
|
||||
StrategyDeletedChange,
|
||||
StrategyEditedChange,
|
||||
} from './StrategyChange';
|
||||
import { VariantPatch } from './VariantPatch/VariantPatch';
|
||||
|
||||
const StyledSingleChangeBox = styled(Box, {
|
||||
shouldForwardProp: (prop: string) => !prop.startsWith('$'),
|
||||
@ -138,6 +139,15 @@ export const Change: FC<{
|
||||
<StrategyExecution strategy={change.payload} />
|
||||
</>
|
||||
)}
|
||||
{change.action === 'patchVariant' && (
|
||||
<VariantPatch
|
||||
feature={feature.name}
|
||||
project={changeRequest.project}
|
||||
environment={changeRequest.environment}
|
||||
change={change}
|
||||
discard={discard}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</StyledSingleChangeBox>
|
||||
);
|
||||
|
@ -0,0 +1,29 @@
|
||||
import { styled } from '@mui/material';
|
||||
import EventDiff from 'component/events/EventDiff/EventDiff';
|
||||
|
||||
const StyledCodeSection = styled('div')(({ theme }) => ({
|
||||
overflowX: 'auto',
|
||||
'& code': {
|
||||
wordWrap: 'break-word',
|
||||
whiteSpace: 'pre-wrap',
|
||||
fontFamily: 'monospace',
|
||||
lineHeight: 1.5,
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
},
|
||||
}));
|
||||
|
||||
interface IDiffProps {
|
||||
preData: any;
|
||||
data: any;
|
||||
}
|
||||
|
||||
export const Diff = ({ preData, data }: IDiffProps) => (
|
||||
<StyledCodeSection>
|
||||
<EventDiff
|
||||
entry={{
|
||||
preData,
|
||||
data,
|
||||
}}
|
||||
/>
|
||||
</StyledCodeSection>
|
||||
);
|
@ -0,0 +1,50 @@
|
||||
import { Box, styled, Typography } from '@mui/material';
|
||||
import { IChangeRequestPatchVariant } from 'component/changeRequest/changeRequest.types';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import { ReactNode } from 'react';
|
||||
import { Diff } from './Diff';
|
||||
|
||||
export const ChangeItemCreateEditWrapper = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const ChangeItemInfo = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
interface IVariantPatchProps {
|
||||
feature: string;
|
||||
project: string;
|
||||
environment: string;
|
||||
change: IChangeRequestPatchVariant;
|
||||
discard?: ReactNode;
|
||||
}
|
||||
|
||||
export const VariantPatch = ({
|
||||
feature,
|
||||
project,
|
||||
environment,
|
||||
change,
|
||||
discard,
|
||||
}: IVariantPatchProps) => {
|
||||
const { feature: featureData } = useFeature(project, feature);
|
||||
|
||||
const preData = featureData.environments.find(
|
||||
({ name }) => environment === name
|
||||
)?.variants;
|
||||
|
||||
return (
|
||||
<ChangeItemCreateEditWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography>Updating variants:</Typography>
|
||||
<Diff preData={preData} data={change.payload.variants} />
|
||||
</ChangeItemInfo>
|
||||
{discard}
|
||||
</ChangeItemCreateEditWrapper>
|
||||
);
|
||||
};
|
@ -1,3 +1,4 @@
|
||||
import { IFeatureVariant } from 'interfaces/featureToggle';
|
||||
import { IFeatureStrategy } from '../../interfaces/strategy';
|
||||
import { IUser } from '../../interfaces/user';
|
||||
|
||||
@ -60,7 +61,8 @@ type ChangeRequestPayload =
|
||||
| ChangeRequestEnabled
|
||||
| ChangeRequestAddStrategy
|
||||
| ChangeRequestEditStrategy
|
||||
| ChangeRequestDeleteStrategy;
|
||||
| ChangeRequestDeleteStrategy
|
||||
| ChangeRequestVariantPatch;
|
||||
|
||||
export interface IChangeRequestAddStrategy extends IChangeRequestBase {
|
||||
action: 'addStrategy';
|
||||
@ -82,11 +84,21 @@ export interface IChangeRequestEnabled extends IChangeRequestBase {
|
||||
payload: ChangeRequestEnabled;
|
||||
}
|
||||
|
||||
export interface IChangeRequestPatchVariant extends IChangeRequestBase {
|
||||
action: 'patchVariant';
|
||||
payload: ChangeRequestVariantPatch;
|
||||
}
|
||||
|
||||
export type IChange =
|
||||
| IChangeRequestAddStrategy
|
||||
| IChangeRequestDeleteStrategy
|
||||
| IChangeRequestUpdateStrategy
|
||||
| IChangeRequestEnabled;
|
||||
| IChangeRequestEnabled
|
||||
| IChangeRequestPatchVariant;
|
||||
|
||||
type ChangeRequestVariantPatch = {
|
||||
variants: IFeatureVariant[];
|
||||
};
|
||||
|
||||
type ChangeRequestEnabled = { enabled: boolean };
|
||||
|
||||
@ -106,7 +118,8 @@ export type ChangeRequestAction =
|
||||
| 'updateEnabled'
|
||||
| 'addStrategy'
|
||||
| 'updateStrategy'
|
||||
| 'deleteStrategy';
|
||||
| 'deleteStrategy'
|
||||
| 'patchVariant';
|
||||
|
||||
export const hasNameField = (payload: unknown): payload is { name: string } =>
|
||||
typeof payload === 'object' && payload !== null && 'name' in payload;
|
||||
|
@ -50,7 +50,7 @@ const EventDiff = ({ entry }: IEventDiffProps) => {
|
||||
|
||||
const buildDiff = (diff: any, idx: number) => {
|
||||
let change;
|
||||
const key = diff.path.join('.');
|
||||
const key = diff.path?.join('.') ?? diff.index;
|
||||
|
||||
if (diff.item) {
|
||||
change = buildItemDiff(diff.item, key);
|
||||
@ -74,13 +74,16 @@ const EventDiff = ({ entry }: IEventDiffProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
return <div key={idx}>{change}</div>;
|
||||
return { key: key.toString(), value: <div key={idx}>{change}</div> };
|
||||
};
|
||||
|
||||
let changes;
|
||||
|
||||
if (diffs) {
|
||||
changes = diffs.map(buildDiff);
|
||||
changes = diffs
|
||||
.map(buildDiff)
|
||||
.sort((a, b) => a.key.localeCompare(b.key))
|
||||
.map(({ value }) => value);
|
||||
} else {
|
||||
// Just show the data if there is no diff yet.
|
||||
const data = entry.data || entry.preData;
|
||||
|
Loading…
Reference in New Issue
Block a user