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

✨ [Hackathon] add diagnostic test suite #3194

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
9 changes: 9 additions & 0 deletions developer-extension/src/common/extension.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export type BackgroundToDevtoolsMessage = {
message: SdkMessage
}

export type BackgroundTestResult = {
id: string
status: 'running' | 'passed' | 'failed'
}

export type DevtoolsToBackgroundMessage = {
type: 'update-net-request-rules'
options: NetRequestRulesOptions
Expand Down Expand Up @@ -42,6 +47,10 @@ export type SdkMessage =
segment: BrowserSegmentMetadata
}
}
| {
type: 'test-result'
payload: BackgroundTestResult
}

export type EventCollectionStrategy = 'sdk' | 'requests'

Expand Down
3 changes: 2 additions & 1 deletion developer-extension/src/common/panelTabConstants.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
export const enum PanelTabs {
Diagnostics = 'diagnostics',
Events = 'events',
Infos = 'infos',
Settings = 'settings',
Replay = 'replay',
Settings = 'settings',
}

export const DEFAULT_PANEL_TAB = PanelTabs.Events
2 changes: 1 addition & 1 deletion developer-extension/src/content-scripts/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ declare global {
type SdkPublicApi = { [key: string]: (...args: any[]) => unknown }

function main() {
// Prevent multiple executions when the devetools are reconnecting
// Prevent multiple executions when the devtools are reconnecting
if (window.__ddBrowserSdkExtensionCallback) {
return
}
Expand Down
5 changes: 5 additions & 0 deletions developer-extension/src/panel/components/panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { SettingsTab } from './tabs/settingsTab'
import { InfosTab } from './tabs/infosTab'
import { EventsTab, DEFAULT_COLUMNS } from './tabs/eventsTab'
import { ReplayTab } from './tabs/replayTab'
import { DiagnosticsTab } from './tabs/diagnosticTab'

import * as classes from './panel.module.css'

Expand Down Expand Up @@ -65,6 +66,7 @@ export function Panel() {
>
Settings
</Tabs.Tab>
<Tabs.Tab value={PanelTabs.Diagnostics}>Diagnostics</Tabs.Tab>
</div>
<Anchor
className={classes.link}
Expand Down Expand Up @@ -95,6 +97,9 @@ export function Panel() {
<Tabs.Panel value={PanelTabs.Settings} className={classes.tab}>
<SettingsTab />
</Tabs.Panel>
<Tabs.Panel value={PanelTabs.Diagnostics} className={classes.tab}>
<DiagnosticsTab />
</Tabs.Panel>
</Tabs>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React from 'react'
import { Badge, Card, Flex, Text } from '@mantine/core'
import { Alert } from '../../alert'
import type { ApiDiagnostic, ApiDiagnosticLevel, ApiPathComponent } from '../../../hooks/useApiDiagnostics'
import { useApiDiagnostics } from '../../../hooks/useApiDiagnostics'

const DIAGNOSTIC_LEVEL_COLOR: { [level in ApiDiagnosticLevel]: string } = {
error: 'red',
info: 'teal',
warning: 'orange',
}

export function ApiDiagnosticsTable() {
const apiDiagnostics = useApiDiagnostics()

switch (apiDiagnostics?.status) {
case undefined:
return <Flex></Flex>

case 'error':
return <Alert level="error" message="Failed to collect API diagnostics." />

case 'success':
return (
<Flex direction="column" style={{ rowGap: '1em', paddingTop: '1em' }}>
{apiDiagnostics.diagnostics.map((diagnostic) => (
<ApiDiagnosticRow diagnostic={diagnostic} />
))}
</Flex>
)
}
}

function ApiDiagnosticRow({ diagnostic }: { diagnostic: ApiDiagnostic }) {
return (
<Flex direction="row" style={{ columnGap: '5px' }}>
<ApiDiagnosticBadge level={diagnostic.level} />
<Card>
<ApiDiagnosticDescription subject={diagnostic.subject} message={diagnostic.message} />
</Card>
</Flex>
)
}

function ApiDiagnosticBadge({ level }: { level: ApiDiagnosticLevel }) {
return (
<Badge
variant="outline"
color={DIAGNOSTIC_LEVEL_COLOR[level]}
style={{
alignSelf: 'center',
minWidth: '10em',
marginRight: '1em',
}}
>
{level}
</Badge>
)
}

function ApiDiagnosticDescription({ subject, message }: { subject: ApiPathComponent[]; message: string }) {
// Drop the leading 'window' component unless it's interesting (e.g. because we're
// talking about a getter or setter directly on the global object, or the global
// object's prototype).
const components =
subject.length >= 2 && subject[0].name === 'window' && subject[1].type !== 'value' ? subject : subject.slice(1)

// We can render the path in a compact way if only one component remains, or if
// two remain, but the first one is a normal value property. In other situations,
// we'll use an expanded rendering that takes up more space vertically but looks
// better for complex paths and requires much less horizontal space.
const useCompactPath = components.length === 1 || (components.length === 2 && components[0].type === 'value')

return (
<Flex direction="column">
{useCompactPath ? (
<ApiDiagnosticCompactPath components={components} />
) : (
<ApiDiagnosticExpandedPath components={components} />
)}
<Text style={{ marginTop: '0.1em' }}>{message}</Text>
</Flex>
)
}

function ApiDiagnosticCompactPath({ components }: { components: ApiPathComponent[] }) {
return <Text style={{ fontWeight: 'bold' }}>{components.map(formatPathComponentText).join('.')}</Text>
}

function ApiDiagnosticExpandedPath({ components }: { components: ApiPathComponent[] }) {
return components.map((component, index) => <ApiDiagnosticPathComponent component={component} index={index} />)
}

function ApiDiagnosticPathComponent({ component, index }: { component: ApiPathComponent; index: number }) {
const leadingDot = index === 0 ? '' : '.'
const text = `${leadingDot}${formatPathComponentText(component)}`
const marginLeft = index === 0 ? '0' : '1em'
return <Text style={{ fontWeight: 'bold', marginLeft }}>{text}</Text>
}

function formatPathComponentText(component: ApiPathComponent): string {
switch (component.type) {
case 'prototype':
return `__proto__ (${component.name})`
case 'value':
return `${component.name}`
case 'get':
return `${component.name} (get)`
case 'set':
return `${component.name} (set)`
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Test } from '../../../../hooks/useTest'

declare function asyncTestPassed(): void
declare function asyncTestFailed(): void

export const data: Test[] = [
{
name: 'XMLHttpRequest',
subtests: [
{
name: 'generates trusted events',
category: 'datadog',
exec() {
const request = new XMLHttpRequest()
// TODO pot to proxy or to datadog intake to go around CSP
request.open('POST', 'https://7fc280bc.datadoghq.com/api/v2/logs', true)

request.addEventListener(
'loadend',
(event) => {
event.isTrusted ? asyncTestPassed() : asyncTestFailed()
},
{ once: true }
)

request.send('')
},
},
],
},
]
Loading
Loading