Skip to content

Commit 7fc0b81

Browse files
authored
feat: add ChangeView component to bulk update preview (#5209)
* plug ChangeView component into bulk update preview * tests for unifyDocuments() * leave space for expand/collapse buttons * css tweaks * toBSON fixes * address TODOs * factor out expand button * fix bulk update e2e test * depcheck * unit tests for ChangeView component * address TODO * don't promote values when loading before&after preview docs * remove TODO * ignore the generated expected results * maybe without the leading slash * consistently use getValueShape() * live with horizontal scrolling rather than weird wrapping * wider preview, remove extra spacing, format the default update text * sans only * sans only * json formatting seems to not be entirely deterministic.. * update e2e expected result * larger modal for bulk update
1 parent 76a38f1 commit 7fc0b81

File tree

50 files changed

+9028
-31
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+9028
-31
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
* text=auto eol=lf
22
/packages/bson-transpilers/lib/**/* linguist-generated=true
3+
packages/compass-crud/test/fixture-results/* linguist-generated=true

package-lock.json

Lines changed: 59 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/compass-crud/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
"enzyme": "^3.11.0",
9393
"eslint": "^7.25.0",
9494
"hadron-app": "^5.16.2",
95+
"jsondiffpatch": "^0.5.0",
9596
"lodash": "^4.17.21",
9697
"mocha": "^10.2.0",
9798
"mongodb-instance-model": "^12.16.4",

packages/compass-crud/src/components/bulk-update-dialog.tsx

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useMemo, useState, useEffect, useCallback } from 'react';
22
import type { UpdatePreview } from 'mongodb-data-service';
3-
import HadronDocument from 'hadron-document';
3+
import type { Document } from 'bson';
44
import { toJSString } from 'mongodb-query-parser';
55
import {
66
css,
@@ -24,22 +24,26 @@ import {
2424
InteractivePopover,
2525
TextInput,
2626
useId,
27+
DocumentIcon,
2728
} from '@mongodb-js/compass-components';
2829
import type { Annotation } from '@mongodb-js/compass-editor';
2930
import { CodemirrorMultilineEditor } from '@mongodb-js/compass-editor';
3031

31-
import Document from './document';
3232
import type { BSONObject } from '../stores/crud-store';
33-
33+
import { ChangeView } from './change-view';
3434
import { ReadonlyFilter } from './readonly-filter';
35-
import { DocumentIcon } from '@mongodb-js/compass-components';
35+
36+
const modalContentStyles = css({
37+
width: '100%',
38+
maxWidth: '1280px',
39+
});
3640

3741
const columnsStyles = css({
3842
marginTop: spacing[4],
3943
display: 'grid',
4044
width: '100%',
4145
gap: spacing[4],
42-
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
46+
gridTemplateColumns: '2fr 3fr',
4347
});
4448

4549
const queryStyles = css({
@@ -60,7 +64,7 @@ const descriptionStyles = css({
6064

6165
const previewStyles = css({
6266
contain: 'size',
63-
overflow: 'scroll',
67+
overflow: 'auto',
6468
});
6569

6670
const previewDescriptionStyles = css({
@@ -79,7 +83,7 @@ const codeLightContainerStyles = css({
7983
});
8084

8185
const multilineContainerStyles = css({
82-
maxHeight: spacing[4] * 20,
86+
maxHeight: spacing[5] * 7, // fit at our default window size
8387
});
8488

8589
const bannerContainerStyles = css({
@@ -252,12 +256,6 @@ const BulkUpdatePreview: React.FunctionComponent<BulkUpdatePreviewProps> = ({
252256
count,
253257
preview,
254258
}) => {
255-
const previewDocuments = useMemo(() => {
256-
return preview.changes.map(
257-
(change) => new HadronDocument(change.after as Record<string, unknown>)
258-
);
259-
}, [preview]);
260-
261259
// show a preview for the edge case where the count is undefined, not the
262260
// empty state
263261
if (count === 0) {
@@ -294,12 +292,13 @@ const BulkUpdatePreview: React.FunctionComponent<BulkUpdatePreviewProps> = ({
294292
</Description>
295293
</Label>
296294
<div className={updatePreviewStyles}>
297-
{previewDocuments.map((doc: HadronDocument, index: number) => {
295+
{preview.changes.map(({ before, after }, index: number) => {
298296
return (
299297
<UpdatePreviewDocument
300298
key={`change=${index}`}
301299
data-testid="bulk-update-preview-document"
302-
doc={doc}
300+
before={before}
301+
after={after}
303302
/>
304303
);
305304
})}
@@ -390,8 +389,8 @@ export default function BulkUpdateDialog({
390389
<Modal
391390
open={isOpen}
392391
setOpen={closeBulkUpdateDialog}
393-
size={enablePreview ? 'large' : 'default'}
394392
data-testid="bulk-update-dialog"
393+
contentClassName={enablePreview ? modalContentStyles : undefined}
395394
initialFocus={`#${bulkUpdateUpdateId} .cm-content`}
396395
>
397396
<ModalHeader title={modalTitleAndButtonText} subtitle={ns} />
@@ -483,16 +482,25 @@ export default function BulkUpdateDialog({
483482
);
484483
}
485484

485+
const previewCardStyles = css({
486+
padding: spacing[3],
487+
});
488+
486489
function UpdatePreviewDocument({
487-
doc,
490+
before,
491+
after,
488492
...props
489493
}: {
490494
'data-testid': string;
491-
doc: HadronDocument;
495+
before: Document;
496+
after: Document;
492497
}) {
493498
return (
494-
<KeylineCard data-testid={props['data-testid']}>
495-
<Document doc={doc} editable={false} />
499+
<KeylineCard
500+
data-testid={props['data-testid']}
501+
className={previewCardStyles}
502+
>
503+
<ChangeView before={before} after={after} name={props['data-testid']} />
496504
</KeylineCard>
497505
);
498506
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { EJSON } from 'bson';
2+
3+
import { getValueShape } from './shape-utils';
4+
5+
export function stringifyBSON(value: any) {
6+
if (value?.inspect) {
7+
return value.inspect();
8+
}
9+
if (value?.toISOString) {
10+
return value.toISOString();
11+
}
12+
return EJSON.stringify(value);
13+
}
14+
15+
export function unBSON(value: any | any[]): any | any[] {
16+
const shape = getValueShape(value);
17+
if (shape === 'array') {
18+
return value.map(unBSON);
19+
} else if (shape === 'object') {
20+
const mapped: Record<string, any | any[]> = {};
21+
for (const [k, v] of Object.entries(value)) {
22+
mapped[k] = unBSON(v);
23+
}
24+
return mapped;
25+
} else if (value?._bsontype) {
26+
return stringifyBSON(value);
27+
} else if (Object.prototype.toString.call(value) === '[object RegExp]') {
28+
// make sure these match when diffing
29+
return value.toString();
30+
} else if (Object.prototype.toString.call(value) === '[object Date]') {
31+
// make sure dates are consistently strings when diffing
32+
return value.toISOString();
33+
} else {
34+
return value;
35+
}
36+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React from 'react';
2+
import { expect } from 'chai';
3+
import { render, screen, cleanup } from '@testing-library/react';
4+
import { ChangeView } from './change-view';
5+
import { fixtureGroups } from '../../../test/before-after-fixtures';
6+
7+
function renderChangeView(
8+
props?: Partial<React.ComponentProps<typeof ChangeView>>
9+
) {
10+
return render(<ChangeView name="test" before={{}} after={{}} {...props} />);
11+
}
12+
13+
describe('ChangeView Component', function () {
14+
afterEach(function () {
15+
cleanup();
16+
});
17+
18+
it('renders', function () {
19+
renderChangeView({
20+
before: { a: 1 },
21+
after: { b: 2 },
22+
});
23+
24+
expect(screen.getByTestId('change-view-test')).to.exist;
25+
});
26+
27+
for (const group of fixtureGroups) {
28+
context(group.name, function () {
29+
for (const { name, before, after } of group.fixtures) {
30+
it(name, function () {
31+
renderChangeView({
32+
name,
33+
before,
34+
after,
35+
});
36+
37+
// A bit primitive. Just trying to see if there are any errors as a
38+
// sort of regression test. Not sure how to write an assertion that
39+
// would workfor each one.
40+
expect(screen.getByTestId(`change-view-${name}`)).to.exist;
41+
});
42+
}
43+
});
44+
}
45+
});

0 commit comments

Comments
 (0)