Skip to content

Subdue empty dashboard tabs#538

Merged
abidlabs merged 8 commits into
mainfrom
subdued-empty-tabs
May 22, 2026
Merged

Subdue empty dashboard tabs#538
abidlabs merged 8 commits into
mainfrom
subdued-empty-tabs

Conversation

@abidlabs
Copy link
Copy Markdown
Member

@abidlabs abidlabs commented Apr 27, 2026

Now that we have quite a bit of tabs open, and many applications might only need a subset of tabs (e.g. Traces only, or no Traces, metrics only), this PR visually subdues optional tabs when their content is empty

image

Specifically, it:

  • computes dashboard tab availability from the selected project/runs
  • auto-opens the first non-empty tab when launching the bare dashboard route
  • refreshes availability periodically so tabs become active as new data arrives

@gradio-pr-bot
Copy link
Copy Markdown
Contributor

gradio-pr-bot commented Apr 27, 2026

🪼 branch checks and previews

Name Status URL
🦄 Changes detected! Details

@gradio-pr-bot
Copy link
Copy Markdown
Contributor

gradio-pr-bot commented Apr 27, 2026

🦄 change detected

This Pull Request includes changes to the following packages.

Package Version
trackio patch

  • Subdue empty dashboard tabs

‼️ Changeset not approved. Ensure the version bump is appropriate for all packages before approving.

  • Maintainers can approve the changeset by checking this checkbox.

Something isn't right?

  • Maintainers can change the version label to modify the version bump.
  • If the bot has failed to detect any changes, or if this pull request needs to update multiple packages to different versions or requires a more comprehensive changelog entry, maintainers can update the changelog file directly.

@HuggingFaceDocBuilderDev
Copy link
Copy Markdown

HuggingFaceDocBuilderDev commented Apr 27, 2026

🪼 branch checks and previews

Name Status URL
Spaces ready! Spaces preview

Install Trackio from this PR (includes built frontend)

pip install "https://huggingface.co/buckets/trackio/trackio-wheels/resolve/243d14650ee54dd58eab446d9c066f052ddc520d/trackio-0.25.1-py3-none-any.whl"

@HuggingFaceDocBuilderDev
Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

# Conflicts:
#	trackio/frontend/src/App.svelte
@abidlabs abidlabs marked this pull request as ready for review May 22, 2026 00:04
Copy link
Copy Markdown
Contributor

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

This PR adds “tab availability” detection to the dashboard so optional tabs (e.g., Traces, System, Media, Reports, Files) are visually subdued when there’s no data for the current project/run selection, and the app can auto-navigate away from the bare dashboard route to the first non-empty tab.

Changes:

  • Compute and periodically refresh per-tab availability based on selected project/runs and available data sources (logs, traces, system metrics, alerts, files).
  • Auto-open the first available tab when landing on the bare dashboard route.
  • Update the navbar UI to visually subdue optional tabs detected as empty, and expose an explanatory tooltip.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
trackio/frontend/src/components/Navbar.svelte Adds empty-tab styling/tooltip driven by tabAvailability and optionalEmptyTabs.
trackio/frontend/src/App.svelte Implements tab availability computation, auto-open behavior, and polling refresh; passes state into Navbar.
.changeset/poor-windows-fold.md Adds a changeset entry for the feature.
Comments suppressed due to low confidence (2)

trackio/frontend/src/App.svelte:294

  • anyRunHasTraces makes one /get_traces call per selected run concurrently. For projects with many runs this becomes an N-request fan-out just to compute tab availability, and can overload both browser and backend. Consider switching to a short-circuiting approach (stop after first hit), adding a concurrency limit, and/or using a cheaper aggregate endpoint (or sampling a limited number of runs).
  async function anyRunHasTraces(project, runRecords) {
    const results = await Promise.all(
      runRecords.map(async (run) => {
        try {
          const traces = await getTraces(project, run, { limit: 1 });
          return (traces || []).length > 0;
        } catch {
          return false;
        }
      }),
    );
    return results.some(Boolean);
  }

