Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/compass-components/src/components/context-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export type {
const menuStyles = css({
paddingTop: spacing[150],
paddingBottom: spacing[150],
width: 'max-content',
// LG default width for menu
minWidth: 210,
});

const itemStyles = css({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,27 @@ describe('HadronElement', function () {
);
});

it('copies value when "Copy value" is clicked', function () {
render(
<HadronElement
value={element}
editable={true}
editingEnabled={true}
lineNumberSize={1}
onAddElement={() => {}}
/>
);

// Open context menu and click the copy option
const elementNode = screen.getByTestId('hadron-document-element');
userEvent.click(elementNode, { button: 2 });
userEvent.click(screen.getByText('Copy value'), undefined, {
skipPointerEventsCheck: true,
});

expect(clipboardWriteTextStub).to.have.been.calledWith("'value'");
});

it('copies field and value when "Copy field & value" is clicked', function () {
render(
<HadronElement
Expand All @@ -115,7 +136,7 @@ describe('HadronElement', function () {
skipPointerEventsCheck: true,
});

expect(clipboardWriteTextStub).to.have.been.calledWith('field: "value"');
expect(clipboardWriteTextStub).to.have.been.calledWith("field: 'value'");
});

it('shows "Open URL in browser" for URL string values', function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -512,11 +512,17 @@ export const HadronElement: React.FunctionComponent<{
},
}
: undefined,
{
label: 'Copy value',
onAction: () => {
void navigator.clipboard.writeText(element.toShellSyntax());
},
},
{
label: 'Copy field & value',
onAction: () => {
void navigator.clipboard.writeText(
`${key.value}: ${element.toEJSON()}`
`${key.value}: ${element.toShellSyntax()}`
);
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ describe('DocumentJsonViewItem', function () {
userEvent.click(element, { button: 2 });

// Should show context menu with expected items
expect(screen.getByText('Copy document')).to.exist;
expect(screen.getByText('Copy document as Shell Syntax')).to.exist;
expect(screen.getByText('Copy document as EJSON')).to.exist;
expect(screen.getByText('Clone document...')).to.exist;
expect(screen.getByText('Delete document')).to.exist;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ describe('DocumentListViewItem', function () {
userEvent.click(element, { button: 2 });

// Should show context menu with expected items
expect(screen.getByText('Copy document')).to.exist;
expect(screen.getByText('Copy document as Shell Syntax')).to.exist;
expect(screen.getByText('Copy document as EJSON')).to.exist;
expect(screen.getByText('Clone document...')).to.exist;
expect(screen.getByText('Delete document')).to.exist;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class EditableDocument extends React.Component<
* Handle copying JSON to clipboard of the document.
*/
handleCopy() {
this.props.copyToClipboard?.(this.props.doc);
this.props.copyToClipboard?.(this.props.doc, 'shell-syntax');
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/compass-crud/src/components/json-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const JSONEditor: React.FunctionComponent<JSONEditorProps> = ({
}, [value, editing, setModifiedEJSONStringRef]);

const handleCopy = useCallback(() => {
copyToClipboard?.(doc);
copyToClipboard?.(doc, 'shell-syntax');
}, [copyToClipboard, doc]);

const handleClone = useCallback(() => {
Expand Down
5 changes: 3 additions & 2 deletions packages/compass-crud/src/components/readonly-document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { TypeCastMap } from 'hadron-type-checker';
import { withPreferences } from 'compass-preferences-model/provider';
import { getInsightsForDocument } from '../utils';
import { DocumentEvents } from 'hadron-document';
import type { CopyDocumentFormat } from '../stores/crud-store';
type BSONObject = TypeCastMap['Object'];

export const documentStyles = css({
Expand All @@ -24,7 +25,7 @@ export const documentContentStyles = css({
});

export type ReadonlyDocumentProps = {
copyToClipboard?: (doc: Document) => void;
copyToClipboard?: (doc: Document, format: CopyDocumentFormat) => void;
openInsertDocumentDialog?: (doc: BSONObject, cloned: boolean) => void;
doc: Document;
showInsights?: boolean;
Expand Down Expand Up @@ -110,7 +111,7 @@ class ReadonlyDocument extends React.Component<
* Handle copying JSON to clipboard of the document.
*/
handleCopy = () => {
this.props.copyToClipboard?.(this.props.doc);
this.props.copyToClipboard?.(this.props.doc, 'shell-syntax');
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ describe('useDocumentItemContextMenu', function () {
// Should show all operations
expect(screen.getByText('Expand all fields')).to.exist;
expect(screen.getByText('Edit document')).to.exist;
expect(screen.getByText('Copy document')).to.exist;
expect(screen.getByText('Copy document as Shell Syntax')).to.exist;
expect(screen.getByText('Copy document as EJSON')).to.exist;
expect(screen.getByText('Clone document...')).to.exist;
expect(screen.getByText('Delete document')).to.exist;
});
Expand Down Expand Up @@ -109,7 +110,8 @@ describe('useDocumentItemContextMenu', function () {
expect(screen.queryByText('Delete document')).to.not.exist;
// But show other operations
expect(screen.getByText('Expand all fields')).to.exist;
expect(screen.getByText('Copy document')).to.exist;
expect(screen.getByText('Copy document as Shell Syntax')).to.exist;
expect(screen.getByText('Copy document as EJSON')).to.exist;
expect(screen.getByText('Clone document...')).to.exist;
});
});
Expand All @@ -133,7 +135,8 @@ describe('useDocumentItemContextMenu', function () {

// Should show non-mutating operations
expect(screen.getByText('Expand all fields')).to.exist;
expect(screen.getByText('Copy document')).to.exist;
expect(screen.getByText('Copy document as Shell Syntax')).to.exist;
expect(screen.getByText('Copy document as EJSON')).to.exist;

// Should hide mutating operations
expect(screen.queryByText('Edit document')).to.not.exist;
Expand Down Expand Up @@ -244,16 +247,32 @@ describe('useDocumentItemContextMenu', function () {
expect(expandStub).to.have.been.calledOnce;
});

it('calls copyToClipboard when copy is clicked', function () {
it('calls copyToClipboard when copy as EJSON is clicked', function () {
// Right-click to open context menu
userEvent.click(screen.getByTestId('test-container'), { button: 2 });

// Click copy
userEvent.click(screen.getByText('Copy document'), undefined, {
userEvent.click(screen.getByText('Copy document as EJSON'), undefined, {
skipPointerEventsCheck: true,
});

expect(copyToClipboardStub).to.have.been.calledWith(doc);
expect(copyToClipboardStub).to.have.been.calledWith(doc, 'ejson');
});

it('calls copyToClipboard when copy as Shell Syntax is clicked', function () {
// Right-click to open context menu
userEvent.click(screen.getByTestId('test-container'), { button: 2 });

// Click copy
userEvent.click(
screen.getByText('Copy document as Shell Syntax'),
undefined,
{
skipPointerEventsCheck: true,
}
);

expect(copyToClipboardStub).to.have.been.calledWith(doc, 'shell-syntax');
});

it('calls openInsertDocumentDialog with cloned document when clone is clicked', function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,8 @@ export function useDocumentItemContextMenu({

return useContextMenuGroups(
() => [
isEditable
? {
telemetryLabel: 'Document Item Edit',
items: [
{
label: isEditing ? 'Cancel editing' : 'Edit document',
onAction: () => {
if (isEditing) {
doc.finishEditing();
} else {
doc.startEditing();
}
},
},
],
}
: undefined,
{
telemetryLabel: 'Document Item',
telemetryLabel: 'Document Expand Collapse',
items: [
{
label: isExpanded ? 'Collapse all fields' : 'Expand all fields',
Expand All @@ -48,10 +31,35 @@ export function useDocumentItemContextMenu({
}
},
},
],
},
{
telemetryLabel: 'Document Item',
items: [
...(isEditable
? [
{
label: isEditing ? 'Cancel editing' : 'Edit document',
onAction: () => {
if (isEditing) {
doc.finishEditing();
} else {
doc.startEditing();
}
},
},
]
: []),
{
label: 'Copy document as Shell Syntax',
onAction: () => {
copyToClipboard?.(doc, 'shell-syntax');
},
},
{
label: 'Copy document',
label: 'Copy document as EJSON',
onAction: () => {
copyToClipboard?.(doc);
copyToClipboard?.(doc, 'ejson');
},
},
isEditable
Expand Down
16 changes: 14 additions & 2 deletions packages/compass-crud/src/stores/crud-store.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,17 +404,29 @@ describe('store', function () {
}
});

it('copies the document to the clipboard', function () {
it('copies the document to the clipboard in ejson format', function () {
expect(mockCopyToClipboard.called).to.equal(false);

const doc = { _id: 'testing', name: 'heart 5' };
const hadronDoc = new HadronDocument(doc);

store.copyToClipboard(hadronDoc);
store.copyToClipboard(hadronDoc, 'ejson');
expect(mockCopyToClipboard).to.have.been.calledOnceWithExactly(
'{\n "_id": "testing",\n "name": "heart 5"\n}'
);
});

it('copies the document to the clipboard in shell syntax', function () {
expect(mockCopyToClipboard.called).to.equal(false);

const doc = { _id: 'testing', count: 2 };
const hadronDoc = new HadronDocument(doc);

store.copyToClipboard(hadronDoc, 'shell-syntax');
expect(mockCopyToClipboard).to.have.been.calledOnceWithExactly(
"{\n _id: 'testing',\n count: NumberInt('2')\n}"
);
});
});

describe('#toggleInsertDocument', function () {
Expand Down
19 changes: 6 additions & 13 deletions packages/compass-crud/src/stores/crud-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import type { CollationOptions, MongoServerError } from 'mongodb';
export type BSONObject = TypeCastMap['Object'];
export type BSONArray = TypeCastMap['Array'];
type Mutable<T> = { -readonly [P in keyof T]: T[P] };
export type CopyDocumentFormat = 'ejson' | 'shell-syntax';

export type EmittedAppRegistryEvents =
| 'open-import'
Expand All @@ -95,7 +96,7 @@ export type CrudActions = {
removeDocument(doc: Document): Promise<void>;
replaceDocument(doc: Document): Promise<void>;
openInsertDocumentDialog(doc: BSONObject, cloned: boolean): Promise<void>;
copyToClipboard(doc: Document): void; //XXX
copyToClipboard(doc: Document, format?: CopyDocumentFormat): void; //XXX
openBulkDeleteDialog(): void;
Comment thread
mabaasit marked this conversation as resolved.
runBulkUpdate(): Promise<void>;
closeBulkDeleteDialog(): void;
Expand Down Expand Up @@ -552,22 +553,14 @@ class CrudStoreImpl
return this.state.view.toLowerCase() as Lowercase<DocumentView>;
}

/**
* Copy the document to the clipboard.
*
* @param {HadronDocument} doc - The document.
*
* @returns {Boolean} If the copy succeeded.
*/
copyToClipboard(doc: Document) {
copyToClipboard(doc: Document, format: CopyDocumentFormat = 'ejson') {
this.track(
'Document Copied',
{ mode: this.modeForTelemetry() },
{ mode: this.modeForTelemetry(), format },
this.connectionInfoRef.current
);
const documentEJSON = doc.toEJSON();
// eslint-disable-next-line no-undef
void navigator.clipboard.writeText(documentEJSON);
const str = format === 'ejson' ? doc.toEJSON() : doc.toShellSyntax();
void navigator.clipboard.writeText(str);
}

getWriteError(error: Error): WriteError {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ FindIterable<Document> result = collection.find(filter);`);

await browser.waitUntil(
async () => {
return !!/^\{ "_id": \{ "\$oid": "[a-f0-9]{24}" \}, "i": 34, "j": 0 \}$/.exec(
return !!/^\{ _id: ObjectId\('[a-f0-9]{24}'\), i: NumberInt\('34'\), j: NumberInt\('0'\) \}$/.exec(
(await clipboard.read()).replace(/\s+/g, ' ').replace(/\n/g, '')
);
},
Expand Down
4 changes: 4 additions & 0 deletions packages/compass-telemetry/src/telemetry-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,10 @@ type DocumentCopiedEvent = ConnectionScopedEvent<{
* The view used to copy the document.
*/
mode: 'list' | 'json' | 'table';
/**
* The format used to copy the document.
*/
format: 'ejson' | 'shell-syntax';
};
}>;

Expand Down
3 changes: 2 additions & 1 deletion packages/hadron-document/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@
"eventemitter3": "^4.0.0",
"hadron-type-checker": "^7.5.8",
"lodash": "^4.18.1",
"mongodb": "^7.1.1"
"mongodb": "^7.1.1",
"mongodb-query-parser": "^4.7.14"
},
"devDependencies": {
"@mongodb-js/eslint-config-compass": "^1.4.25",
Expand Down
Loading
Loading