diff --git a/.changeset/shy-carrots-wash.md b/.changeset/shy-carrots-wash.md new file mode 100644 index 000000000..28596feeb --- /dev/null +++ b/.changeset/shy-carrots-wash.md @@ -0,0 +1,5 @@ +--- +"@hyperdx/app": patch +--- + +fix: Update "Copy Object" in line viewer to work with nested objects and arrays diff --git a/packages/app/src/components/DBRowJsonViewer.test.tsx b/packages/app/src/components/DBRowJsonViewer.test.tsx index 75dea3d2b..22fe2af87 100644 --- a/packages/app/src/components/DBRowJsonViewer.test.tsx +++ b/packages/app/src/components/DBRowJsonViewer.test.tsx @@ -52,15 +52,6 @@ describe('DBRowJsonViewer', () => { jest.clearAllMocks(); }); - // Helper function to simulate clicking a button in a line - const clickLineButton = (fieldText: string, buttonText: string) => { - const line = screen.getByText(fieldText).closest('.line')! as HTMLElement; - fireEvent.mouseEnter(line); - const lineMenu = line.querySelector('.lineMenu')! as HTMLElement; - const button = within(lineMenu).getByText(buttonText); - fireEvent.click(button); - }; - // Helper to render component const renderComponent = (data: any) => { return renderWithMantine( @@ -70,6 +61,33 @@ describe('DBRowJsonViewer', () => { ); }; + // Helper to click a button on a line + const clickLineButton = (fieldText: string, buttonText: string) => { + const line = screen.getByText(fieldText).closest('.line')! as HTMLElement; + fireEvent.mouseEnter(line); + const button = within(line).getByText(buttonText); + fireEvent.click(button); + }; + + // Helper to expand a field and click a button on a nested field + const expandAndClickButton = ( + parentField: string, + childField: string, + buttonText: string, + ) => { + const parentLine = screen + .getByText(parentField) + .closest('.line')! as HTMLElement; + fireEvent.click(parentLine); + + const childLine = screen + .getByText(childField) + .closest('.line')! as HTMLElement; + fireEvent.mouseEnter(childLine); + const button = within(childLine).getByText(buttonText); + fireEvent.click(button); + }; + it('formats log attributes correctly', () => { renderComponent(logData); clickLineButton('field1', 'Search'); @@ -136,4 +154,45 @@ describe('DBRowJsonViewer', () => { }, ); }); + + describe('copy functionality', () => { + const mockClipboard = jest.fn(); + + beforeEach(() => { + Object.assign(navigator, { + clipboard: { writeText: mockClipboard }, + }); + }); + + it('copies array elements from expanded stringified JSON', () => { + const arrayObject = { status: 'True', type: 'PodReady' }; + const data = { conditions: JSON.stringify([arrayObject]) }; + + renderComponent(data); + expandAndClickButton('conditions', '0', 'Copy Object'); + + expect(mockClipboard).toHaveBeenCalledWith( + JSON.stringify(arrayObject, null, 2), + ); + }); + + it('copies entire stringified value when not expanded', () => { + const arrayData = [{ type: 'Ready' }, { type: 'Init' }]; + const data = { conditions: JSON.stringify(arrayData) }; + + renderComponent(data); + clickLineButton('conditions', 'Copy Value'); + + expect(mockClipboard).toHaveBeenCalledWith(JSON.stringify(arrayData)); + }); + + it('copies regular nested objects', () => { + renderComponent(logData); + clickLineButton('nested', 'Copy Object'); + + expect(mockClipboard).toHaveBeenCalledWith( + JSON.stringify({ field3: 'nested value' }, null, 2), + ); + }); + }); }); diff --git a/packages/app/src/components/DBRowJsonViewer.tsx b/packages/app/src/components/DBRowJsonViewer.tsx index ed71b9a70..585b747a0 100644 --- a/packages/app/src/components/DBRowJsonViewer.tsx +++ b/packages/app/src/components/DBRowJsonViewer.tsx @@ -4,7 +4,6 @@ import { useAtom, useAtomValue } from 'jotai'; import { atomWithStorage } from 'jotai/utils'; import get from 'lodash/get'; import { - ActionIcon, Box, Button, Group, @@ -344,8 +343,17 @@ export function DBRowJsonViewer({ } const handleCopyObject = () => { - const copiedObj = - keyPath.length === 0 ? rowData : get(rowData, keyPath); + let copiedObj; + + // When in parsed JSON context (e.g., expanded stringified JSON), + // use the value directly since keyPath doesn't match rowData structure + if (isInParsedJson && parsedJsonRootPath) { + copiedObj = value; + } else { + // For regular nested objects, use keyPath to navigate rowData + copiedObj = keyPath.length === 0 ? rowData : get(rowData, keyPath); + } + window.navigator.clipboard.writeText( JSON.stringify(copiedObj, null, 2), );