Skip to content

not ready to merge - This prototype adds categorical metadata distributions and filtering to the dataset distribution panel.#1687

Open
ikondrat wants to merge 20 commits into
mainfrom
kondrat-lig-9585-frontend-categorical-metadata-bar-chart-filter-ui-8-sp
Open

not ready to merge - This prototype adds categorical metadata distributions and filtering to the dataset distribution panel.#1687
ikondrat wants to merge 20 commits into
mainfrom
kondrat-lig-9585-frontend-categorical-metadata-bar-chart-filter-ui-8-sp

Conversation

@ikondrat

@ikondrat ikondrat commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Important

Functional prototype — not ready to merge.

This PR implements the feature end to end so we can evaluate the UI/UX, collect feedback,
and identify quick fixes. A production-ready implementation will require code polishing
and splitting this work into smaller, focused PRs.

What has changed and why?

This prototype adds categorical metadata distributions and filtering to the dataset
distribution panel.

The goal is to validate whether users can:

  • understand the distribution of categorical metadata;
  • identify missing or imbalanced values;
  • filter the dataset by one or more categorical values;
  • understand how the distribution responds to active filters;
  • configure and expand the categorical chart consistently with numeric distributions.

The PR combines LIG-9584
and LIG-9585
to provide a functional end-to-end prototype.

Included in the prototype

  • Backend endpoint for categorical metadata value counts.
  • Top-value, Other, and Missing bucket handling.
  • Multi-value categorical filtering, including missing values.
  • Categorical bar charts in the distribution panel.
  • Searchable value selection and filter chips.
  • Chart configuration, sorting, orientation, and expanded view.
  • Shared filtering across images, video frames, and videos.
  • Backend and frontend tests for the main behavior.

Feedback requested

Please focus feedback on the product behavior and UI/UX rather than detailed code quality:

  • Is the categorical distribution easy to understand?
  • Are value selection and clearing intuitive?
  • Are Missing and Other represented clearly?
  • Does the interaction between filters and distributions behave as expected?
  • Are the chart configuration and expanded view useful?
  • Which changes are required before investing in production-ready implementation?

Not production-ready

This PR is intentionally broad and is not intended to be merged as-is. Production readiness
will require:

  • splitting the backend, filter plumbing, and UI into focused PRs;
  • simplifying and polishing the component structure;
  • addressing review feedback and removing prototype-only code;
  • completing accessibility and responsive-layout QA;
  • validating performance on large and high-cardinality datasets;
  • completing full regression and end-to-end testing;
  • updating user-facing documentation and the changelog where appropriate.

A likely split is:

  1. Categorical value-count backend endpoint.
  2. Categorical metadata filter operators.
  3. Shared frontend filter state and request plumbing.
  4. Categorical chart and value-selector UI.
  5. Configuration, expanded view, accessibility, and final polish.

How has it been tested?

  • Backend API, resolver, filtering, and database tests were added.
  • Frontend hook, filter, chart, and component tests were added.
  • End-to-end test data was extended with categorical metadata.
  • The primary workflow has been exercised manually as a functional prototype.
  • Full CI and production-readiness validation remain pending.

Did you update CHANGELOG.md?

  • Yes
  • Not needed — this is a non-mergeable prototype; release notes should be added by the production PRs.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Added categorical metadata distributions for string and boolean fields, with top-value counts and “Missing”/display-only “Other” handling.
    • Introduced a categorical value-counts experience and popover filter UI with search, clear, and loading/error/retry states.
    • Enabled categorical metadata selections across images, videos, and frames, with selection persistence.
    • Improved distribution chart interactions (selection/dimming behavior) and configuration wording/orientation controls.
  • Documentation
    • Added feature specifications for categorical value counts and distribution filtering semantics.
  • Tests
    • Expanded coverage for categorical counting, filtering, chart interactions, persistence, and edge cases.

ikondrat and others added 17 commits July 20, 2026 10:54
Adds an 'in' operator that accepts a list of string/boolean/null values
and builds an OR predicate, using a LEFT OUTER JOIN when null is present
to match samples with no metadata entry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces the CategoricalMetadataValue/CategoricalMetadataValues types
and a session-storage-backed store for selected categorical filter values,
alongside an export for the upcoming useCategoricalMetadataDistribution hook.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
createMetadataFilters now accepts an optional categoricalMetadataValues map
and appends one 'in' filter per non-empty key; also clears the categorical
store when the collection changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fetches value-counts for all categorical metadata fields and shapes the
response into typed CategoricalMetadataBucket arrays, disambiguating
literal 'Missing'/'Other' values from the sentinel missing/other buckets.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the optional categorical_metadata_values param to useImageFilters,
useVideoFilters, useFramesFilter, and useImagesInfinite so that active
categorical selections are included in every sample filter request.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CategoryCount gains optional selected and selectable fields; buildEchartsOption
renders a bright border on selected bars and dims unselectable ones.
Clicks on bars with selectable=false are suppressed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renders a checkbox list for selecting categorical metadata values,
with a 'Clear' action and a missing-value entry as a first-class option.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a 'categorical' slot to DistributionSourceGroup; the panel renders
the MetadataCategoricalFilter plus a horizontal BarChart with selection
visuals and loading/error/retry states when a group carries categorical data.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
useMetadataFilterChips now tracks categorical selections and produces chips
with a disambiguated label format for literal 'Missing'/'Other' values vs
the sentinel missing bucket.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pages

