Skip to content

Add SQLite export and E2E coverage#7

Merged
Tiryoh merged 2 commits intomainfrom
feature/sqlite-output
Mar 23, 2026
Merged

Add SQLite export and E2E coverage#7
Tiryoh merged 2 commits intomainfrom
feature/sqlite-output

Conversation

@Tiryoh
Copy link
Copy Markdown
Owner

@Tiryoh Tiryoh commented Mar 22, 2026

Summary

  • add SQLite export for rosout and diagnostics results using sql.js
  • add unit tests and Playwright E2E coverage for SQLite downloads
  • update docs and test scripts for the new export flow
image

Testing

  • npm run test
  • npm run build
  • npm run test:e2e
  • Verified SQLite downloads for both rosout and diagnostics paths via Playwright

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 22, 2026

Test Results

42 tests  +2   42 ✅ +2   0s ⏱️ ±0s
 1 suites ±0    0 💤 ±0 
 1 files   ±0    0 ❌ ±0 

Results for commit bc938cf. ± Comparison against base commit 3dce607.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 22, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds SQLite export support (via sql.js) for rosout logs and diagnostics, along with unit/E2E test coverage to validate downloaded .sqlite contents.

Changes:

  • Add sql.js integration and new SQLite export functions for rosout + diagnostics (including nested diagnostic values).
  • Update UI to offer SQLite as an export format and add stable data-testid hooks for E2E.
  • Add Vitest + Playwright coverage and update docs/configs for the new E2E flow.

Reviewed changes

Copilot reviewed 8 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/types/sqljs.d.ts Adds local TypeScript declarations for sql.js and *.wasm?url imports.
src/rosbagUtils.ts Implements SQLite export logic using sql.js, plus updates download helper to accept binary.
src/rosbagUtils.test.ts Adds unit tests validating SQLite schema/row contents for both exports.
src/App.tsx Adds SQLite export option in UI and adds test IDs used by Playwright.
playwright.config.ts Changes webServer reuse behavior for E2E runs.
package.json Adds sql.js dependency.
package-lock.json Locks sql.js dependency.
e2e/sqlite-export.spec.ts Adds Playwright tests that download SQLite files and assert query results.
docs/testing.md Documents Playwright E2E testing command.
README.md Updates export feature list and adds e2e test command mention.
.gitignore Keeps Playwright artifacts ignored.
Comments suppressed due to low confidence (1)

src/App.tsx:172

  • handleExport is now async, but errors from exportToSQLite/exportDiagnosticsToSQLite (e.g., failing to load the wasm) will become unhandled promise rejections because nothing catches them. Wrap the body in try/catch and surface failures via setError (and/or disable the export buttons while an export is in progress).
  const handleExport = async (format: 'csv' | 'json' | 'txt' | 'sqlite') => {
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
    let content: string | Uint8Array;
    let filename: string;
    let type: string;

    if (activeTab === 'diagnostics') {
      const prefix = 'diagnostics_export';
      switch (format) {
        case 'csv':
          content = exportDiagnosticsToCSV(filteredDiagnostics, timezone);
          filename = `${prefix}_${timestamp}.csv`;
          type = 'text/csv';
          break;
        case 'json':
          content = exportDiagnosticsToJSON(filteredDiagnostics, timezone);
          filename = `${prefix}_${timestamp}.json`;
          type = 'application/json';
          break;
        case 'txt':
          content = exportDiagnosticsToTXT(filteredDiagnostics, timezone);
          filename = `${prefix}_${timestamp}.txt`;
          type = 'text/plain';
          break;
        case 'sqlite':
          content = await exportDiagnosticsToSQLite(filteredDiagnostics, timezone);
          filename = `${prefix}_${timestamp}.sqlite`;
          type = 'application/vnd.sqlite3';
          break;
        default: {
          const exhaustiveFormat: never = format;
          throw new Error(`Unsupported export format: ${exhaustiveFormat}`);
        }
      }
    } else {
      switch (format) {
        case 'csv':
          content = exportToCSV(filteredMessages, timezone);
          filename = `rosout_export_${timestamp}.csv`;
          type = 'text/csv';
          break;
        case 'json':
          content = exportToJSON(filteredMessages, timezone);
          filename = `rosout_export_${timestamp}.json`;
          type = 'application/json';
          break;
        case 'txt':
          content = exportToTXT(filteredMessages, timezone);
          filename = `rosout_export_${timestamp}.txt`;
          type = 'text/plain';
          break;
        case 'sqlite':
          content = await exportToSQLite(filteredMessages, timezone);
          filename = `rosout_export_${timestamp}.sqlite`;
          type = 'application/vnd.sqlite3';
          break;
        default: {
          const exhaustiveFormat: never = format;
          throw new Error(`Unsupported export format: ${exhaustiveFormat}`);
        }
      }
    }

    downloadFile(content, filename, type);
  };

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/rosbagUtils.ts Outdated
Comment on lines +551 to +552
const blobPart: BlobPart = content instanceof Uint8Array ? new Uint8Array(content).buffer as ArrayBuffer : content;
const blob = new Blob([blobPart], { type });
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

downloadFile converts Uint8Array to a fresh ArrayBuffer via new Uint8Array(content).buffer, which copies the full database into a new buffer. Blob can accept a Uint8Array directly; passing the Uint8Array as-is avoids a potentially large extra allocation during SQLite downloads.

Suggested change
const blobPart: BlobPart = content instanceof Uint8Array ? new Uint8Array(content).buffer as ArrayBuffer : content;
const blob = new Blob([blobPart], { type });
const blob = new Blob([content], { type });

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit bc938cf. The unnecessary buffer copy has been removed. Note: the current code uses content as unknown as BlobPart cast, but Blob constructor accepts string | Uint8Array directly, so the cast could be simplified to just new Blob([content], { type }).

Comment thread src/rosbagUtils.ts Outdated
Comment thread playwright.config.ts Outdated
Comment thread src/rosbagUtils.ts Outdated
Comment thread src/rosbagUtils.ts Outdated
Comment thread src/rosbagUtils.ts Outdated
Comment on lines +8 to +14

type Timezone = 'local' | 'utc';

let sqlJsPromise: Promise<SqlJsStatic> | null = null;
const resolvedSqlWasmUrl =
typeof window === 'undefined'
? new URL('../node_modules/sql.js/dist/sql-wasm.wasm', import.meta.url).pathname
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using new URL(...).pathname for the sql-wasm.wasm path is not reliably cross-platform (notably on Windows) and can produce an invalid filesystem path. Prefer fileURLToPath(new URL(...)) when targeting Node, and consider avoiding a runtime dependency on ../node_modules/... if this path can ever be used outside tests.

Suggested change
type Timezone = 'local' | 'utc';
let sqlJsPromise: Promise<SqlJsStatic> | null = null;
const resolvedSqlWasmUrl =
typeof window === 'undefined'
? new URL('../node_modules/sql.js/dist/sql-wasm.wasm', import.meta.url).pathname
import { fileURLToPath } from 'url';
type Timezone = 'local' | 'utc';
let sqlJsPromise: Promise<SqlJsStatic> | null = null;
const resolvedSqlWasmUrl =
typeof window === 'undefined'
? fileURLToPath(new URL('../node_modules/sql.js/dist/sql-wasm.wasm', import.meta.url))

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit bc938cf with a custom resolveNodeFilePath helper instead of fileURLToPath from the url module. This avoids importing a Node.js-only module in a codebase that also runs in the browser. The helper handles Windows drive-letter paths (/C:/...C:/...) so cross-platform behavior is covered.

Comment thread src/rosbagUtils.test.ts
@Tiryoh Tiryoh merged commit 4ae368a into main Mar 23, 2026
6 checks passed
@Tiryoh Tiryoh deleted the feature/sqlite-output branch March 23, 2026 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants