1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

fix: hide warnings that you'll overwrite changes on CRs that are already applied (#6214)

The current approach uses adds an extra parameter to the components and
passes it through from the parent components. It's never a lot of
levels, so it feels alright, but it's feels like a bit of a code smell.
I wonder if it would make sense to use a context for each change
request? 🤔

Supersedes: https://github.com/Unleash/unleash/pull/6181
This commit is contained in:
Thomas Heartman 2024-02-16 12:41:14 +08:00 committed by GitHub
parent 64a6af2858
commit a8fa1ae347
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 171 additions and 108 deletions

View File

@ -34,6 +34,7 @@ export const ChangeRequest: VFC<IChangeRequestProps> = ({
key={segmentChange.payload.id}
segmentChange={segmentChange}
onNavigate={onNavigate}
changeRequestState={changeRequest.state}
actions={
<ChangeActions
changeRequest={changeRequest}

View File

@ -0,0 +1,73 @@
import {
ChangeRequestState,
IChangeRequestPatchVariant,
IChangeRequestUpdateSegment,
IChangeRequestUpdateStrategy,
} from 'component/changeRequest/changeRequest.types';
import { useChangeRequestPlausibleContext } from 'component/changeRequest/ChangeRequestContext';
import { useUiFlag } from 'hooks/useUiFlag';
import { IFeatureVariant } from 'interfaces/featureToggle';
import { ISegment } from 'interfaces/segment';
import { IFeatureStrategy } from 'interfaces/strategy';
import { useEffect } from 'react';
import { OverwriteWarning } from './OverwriteWarning';
import {
getEnvVariantChangesThatWouldBeOverwritten,
getSegmentChangesThatWouldBeOverwritten,
getStrategyChangesThatWouldBeOverwritten,
} from './strategy-change-diff-calculation';
type ChangeData =
| {
changeType: 'environment variant configuration';
current?: IFeatureVariant[];
change: IChangeRequestPatchVariant;
}
| {
changeType: 'segment';
current?: ISegment;
change: IChangeRequestUpdateSegment;
}
| {
changeType: 'strategy';
current?: IFeatureStrategy;
change: IChangeRequestUpdateStrategy;
};
export const ChangeOverwriteWarning: React.FC<{
data: ChangeData;
changeRequestState: ChangeRequestState;
}> = ({ data, changeRequestState }) => {
const checkForChanges = useUiFlag('changeRequestConflictHandling');
if (!checkForChanges) {
return null;
}
const getChangesThatWouldBeOverwritten = () => {
switch (data.changeType) {
case 'segment':
return getSegmentChangesThatWouldBeOverwritten(
data.current,
data.change,
);
case 'strategy':
return getStrategyChangesThatWouldBeOverwritten(
data.current,
data.change,
);
case 'environment variant configuration':
return getEnvVariantChangesThatWouldBeOverwritten(
data.current,
data.change,
);
}
};
return (
<OverwriteWarning
changeRequestState={changeRequestState}
changeType={data.changeType}
changesThatWouldBeOverwritten={getChangesThatWouldBeOverwritten()}
/>
);
};

View File

@ -0,0 +1,27 @@
import { render } from 'utils/testRenderer';
import { screen } from '@testing-library/react';
import { OverwriteWarning } from './OverwriteWarning';
import { ChangeRequestState } from 'component/changeRequest/changeRequest.types';
test.each([
['Draft', true],
['Approved', true],
['In review', true],
['Applied', false],
['Scheduled', true],
['Cancelled', false],
['Rejected', false],
])('Shows warnings for CRs in the "%s" state: %s', (status, showWarning) => {
render(
<OverwriteWarning
changesThatWouldBeOverwritten={[
{ property: 'some-prop', oldValue: 'old', newValue: 'new' },
]}
changeType={'strategy'}
changeRequestState={status as ChangeRequestState}
/>,
);
const hasRendered = screen.queryByText('Heads up!') !== null;
expect(hasRendered).toBe(showWarning);
});

View File

@ -1,21 +1,6 @@
import { Box, styled } from '@mui/material';
import { useChangeRequestPlausibleContext } from 'component/changeRequest/ChangeRequestContext';
import {
IChangeRequestPatchVariant,
IChangeRequestUpdateSegment,
IChangeRequestUpdateStrategy,
} from 'component/changeRequest/changeRequest.types';
import { useUiFlag } from 'hooks/useUiFlag';
import { ISegment } from 'interfaces/segment';
import { IFeatureStrategy } from 'interfaces/strategy';
import {
ChangesThatWouldBeOverwritten,
getEnvVariantChangesThatWouldBeOverwritten,
getStrategyChangesThatWouldBeOverwritten,
getSegmentChangesThatWouldBeOverwritten,
} from './strategy-change-diff-calculation';
import { useEffect } from 'react';
import { IFeatureVariant } from 'interfaces/featureToggle';
import { ChangeRequestState } from 'component/changeRequest/changeRequest.types';
import { ChangesThatWouldBeOverwritten } from './strategy-change-diff-calculation';
const ChangesToOverwriteContainer = styled(Box)(({ theme }) => ({
color: theme.palette.warning.dark,
@ -137,10 +122,19 @@ const DetailsTable: React.FC<{
);
};
const OverwriteWarning: React.FC<{
export const OverwriteWarning: React.FC<{
changeType: 'segment' | 'strategy' | 'environment variant configuration';
changesThatWouldBeOverwritten: ChangesThatWouldBeOverwritten;
}> = ({ changeType, changesThatWouldBeOverwritten }) => {
changesThatWouldBeOverwritten: ChangesThatWouldBeOverwritten | null;
changeRequestState: ChangeRequestState;
}> = ({ changeType, changesThatWouldBeOverwritten, changeRequestState }) => {
const changeRequestIsClosed = ['Applied', 'Cancelled', 'Rejected'].includes(
changeRequestState,
);
if (!changesThatWouldBeOverwritten || changeRequestIsClosed) {
return null;
}
return (
<ChangesToOverwriteContainer>
<p>
@ -159,74 +153,3 @@ const OverwriteWarning: React.FC<{
</ChangesToOverwriteContainer>
);
};
export const EnvVariantChangesToOverwrite: React.FC<{
currentVariants?: IFeatureVariant[];
change: IChangeRequestPatchVariant;
}> = ({ change, currentVariants }) => {
const checkForChanges = useUiFlag('changeRequestConflictHandling');
const changesThatWouldBeOverwritten = checkForChanges
? getEnvVariantChangesThatWouldBeOverwritten(currentVariants, change)
: null;
if (!changesThatWouldBeOverwritten) {
return null;
}
return (
<OverwriteWarning
changeType='environment variant configuration'
changesThatWouldBeOverwritten={changesThatWouldBeOverwritten}
/>
);
};
export const SegmentChangesToOverwrite: React.FC<{
currentSegment?: ISegment;
change: IChangeRequestUpdateSegment;
}> = ({ change, currentSegment }) => {
const checkForChanges = useUiFlag('changeRequestConflictHandling');
const changesThatWouldBeOverwritten = checkForChanges
? getSegmentChangesThatWouldBeOverwritten(currentSegment, change)
: null;
if (!changesThatWouldBeOverwritten) {
return null;
}
return (
<OverwriteWarning
changeType='segment'
changesThatWouldBeOverwritten={changesThatWouldBeOverwritten}
/>
);
};
export const StrategyChangesToOverwrite: React.FC<{
currentStrategy?: IFeatureStrategy;
change: IChangeRequestUpdateStrategy;
}> = ({ change, currentStrategy }) => {
const checkForChanges = useUiFlag('changeRequestConflictHandling');
const changesThatWouldBeOverwritten = checkForChanges
? getStrategyChangesThatWouldBeOverwritten(currentStrategy, change)
: null;
const { registerWillOverwriteStrategyChanges } =
useChangeRequestPlausibleContext();
useEffect(() => {
if (changesThatWouldBeOverwritten) {
registerWillOverwriteStrategyChanges();
}
}, [changesThatWouldBeOverwritten]);
if (!changesThatWouldBeOverwritten) {
return null;
}
return (
<OverwriteWarning
changeType='strategy'
changesThatWouldBeOverwritten={changesThatWouldBeOverwritten}
/>
);
};

View File

@ -170,12 +170,14 @@ export const FeatureChange: FC<{
featureName={feature.name}
environmentName={changeRequest.environment}
projectId={changeRequest.project}
changeRequestState={changeRequest.state}
/>
) : null}
{change.action === 'patchVariant' && (
<VariantPatch
feature={feature.name}
project={changeRequest.project}
changeRequestState={changeRequest.state}
environment={changeRequest.environment}
change={change}
actions={actions}

View File

@ -1,7 +1,10 @@
import { FC, ReactNode } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import { Box, Card, Typography, Link } from '@mui/material';
import { ISegmentChange } from '../../../changeRequest.types';
import {
ChangeRequestState,
ISegmentChange,
} from '../../../changeRequest.types';
import { SegmentChangeDetails } from './SegmentChangeDetails';
import { ConflictWarning } from './ConflictWarning';
@ -9,12 +12,14 @@ interface ISegmentChangeProps {
segmentChange: ISegmentChange;
onNavigate?: () => void;
actions: ReactNode;
changeRequestState: ChangeRequestState;
}
export const SegmentChange: FC<ISegmentChangeProps> = ({
segmentChange,
onNavigate,
actions,
changeRequestState,
}) => (
<Card
elevation={0}
@ -66,6 +71,10 @@ export const SegmentChange: FC<ISegmentChangeProps> = ({
</Link>
</Box>
</Box>
<SegmentChangeDetails change={segmentChange} actions={actions} />
<SegmentChangeDetails
change={segmentChange}
actions={actions}
changeRequestState={changeRequestState}
/>
</Card>
);

View File

@ -1,13 +1,14 @@
import { VFC, FC, ReactNode } from 'react';
import { Box, styled, Typography } from '@mui/material';
import {
ChangeRequestState,
IChangeRequestDeleteSegment,
IChangeRequestUpdateSegment,
} from 'component/changeRequest/changeRequest.types';
import { useSegment } from 'hooks/api/getters/useSegment/useSegment';
import { SegmentDiff, SegmentTooltipLink } from '../../SegmentTooltipLink';
import { ConstraintAccordionList } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList';
import { SegmentChangesToOverwrite } from './StrategyChangeOverwriteWarning';
import { ChangeOverwriteWarning } from './ChangeOverwriteWarning/ChangeOverwriteWarning';
const ChangeItemCreateEditWrapper = styled(Box)(({ theme }) => ({
display: 'grid',
@ -51,7 +52,8 @@ const SegmentContainer = styled(Box, {
export const SegmentChangeDetails: VFC<{
actions?: ReactNode;
change: IChangeRequestUpdateSegment | IChangeRequestDeleteSegment;
}> = ({ actions, change }) => {
changeRequestState: ChangeRequestState;
}> = ({ actions, change, changeRequestState }) => {
const { segment: currentSegment } = useSegment(change.payload.id);
return (
@ -78,9 +80,13 @@ export const SegmentChangeDetails: VFC<{
)}
{change.action === 'updateSegment' && (
<>
<SegmentChangesToOverwrite
currentSegment={currentSegment}
change={change}
<ChangeOverwriteWarning
data={{
current: currentSegment,
change,
changeType: 'segment',
}}
changeRequestState={changeRequestState}
/>
<ChangeItemCreateEditWrapper>
<ChangeItemInfo>

View File

@ -8,6 +8,7 @@ import {
} from '../../StrategyTooltipLink/StrategyTooltipLink';
import { StrategyExecution } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution';
import {
ChangeRequestState,
IChangeRequestAddStrategy,
IChangeRequestDeleteStrategy,
IChangeRequestUpdateStrategy,
@ -17,7 +18,7 @@ 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 { StrategyChangesToOverwrite } from './StrategyChangeOverwriteWarning';
import { ChangeOverwriteWarning } from './ChangeOverwriteWarning/ChangeOverwriteWarning';
export const ChangeItemWrapper = styled(Box)({
display: 'flex',
@ -120,7 +121,15 @@ export const StrategyChange: VFC<{
environmentName: string;
featureName: string;
projectId: string;
}> = ({ actions, change, featureName, environmentName, projectId }) => {
changeRequestState: ChangeRequestState;
}> = ({
actions,
change,
featureName,
environmentName,
projectId,
changeRequestState,
}) => {
const currentStrategy = useCurrentStrategy(
change,
projectId,
@ -224,9 +233,13 @@ export const StrategyChange: VFC<{
)}
{change.action === 'updateStrategy' && (
<>
<StrategyChangesToOverwrite
currentStrategy={currentStrategy}
change={change}
<ChangeOverwriteWarning
data={{
current: currentStrategy,
change,
changeType: 'strategy',
}}
changeRequestState={changeRequestState}
/>
<ChangeItemCreateEditDeleteWrapper>
<ChangeItemInfo>

View File

@ -1,12 +1,15 @@
import { Box, styled } from '@mui/material';
import { IChangeRequestPatchVariant } from 'component/changeRequest/changeRequest.types';
import {
ChangeRequestState,
IChangeRequestPatchVariant,
} from 'component/changeRequest/changeRequest.types';
import { Badge } from 'component/common/Badge/Badge';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
import { EnvironmentVariantsTable } from 'component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsCard/EnvironmentVariantsTable/EnvironmentVariantsTable';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { ReactNode } from 'react';
import { EnvVariantChangesToOverwrite } from '../StrategyChangeOverwriteWarning';
import { ChangeOverwriteWarning } from '../ChangeOverwriteWarning/ChangeOverwriteWarning';
import { VariantDiff } from './VariantDiff';
const ChangeItemInfo = styled(Box)({
@ -37,6 +40,7 @@ interface IVariantPatchProps {
environment: string;
change: IChangeRequestPatchVariant;
actions?: ReactNode;
changeRequestState: ChangeRequestState;
}
export const VariantPatch = ({
@ -45,6 +49,7 @@ export const VariantPatch = ({
environment,
change,
actions,
changeRequestState,
}: IVariantPatchProps) => {
const { feature: featureData } = useFeature(project, feature);
@ -54,9 +59,13 @@ export const VariantPatch = ({
return (
<ChangeItemInfo>
<EnvVariantChangesToOverwrite
currentVariants={preData}
change={change}
<ChangeOverwriteWarning
data={{
current: preData,
change,
changeType: 'environment variant configuration',
}}
changeRequestState={changeRequestState}
/>
<StyledChangeHeader>
<TooltipLink