The layout fetches categorical distributions, exposes toggle/clear handlers
to DatasetDistributionPanel, and passes categoricalMetadataValues into the
shared metadataFilters. Frames and videos pages forward the same values to
their respective filter hooks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds weather (skewed string), reviewed (boolean), camera_id (25 values,
exercises the "Other" bucket), and split (~20% missing, exercises the
"Missing" bucket) alongside the existing numeric fields. location now
uses weighted sampling instead of uniform.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ikondrat
ikondrat requested a review from a team as a code owner July 20, 2026 12:53
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds categorical metadata value-count APIs, validated in filters, frontend categorical distribution queries, persistent selections, bar-chart controls, filter chips, and propagation of categorical selections through image, frame, video, and infinite-query requests.

Changes

Categorical metadata workflow

Layer / File(s) Summary
Feature contracts and backend aggregation
docs/features/*, lightly_studio/src/lightly_studio/api/..., lightly_studio/src/lightly_studio/resolvers/...
Defines categorical value-count semantics, adds response models and a value-count endpoint, validates in filters, supports missing-value predicates, and computes deterministic top-20 counts.
Frontend filtering and query propagation
lightly_studio_view/src/lib/hooks/..., lightly_studio_view/src/routes/..., lightly_studio_view/src/lib/components/Images/...
Adds categorical metadata types and session state, combines categorical and numeric filters, and includes categorical selections in request bodies and query keys.
Categorical distribution UI
lightly_studio_view/src/lib/components/DatasetDistributionPanel/..., lightly_studio_view/src/lib/components/BarChart/..., lightly_studio_view/src/lib/components/MetadataFilterChips/...
Adds categorical bucket mapping, selectors, chart rendering, stable selection identities, orientation/configuration controls, loading/error states, retry handling, and categorical filter chips.
Validation and seeded coverage
lightly_studio/tests/..., lightly_studio_view/src/**/*.test.ts, lightly_studio/e2e-tests/index_distribution.py
Adds backend, frontend, component, query propagation, JSON extraction, and seeded metadata coverage for categorical values, missing values, aggregate buckets, and filtering.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant DistributionPanel
  participant CategoricalQuery
  participant MetadataAPI
  participant GlobalStorage
  participant ImagesQuery
  User->>DistributionPanel: Select categorical value
  DistributionPanel->>GlobalStorage: Store selected value
  GlobalStorage->>CategoricalQuery: Update active filter
  CategoricalQuery->>MetadataAPI: POST metadata/value-counts
  MetadataAPI-->>CategoricalQuery: Return categorical buckets
  CategoricalQuery-->>DistributionPanel: Render updated counts
  GlobalStorage->>ImagesQuery: Build categorical in-filter
  ImagesQuery-->>User: Refresh filtered samples
Loading

Possibly related PRs

Suggested reviewers: leonardorosaa

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific and matches the PR’s main change: adding categorical metadata distributions and filtering to the dataset distribution panel.
Description check ✅ Passed The description follows the required sections and covers motivation, implementation scope, testing, and changelog status with enough detail.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch kondrat-lig-9585-frontend-categorical-metadata-bar-chart-filter-ui-8-sp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ikondrat ikondrat changed the title Kondrat lig 9585 frontend categorical metadata bar chart filter UI 8 sp not ready to merge - This prototype adds categorical metadata distributions and filtering to the dataset distribution panel. Jul 20, 2026
@ikondrat

Copy link
Copy Markdown
Contributor Author

/review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8ef4bc588a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

export const useCategoricalMetadataDistribution = (
getOptions: () => CategoricalMetadataDistributionOptions & { enabled?: boolean }
) =>
createQuery(() => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Move the query wrapper into a .svelte.ts file

The Frontend guidelines require hooks that call TanStack Query's createQuery/createInfiniteQuery to live in .svelte.ts files. This new hook wraps createQuery from a plain .ts module, so please rename the module/export to .svelte.ts before building more reactive query behavior on it.

Useful? React with 👍 / 👎.

import { Button } from '$lib/components/ui/button';
import { Checkbox } from '$lib/components/ui/checkbox';
import { Input } from '$lib/components/ui/input';
import type { CategoricalMetadataBucket } from '$lib/hooks/useCategoricalMetadataDistribution/useCategoricalMetadataDistribution';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Stop importing the hook type through a deep path

The Frontend import guidelines ask shared modules to be imported from their module/barrel API rather than deep paths. This component reaches into the hook implementation just for CategoricalMetadataBucket; export the type from the hook module's public API (or move it to a shared types file) and import from there instead.

Useful? React with 👍 / 👎.

Comment on lines +49 to +51
schema = metadata_info_resolver._get_merged_schema( # noqa: SLF001
session=session, collection_id=collection_id
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Extract public shared metadata query helpers

This resolver calls _get_merged_schema (and below, other underscored helpers) from get_metadata_info while suppressing SLF001. That couples the new value-counts resolver to private histogram internals, so a local refactor there can silently break this endpoint; move the shared schema/filter helpers to a public resolver utility instead.

Useful? React with 👍 / 👎.

@@ -0,0 +1,163 @@
<script lang="ts">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Split the oversized categorical filter component

The Frontend guidelines ask components to stay under about 100 lines and split logic into focused, testable pieces. This new component is roughly 150 lines and combines bucket retention, label disambiguation, search state, and popover rendering; extracting the option/summary logic or a list subcomponent would keep the UI easier to maintain.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
lightly_studio_view/src/lib/components/ClassSetConfig/ClassSetConfigDialog.svelte (1)

28-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive itemNoun from an explicit prop instead of a hardcoded string match.

itemNoun={itemNounPlural === 'classes' ? 'class' : 'value'} only works for the two current callers ('classes'/'values'). Any future caller passing a different plural (e.g. 'samples', 'tags') silently gets the wrong singular noun ('value').

♻️ Proposed fix: add an explicit `itemNoun` prop
     /** Singular/plural labels for the configured chart items. */
     itemNounPlural?: string;
+    /** Singular label for the configured chart items (default 'class'). */
+    itemNoun?: string;
         showAllButton = false,
         itemNounPlural = 'classes',
+        itemNoun = 'class',
                     {items}
                     {itemNounPlural}
-                    itemNoun={itemNounPlural === 'classes' ? 'class' : 'value'}
+                    {itemNoun}

Callers (e.g. DistributionConfigDialog) would then pass itemNoun="value" alongside itemNounPlural="values" explicitly.

Also applies to: 39-50, 133-135

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@lightly_studio_view/src/lib/components/ClassSetConfig/ClassSetConfigDialog.svelte`
around lines 28 - 29, Replace the hardcoded singular-noun derivation in
ClassSetConfigDialog with an explicit itemNoun prop, and update the component’s
prop definition and all callers, including DistributionConfigDialog, to pass the
correct singular alongside itemNounPlural. Remove the classes-specific
conditional so arbitrary plural labels such as samples or tags retain their
intended singular noun.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@lightly_studio/src/lightly_studio/resolvers/metadata_resolver/sample/categorical_value_counts.py`:
- Around line 19-21: The categorical value counts resolver directly depends on
private helpers from metadata_info_resolver. Move _get_merged_schema,
_without_metadata_key_filter, and _apply_image_filters into a shared module with
non-private names, then update both get_metadata_info and
categorical_value_counts to import and use the shared helpers, removing the
SLF001 suppressions.

---

Nitpick comments:
In
`@lightly_studio_view/src/lib/components/ClassSetConfig/ClassSetConfigDialog.svelte`:
- Around line 28-29: Replace the hardcoded singular-noun derivation in
ClassSetConfigDialog with an explicit itemNoun prop, and update the component’s
prop definition and all callers, including DistributionConfigDialog, to pass the
correct singular alongside itemNounPlural. Remove the classes-specific
conditional so arbitrary plural labels such as samples or tags retain their
intended singular noun.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fc5c8556-a56e-4f32-91aa-85c42f66fc1f

📥 Commits

Reviewing files that changed from the base of the PR and between 30a7486 and 8ef4bc5.

📒 Files selected for processing (64)
  • AGENTS.md
  • docs/features/lig-9584-categorical-metadata-value-counts.md
  • docs/features/lig-9585-categorical-metadata-distribution-filter.md
  • lightly_studio/e2e-tests/index_distribution.py
  • lightly_studio/src/lightly_studio/api/routes/api/metadata.py
  • lightly_studio/src/lightly_studio/database/db_json.py
  • lightly_studio/src/lightly_studio/models/metadata.py
  • lightly_studio/src/lightly_studio/resolvers/metadata_resolver/metadata_filter.py
  • lightly_studio/src/lightly_studio/resolvers/metadata_resolver/sample/categorical_value_counts.py
  • lightly_studio/src/lightly_studio/type_definitions.py
  • lightly_studio/tests/api/routes/api/test_metadata.py
  • lightly_studio/tests/database/test_db_json.py
  • lightly_studio/tests/metadata/test_get_metadata_value_counts.py
  • lightly_studio/tests/resolvers/metadata_resolvers/sample/test_sample_metadata_filtering.py
  • lightly_studio/tests/resolvers/metadata_resolvers/test_metadata_filter.py
  • lightly_studio_view/src/lib/components/BarChart/BarChart.svelte
  • lightly_studio_view/src/lib/components/BarChart/buildEchartsOption.test.ts
  • lightly_studio_view/src/lib/components/BarChart/buildEchartsOption.ts
  • lightly_studio_view/src/lib/components/BarChart/types.ts
  • lightly_studio_view/src/lib/components/ClassSetConfig/ClassSetConfigDialog.svelte
  • lightly_studio_view/src/lib/components/ClassSetConfig/ClassSetConfigDialog.test.ts
  • lightly_studio_view/src/lib/components/ClassSetConfig/ManualClassSelector.svelte
  • lightly_studio_view/src/lib/components/ClassSetConfig/ManualClassSelector.test.ts
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/DatasetDistributionPanel.svelte
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/DatasetDistributionPanel.test.ts
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/DistributionConfigDialog/DistributionConfigDialog.svelte
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/DistributionConfigDialog/DistributionConfigDialog.test.ts
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/ExpandDialog/ExpandDialog.svelte
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/ExpandDialog/ExpandDialog.test.ts
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/MetadataCategoricalFilter.svelte
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/MetadataCategoricalFilter.test.ts
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/PanelHeader/PanelHeader.svelte
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/PanelHeader/PanelHeader.test.ts
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/selectVisibleCounts.test.ts
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/selectVisibleCounts.ts
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/types.ts
  • lightly_studio_view/src/lib/components/Histogram/buildHistogramOption.test.ts
  • lightly_studio_view/src/lib/components/Histogram/buildHistogramOption.ts
  • lightly_studio_view/src/lib/components/Images/Images.svelte
  • lightly_studio_view/src/lib/components/MetadataFilterChips/MetadataFilterChips.svelte
  • lightly_studio_view/src/lib/components/MetadataFilterChips/MetadataFilterChips.test.ts
  • lightly_studio_view/src/lib/components/MetadataFilterChips/useMetadataFilterChips.svelte.ts
  • lightly_studio_view/src/lib/components/MetadataFilterChips/useMetadataFilterChips.test.ts
  • lightly_studio_view/src/lib/hooks/index.ts
  • lightly_studio_view/src/lib/hooks/useCategoricalMetadataDistribution/useCategoricalMetadataDistribution.test.ts
  • lightly_studio_view/src/lib/hooks/useCategoricalMetadataDistribution/useCategoricalMetadataDistribution.ts
  • lightly_studio_view/src/lib/hooks/useFramesFilter/frameFilter.test.ts
  • lightly_studio_view/src/lib/hooks/useFramesFilter/frameFilter.ts
  • lightly_studio_view/src/lib/hooks/useGlobalStorage.ts
  • lightly_studio_view/src/lib/hooks/useImageFilters/useImageFilters.test.ts
  • lightly_studio_view/src/lib/hooks/useImageFilters/useImageFilters.ts
  • lightly_studio_view/src/lib/hooks/useImagesInfinite/buildRequestBody.test.ts
  • lightly_studio_view/src/lib/hooks/useImagesInfinite/buildRequestBody.ts
  • lightly_studio_view/src/lib/hooks/useImagesInfinite/createImagesInfiniteOptions.test.ts
  • lightly_studio_view/src/lib/hooks/useImagesInfinite/createImagesInfiniteOptions.ts
  • lightly_studio_view/src/lib/hooks/useImagesInfinite/types.ts
  • lightly_studio_view/src/lib/hooks/useMetadataFilters/useMetadataFilters.test.ts
  • lightly_studio_view/src/lib/hooks/useMetadataFilters/useMetadataFilters.ts
  • lightly_studio_view/src/lib/hooks/useVideoFilters/useVideoFilters.test.ts
  • lightly_studio_view/src/lib/hooks/useVideoFilters/useVideoFilters.ts
  • lightly_studio_view/src/lib/services/types.ts
  • lightly_studio_view/src/routes/datasets/[dataset_id]/[collection_type]/[collection_id]/+layout.svelte
  • lightly_studio_view/src/routes/datasets/[dataset_id]/[collection_type]/[collection_id]/frames/+page.svelte
  • lightly_studio_view/src/routes/datasets/[dataset_id]/[collection_type]/[collection_id]/videos/+page.svelte

Comment on lines +19 to +21
from lightly_studio.resolvers.metadata_resolver.sample import (
get_metadata_info as metadata_info_resolver,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Cross-module reliance on get_metadata_info's private helpers.

_get_merged_schema, _without_metadata_key_filter, and _apply_image_filters are all underscore-prefixed (private) in get_metadata_info, yet this module imports and calls them directly with # noqa: SLF001 suppressions in three places. This couples the resolver to internal implementation details that carry no API stability guarantee — a future rename/refactor of those helpers (which look "private" and safe to change) could silently break this file.

Consider promoting these three helpers to a shared, non-underscored module (e.g. a small metadata_query_helpers module imported by both get_metadata_info and categorical_value_counts), or exposing them as an explicit internal API on get_metadata_info without the underscore prefix.

Also applies to: 49-58, 119-121, 147-149

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@lightly_studio/src/lightly_studio/resolvers/metadata_resolver/sample/categorical_value_counts.py`
around lines 19 - 21, The categorical value counts resolver directly depends on
private helpers from metadata_info_resolver. Move _get_merged_schema,
_without_metadata_key_filter, and _apply_image_filters into a shared module with
non-private names, then update both get_metadata_info and
categorical_value_counts to import and use the shared helpers, removing the
SLF001 suppressions.

ikondrat and others added 3 commits July 22, 2026 11:03
When the user selects one or more categorical metadata values the remaining
bars now render in grey (#4b5563), matching the existing behaviour of the
numeric Histogram where bins outside the selected range are dimmed.  The
white border on the selected bar has been removed because the green/grey
colour contrast already communicates selection clearly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Metadata distribution plots previously passed the full imageAnnotationCountsFilter
to the histogram and categorical queries, causing bar heights to shrink as the
user applied annotation, dimension, or metadata filters from the left sidebar.
This made it hard to understand what was being filtered away because the original
distribution context was lost.

Introduce distributionBaseFilter — a derived value that omits the analysis
filters (metadataFilters, annotationFilter, dimensionsValues) while retaining
the collection-scoping context (tagIds, sampleIds, confusionCell, queryExpr).
Both distribution queries now use this filter so bar heights always reflect the
full dataset; the active selection is communicated through bar colour alone
(green = in selection, grey = outside).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…al distributions

When sidebar filters (annotation class, image dimensions, metadata of other
fields) are active, categorical metadata bars previously shrank to show only the
filtered counts, losing the original distribution as context.

This change fetches a second distribution using the full sidebar filter
(imageAnnotationCountsFilter) alongside the existing base-filter query, then
passes the filtered counts as `filteredBuckets` to each categorical group.

The bar chart renders two ECharts series when any bar's filteredCount differs
from its full count:
  - Background series (grey, #374151): full unfiltered count — stable height
  - Foreground series (green/grey): filtered count — shows filter effect

The tooltip updates to "Total / In filter" for bars where the filter actually
reduces the count, falling back to the single-value "Count" line otherwise.

When no filter is active the filtered query returns the same counts as the
base query, so hasActiveFilter is false and a single series is rendered — no
visual change from the pre-filter state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lightly-fast-track-bot

Copy link
Copy Markdown

❌ Fast Track: checks did not pass

Failed guardrails: frontend/complexity (Guardrail threw: Cannot find module 'eslint'
Require stack:

  • /home/runner/work/lightly-studio/lightly-studio/lightly_studio_view/package.json), backend/complexity (Guardrail threw: spawn uv ENOENT), backend/coverage (Guardrail threw: spawn uv ENOENT), diff-size (PR adds 3063 line(s), which exceeds the limit of 215.), frontend/coverage ( [FAIL] lightly_studio_view/src/lib/components/BarChart/types.ts: no test file found
    [FAIL] lightly_studio_view/src/lib/components/DatasetDistributionPanel/types.ts: no test file found
    [FAIL] lightly_studio_view/src/lib/components/Images/Images.svelte: no test file found
    [FAIL] lightly_studio_view/src/lib/hooks/index.ts: no test file found
    [FAIL] lightly_studio_view/src/lib/hooks/useImagesInfinite/types.ts: no test file found
    [FAIL] lightly_studio_view/src/lib/services/types.ts: no test file found
    [FAIL] lightly_studio_view/src/routes/datasets/[dataset_id]/[collection_type]/[collection_id]/frames/+page.svelte: no test file found
    [FAIL] lightly_studio_view/src/routes/datasets/[dataset_id]/[collection_type]/[collection_id]/videos/+page.svelte: no test file found
    [FAIL] lightly_studio_view/src/lib/components/BarChart/BarChart.svelte: coverage data not found
    [FAIL] lightly_studio_view/src/lib/components/BarChart/buildEchartsOption.ts: coverage data not found
    [FAIL] lightly_studio_view/src/lib/components/ClassSetConfig/ClassSetConfigDialog.svelte: coverage data not found
    [FAIL] lightly_studio_view/src/lib/components/ClassSetConfig/ManualClassSelector.svelte: coverage data not found
    [FAIL] lightly_studio_view/src/lib/components/DatasetDistributionPanel/DatasetDistributionPanel.svelte: coverage data not found
    [FAIL] lightly_studio_view/src/lib/components/DatasetDistributionPanel/DistributionConfigDialog/DistributionConfigDialog.svelte: coverage data not found
    [FAIL] lightly_studio_view/src/lib/components/DatasetDistributionPanel/ExpandDialog/ExpandDialog.svelte: coverage data not found
    [FAIL] lightly_studio_view/src/lib/components/DatasetDistributionPanel/MetadataCategoricalFilter.svelte: coverage data not found
    [FAIL] lightly_studio_view/src/lib/components/DatasetDistributionPanel/PanelHeader/PanelHeader.svelte: coverage data not found
    [FAIL] lightly_studio_view/src/lib/components/DatasetDistributionPanel/selectVisibleCounts.ts: coverage data not found
    [FAIL] lightly_studio_view/src/lib/components/Histogram/buildHistogramOption.ts: coverage data not found
    [FAIL] lightly_studio_view/src/lib/components/MetadataFilterChips/MetadataFilterChips.svelte: coverage data not found
    [FAIL] lightly_studio_view/src/lib/components/MetadataFilterChips/useMetadataFilterChips.svelte.ts: coverage data not found
    [FAIL] lightly_studio_view/src/lib/hooks/useCategoricalMetadataDistribution/useCategoricalMetadataDistribution.ts: coverage data not found
    [FAIL] lightly_studio_view/src/lib/hooks/useFramesFilter/frameFilter.ts: coverage data not found
    [FAIL] lightly_studio_view/src/lib/hooks/useGlobalStorage.ts: coverage data not found
    [FAIL] lightly_studio_view/src/lib/hooks/useImageFilters/useImageFilters.ts: coverage data not found
    [FAIL] lightly_studio_view/src/lib/hooks/useImagesInfinite/buildRequestBody.ts: coverage data not found
    [FAIL] lightly_studio_view/src/lib/hooks/useImagesInfinite/createImagesInfiniteOptions.ts: coverage data not found
    [FAIL] lightly_studio_view/src/lib/hooks/useMetadataFilters/useMetadataFilters.ts: coverage data not found
    [FAIL] lightly_studio_view/src/lib/hooks/useVideoFilters/useVideoFilters.ts: coverage data not found
    [FAIL] lightly_studio_view/src/routes/datasets/[dataset_id]/[collection_type]/[collection_id]/+layout.svelte: coverage data not found)
Guardrail Result Message
dummy Always passes.
frontend/complexity Guardrail threw: Cannot find module 'eslint' Require stack: - /home/runner/work/lightly-studio/lightly-studio/lightly_studio_view/package.json
backend/complexity Guardrail threw: spawn uv ENOENT
backend/coverage Guardrail threw: spawn uv ENOENT
diff-size PR adds 3063 line(s), which exceeds the limit of 215.
frontend/coverage [FAIL] lightly_studio_view/src/lib/components/BarChart/types.ts: no test file found [FAIL] lightly_studio_view/src/lib/components/DatasetDistributionPanel/types.ts: no test file found [FAIL] lightly_studio_view/src/lib/components/Images/Images.svelte: no test file found [FAIL] lightly_studio_view/src/lib/hooks/index.ts: no test file found [FAIL] lightly_studio_view/src/lib/hooks/useImagesInfinite/types.ts: no test file found [FAIL] lightly_studio_view/src/lib/services/types.ts: no test file found [FAIL] lightly_studio_view/src/routes/datasets/[dataset_id]/[collection_type]/[collection_id]/frames/+page.svelte: no test file found [FAIL] lightly_studio_view/src/routes/datasets/[dataset_id]/[collection_type]/[collection_id]/videos/+page.svelte: no test file found [FAIL] lightly_studio_view/src/lib/components/BarChart/BarChart.svelte: coverage data not found [FAIL] lightly_studio_view/src/lib/components/BarChart/buildEchartsOption.ts: coverage data not found [FAIL] lightly_studio_view/src/lib/components/ClassSetConfig/ClassSetConfigDialog.svelte: coverage data not found [FAIL] lightly_studio_view/src/lib/components/ClassSetConfig/ManualClassSelector.svelte: coverage data not found [FAIL] lightly_studio_view/src/lib/components/DatasetDistributionPanel/DatasetDistributionPanel.svelte: coverage data not found [FAIL] lightly_studio_view/src/lib/components/DatasetDistributionPanel/DistributionConfigDialog/DistributionConfigDialog.svelte: coverage data not found [FAIL] lightly_studio_view/src/lib/components/DatasetDistributionPanel/ExpandDialog/ExpandDialog.svelte: coverage data not found [FAIL] lightly_studio_view/src/lib/components/DatasetDistributionPanel/MetadataCategoricalFilter.svelte: coverage data not found [FAIL] lightly_studio_view/src/lib/components/DatasetDistributionPanel/PanelHeader/PanelHeader.svelte: coverage data not found [FAIL] lightly_studio_view/src/lib/components/DatasetDistributionPanel/selectVisibleCounts.ts: coverage data not found [FAIL] lightly_studio_view/src/lib/components/Histogram/buildHistogramOption.ts: coverage data not found [FAIL] lightly_studio_view/src/lib/components/MetadataFilterChips/MetadataFilterChips.svelte: coverage data not found [FAIL] lightly_studio_view/src/lib/components/MetadataFilterChips/useMetadataFilterChips.svelte.ts: coverage data not found [FAIL] lightly_studio_view/src/lib/hooks/useCategoricalMetadataDistribution/useCategoricalMetadataDistribution.ts: coverage data not found [FAIL] lightly_studio_view/src/lib/hooks/useFramesFilter/frameFilter.ts: coverage data not found [FAIL] lightly_studio_view/src/lib/hooks/useGlobalStorage.ts: coverage data not found [FAIL] lightly_studio_view/src/lib/hooks/useImageFilters/useImageFilters.ts: coverage data not found [FAIL] lightly_studio_view/src/lib/hooks/useImagesInfinite/buildRequestBody.ts: coverage data not found [FAIL] lightly_studio_view/src/lib/hooks/useImagesInfinite/createImagesInfiniteOptions.ts: coverage data not found [FAIL] lightly_studio_view/src/lib/hooks/useMetadataFilters/useMetadataFilters.ts: coverage data not found [FAIL] lightly_studio_view/src/lib/hooks/useVideoFilters/useVideoFilters.ts: coverage data not found [FAIL] lightly_studio_view/src/routes/datasets/[dataset_id]/[collection_type]/[collection_id]/+layout.svelte: coverage data not found

Reflects 87d32e1.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
lightly_studio_view/src/lib/components/BarChart/buildEchartsOption.test.ts (1)

64-93: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Solid coverage of the overlay/no-overlay paths; consider a combined selection+filter case.

These new cases correctly pin down the background/foreground overlay behavior and the no-op single-series fallback. One gap: no test exercises hasAnySelected && hasActiveFilter together (e.g., a selected item with a reduced filteredCount), which is the most complex branch combination in buildEchartsOption.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lightly_studio_view/src/lib/components/BarChart/buildEchartsOption.test.ts`
around lines 64 - 93, Add a test for buildEchartsOption covering both a selected
item and an active filter, using input where at least one item is selected and
its filteredCount is lower than count. Assert the combined branch produces the
expected selection and filtered overlay series behavior, including relevant
series count and values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lightly_studio_view/src/lib/components/BarChart/buildEchartsOption.ts`:
- Around line 127-157: The foregroundSeries and backgroundSeries type literals
are inferred too broadly, and the series construction should use the background
value as the condition. Add as const to both type: 'bar' literals, then build
series with backgroundSeries ? [backgroundSeries, foregroundSeries] :
[foregroundSeries] so null is excluded.

---

Nitpick comments:
In `@lightly_studio_view/src/lib/components/BarChart/buildEchartsOption.test.ts`:
- Around line 64-93: Add a test for buildEchartsOption covering both a selected
item and an active filter, using input where at least one item is selected and
its filteredCount is lower than count. Assert the combined branch produces the
expected selection and filtered overlay series behavior, including relevant
series count and values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 96659d3f-72da-45b0-a542-6f64217a708e

📥 Commits

Reviewing files that changed from the base of the PR and between 8ef4bc5 and 87d32e1.

📒 Files selected for processing (7)
  • lightly_studio_view/src/lib/components/BarChart/buildEchartsOption.test.ts
  • lightly_studio_view/src/lib/components/BarChart/buildEchartsOption.ts
  • lightly_studio_view/src/lib/components/BarChart/types.ts
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/DatasetDistributionPanel.svelte
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/DatasetDistributionPanel.test.ts
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/types.ts
  • lightly_studio_view/src/routes/datasets/[dataset_id]/[collection_type]/[collection_id]/+layout.svelte
🚧 Files skipped from review as they are similar to previous changes (5)
  • lightly_studio_view/src/lib/components/BarChart/types.ts
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/types.ts
  • lightly_studio_view/src/routes/datasets/[dataset_id]/[collection_type]/[collection_id]/+layout.svelte
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/DatasetDistributionPanel.test.ts
  • lightly_studio_view/src/lib/components/DatasetDistributionPanel/DatasetDistributionPanel.svelte

Comment on lines +127 to +157
const foregroundSeries = {
type: 'bar',
data: foregroundData,
itemStyle: { color: BAR_COLOR },
barCategoryGap: '25%',
// Overlay on top of the background series (barGap: '-100%' makes the bar
// occupy the same slot as the preceding series instead of shifting right).
...(hasActiveFilter ? { barGap: '-100%' } : {}),
emphasis: CHART_EMPHASIS
};

// Background series: full (unfiltered) count, always grey — only rendered
// when the filter is active and at least one bar is visibly reduced.
const backgroundSeries = hasActiveFilter
? {
type: 'bar',
data: data.map((item) => ({
value: item.count,
itemStyle: {
color: BAR_COLOR_BACKGROUND,
opacity: item.selectable === false ? 0.45 : 1
}
})),
barCategoryGap: '25%',
// Suppress hover highlight on the background so emphasis styling
// (colour change, scale) only fires on the foreground bars.
emphasis: { disabled: true }
}
: null;

const series = hasActiveFilter ? [backgroundSeries, foregroundSeries] : [foregroundSeries];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the target file and inspect the relevant section.
sed -n '1,240p' lightly_studio_view/src/lib/components/BarChart/buildEchartsOption.ts

# Find the option type definitions used by this component.
rg -n "EChartsCoreOption|ECBasicOption|series:" lightly_studio_view/src -g '!**/node_modules/**'

Repository: lightly-ai/lightly-studio

Length of output: 10223


🏁 Script executed:

#!/bin/bash
set -euo pipefail

nl -ba lightly_studio_view/src/lib/components/BarChart/buildEchartsOption.ts | sed -n '1,220p'

Repository: lightly-ai/lightly-studio

Length of output: 203


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the bar chart tests and the local type imports around ECharts usage.
sed -n '1,220p' lightly_studio_view/src/lib/components/BarChart/buildEchartsOption.test.ts
printf '\n---\n'
sed -n '1,220p' lightly_studio_view/src/lib/components/Histogram/buildHistogramOption.ts

Repository: lightly-ai/lightly-studio

Length of output: 14747


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node - <<'JS'
for (const mod of ['typescript', 'ts-node']) {
  try {
    const path = require.resolve(mod);
    console.log(mod + ': ' + path);
  } catch (e) {
    console.log(mod + ': not found');
  }
}
JS

Repository: lightly-ai/lightly-studio

Length of output: 249


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n '"echarts"|echarts/core|EChartsCoreOption' lightly_studio_view/package.json lightly_studio_view/pnpm-lock.yaml lightly_studio_view/package-lock.json lightly_studio_view/yarn.lock lightly_studio_view/src -g '!**/node_modules/**'

Repository: lightly-ai/lightly-studio

Length of output: 3346


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "type: 'bar' as const|type: 'custom' as const|series:" lightly_studio_view/src/lib/components -g '!**/node_modules/**'

Repository: lightly-ai/lightly-studio

Length of output: 2038


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' lightly_studio_view/package.json
printf '\n--- TS probe ---\n'
node - <<'JS'
const ts = require('/usr/local/lib/node_modules/typescript');

const source = `
const hasActiveFilter = true as boolean;
const foregroundData = [] as number[];
const data = [] as { count: number }[];

const foregroundSeries = {
  type: 'bar',
  data: foregroundData,
  itemStyle: { color: 'x' },
  barCategoryGap: '25%',
  ...(hasActiveFilter ? { barGap: '-100%' } : {}),
  emphasis: {}
};

const backgroundSeries = hasActiveFilter
  ? {
      type: 'bar',
      data: data.map((item) => ({ value: item.count })),
      barCategoryGap: '25%',
      emphasis: { disabled: true }
    }
  : null;

const series = hasActiveFilter ? [backgroundSeries, foregroundSeries] : [foregroundSeries];
`;

const fileName = 'probe.ts';
const sf = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
const host = {
  fileExists: (f) => f === fileName,
  readFile: (f) => (f === fileName ? source : undefined),
  getSourceFile: (f, lang) => (f === fileName ? sf : undefined),
  getDefaultLibFileName: () => ts.getDefaultLibFileName({ target: ts.ScriptTarget.Latest }),
  writeFile: () => {},
  getCurrentDirectory: () => process.cwd(),
  getDirectories: () => [],
  getCanonicalFileName: (f) => f,
  useCaseSensitiveFileNames: () => true,
  getNewLine: () => '\n',
  readDirectory: () => [],
};
const program = ts.createProgram([fileName], { noResolve: true, target: ts.ScriptTarget.Latest }, host);
const checker = program.getTypeChecker();

function findVar(name) {
  let found;
  function visit(node) {
    if (ts.isVariableDeclaration(node) && node.name.kind === ts.SyntaxKind.Identifier && node.name.text === name) {
      found = node;
    }
    ts.forEachChild(node, visit);
  }
  visit(sf);
  return found;
}

for (const name of ['foregroundSeries', 'backgroundSeries', 'series']) {
  const decl = findVar(name);
  const type = checker.getTypeAtLocation(decl.name);
  console.log(name + ': ' + checker.typeToString(type));
}
JS

Repository: lightly-ai/lightly-studio

Length of output: 4827


Keep the bar series types narrow. Add as const to both type: 'bar' literals, and branch on backgroundSeries itself so null doesn’t flow into series:
backgroundSeries ? [backgroundSeries, foregroundSeries] : [foregroundSeries].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lightly_studio_view/src/lib/components/BarChart/buildEchartsOption.ts` around
lines 127 - 157, The foregroundSeries and backgroundSeries type literals are
inferred too broadly, and the series construction should use the background
value as the condition. Add as const to both type: 'bar' literals, then build
series with backgroundSeries ? [backgroundSeries, foregroundSeries] :
[foregroundSeries] so null is excluded.

Source: Learnings

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.

1 participant