trackio/frontend/src/App.svelte:330

  • refreshTabAvailability calls getLogsBatch(selectedProject, runRecords) for all selected runs to detect whether metrics/media/reports exist. Since the default selection is typically "all runs", this can fetch a very large payload and then iterate every log entry just to set boolean flags, and it can run repeatedly via polling/effects. Consider limiting the number of runs/logs inspected (sample/cap), batching like Metrics.svelte does, and/or adding a dedicated lightweight API that returns presence/summary booleans instead of full logs.
      ] = await Promise.all([
        runRecords.length ? getLogsBatch(selectedProject, runRecords) : [],
        getAlerts(selectedProject, null, null, null).catch(() => []),
        runRecords.length ? anyRunHasSystemMetrics(selectedProject, runRecords) : false,
        runRecords.length ? anyRunHasTraces(selectedProject, runRecords) : false,
        getProjectFiles(selectedProject).catch(() => []),
      ]);

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

Comment thread trackio/frontend/src/App.svelte Outdated
Comment on lines +268 to +293
async function anyRunHasSystemMetrics(project, runRecords) {
const results = await Promise.all(
runRecords.map(async (run) => {
try {
const metrics = await getSystemMetricsForRun(project, run);
return (metrics || []).length > 0;
} catch {
return false;
}
}),
);
return results.some(Boolean);
}

async function anyRunHasTraces(project, runRecords) {
const results = await Promise.all(
runRecords.map(async (run) => {
try {
const traces = await getTraces(project, run, { limit: 1 });
return (traces || []).length > 0;
} catch {
return false;
}
}),
);
return results.some(Boolean);
Copy link
Copy Markdown
Collaborator

@znation znation left a comment

Choose a reason for hiding this comment

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

Other than agreeing with Copilot's suggestion, LGTM.

Copy link
Copy Markdown
Contributor

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

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

Comment thread trackio/sqlite_storage.py
Comment on lines +1889 to +1895
with SQLiteStorage._get_connection(db_path) as conn:
flags["metrics"] = _exists(
conn,
"SELECT 1 FROM metrics "
"WHERE metrics GLOB '*:[0-9]*' OR metrics GLOB '*:-[0-9]*' "
"LIMIT 1",
)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks — considered this but keeping the current heuristic. The narrow case it can false-positive on is a project that only logs typed values (histogram/table) where those typed values happen to contain numeric metadata adjacent to a : (e.g. "bins":50). For that to mislead the user, the project must additionally never log any scalar metric in any run — quite rare in practice, and the worst-case outcome is "Metrics tab is highlighted but the page is empty when clicked," not a correctness bug.

The alternative (deserialize each row's JSON with orjson.loads and inspect top-level keys) brings back per-row JSON parsing in Python — exactly the cost this endpoint was introduced to avoid (the original frontend code shipped 3000 logs × N runs over the wire to do this). A bounded-sample variant would either miss data or still parse N rows.

A stricter SQL-only heuristic — "row has a digit and no _type marker" — is appealing but regresses the more common case of users logging scalars + media in the same log() call (e.g. {"loss": 0.5, "img": Image()}): that row contains _type and would be excluded, hiding the Metrics tab from users who clearly have metrics. False negatives on a common path are worse than false positives on a rare path.

Comment on lines +494 to +498
for (const row of metricsRows) {
if (!metrics && rowHasScalarMetric(row)) metrics = true;
if (!media && rowHasTypedValue(row, MEDIA_TYPES)) media = true;
if (!reports && rowHasTypedValue(row, new Set(["trackio.markdown"]))) reports = true;
if (metrics && media && reports) break;
Comment thread .changeset/poor-windows-fold.md Outdated
"trackio": patch
---

feat:Subdue empty dashboard tabs
abidlabs and others added 2 commits May 22, 2026 16:38
Python sqlite3 stores orjson bytes as BLOB; GLOB pattern matching
against BLOB-typed values is unreliable on older SQLite builds
(reproduced on Ubuntu CI Python 3.10.20). Force TEXT comparison.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Hoist MARKDOWN_TYPES to module-level constant in staticApi.js to
  avoid per-row Set allocation in tab-availability scan.
- Add missing space in changeset entry: "feat:Subdue" -> "feat: Subdue".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@abidlabs
Copy link
Copy Markdown
Member Author

Thanks @znation! Addressed copilot's comments, will merge this in

@abidlabs abidlabs merged commit a15c1a8 into main May 22, 2026
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants