diff --git a/.gitignore b/.gitignore index b9f8d23..462b70f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,8 @@ .DS_Store *.pem +.vscode + # debug npm-debug.log* yarn-debug.log* diff --git a/next.config.mjs b/next.config.mjs index 1d61478..3382c22 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,30 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = { + webpack(config) { + // Grab the existing rule that handles SVG imports + const fileLoaderRule = config.module.rules.find((rule) => rule.test?.test?.('.svg')) + + config.module.rules.push( + // Reapply the existing rule, but only for svg imports ending in ?url + { + ...fileLoaderRule, + test: /\.svg$/i, + resourceQuery: /url/, // *.svg?url + }, + // Convert all other *.svg imports to React components + { + test: /\.svg$/i, + issuer: /\.[jt]sx?$/, + resourceQuery: { not: /url/ }, // exclude if *.svg?url + use: ['@svgr/webpack'], + } + ) + + // Modify the file loader rule to ignore *.svg, since we have it handled now. + fileLoaderRule.exclude = /\.svg$/i + + return config + }, +} export default nextConfig diff --git a/package.json b/package.json index cde6a5e..0913da0 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "react": "^18", "react-dom": "^18", "react-dropzone": "^14.2.3", - "react-json-view": "^1.21.3", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7" }, diff --git a/public/metabase.svg b/public/metabase.svg new file mode 100644 index 0000000..5752975 --- /dev/null +++ b/public/metabase.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/globals.css b/src/app/globals.css index 38ee873..e8a26e6 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -22,8 +22,8 @@ --muted: 210 40% 96.1%; --muted-foreground: 215.4 16.3% 46.9%; - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; + --accent: theme('colors.sky.500'); + --accent-foreground: theme('colors.sky.500'); --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; @@ -99,6 +99,16 @@ animation: slideUp 100ms ease-out; } +a, +a:visited, +a:hover { + color: var(--accent-foreground); +} + +a:hover { + text-decoration: underline; +} + @keyframes slideDown { from { height: 0; diff --git a/src/app/page.tsx b/src/app/page.tsx index be4d787..c7ca8a9 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,5 @@ 'use client' -import Image from 'next/image' import { useState, useMemo } from 'react' import { DevToolsUI } from '@/components/DevToolsUi' @@ -19,11 +18,7 @@ export default function Home({ const fileId = searchParams.fileId as string | undefined const handleFileUpload = (data: DiagnosticData) => { - if ( - data.basicInfo.url && - Array.isArray(data.frontendErrors) && - Array.isArray(data.backendErrors) - ) { + if (data.url && Array.isArray(data.frontendErrors) && Array.isArray(data.backendErrors)) { setDiagnosticData(data) } else { throw new Error('Invalid file structure: missing required data') @@ -48,7 +43,7 @@ export default function Home({ }) .catch((err) => { console.error('Error fetching Slack file:', err) - setError('Failed to fetch file from Slack. Please check the fileId and try again.') + setError('Failed to fetch file from Slack. ' + err?.message) }) .finally(() => { setIsLoading(false) @@ -58,25 +53,17 @@ export default function Home({ return (
-
-

(window.location.href = '/')} - > - Metabase Logo - Debugger -

-
{isLoading ? (

Loading file from Slack...

- ) : error ? ( -
-

{error}

-
) : !diagnosticData ? ( -
+
+ {error && ( +
+

{error}

+
+ )}
) : ( diff --git a/src/components/BrowserInfo.test.tsx b/src/components/BrowserInfo.test.tsx new file mode 100644 index 0000000..325e567 --- /dev/null +++ b/src/components/BrowserInfo.test.tsx @@ -0,0 +1,65 @@ +import { describe, it, expect } from 'vitest' + +import { BrowserInfo } from './BrowserInfo' +import { render, screen } from '../test/test-utils' + +const sampleBrowserInfo = { + platform: 'mobile', + os: 'Windows', + osVersion: '10.0.0', + language: 'en-US', + browserName: 'Chrome', + browserVersion: '32.1.1', +} + +describe('BrowserInfo', () => { + it('renders without crashing', () => { + expect(() => render()).not.toThrow() + }) + + it('displays basic info', () => { + render() + + expect(screen.getByTitle('mobile')).toBeInTheDocument() + expect(screen.getByText('Chrome 32.1.1')).toBeInTheDocument() + expect(screen.getByText('Windows 10.0.0')).toBeInTheDocument() + expect(screen.getByText('en-US')).toBeInTheDocument() + }) + + it('displays the Safari and macOS', () => { + render( + + ) + + expect(screen.getByText('Safari 1.2.3.4')).toBeInTheDocument() + expect(screen.getByText('macOS Sierra nevada')).toBeInTheDocument() + }) + it('displays the Firefox and Linux', () => { + render( + + ) + + expect(screen.getByText('Firefox 1.2.3.4')).toBeInTheDocument() + expect(screen.getByText('Linux debian foreva')).toBeInTheDocument() + }) +}) diff --git a/src/components/BrowserInfo.tsx b/src/components/BrowserInfo.tsx new file mode 100644 index 0000000..2a869ff --- /dev/null +++ b/src/components/BrowserInfo.tsx @@ -0,0 +1,54 @@ +import { Languages, Laptop, Smartphone } from 'lucide-react' + +import { Badge } from './ui/badge' + +interface BrowserInfoProps { + browserInfo: Record +} + +const ICON_SIZE = 16 + +const platformIcon = (platform: string) => { + return platform == 'mobile' ? ( + + ) : ( + + ) +} + +export const BrowserInfo = ({ browserInfo }: BrowserInfoProps) => { + return ( + <> + {browserInfo.platform && ( + + {platformIcon(browserInfo.platform)} + + )} + {browserInfo.language && ( + + {' '} + {browserInfo.language} + + )} + {browserInfo.os && ( + + {browserInfo.os} {browserInfo.osVersion} + + )} + {browserInfo.browserName && ( + + {browserInfo.browserName} {browserInfo.browserVersion} + + )} + + ) +} diff --git a/src/components/ConsoleOutput.test.tsx b/src/components/ConsoleOutput.test.tsx index ec9e257..c30a384 100644 --- a/src/components/ConsoleOutput.test.tsx +++ b/src/components/ConsoleOutput.test.tsx @@ -73,9 +73,7 @@ describe('ConsoleOutput', () => { it('handles empty error list', () => { render() - const searchInput = screen.getByPlaceholderText('Search logs...') - expect(searchInput).toBeInTheDocument() - expect(screen.queryByRole('button')).not.toBeInTheDocument() + screen.getByText(/No errors to show/i) }) it('expands info logs to show stack trace', () => { diff --git a/src/components/ConsoleOutput.tsx b/src/components/ConsoleOutput.tsx index 08e204f..3c74e35 100644 --- a/src/components/ConsoleOutput.tsx +++ b/src/components/ConsoleOutput.tsx @@ -35,7 +35,7 @@ export const ConsoleOutput: React.FC = ({ errors, onErrorCou const parsedErrors = useMemo(() => { const parsed = errors.map((error, index) => { - const cleanError = error.replace(/^"|"$/g, '').replace(/\\n/g, '\n') + const cleanError = error?.replace(/^"|"$/g, '').replace(/\\n/g, '\n') let message = cleanError let jsonData = null let stack = '' @@ -97,6 +97,10 @@ export const ConsoleOutput: React.FC = ({ errors, onErrorCou [parsedErrors, searchQuery] ) + if (!parsedErrors.length) { + return
No errors to show 🙂
+ } + return ( <> { const mockDiagnosticData = { - basicInfo: { - url: 'https://test.metabase.com', - description: 'Test description', - }, + url: 'https://test.metabase.com', + description: 'Test description', entityInfo: { name: 'Test Entity', entityName: 'Test Entity', @@ -52,7 +50,7 @@ describe('CreateGithubIssue', () => { expect(window.open).toHaveBeenCalledWith( expect.stringContaining( - 'https://github.com/metabase/metabase/issues/new?title=%5BBug+Report%5D+Test+Entity+-+https%3A%2F%2Ftest.metabase.com&body=%0A%23%23%23+Description%0ATest+description%0A%0A%23%23%23+Links%0A-+Original+URL%3A+https%3A%2F%2Ftest.metabase.com%0A-+Slack+File%3A+https%3A%2F%2Fmetaboat.slack.com%2Ffiles%2FU02T6V8MXN2%2F123ABC%2Fdiagnostic-info.json%0A-+Bug+Report+Debugger%3A+https%3A%2F%2Fdebugger.test.com%0A&labels=Bug' + 'https://github.com/metabase/metabase/issues/new?title=%5BBug+Report%5D+Test+Entity+-+https%3A%2F%2Ftest.metabase.com&body=%0A%23%23%23+Description%0ATest+description%0A%0A%23%23%23+Links%0A-+Original+URL%3A+https%3A%2F%2Ftest.metabase.com%0A-+Slack+File%3A+https%3A%2F%2Fmetaboat.slack.com%2Ffiles%2FU02T6V8MXN2%2F123ABC%2Fdiagnostic-info.json' ), '_blank' ) @@ -70,15 +68,12 @@ describe('CreateGithubIssue', () => { '[Bug Report] Test Entity - https://test.metabase.com' ) expect(url.searchParams.get('body')).toContain('Test description') - expect(url.searchParams.get('body')).toContain('https://debugger.test.com') - expect(url.searchParams.get('labels')).toBe('Bug') + expect(url.searchParams.get('labels')).toBe('Type:Bug') }) it('handles missing optional data gracefully', () => { const minimalData = { - basicInfo: { - url: '', - }, + url: '', entityInfo: { entityName: '', name: '', diff --git a/src/components/CreateGithubIssue.tsx b/src/components/CreateGithubIssue.tsx index fc3250e..e429bdb 100644 --- a/src/components/CreateGithubIssue.tsx +++ b/src/components/CreateGithubIssue.tsx @@ -4,46 +4,39 @@ import type { DiagnosticData } from '@/types/DiagnosticData' import { Button } from './ui/button' interface CreateGithubIssueProps { - diagnosticData: Pick + diagnosticData: Pick slackFileId?: string } export function CreateGithubIssue({ diagnosticData, slackFileId }: CreateGithubIssueProps) { const createGithubIssue = () => { - const debuggerUrl = window.location.href const slackFileUrl = slackFileId ? `https://metaboat.slack.com/files/U02T6V8MXN2/${slackFileId}/diagnostic-info.json` : null const issueTitle = `[Bug Report] ${diagnosticData.entityInfo.entityName || 'Issue'} - ${ - diagnosticData.basicInfo.url || 'No URL' + diagnosticData.url || 'No URL' }` const issueBody = ` ### Description -${diagnosticData.basicInfo.description || 'No description provided'} +${diagnosticData.description || 'No description provided'} ### Links -- Original URL: ${diagnosticData.basicInfo.url || 'N/A'} +- Original URL: ${diagnosticData.url || 'N/A'} ${slackFileUrl ? `- Slack File: ${slackFileUrl}` : ''} -- Bug Report Debugger: ${debuggerUrl} ` const githubUrl = new URL('https://github.com/metabase/metabase/issues/new') githubUrl.searchParams.set('title', issueTitle) githubUrl.searchParams.set('body', issueBody) - githubUrl.searchParams.set('labels', 'Bug') + githubUrl.searchParams.set('labels', 'Type:Bug') window.open(githubUrl.toString(), '_blank') } return ( - diff --git a/src/components/DescriptionInfo.test.tsx b/src/components/DescriptionInfo.test.tsx new file mode 100644 index 0000000..31e46bd --- /dev/null +++ b/src/components/DescriptionInfo.test.tsx @@ -0,0 +1,43 @@ +import { describe, it, expect } from 'vitest' + +import { DescriptionInfo } from './DescriptionInfo' +import { render, screen } from '../test/test-utils' + +const sampleDescriptionInfo = { + url: 'https://test.com', + description: 'This is a description', +} +const sampleDescriptionInfoLongUrl = { + url: 'https://test.com?this_is_a_long_url_to_test_url_truncation_is_working_nicely_with_super_long_urls_that_would_not_fit_well_on_a_wide_screen_that_most_us_priviledged_geeks_use_nowadays', + description: '', +} + +describe('DescriptionInfo', () => { + it('renders without crashing', () => { + expect(() => render()).not.toThrow() + }) + + it('displays basic info', () => { + render() + + expect(screen.getByText(sampleDescriptionInfo.description)).toBeInTheDocument() + expect(screen.getByTitle(sampleDescriptionInfo.url)).toBeInTheDocument() + }) + + it('truncates long URLs nicely', () => { + render() + + expect(screen.getByTitle(sampleDescriptionInfoLongUrl.url)).toBeInTheDocument() + expect( + screen.getByText( + 'https://test.com?this_is_a_long_url_to_test_url_truncation_is_working_nicely_with_super_lo...owadays' + ) + ).toBeInTheDocument() + }) + + it('renders no description properly', () => { + render() + + expect(screen.getByText('No description provided')).toBeInTheDocument() + }) +}) diff --git a/src/components/DescriptionInfo.tsx b/src/components/DescriptionInfo.tsx new file mode 100644 index 0000000..14745ba --- /dev/null +++ b/src/components/DescriptionInfo.tsx @@ -0,0 +1,32 @@ +import { Link } from 'lucide-react' + +import { DiagnosticData } from '@/types/DiagnosticData' + +import { Badge } from './ui/badge' + +interface DescriptionInfoProps { + diagnosticData: Pick +} + +const truncateUrl = (url: string) => { + return url.length > 100 ? `${url.substring(0, 90)}...${url.substring(url.length - 7)}` : url +} + +export const DescriptionInfo = ({ diagnosticData }: DescriptionInfoProps) => { + return ( +
+ {diagnosticData.url && ( + + Occurred at: + + {' '} + {truncateUrl(diagnosticData.url)} + + + )} +
+
{diagnosticData.description || No description provided}
+
+
+ ) +} diff --git a/src/components/DevToolsUi.test.tsx b/src/components/DevToolsUi.test.tsx index 3d35d15..9a736e1 100644 --- a/src/components/DevToolsUi.test.tsx +++ b/src/components/DevToolsUi.test.tsx @@ -4,43 +4,9 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' import { DiagnosticData } from '@/types/DiagnosticData' import { DevToolsUI } from './DevToolsUi' -import { render, screen } from '../test/test-utils' +import { render, screen, sampleDiagnosticData } from '../test/test-utils' describe('DevToolsUI', () => { - const sampleDiagnosticData: DiagnosticData = { - basicInfo: { - url: 'https://test.com', - description: 'Test description', - bugReportDetails: {}, - browserInfo: { - browserName: 'Chrome', - browserVersion: '100.0.0', - os: 'Windows', - osVersion: '10', - platform: 'Desktop', - language: 'en-US', - }, - 'metabase-info': {}, - 'system-info': {}, - }, - entityInfo: { - entityName: 'question', - name: "Test Question's Name", - }, - frontendErrors: [ - '"[webpack-dev-server] ERROR in ./components/ErrorPages/utils.ts\\n × Module not found: Error message 1"', - '"[webpack-dev-server] ERROR in ./components/ErrorPages/tab.ts\\n × Module not found: Error message 1"', - '"Warning: Something went wrong\\nStack trace for warning"', - '"[webpack-dev-server] Another error occurred\\nStack trace for error"', - ], - backendErrors: [ - { message: 'Backend Error 1', timestamp: '2024-01-01' }, - { message: 'Backend Error 2', timestamp: '2024-01-02' }, - ], - userLogs: [{ message: 'User Log 1', timestamp: '2024-01-01' }], - logs: [{ message: 'System Log 1', timestamp: '2024-01-01' }], - } - beforeEach(() => { // Mock window.open for GitHub issue creation vi.spyOn(window, 'open').mockImplementation(() => null) @@ -85,17 +51,14 @@ describe('DevToolsUI', () => { render() // Check URL and description - expect(screen.getByText('url')).toBeInTheDocument() + expect(screen.getByText('Occurred at:')).toBeInTheDocument() expect(screen.getByText('https://test.com')).toBeInTheDocument() - expect(screen.getByText('description')).toBeInTheDocument() expect(screen.getByText('Test description')).toBeInTheDocument() // Check browser info - expect(screen.getByText('browserInfo')).toBeInTheDocument() - expect(screen.getByText('Chrome')).toBeInTheDocument() - expect(screen.getByText('100.0.0')).toBeInTheDocument() - expect(screen.getByText('os')).toBeInTheDocument() - expect(screen.getByText('Windows')).toBeInTheDocument() + expect(screen.getByText('Chrome 100.0.0')).toBeInTheDocument() + expect(screen.getByText('Windows 10')).toBeInTheDocument() + expect(screen.getByText('en-US')).toBeInTheDocument() }) it('switches tabs correctly', async () => { @@ -103,12 +66,56 @@ describe('DevToolsUI', () => { render() // Click Console output tab - await user.click(screen.getByRole('tab', { name: 'Console output' })) + await user.click(screen.getByRole('tab', { name: 'Browser console 4' })) expect( screen.getByText('[webpack-dev-server] ERROR in ./components/ErrorPages/utils.ts') ).toBeInTheDocument() }) + it('is smart about selecting the details tab', async () => { + const defaultsToDetails: DiagnosticData = { + url: 'https://test.com', + bugReportDetails: { + 'metabase-info': {}, + 'system-info': {}, + }, + browserInfo: undefined, + frontendErrors: [], + entityInfo: { + entityName: 'question', + name: "Test Question's Name", + }, + backendErrors: [], + userLogs: [], + logs: [], + } + + render() + + // Should default to the bug report details tab + expect(screen.getByText(/metabase-info/)).toBeInTheDocument() + }) + + it('is smart about selecting the entity tab', async () => { + const defaultsToDetails: DiagnosticData = { + url: 'https://test.com', + bugReportDetails: undefined, + browserInfo: undefined, + frontendErrors: [], + entityInfo: { + entityName: 'question', + name: "Test Question's Name", + }, + backendErrors: [], + userLogs: [], + logs: [], + } + + render() + + // Should default to the bug report details tab + expect(screen.getByText(/entityName/)).toBeInTheDocument() + }) it('displays error badges correctly', () => { render() @@ -126,18 +133,16 @@ describe('DevToolsUI', () => { await user.click(screen.getByRole('tab', { name: 'Raw Data' })) // Check if JSON data is displayed - expect(screen.getByText(/"url":/)).toBeInTheDocument() - expect(screen.getByText(/"description":/)).toBeInTheDocument() + expect(screen.getByText(/system-info/)).toBeInTheDocument() + expect(screen.getByText(/metabase-info/)).toBeInTheDocument() }) it('handles empty diagnostic data', () => { const emptyData: DiagnosticData = { - basicInfo: { - url: '', - bugReportDetails: {}, - description: '', - browserInfo: {}, - }, + url: '', + bugReportDetails: {}, + description: '', + browserInfo: {}, entityInfo: { entityName: '', name: '', @@ -151,9 +156,7 @@ describe('DevToolsUI', () => { render() // Should still render without errors - expect(screen.getByText('url')).toBeInTheDocument() - expect(screen.getByText('browserInfo')).toBeInTheDocument() - expect(screen.getByText('description')).toBeInTheDocument() + expect(screen.getByText('No description provided')).toBeInTheDocument() }) it('updates frontend error count', async () => { @@ -161,7 +164,7 @@ describe('DevToolsUI', () => { render() // Switch to console output tab - await user.click(screen.getByRole('tab', { name: 'Console output' })) + await user.click(screen.getByRole('tab', { name: 'Browser console 4' })) // Check if error count badge is updated const frontendErrorsBadge = screen.getByText('3') diff --git a/src/components/DevToolsUi.tsx b/src/components/DevToolsUi.tsx index 3bb4e4d..d009714 100644 --- a/src/components/DevToolsUi.tsx +++ b/src/components/DevToolsUi.tsx @@ -5,10 +5,10 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { DiagnosticData } from '@/types/DiagnosticData' import { ConsoleOutput } from './ConsoleOutput' -import { CreateGithubIssue } from './CreateGithubIssue' +import { DescriptionInfo } from './DescriptionInfo' +import { Header } from './Header' import { LogsTable } from './LogsTable' -import { MetadataTable } from './MetadataTable' -import { QueryResults } from './QueryResults' +import { QueryResultsPanel } from './QueryResultsPanel' import { RawContent } from './RawContent' interface DevToolsUIProps { @@ -16,54 +16,87 @@ interface DevToolsUIProps { slackFileId?: string } +interface TabHeaderProps { + id: string + title: string + count: number | null + variant?: 'destructive' | 'light' +} + +const TabHeader = ({ id, title, count, variant = 'light' }: TabHeaderProps) => { + if (count == null) { + return null + } + + const hasCount = count > -1 + + return ( + + {title} + {hasCount && ( + + {count} + + )} + + ) +} + export const DevToolsUI = ({ diagnosticData, slackFileId }: DevToolsUIProps) => { - const [frontendErrorCount, setFrontendErrorCount] = useState(0) + const [frontendErrorCount, setFrontendErrorCount] = useState( + diagnosticData.frontendErrors?.length + ) - const jsonString = JSON.stringify(diagnosticData, null, 2) + // Since quite a bit is optional, be smart about what to show first based on importance and fallback to the raw content + const initialTab = + diagnosticData.backendErrors && diagnosticData.backendErrors.length > 0 + ? 'backendErrors' + : diagnosticData.bugReportDetails + ? 'details' + : diagnosticData.entityInfo + ? 'entity' + : 'raw' return (
- +
+
- +
- Basic Info - Entity Info - - Console output - {frontendErrorCount > 0 && ( - - {frontendErrorCount} - - )} - - - Backend Errors - {diagnosticData.backendErrors.length > 0 && ( - - {diagnosticData.backendErrors.length} - - )} - - User Logs - System Logs - Query Results - Raw Data + + + + + + + +
- - - - - - - - + + @@ -71,14 +104,24 @@ export const DevToolsUI = ({ diagnosticData, slackFileId }: DevToolsUIProps) => + + + + + + + - + - +
diff --git a/src/components/Header.test.tsx b/src/components/Header.test.tsx new file mode 100644 index 0000000..4b29509 --- /dev/null +++ b/src/components/Header.test.tsx @@ -0,0 +1,36 @@ +import { fail } from 'assert' + +import userEvent from '@testing-library/user-event' +import { describe, it, expect, beforeEach } from 'vitest' + +import { Header } from './Header' +import { render, screen, sampleDiagnosticData } from '../test/test-utils' + +describe('Header', () => { + beforeEach(() => { + // Mock window.location + Object.defineProperty(window, 'location', { + value: { href: 'https://debugger.test.com/' }, + writable: true, + }) + }) + + it('renders without crashing', () => { + expect(() => + render(
) + ).not.toThrow() + }) + + it('navigates when clicking the logo', async () => { + const user = userEvent.setup() + + render(
) + const titleHeader = screen.getByText('Metabase Debugger').closest('h1') + expect(titleHeader).toBeInTheDocument() + if (!titleHeader) fail('No title found') + + await user.click(titleHeader) + + expect(window.location.href).toBe('/') + }) +}) diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..00a58c8 --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,29 @@ +import Image from 'next/image' + +import { DiagnosticData } from '@/types/DiagnosticData' + +import { BrowserInfo } from './BrowserInfo' +import { CreateGithubIssue } from './CreateGithubIssue' + +interface HeaderProps { + diagnosticData: DiagnosticData + slackFileId: string | undefined +} + +export const Header = ({ diagnosticData, slackFileId }: HeaderProps) => { + return ( +
+

(window.location.href = '/')} + > + Logo + Metabase Debugger +

+
+ {diagnosticData.browserInfo && } + +
+
+ ) +} diff --git a/src/components/LogsTable.tsx b/src/components/LogsTable.tsx index c62d6eb..596a129 100644 --- a/src/components/LogsTable.tsx +++ b/src/components/LogsTable.tsx @@ -17,7 +17,7 @@ interface LogEntry { } interface LogsTableProps { - logs: LogEntry[] + logs: LogEntry[] | null title: string } @@ -123,12 +123,12 @@ const LogsTable: React.FC = ({ logs }) => { const filteredLogs = useMemo( () => - logs.filter( + logs?.filter( (log) => log.msg?.toLowerCase().includes(searchQuery.toLowerCase()) || log.level?.toLowerCase().includes(searchQuery.toLowerCase()) || log.fqns?.toLowerCase().includes(searchQuery.toLowerCase()) - ), + ) ?? [], [logs, searchQuery] ) @@ -142,6 +142,10 @@ const LogsTable: React.FC = ({ logs }) => { const [showOnlyMetabaseFrames, setShowOnlyMetabaseFrames] = useState(true) + if (!logs?.length) { + return
No logs to show 🙂
+ } + return ( <> { - const sampleMetadata = { - 'metabase-info': { - databases: ['h2'], - 'run-mode': 'dev', - 'plan-alias': '', - version: { - date: '2024-11-22', - src_hash: '282c501f2f3f6d6c3b0b13585ece73a707676311', - tag: 'v0.52.3-SNAPSHOT', - hash: '686c589', - }, - settings: { - 'report-timezone': null, - }, - 'hosting-env': 'unknown', - 'application-database': 'h2', - 'application-database-details': { - database: { - name: 'H2', - version: '2.1.214 (2022-06-13)', - }, - 'jdbc-driver': { - name: 'H2 JDBC Driver', - version: '2.1.214 (2022-06-13)', - }, - }, - }, - 'system-info': { - 'java.vm.name': 'OpenJDK 64-Bit Server VM', - 'java.vm.version': '21.0.1+12-LTS', - 'java.runtime.name': 'OpenJDK Runtime Environment', - 'os.name': 'Mac OS X', - }, - } - - it('renders without crashing', () => { - expect(() => render()).not.toThrow() - }) - - it('displays metadata keys and values correctly', () => { - render() - - // Check for metabase-info section - expect(screen.getByText('run-mode')).toBeInTheDocument() - expect(screen.getByText('dev')).toBeInTheDocument() - expect(screen.getByText('hosting-env')).toBeInTheDocument() - expect(screen.getByText('unknown')).toBeInTheDocument() - }) - - it('handles empty metadata object', () => { - render() - expect(screen.queryByRole('table')).not.toBeInTheDocument() - expect(screen.getByText('No data 🙁')).toBeInTheDocument() - }) - - it('renders nested objects correctly', () => { - render() - - // Check version object rendering - expect(screen.getByText('date')).toBeInTheDocument() - expect(screen.getByText('2024-11-22')).toBeInTheDocument() - expect(screen.getByText('tag')).toBeInTheDocument() - expect(screen.getByText('v0.52.3-SNAPSHOT')).toBeInTheDocument() - }) - - it('handles null values', () => { - render() - - // Check report-timezone null value - expect(screen.getByText('report-timezone')).toBeInTheDocument() - expect(screen.getByText('null')).toBeInTheDocument() - }) - - it('displays system info correctly', () => { - render() - - // Check system-info section - expect(screen.getByText('java.vm.name')).toBeInTheDocument() - expect(screen.getByText('OpenJDK 64-Bit Server VM')).toBeInTheDocument() - expect(screen.getByText('os.name')).toBeInTheDocument() - expect(screen.getByText('Mac OS X')).toBeInTheDocument() - }) -}) diff --git a/src/components/MetadataTable.tsx b/src/components/MetadataTable.tsx deleted file mode 100644 index 4a87f47..0000000 --- a/src/components/MetadataTable.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react' - -import { ScrollArea } from '@/components/ui/scroll-area' -import { Table, TableBody, TableHead, TableHeader, TableRow } from '@/components/ui/table' - -interface MetadataTableProps { - metadata: Record -} - -const MetadataCell = ({ - children, - className, -}: { - children: React.ReactNode - className?: string -}) => {children} - -const MetadataTable: React.FC = ({ metadata }) => { - const renderValue = (value: any): React.ReactNode => { - if (Array.isArray(value)) { - return ( -
    - {value.map((item, index) => ( -
  • {renderValue(item)}
  • - ))} -
- ) - } else if (typeof value === 'object' && value !== null) { - return ( - - - {Object.entries(value).map(([subKey, subValue]) => ( - - {subKey} - {renderValue(subValue)} - - ))} - -
- ) - } - return typeof value === 'string' ? value : JSON.stringify(value) - } - - const renderTable = (data: Record) => ( -
- - - - Key - Value - - - - {Object.entries(data).map(([key, value]) => ( - - {key} - {renderValue(value)} - - ))} - -
-
- ) - - if (!metadata || !Object.keys(metadata).length) { - return

No data 🙁

- } - - return {renderTable(metadata)} -} - -export { MetadataTable } diff --git a/src/components/QueryResults.test.tsx b/src/components/QueryResultsPanel.test.tsx similarity index 80% rename from src/components/QueryResults.test.tsx rename to src/components/QueryResultsPanel.test.tsx index 1ff6b75..5b0d920 100644 --- a/src/components/QueryResults.test.tsx +++ b/src/components/QueryResultsPanel.test.tsx @@ -1,9 +1,9 @@ import { describe, it, expect } from 'vitest' -import { QueryResults } from './QueryResults' +import { QueryResultsPanel } from './QueryResultsPanel' import { render, screen } from '../test/test-utils' -describe('QueryResults', () => { +describe('QueryResultsPanel', () => { const sampleData = { data: { rows: [ @@ -23,16 +23,16 @@ describe('QueryResults', () => { } it('renders without crashing', () => { - expect(() => render()).not.toThrow() + expect(() => render()).not.toThrow() }) it('displays no results message when data is missing', () => { - render() + render() expect(screen.getByText('No query results available')).toBeInTheDocument() }) it('renders table headers correctly', () => { - render() + render() expect(screen.getByText('ID')).toBeInTheDocument() expect(screen.getByText('Name')).toBeInTheDocument() @@ -40,20 +40,20 @@ describe('QueryResults', () => { }) it('renders table data correctly', () => { - render() + render() expect(screen.getByText('John Doe')).toBeInTheDocument() expect(screen.getByText('jane@example.com')).toBeInTheDocument() }) it('displays native query when available', () => { - render() + render() expect(screen.getByText('SELECT * FROM users LIMIT 2')).toBeInTheDocument() }) it('shows truncated rows message', () => { - render() + render() expect(screen.getByText('Showing 2 rows (truncated)')).toBeInTheDocument() }) @@ -70,7 +70,7 @@ describe('QueryResults', () => { }, } - render() + render() expect(screen.getByText('null')).toBeInTheDocument() }) @@ -86,7 +86,7 @@ describe('QueryResults', () => { }, } - render() + render() expect(screen.getByText('{"firstName":"John","lastName":"Doe"}')).toBeInTheDocument() }) @@ -98,7 +98,7 @@ describe('QueryResults', () => { }, } - render() + render() expect(screen.getByText('Total rows: 2')).toBeInTheDocument() }) }) diff --git a/src/components/QueryResults.tsx b/src/components/QueryResultsPanel.tsx similarity index 97% rename from src/components/QueryResults.tsx rename to src/components/QueryResultsPanel.tsx index 2469a43..53e7072 100644 --- a/src/components/QueryResults.tsx +++ b/src/components/QueryResultsPanel.tsx @@ -14,7 +14,7 @@ interface QueryResultsProps { } } -export function QueryResults({ data }: QueryResultsProps) { +export function QueryResultsPanel({ data }: QueryResultsProps) { if (!data?.data?.rows || !data?.data?.cols) { return
No query results available
} diff --git a/src/components/RawContent.test.tsx b/src/components/RawContent.test.tsx index 3f3c8a6..a6ddecc 100644 --- a/src/components/RawContent.test.tsx +++ b/src/components/RawContent.test.tsx @@ -9,19 +9,20 @@ describe('RawContent', () => { Element.prototype.scrollIntoView = vi.fn() }) - const sampleContent = `{ - "first": "apple", - "second": "banana", - "third": "apple", - "fourth": "cherry" - }` + const sampleContent = { + first: 'apple', + second: 'banana', + third: 'apple', + fourth: 'cherry', + } it('renders without crashing', () => { - expect(() => render()).not.toThrow() + expect(() => render()).not.toThrow() }) it('displays JSON content correctly', () => { render() - expect(screen.getByText(/"first": "apple"/)).toBeInTheDocument() + expect(screen.getByText(/first/)).toBeInTheDocument() + expect(screen.getByText(/cherry/)).toBeInTheDocument() }) }) diff --git a/src/components/RawContent.tsx b/src/components/RawContent.tsx index dcd70d1..93122fe 100644 --- a/src/components/RawContent.tsx +++ b/src/components/RawContent.tsx @@ -1,18 +1,26 @@ -import { ScrollArea } from '@/components/ui/scroll-area' +import { ScrollArea } from '@radix-ui/react-scroll-area' interface RawContentProps { - content: string + content: Record | undefined } -export function RawContent({ content }: RawContentProps) { +const RawContent: React.FC = ({ content }) => { + if (!content) { + return null + } + return ( -
+
Press {navigator.platform.includes('Mac') ? '⌘' : 'Ctrl'}+F to search
- -
{content}
+ +
+          {JSON.stringify(content, null, 2)}
+        
) } + +export { RawContent } diff --git a/src/components/UploadDropzone.test.tsx b/src/components/UploadDropzone.test.tsx index 0e187c0..24575cd 100644 --- a/src/components/UploadDropzone.test.tsx +++ b/src/components/UploadDropzone.test.tsx @@ -2,7 +2,7 @@ import userEvent from '@testing-library/user-event' import { describe, it, expect, vi, beforeEach } from 'vitest' import { UploadDropzone } from './UploadDropzone' -import { render, screen } from '../test/test-utils' +import { render, sampleDiagnosticData, screen } from '../test/test-utils' describe('UploadDropzone', () => { const mockOnFileUpload = vi.fn() @@ -25,19 +25,9 @@ describe('UploadDropzone', () => { it('handles file upload correctly', async () => { const user = userEvent.setup() - const fileContent = { - url: 'https://test.com', - description: 'Test description', - entityInfo: {}, - - browserInfo: { browserName: 'Chrome' }, - frontendErrors: [], - backendErrors: [], - userLogs: [], - logs: [], - } - - const file = new File([JSON.stringify(fileContent)], 'test.json', { type: 'application/json' }) + const file = new File([JSON.stringify(sampleDiagnosticData)], 'test.json', { + type: 'application/json', + }) render() @@ -47,10 +37,8 @@ describe('UploadDropzone', () => { expect(mockOnFileUpload).toHaveBeenCalledTimes(1) expect(mockOnFileUpload).toHaveBeenCalledWith( expect.objectContaining({ - basicInfo: expect.objectContaining({ - url: 'https://test.com', - description: 'Test description', - }), + url: 'https://test.com', + description: 'Test description', }) ) }) diff --git a/src/components/UploadDropzone.tsx b/src/components/UploadDropzone.tsx index 5256e4d..76b1a78 100644 --- a/src/components/UploadDropzone.tsx +++ b/src/components/UploadDropzone.tsx @@ -20,12 +20,10 @@ const UploadDropzone: React.FC = ({ onFileUpload }) => { const parsedData = JSON.parse(result) const diagnosticData: DiagnosticData = { - basicInfo: { - description: parsedData.description || 'No description provided', - url: parsedData.url || '', - bugReportDetails: parsedData.bugReportDetails || {}, - browserInfo: parsedData.browserInfo || {}, - }, + description: parsedData.description || 'No description provided', + url: parsedData.url || '', + bugReportDetails: parsedData.bugReportDetails || {}, + browserInfo: parsedData.browserInfo || {}, entityInfo: { entityName: parsedData.entityName || '', ...parsedData.entityInfo, diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx index 70bd979..3e866fe 100644 --- a/src/components/ui/badge.tsx +++ b/src/components/ui/badge.tsx @@ -9,11 +9,11 @@ const badgeVariants = cva( variants: { variant: { default: 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80', + light: 'bg-white text-primary', secondary: 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', destructive: 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', - outline: 'text-foreground', }, }, defaultVariants: { diff --git a/src/pages/api/fetchSlackFile.test.ts b/src/pages/api/fetchSlackFile.test.ts index 966cba0..bb5b6f4 100644 --- a/src/pages/api/fetchSlackFile.test.ts +++ b/src/pages/api/fetchSlackFile.test.ts @@ -28,7 +28,7 @@ vi.mock('@/utils/slackClient', () => ({ const mockFetch = vi.fn() global.fetch = mockFetch -describe('fetchSlackFile API', () => { +describe.skip('fetchSlackFile API', () => { let mockReq: Partial let mockRes: Partial diff --git a/src/pages/api/fetchSlackFile.ts b/src/pages/api/fetchSlackFile.ts index 113071f..fbf398d 100644 --- a/src/pages/api/fetchSlackFile.ts +++ b/src/pages/api/fetchSlackFile.ts @@ -1,29 +1,31 @@ import { NextApiRequest, NextApiResponse } from 'next' -import { slackClient } from '@/utils/slackClient' +// import { slackClient } from '@/utils/slackClient' export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const { fileId } = req.query + // const { fileId } = req.query - if (!fileId || typeof fileId !== 'string') { - return res.status(400).json({ error: 'Invalid fileId' }) - } + return res.status(403).json({ error: 'Unauthorized' }) - try { - const result = await slackClient.files.info({ file: fileId }) + // if (!fileId || typeof fileId !== 'string') { + // return res.status(400).json({ error: 'Invalid fileId' }) + // } - if (!result.file || !result.file.url_private) { - return res.status(404).json({ error: 'File not found' }) - } + // try { + // const result = await slackClient.files.info({ file: fileId }) - const fileContent = await fetch(result.file.url_private, { - headers: { Authorization: `Bearer ${process.env.SLACK_BOT_TOKEN}` }, - }) + // if (!result.file || !result.file.url_private) { + // return res.status(404).json({ error: 'File not found' }) + // } - const jsonData = await fileContent.json() - res.status(200).json(jsonData) - } catch (error) { - console.error('Error fetching Slack file:', error) - res.status(500).json({ error: 'Error fetching Slack file' }) - } + // const fileContent = await fetch(result.file.url_private, { + // headers: { Authorization: `Bearer ${process.env.SLACK_BOT_TOKEN}` }, + // }) + + // const jsonData = await fileContent.json() + // res.status(200).json(jsonData) + // } catch (error) { + // console.error('Error fetching Slack file:', error) + // res.status(500).json({ error: 'Error fetching Slack file' }) + // } } diff --git a/src/test/test-utils.tsx b/src/test/test-utils.tsx index 45a7f15..ebf00c1 100644 --- a/src/test/test-utils.tsx +++ b/src/test/test-utils.tsx @@ -1,6 +1,8 @@ import { render } from '@testing-library/react' import { ReactElement } from 'react' +import { DiagnosticData } from '@/types/DiagnosticData' + const Providers = ({ children }: { children: React.ReactNode }) => { return
{children}
} @@ -11,7 +13,65 @@ const customRender = (ui: ReactElement, options = {}) => ...options, }) +const sampleDiagnosticData: DiagnosticData = { + url: 'https://test.com', + description: 'Test description', + bugReportDetails: { + 'metabase-info': { + databases: [ + 'athena', + 'postgres', + 'mysql', + 'redshift', + 'bigquery-cloud-sdk', + 'h2', + 'druid-jdbc', + 'databricks', + 'mongo', + 'snowflake', + ], + 'run-mode': 'prod', + 'plan-alias': 'internal', + version: { + date: '2025-01-10', + tag: 'vUNKNOWN', + hash: '68b5038', + }, + settings: { + 'report-timezone': 'US/Pacific', + }, + 'hosting-env': 'unknown', + 'application-database': 'postgres', + }, + 'system-info': {}, + }, + browserInfo: { + browserName: 'Chrome', + browserVersion: '100.0.0', + os: 'Windows', + osVersion: '10', + platform: 'Desktop', + language: 'en-US', + }, + entityInfo: { + entityName: 'question', + name: "Test Question's Name", + }, + frontendErrors: [ + '"[webpack-dev-server] ERROR in ./components/ErrorPages/utils.ts\\n × Module not found: Error message 1"', + '"[webpack-dev-server] ERROR in ./components/ErrorPages/tab.ts\\n × Module not found: Error message 1"', + '"Warning: Something went wrong\\nStack trace for warning"', + '"[webpack-dev-server] Another error occurred\\nStack trace for error"', + ], + backendErrors: [ + { message: 'Backend Error 1', timestamp: '2024-01-01' }, + { message: 'Backend Error 2', timestamp: '2024-01-02' }, + ], + userLogs: [{ message: 'User Log 1', timestamp: '2024-01-01' }], + logs: [{ message: 'System Log 1', timestamp: '2024-01-01' }], +} + export * from '@testing-library/react' export { default as userEvent } from '@testing-library/user-event' // override render export -export { customRender as render } +export { customRender as render, sampleDiagnosticData } diff --git a/src/types/DiagnosticData.ts b/src/types/DiagnosticData.ts index ade654d..6bee90e 100644 --- a/src/types/DiagnosticData.ts +++ b/src/types/DiagnosticData.ts @@ -1,12 +1,12 @@ export interface DiagnosticData { - basicInfo: { - url: string - description?: string - bugReportDetails?: Record - browserInfo?: Record + url: string + description?: string + bugReportDetails?: { 'metabase-info'?: Record 'system-info'?: Record + [key: string]: any } + browserInfo?: Record entityInfo: { entityName: string name: string diff --git a/svg.d.ts b/svg.d.ts new file mode 100644 index 0000000..07f5ccd --- /dev/null +++ b/svg.d.ts @@ -0,0 +1,5 @@ +declare module '*.svg' { + import * as React from 'react' + const component: React.FC> + export default component +} diff --git a/yarn.lock b/yarn.lock index ac316e6..b34063b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -150,13 +150,6 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.20.13": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2" - integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ== - dependencies: - regenerator-runtime "^0.14.0" - "@babel/template@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" @@ -1385,11 +1378,6 @@ arraybuffer.prototype.slice@^1.0.3: is-array-buffer "^3.0.4" is-shared-array-buffer "^1.0.2" -asap@~2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== - assertion-error@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" @@ -1441,11 +1429,6 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base16@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70" - integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ== - binary-extensions@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" @@ -1629,13 +1612,6 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -cross-fetch@^3.1.5: - version "3.1.8" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" - integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== - dependencies: - node-fetch "^2.6.12" - cross-spawn@^7.0.0, cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2344,31 +2320,6 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -fbemitter@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-3.0.0.tgz#00b2a1af5411254aab416cd75f9e6289bee4bff3" - integrity sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw== - dependencies: - fbjs "^3.0.0" - -fbjs-css-vars@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" - integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== - -fbjs@^3.0.0, fbjs@^3.0.1: - version "3.0.5" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.5.tgz#aa0edb7d5caa6340011790bd9249dbef8a81128d" - integrity sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg== - dependencies: - cross-fetch "^3.1.5" - fbjs-css-vars "^1.0.0" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - setimmediate "^1.0.5" - ua-parser-js "^1.0.35" - fdir@^6.4.2: version "6.4.2" resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.2.tgz#ddaa7ce1831b161bc3657bb99cb36e1622702689" @@ -2427,14 +2378,6 @@ flatted@^3.3.1: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== -flux@^4.0.1: - version "4.0.4" - resolved "https://registry.yarnpkg.com/flux/-/flux-4.0.4.tgz#9661182ea81d161ee1a6a6af10d20485ef2ac572" - integrity sha512-NCj3XlayA2UsapRpM7va6wU1+9rE5FIL7qoMcmxWHRzbp0yujihMBm9BBHZ1MDIk5h5o2Bl6eGiCe8rYELAmYw== - dependencies: - fbemitter "^3.0.0" - fbjs "^3.0.1" - follow-redirects@^1.15.6: version "1.15.9" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" @@ -3166,16 +3109,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash.curry@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170" - integrity sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA== - -lodash.flow@^3.3.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a" - integrity sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw== - lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -3356,13 +3289,6 @@ next@14.2.8: "@next/swc-win32-ia32-msvc" "14.2.8" "@next/swc-win32-x64-msvc" "14.2.8" -node-fetch@^2.6.12: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - node-releases@^2.0.18: version "2.0.18" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" @@ -3378,7 +3304,7 @@ nwsapi@^2.2.12: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.16.tgz#177760bba02c351df1d2644e220c31dfec8cdb43" integrity sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ== -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -3701,13 +3627,6 @@ pretty-format@^27.0.2: ansi-styles "^5.0.0" react-is "^17.0.1" -promise@^7.1.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== - dependencies: - asap "~2.0.3" - prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -3727,26 +3646,11 @@ punycode@^2.1.0, punycode@^2.3.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -pure-color@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" - integrity sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA== - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -react-base16-styling@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c" - integrity sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ== - dependencies: - base16 "^1.0.0" - lodash.curry "^4.0.1" - lodash.flow "^3.3.0" - pure-color "^1.2.0" - react-dom@^18: version "18.3.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" @@ -3774,21 +3678,6 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-json-view@^1.21.3: - version "1.21.3" - resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.21.3.tgz#f184209ee8f1bf374fb0c41b0813cff54549c475" - integrity sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw== - dependencies: - flux "^4.0.1" - react-base16-styling "^0.6.0" - react-lifecycles-compat "^3.0.4" - react-textarea-autosize "^8.3.2" - -react-lifecycles-compat@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" - integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== - react-refresh@^0.14.2: version "0.14.2" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" @@ -3822,15 +3711,6 @@ react-style-singleton@^2.2.1: invariant "^2.2.4" tslib "^2.0.0" -react-textarea-autosize@^8.3.2: - version "8.5.3" - resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz#d1e9fe760178413891484847d3378706052dd409" - integrity sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ== - dependencies: - "@babel/runtime" "^7.20.13" - use-composed-ref "^1.3.0" - use-latest "^1.2.1" - react@^18: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" @@ -4042,11 +3922,6 @@ set-function-name@^2.0.1, set-function-name@^2.0.2: functions-have-names "^1.2.3" has-property-descriptors "^1.0.2" -setimmediate@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -4121,7 +3996,6 @@ streamsearch@^1.1.0: integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== "string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: - name string-width-cjs version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4202,7 +4076,6 @@ string.prototype.trimstart@^1.0.8: es-object-atoms "^1.0.0" "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: - name strip-ansi-cjs version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -4412,11 +4285,6 @@ tr46@^5.0.0: dependencies: punycode "^2.3.1" -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - ts-api-utils@^1.0.1: version "1.3.0" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" @@ -4503,11 +4371,6 @@ typescript@^5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== -ua-parser-js@^1.0.35: - version "1.0.39" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.39.tgz#bfc07f361549bf249bd8f4589a4cccec18fd2018" - integrity sha512-k24RCVWlEcjkdOxYmVJgeD/0a1TiSpqLg+ZalVGV9lsnr4yqu0w7tX/x2xX6G4zpkgQnRf89lxuZ1wsbjXM8lw== - unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -4545,23 +4408,6 @@ use-callback-ref@^1.3.0: dependencies: tslib "^2.0.0" -use-composed-ref@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda" - integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== - -use-isomorphic-layout-effect@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" - integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== - -use-latest@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.1.tgz#d13dfb4b08c28e3e33991546a2cee53e14038cf2" - integrity sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw== - dependencies: - use-isomorphic-layout-effect "^1.1.1" - use-sidecar@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2" @@ -4630,11 +4476,6 @@ w3c-xmlserializer@^5.0.0: dependencies: xml-name-validator "^5.0.0" -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" @@ -4660,14 +4501,6 @@ whatwg-url@^14.0.0: tr46 "^5.0.0" webidl-conversions "^7.0.0" -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"