Skip to content

Commit

Permalink
fix: show the updated value instead of the snapshot value (#5989)
Browse files Browse the repository at this point in the history
This PR fixes a bug in the displayed value of the conflict list so that
it shows the value it would update to instead of the snapshot value.

In doing so, it updates the logic of the algorithm to:

1. if the snapshot value and the current value are the same, it's not a
conflict (it's an intended change)
2. If the snapshot value differs from the current value, it is a
conflict if and only if the value in the change differs from the current
value. Otherwise, it's not a conflict.

The new test cases are:
- it shows a diff for a property if the snapshot and live version differ
for that property and the changed value is different from the live
version
- it does not show a diff for a property if the live version and the
change have the same value, even if the snapshot differs from the live
version
- it does not show a diff for a property if the snapshot and the live
version are the same
  • Loading branch information
thomasheartman committed Jan 24, 2024
1 parent 13a9b1b commit 01318b1
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 33 deletions.
@@ -1,4 +1,7 @@
import { IChangeRequestUpdateStrategy } from 'component/changeRequest/changeRequest.types';
import {
ChangeRequestEditStrategy,
IChangeRequestUpdateStrategy,
} from 'component/changeRequest/changeRequest.types';
import { IFeatureStrategy } from 'interfaces/strategy';
import omit from 'lodash.omit';
import { getChangesThatWouldBeOverwritten } from './strategy-change-diff-calculation';
Expand Down Expand Up @@ -181,9 +184,7 @@ describe('Strategy change conflict detection', () => {
property,
oldValue,
newValue:
change.payload.snapshot![
property as keyof IFeatureStrategy
],
change.payload[property as keyof ChangeRequestEditStrategy],
}),
);

Expand Down Expand Up @@ -269,7 +270,7 @@ describe('Strategy change conflict detection', () => {
{
property: 'variants',
oldValue: existingStrategyWithVariants.variants,
newValue: undefined,
newValue: [],
},
]);
});
Expand Down Expand Up @@ -343,4 +344,68 @@ describe('Strategy change conflict detection', () => {

expect(cases.every((result) => result === null)).toBeTruthy();
});

test('it shows a diff for a property if the snapshot and live version differ for that property and the changed value is different from the live version', () => {
const liveVersion = {
...existingStrategy,
title: 'new-title',
};

const changedVersion = {
...change,
payload: {
...change.payload,
title: 'other-new-title',
},
};
const result = getChangesThatWouldBeOverwritten(
liveVersion,
changedVersion,
);

expect(result).toStrictEqual([
{
property: 'title',
oldValue: liveVersion.title,
newValue: changedVersion.payload.title,
},
]);
});

test('it does not show a diff for a property if the live version and the change have the same value, even if the snapshot differs from the live version', () => {
const liveVersion = {
...existingStrategy,
title: 'new-title',
};

const changedVersion = {
...change,
payload: {
...change.payload,
title: liveVersion.title,
},
};
const result = getChangesThatWouldBeOverwritten(
liveVersion,
changedVersion,
);

expect(result).toBeNull();
});

test('it does not show a diff for a property if the snapshot and the live version are the same', () => {
const changedVersion = {
...change,
payload: {
...change.payload,
title: 'new-title',
},
};
const result = getChangesThatWouldBeOverwritten(
existingStrategy,
changedVersion,
);

expect(result).toBeNull();
});
});
@@ -1,80 +1,124 @@
import { IChangeRequestUpdateStrategy } from 'component/changeRequest/changeRequest.types';
import {
ChangeRequestEditStrategy,
IChangeRequestUpdateStrategy,
} from 'component/changeRequest/changeRequest.types';
import { IFeatureStrategy } from 'interfaces/strategy';
import isEqual from 'lodash.isequal';
import omit from 'lodash.omit';

const hasJsonDiff = (object: unknown, objectToCompare: unknown) =>
JSON.stringify(object) !== JSON.stringify(objectToCompare);
type JsonDiffProps = {
snapshotValue: unknown;
currentValue: unknown;
changeValue: unknown;
fallback?: unknown;
};
const hasJsonDiff = ({
snapshotValue,
currentValue,
changeValue,
fallback,
}: JsonDiffProps) => {
const currentJson = JSON.stringify(currentValue ?? fallback);
return (
JSON.stringify(snapshotValue ?? fallback) !== currentJson &&
JSON.stringify(changeValue ?? fallback) !== currentJson
);
};

