1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-24 01:18:01 +02:00

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

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