mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
chore(1-3835): improve json diff view (#10146)
Replaces the existing JSON diff implementation we use with `json-diff-react` (35kb unpacked, according to npm), a react-fork of the popular `json-diff` library. The change is behind a new flag. The new library has several advantages: - nicer formatting (including nested objects) - we don't need to calculate the diff manually anymore - option to hide/reveal unchanged properties There's still a few more things to put in place (such as handling of no changes) and overflow handling when you have very long properties. Here's a few comparison screenies: Old (below) vs new (above):  Fold and unfold:   In change requests:  Strategy re-ordering: Folded:  Unfolded:  Old: 
This commit is contained in:
parent
d2b9751e01
commit
c619cb9ec5
@ -160,6 +160,7 @@
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"dependencies": {
|
||||
"chartjs-plugin-datalabels": "^2.2.0",
|
||||
"json-2-csv": "^5.5.5"
|
||||
"json-2-csv": "^5.5.5",
|
||||
"json-diff-react": "^1.0.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { diff } from 'deep-diff';
|
||||
import { useTheme } from '@mui/system';
|
||||
import type { JSX, CSSProperties } from 'react';
|
||||
import { type JSX, type CSSProperties, useState, type FC, useId } from 'react';
|
||||
import { JsonDiffComponent, type JsonValue } from 'json-diff-react';
|
||||
import { Button, styled, useTheme } from '@mui/material';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
const DIFF_PREFIXES: Record<string, string> = {
|
||||
A: ' ',
|
||||
@ -17,10 +19,71 @@ interface IEventDiffResult {
|
||||
|
||||
interface IEventDiffProps {
|
||||
entry: { data?: unknown; preData?: unknown };
|
||||
/**
|
||||
* @deprecated remove with flag improvedJsonDiff
|
||||
*/
|
||||
sort?: (a: IEventDiffResult, b: IEventDiffResult) => number;
|
||||
}
|
||||
|
||||
const EventDiff = ({
|
||||
const DiffStyles = styled('div')(({ theme }) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
fontFamily: 'monospace',
|
||||
whiteSpace: 'pre',
|
||||
fontSize: theme.typography.body2.fontSize,
|
||||
|
||||
'.deletion, .addition': {
|
||||
position: 'relative',
|
||||
'::before': {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
marginLeft: '-10px',
|
||||
},
|
||||
},
|
||||
|
||||
'.addition': {
|
||||
color: theme.palette.eventLog.diffAdd,
|
||||
'::before': {
|
||||
content: '"+"',
|
||||
},
|
||||
},
|
||||
'.deletion': {
|
||||
color: theme.palette.eventLog.diffSub,
|
||||
'::before': {
|
||||
content: '"-"',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const NewEventDiff: FC<IEventDiffProps> = ({ entry }) => {
|
||||
const [full, setFull] = useState(false);
|
||||
const diffId = useId();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => setFull(!full)}
|
||||
aria-controls={diffId}
|
||||
aria-expanded={full}
|
||||
>
|
||||
{full ? 'Show only changed properties' : 'Show all properties'}
|
||||
</Button>
|
||||
<DiffStyles id={diffId}>
|
||||
<JsonDiffComponent
|
||||
jsonA={(entry.preData ?? {}) as JsonValue}
|
||||
jsonB={(entry.data ?? {}) as JsonValue}
|
||||
jsonDiffOptions={{
|
||||
full: full,
|
||||
maxElisions: 2,
|
||||
excludeKeys: ['id', 'createdAt', 'updatedAt'],
|
||||
}}
|
||||
/>
|
||||
</DiffStyles>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const OldEventDiff: FC<IEventDiffProps> = ({
|
||||
entry,
|
||||
sort = (a, b) => a.key.localeCompare(b.key),
|
||||
}: IEventDiffProps) => {
|
||||
@ -113,11 +176,18 @@ const EventDiff = ({
|
||||
}
|
||||
|
||||
return (
|
||||
// biome-ignore lint/a11y/noNoninteractiveTabindex: <explanation>
|
||||
<pre style={{ overflowX: 'auto', overflowY: 'hidden' }} tabIndex={0}>
|
||||
<pre style={{ overflowX: 'auto', overflowY: 'hidden' }}>
|
||||
<code>{changes.length === 0 ? '(no changes)' : changes}</code>
|
||||
</pre>
|
||||
);
|
||||
};
|
||||
|
||||
const EventDiff: FC<IEventDiffProps> = (props) => {
|
||||
const useNewJsonDiff = useUiFlag('improvedJsonDiff');
|
||||
if (useNewJsonDiff) {
|
||||
return <NewEventDiff {...props} />;
|
||||
}
|
||||
return <OldEventDiff {...props} />;
|
||||
};
|
||||
|
||||
export default EventDiff;
|
||||
|
@ -90,6 +90,7 @@ export type UiFlags = {
|
||||
lifecycleMetrics?: boolean;
|
||||
createFlagDialogCache?: boolean;
|
||||
healthToTechDebt?: boolean;
|
||||
improvedJsonDiff?: boolean;
|
||||
};
|
||||
|
||||
export interface IVersionInfo {
|
||||
|
@ -1277,6 +1277,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ewoudenberg/difflib@npm:^0.1.0":
|
||||
version: 0.1.0
|
||||
resolution: "@ewoudenberg/difflib@npm:0.1.0"
|
||||
dependencies:
|
||||
heap: "npm:>= 0.2.0"
|
||||
checksum: 10c0/3060807c91f39c5c1e5421fe51573bb2bffcab48cb32e9dcc69ce0d43e658dbf5959421382541012628ca6b842d252fc157a79f70cea8088a864572fec2bd094
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@exodus/schemasafe@npm:^1.0.0-rc.2":
|
||||
version: 1.3.0
|
||||
resolution: "@exodus/schemasafe@npm:1.3.0"
|
||||
@ -6079,6 +6088,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"heap@npm:>= 0.2.0":
|
||||
version: 0.2.7
|
||||
resolution: "heap@npm:0.2.7"
|
||||
checksum: 10c0/341c5d51ae13dc8346c371a8a69c57c972fcb9a3233090d3dd5ba29d483d6b5b4e75492443cbfeacd46608bb30e6680f646ffb7a6205900221735587d07a79b6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hoist-non-react-statics@npm:^3.3.1":
|
||||
version: 3.3.2
|
||||
resolution: "hoist-non-react-statics@npm:3.3.2"
|
||||
@ -6806,6 +6822,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"json-diff-react@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "json-diff-react@npm:1.0.1"
|
||||
dependencies:
|
||||
"@ewoudenberg/difflib": "npm:^0.1.0"
|
||||
react: "npm:^18.2.0"
|
||||
react-dom: "npm:^18.2.0"
|
||||
checksum: 10c0/085c395f61a8fb148d60c91bb279dbc071e76a89f96634da41df03b72ae365f916f3e5a12e77d6f4f64a680572849ae61d5a5853e1b83f9d7f31e7e8aa8e6de0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"json-parse-even-better-errors@npm:^2.3.0":
|
||||
version: 2.3.1
|
||||
resolution: "json-parse-even-better-errors@npm:2.3.1"
|
||||
@ -8567,7 +8594,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-dom@npm:18.3.1":
|
||||
"react-dom@npm:18.3.1, react-dom@npm:^18.2.0":
|
||||
version: 18.3.1
|
||||
resolution: "react-dom@npm:18.3.1"
|
||||
dependencies:
|
||||
@ -8826,7 +8853,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react@npm:18.3.1":
|
||||
"react@npm:18.3.1, react@npm:^18.2.0":
|
||||
version: 18.3.1
|
||||
resolution: "react@npm:18.3.1"
|
||||
dependencies:
|
||||
@ -10369,6 +10396,7 @@ __metadata:
|
||||
immer: "npm:9.0.21"
|
||||
jsdom: "npm:25.0.1"
|
||||
json-2-csv: "npm:^5.5.5"
|
||||
json-diff-react: "npm:^1.0.1"
|
||||
lodash.clonedeep: "npm:4.5.0"
|
||||
lodash.isequal: "npm:^4.5.0"
|
||||
lodash.mapvalues: "npm:^4.6.0"
|
||||
|
@ -61,6 +61,7 @@ export type IFlagKey =
|
||||
| 'lifecycleMetrics'
|
||||
| 'customMetrics'
|
||||
| 'createFlagDialogCache'
|
||||
| 'improvedJsonDiff'
|
||||
| 'changeRequestApproverEmails';
|
||||
|
||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
||||
@ -286,6 +287,10 @@ const flags: IFlags = {
|
||||
process.env.UNLEASH_EXPERIMENTAL_CHANGE_REQUEST_APPROVER_EMAILS,
|
||||
false,
|
||||
),
|
||||
improvedJsonDiff: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_IMPROVED_JSON_DIFF,
|
||||
false,
|
||||
),
|
||||
};
|
||||
|
||||
export const defaultExperimentalOptions: IExperimentalOptions = {
|
||||
|
@ -55,6 +55,7 @@ process.nextTick(async () => {
|
||||
reportUnknownFlags: true,
|
||||
customMetrics: true,
|
||||
lifecycleMetrics: true,
|
||||
improvedJsonDiff: true,
|
||||
},
|
||||
},
|
||||
authentication: {
|
||||
|
Loading…
Reference in New Issue
Block a user