Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Developer Extension improvements #2516

Merged
merged 18 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from 8 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
1 change: 1 addition & 0 deletions LICENSE-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ file,tracekit,MIT,Copyright 2013 Onur Can Cakmak and all TraceKit contributors
file,web-vitals,Apache-2.0,Copyright 2020 Google LLC
prod,@mantine/core,MIT,Copyright (c) 2021 Vitaly Rtishchev
prod,@mantine/hooks,MIT,Copyright (c) 2021 Vitaly Rtishchev
prod,@tabler/icons-react,MIT,Copyright (c) 2020-2023 Paweł Kuna
prod,clsx,MIT,Copyright (c) Luke Edwards <[email protected]> (lukeed.com)
prod,react,MIT,Copyright (c) Facebook, Inc. and its affiliates.
prod,react-dom,MIT,Copyright (c) Facebook, Inc. and its affiliates.
Expand Down
2 changes: 2 additions & 0 deletions developer-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"dev": "webpack --mode development --watch"
},
"devDependencies": {
"@tabler/icons-react": "2.42.0",
"@types/chrome": "0.0.251",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
Expand All @@ -18,6 +19,7 @@
"webpack": "5.89.0"
},
"dependencies": {
"@datadog/browser-core": "workspace:*",
"@datadog/browser-logs": "workspace:*",
"@datadog/browser-rum": "workspace:*",
"@mantine/core": "7.2.2",
Expand Down
2 changes: 2 additions & 0 deletions developer-extension/src/panel/components/json.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

.valueChildrenIndent {
position: absolute;
z-index: var(--dd-json-z-index);
left: 0px;
width: var(--indent);

Expand Down Expand Up @@ -62,4 +63,5 @@

.jsonLine[data-floating] {
position: absolute;
z-index: var(--dd-json-z-index);
}
13 changes: 3 additions & 10 deletions developer-extension/src/panel/components/json.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { BoxProps, MantineColor } from '@mantine/core'
import { Box, Collapse, Menu, Text } from '@mantine/core'
import { useColorScheme } from '@mantine/hooks'
import { IconCopy } from '@tabler/icons-react'
import type { ForwardedRef, ReactNode } from 'react'
import React, { forwardRef, useContext, createContext, useState } from 'react'
import { copy } from '../copy'
import { formatNumber } from '../formatNumber'

import classes from './json.module.css'
Expand Down Expand Up @@ -314,22 +316,13 @@ function CopyMenuItem({ value, children }: { value: unknown; children: ReactNode
onClick={() => {
copy(JSON.stringify(value, null, 2))
}}
leftSection={<IconCopy size={14} />}
>
{children}
</Menu.Item>
)
}

function copy(text: string) {
// Unfortunately, navigator.clipboard.writeText does not seem to work in extensions
const container = document.createElement('textarea')
container.innerHTML = text
document.body.appendChild(container)
container.select()
document.execCommand('copy')
document.body.removeChild(container)
}

function doesValueHasChildren(value: unknown) {
if (Array.isArray(value)) {
return value.length > 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
--vertical-padding: 7px;

position: fixed;

z-index: var(--dd-column-drag-ghost-z-index);

opacity: 0.5;
border-radius: var(--mantine-radius-sm);
top: var(--drag-target-top);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { EndpointBuilder, InitConfiguration } from '@datadog/browser-core'
BenoitZugmeyer marked this conversation as resolved.
Show resolved Hide resolved
import { computeTransportConfiguration } from '@datadog/browser-core/esm/domain/configuration/transportConfiguration'
import { copy } from '../../../copy'
import type { SdkInfos } from '../../../hooks/useSdkInfos'
import type { SdkEvent } from '../../../sdkEvent'
import { getEventSource, EventSource } from '../../../sdkEvent'

export function canCopyEvent(sdkInfos: SdkInfos | undefined, event: SdkEvent): sdkInfos is SdkInfos {
return Boolean(sdkInfos && getIntakeUrlForEvent(sdkInfos, event))
}

/**
* Copy the event as a curl command to the clipboard.
*
* This function is "naive" in the sense that it does not reflect the actual request done by the
* SDK:
*
* * Request payloads are sometimes compressed, and we don't compress them here.
*
* * The intake URL is computed using the a version of the SDK that might not match one used by the
* website.
*
* * Various tags like "api", "flush_reason", "retry_count" and "retry_after" are not included or
* hardcoded.
*
* * Various browser headers like "User-Agent" are not included.
*/
export function copyEventAsCurl(sdkInfos: SdkInfos, event: SdkEvent) {
const url = getIntakeUrlForEvent(sdkInfos, event)
const escapedEvent = JSON.stringify(event).replace(/\\/g, '\\\\').replace(/'/g, "\\'")
copy(`curl '${url}' \\
-X POST \\
-H 'Content-Type: text/plain' \\
--data-raw $'${escapedEvent}'`)
}

/**
* Copy the event as a fetch API call to the clipboard.
*
* This function is "naive" in the sense that it does not reflect the actual request done by the
* SDK:
*
* * Request payloads are sometimes compressed, and we don't compress them here.
*
* * The intake URL is computed using the a version of the SDK that might not match one used by the
* website.
*
* * Various tags like "api", "flush_reason", "retry_count" and "retry_after" are not included or
* hardcoded.
*
* * Various browser headers like "User-Agent" are not included.
bcaudan marked this conversation as resolved.
Show resolved Hide resolved
*/
export function copyEventAsFetch(sdkInfos: SdkInfos, event: SdkEvent) {
const url = getIntakeUrlForEvent(sdkInfos, event)
copy(`fetch('${url}', {
method: 'POST',
headers: {
'Content-Type': 'text/plain'
},
body: JSON.stringify(${JSON.stringify(event, null, 2)})
})`)
}

function getIntakeUrlForEvent(sdkInfos: SdkInfos, event: SdkEvent) {
let builder: EndpointBuilder
let version: string

switch (getEventSource(event)) {
case EventSource.RUM:
case EventSource.TELEMETRY: {
if (!sdkInfos.rum?.config || !sdkInfos.rum?.version) {
return
}
version = sdkInfos.rum.version
builder = computeTransportConfiguration(sdkInfos.rum.config as InitConfiguration).rumEndpointBuilder
break
}

case EventSource.LOGS:
if (!sdkInfos.logs?.config || !sdkInfos.logs?.version) {
return
}
version = sdkInfos.logs.version
builder = computeTransportConfiguration(sdkInfos.logs.config as InitConfiguration).logsEndpointBuilder
break
}

return builder
.build('xhr', { data: 'a', bytesCount: 1 })
.replace(/sdk_version%3A[^%&]+/g, `sdk_version%3A${encodeURIComponent(version)}`)
.replace(/dd-evp-origin-version=[^&]+/g, `dd-evp-origin-version=${encodeURIComponent(version)}`)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
.cell {
vertical-align: top;
}

.cell:not([data-no-wrap]) {
word-break: break-word;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Table, Badge, Menu } from '@mantine/core'
import { IconCopy, IconDotsVertical, IconColumnInsertRight } from '@tabler/icons-react'
import type { ComponentPropsWithoutRef, ReactNode } from 'react'
import React, { useRef, useState } from 'react'
import clsx from 'clsx'
Expand All @@ -17,9 +18,12 @@ import { formatDate, formatDuration } from '../../../formatNumber'
import { defaultFormatValue, Json } from '../../json'
import { LazyCollapse } from '../../lazyCollapse'
import type { FacetRegistry } from '../../../hooks/useEvents'
import { useSdkInfos } from '../../../hooks/useSdkInfos'
import type { EventListColumn } from './columnUtils'
import { addColumn, includesColumn } from './columnUtils'
import classes from './eventRow.module.css'
import { RowButton } from './rowButton'
import { canCopyEvent, copyEventAsCurl, copyEventAsFetch } from './copyEvent'

const RUM_EVENT_TYPE_COLOR = {
action: 'violet',
Expand Down Expand Up @@ -76,6 +80,7 @@ export const EventRow = React.memo(
onClick={() => {
onColumnsChange(addColumn(columns, newColumn))
}}
leftSection={<IconColumnInsertRight size={14} />}
>
Add column
</Menu.Item>
Expand All @@ -85,26 +90,27 @@ export const EventRow = React.memo(

return (
<Table.Tr>
{columns.map((column, index): React.ReactElement => {
const isLast = index === columns.length - 1
{columns.map((column): React.ReactElement => {
switch (column.type) {
case 'date':
return (
<Cell key="date" isLast={isLast}>
<Cell key="date" noWrap>
{formatDate(event.date)}
</Cell>
)
case 'description':
return (
<Cell
key="description"
isLast={isLast}
className={classes.descriptionCell}
onClick={(event) => {
if (jsonRef.current?.contains(event.target as Node)) {
// Ignore clicks on the collapsible area
const target = event.target as Element

// Ignore clicks on menus or the JSON contained in the collapsible area
if (target.matches('[role="menu"] *') || jsonRef.current?.contains(target)) {
return
}

setIsCollapsed((previous) => !previous)
}}
>
Expand All @@ -123,7 +129,7 @@ export const EventRow = React.memo(
)
case 'type':
return (
<Cell key="type" isLast={isLast}>
<Cell key="type">
{isRumEvent(event) || isTelemetryEvent(event) ? (
<Badge variant="outline" color={RUM_EVENT_TYPE_COLOR[event.type]}>
{event.type}
Expand All @@ -138,7 +144,7 @@ export const EventRow = React.memo(
case 'field': {
const value = facetRegistry.getFieldValueForEvent(event, column.path)
return (
<Cell key={`field-${column.path}`} isLast={isLast}>
<Cell key={`field-${column.path}`}>
{value !== undefined && (
<Json
value={value}
Expand All @@ -152,24 +158,63 @@ export const EventRow = React.memo(
}
}
})}
<Cell>
<EventMenu event={event} />
</Cell>
</Table.Tr>
)
}
)

function EventMenu({ event }: { event: SdkEvent }) {
return (
<Menu shadow="md" width={200}>
<Menu.Target>
<RowButton icon={IconDotsVertical} title="Actions" />
</Menu.Target>

<Menu.Dropdown>
<EventMenuDropdown event={event} />
</Menu.Dropdown>
</Menu>
)
}

function EventMenuDropdown({ event }: { event: SdkEvent }) {
const infos = useSdkInfos()
if (!canCopyEvent(infos, event)) {
return (
<>
<Menu.Item disabled>Copy as cURL</Menu.Item>
<Menu.Item disabled>Copy as fetch</Menu.Item>
</>
)
}
return (
<>
<Menu.Item leftSection={<IconCopy size={14} />} onClick={() => copyEventAsCurl(infos, event)}>
Copy as cURL
</Menu.Item>
<Menu.Item leftSection={<IconCopy size={14} />} onClick={() => copyEventAsFetch(infos, event)}>
Copy as fetch
</Menu.Item>
</>
)
}

function Cell({
isLast,
children,
className,
onClick,
noWrap,
}: {
isLast: boolean
children: ReactNode
className?: string
onClick?: ComponentPropsWithoutRef<'td'>['onClick']
noWrap?: boolean
}) {
return (
<Table.Td colSpan={isLast ? 2 : 1} className={clsx(className, classes.cell)} onClick={onClick}>
<Table.Td className={clsx(className, classes.cell)} data-no-wrap={noWrap || undefined} onClick={onClick}>
{children}
</Table.Td>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
.root {
margin: 0 var(--mantine-spacing-md) var(--mantine-spacing-md) var(--mantine-spacing-md);
border: 1px solid var(--dd-border-color);
border-radius: var(--mantine-radius-md);
}

.root thead {
z-index: var(--dd-events-list-header-z-index);
}

/*
BenoitZugmeyer marked this conversation as resolved.
Show resolved Hide resolved
When the header is positioned as 'sticky', its bottom border disappears when scrolled down,
making difficult to differentiate the header from the rows. This pseudo-element is used to
draw a border at the bottom of the header, so that it is always visible.
*/
.root thead::after {
content: '';
border-bottom: 1px solid var(--dd-border-color);
width: 100%;
display: block;
position: absolute;
margin-top: -1px;
}

.addColumnCell {
Expand Down
Loading