const hasChanged = (
snapshotValue: unknown,
currentValue: unknown,
changeValue: unknown,
) => {
if (typeof snapshotValue === 'object') {
return (
!isEqual(snapshotValue, currentValue) &&
!isEqual(currentValue, changeValue)
);
}
return hasJsonDiff({ snapshotValue, currentValue, changeValue });
};

type DataToOverwrite<Prop extends keyof IFeatureStrategy> = {
type DataToOverwrite<Prop extends keyof ChangeRequestEditStrategy> = {
property: Prop;
oldValue: IFeatureStrategy[Prop];
newValue: IFeatureStrategy[Prop];
oldValue: ChangeRequestEditStrategy[Prop];
newValue: ChangeRequestEditStrategy[Prop];
};
type ChangesThatWouldBeOverwritten = DataToOverwrite<keyof IFeatureStrategy>[];
type ChangesThatWouldBeOverwritten = DataToOverwrite<
keyof ChangeRequestEditStrategy
>[];

export const getChangesThatWouldBeOverwritten = (
currentStrategyConfig: IFeatureStrategy | undefined,
change: IChangeRequestUpdateStrategy,
): ChangesThatWouldBeOverwritten | null => {
const { snapshot } = change.payload;
if (snapshot && currentStrategyConfig) {
const hasChanged = (a: unknown, b: unknown) => {
if (typeof a === 'object') {
return !isEqual(a, b);
}
return hasJsonDiff(a, b);
};

if (snapshot && currentStrategyConfig) {
// compare each property in the snapshot. The property order
// might differ, so using JSON.stringify to compare them
// doesn't work.
const changes: ChangesThatWouldBeOverwritten = Object.entries(
currentStrategyConfig,
omit(currentStrategyConfig, 'strategyName'),
)
.map(([key, currentValue]: [string, unknown]) => {
const snapshotValue = snapshot[key as keyof IFeatureStrategy];
const changeValue =
change.payload[key as keyof ChangeRequestEditStrategy];

const hasJsonDiffWithFallback = (fallback: unknown) =>
hasJsonDiff({
snapshotValue,
currentValue,
changeValue,
fallback,
});

// compare, assuming that order never changes
if (key === 'segments') {
// segments can be undefined on the original
// object, but that doesn't mean it has changed
if (hasJsonDiff(snapshotValue, currentValue ?? [])) {
if (hasJsonDiffWithFallback([])) {
return {
property: key as keyof IFeatureStrategy,
property: key as keyof ChangeRequestEditStrategy,
oldValue: currentValue,
newValue: snapshotValue,
newValue: changeValue,
};
}
} else if (key === 'variants') {
// strategy variants might not be defined, so use
// fallback values
if (hasJsonDiff(snapshotValue ?? [], currentValue ?? [])) {
if (hasJsonDiffWithFallback([])) {
return {
property: key as keyof IFeatureStrategy,
property: key as keyof ChangeRequestEditStrategy,
oldValue: currentValue,
newValue: snapshotValue,
newValue: changeValue,
};
}
} else if (key === 'title') {
// the title can be defined as `null` or
// `undefined`, so we fallback to an empty string
if (hasJsonDiff(snapshotValue ?? '', currentValue ?? '')) {
if (hasJsonDiffWithFallback('')) {
return {
property: key as keyof IFeatureStrategy,
property: key as keyof ChangeRequestEditStrategy,
oldValue: currentValue,
newValue: snapshotValue,
newValue: changeValue,
};
}
} else if (hasChanged(snapshotValue, currentValue)) {
} else if (
hasChanged(snapshotValue, currentValue, changeValue)
) {
return {
property: key as keyof IFeatureStrategy,
property: key as keyof ChangeRequestEditStrategy,
oldValue: currentValue,
newValue: snapshotValue,
newValue: changeValue,
};
}
})
.filter(
(change): change is DataToOverwrite<keyof IFeatureStrategy> =>
(
change,
): change is DataToOverwrite<keyof ChangeRequestEditStrategy> =>
Boolean(change),
);

Expand Down

0 comments on commit 01318b1

Please sign in to comment.