Skip to content

Conversation

@Tschuppi81
Copy link
Contributor

@Tschuppi81 Tschuppi81 commented Jan 6, 2026

Search: Add breadcrumbs to search results in order to provide more context

TYPE: Feature
LINK: ogc-2880

@linear
Copy link

linear bot commented Jan 6, 2026

@codecov
Copy link

codecov bot commented Jan 6, 2026

Codecov Report

❌ Patch coverage is 92.92929% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.39%. Comparing base (15569d6) to head (2314336).
⚠️ Report is 12 commits behind head on master.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/onegov/org/layout.py 80.00% 4 Missing ⚠️
src/onegov/town6/layout.py 94.73% 3 Missing ⚠️
Additional details and impacted files
Files with missing lines Coverage Δ
src/onegov/org/app.py 96.40% <100.00%> (+<0.01%) ⬆️
src/onegov/org/directives.py 95.34% <100.00%> (+0.47%) ⬆️
src/onegov/org/exports/base.py 90.90% <100.00%> (ø)
src/onegov/org/request.py 92.05% <100.00%> (+0.68%) ⬆️
src/onegov/town6/layout.py 89.59% <94.73%> (+0.51%) ⬆️
src/onegov/org/layout.py 91.85% <80.00%> (-0.19%) ⬇️

... and 1 file with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 15569d6...2314336. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Tschuppi81
Copy link
Contributor Author

Tschuppi81 commented Jan 9, 2026

Also i switched the red color in the results to primary and italic

left: previous, right: now
image

@Tschuppi81
Copy link
Contributor Author

Not sure if the breadcrumbs color in search shall be primary or oil, what do you think?

Copy link

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 pull request adds breadcrumb navigation to search results for various content types (Topics, News, People, Files, Tickets, Events, Directory Entries, etc.) to provide better context when viewing search results. The implementation includes:

Changes:

  • Renamed PageLayout to TopicLayout throughout the codebase for clarity
  • Added a new get_layout() method to the request object to retrieve layouts for model instances
  • Implemented a layout registry system via a new Layout directive
  • Updated search result templates to display breadcrumbs for various content types
  • Added breadcrumb styling and fixed CSS issues related to z-index stacking
  • Added file ID anchors and highlight styling for targeted files
  • Updated test selectors to use regex anchors for more precise matching

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
tests/onegov/winterthur/test_search.py Updated click selectors to use regex anchors for precise matching
tests/onegov/town6/test_views_directory.py Updated click selectors to use regex anchors for precise matching
tests/onegov/org/test_views_ticket.py Updated click selectors to use regex anchors for precise matching
tests/onegov/org/test_views_directory.py Updated click selectors to use regex anchors for precise matching
tests/onegov/org/test_layout.py Updated imports from PageLayout to TopicLayout
src/onegov/town6/theme/styles/town6.scss Added z-index reset for breadcrumb links
src/onegov/town6/theme/styles/search.scss Added breadcrumb styling and updated search preview styles
src/onegov/town6/theme/styles/files.scss Added highlight and scroll margin for targeted file rows
src/onegov/town6/templates/macros.pt Added breadcrumbs to search result macros, changed p to div tags for consistency
src/onegov/town6/layout.py Renamed PageLayout to TopicLayout, added layout decorators for multiple models, implemented breadcrumbs for GeneralFile and various RIS models
src/onegov/org/views/page.py Updated imports and references from PageLayout to TopicLayout
src/onegov/org/views/editor.py Updated imports and references from PageLayout to TopicLayout
src/onegov/org/request.py Added get_layout() method to retrieve layouts for model instances
src/onegov/org/layout.py Renamed PageLayout to TopicLayout, added FormDefinitionLayout, DirectoryLayout, improved breadcrumbs for various layouts
src/onegov/org/exports/base.py Fixed import to use onegov.org instead of onegov.town6
src/onegov/org/directives.py Added Layout directive for registering layouts to models
src/onegov/org/app.py Added layout directive to OrgApp
src/onegov/agency/views/page.py Updated imports and references from PageLayout to TopicLayout
src/onegov/agency/layout.py Updated imports and class name from PageLayout to TopicLayout

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

Comment on lines 1238 to 1242
Link(
self.model.meeting.title,
self.request.link(self.model.meeting)
),
Link(title, self.request.link(self.model))
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

The breadcrumbs list includes both self.model.meeting.title (line 1239) and title (line 1242), where title is the same meeting title with an optional date suffix. This creates redundant breadcrumb entries pointing to the same meeting but with different labels. Consider removing the duplicate entry or clarifying the intended breadcrumb structure.

Copilot uses AI. Check for mistakes.
Comment on lines +313 to +320
layout_registry: dict[type, Layout]
) -> str:
return str(self.model)

def perform( # type:ignore[override]
self,
layout: Layout,
layout_registry: dict[type, Layout]
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

The type annotation for layout_registry should be dict[type, type] instead of dict[type, Layout] since the layout parameter represents a layout class (type), not an instance of the Layout directive class. The perform method stores layout classes in the registry, not Layout directive instances.

Suggested change
layout_registry: dict[type, Layout]
) -> str:
return str(self.model)
def perform( # type:ignore[override]
self,
layout: Layout,
layout_registry: dict[type, Layout]
layout_registry: dict[type, type[Layout]]
) -> str:
return str(self.model)
def perform( # type:ignore[override]
self,
layout: type[Layout],
layout_registry: dict[type, type[Layout]]

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

I cleaned up the type annotations to still include Layout, but otherwise a correct suggestion.

Comment on lines 150 to 155
.search-preview em {
color: $primary-color !important;
font-style: italic;
font-weight: bold;
text-decoration: underline;
}
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

The CSS selector has been changed from .search-results em to .search-preview em, narrowing the scope to only emphasize text within search preview sections. This change may affect emphasis styling for other parts of search results that fall outside the search-preview div.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@BreathingFlesh BreathingFlesh left a comment

Choose a reason for hiding this comment

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

I would give the breadcrumbs a light background so they look more like a separate element

@Tschuppi81
Copy link
Contributor Author

Tschuppi81 commented Jan 13, 2026

Here two examples of how search results look
Screenshot from 2026-01-13 15-35-24

Screenshot from 2026-01-13 15-35-07

…m:OneGov/onegov-cloud into feature/ogc-2880-search-results-with-path
Copy link
Member

@Daverball Daverball left a comment

Choose a reason for hiding this comment

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

Looks good, but we should also register layouts for Org. Other than there's some minor things.

We can probably do a better job for GeneralFile, since only publications will have a simple link to a list of files, we should look at which models have linked to the file and then display the breadcrumbs for each of those models. Performance is definitely a concern when looking for links, so we should probably cache them on the model, just like we cache the access of the linked models. But we can improve handling of general files in a follow-up change, for now I would only display breadcrumbs for publications, we can maybe link to the files management view for logged in users, but I'm not sure it's worth it.

Copy link
Member

Choose a reason for hiding this comment

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

Remember to also register the layouts for OrgApp here, while we still have some non-Town6 derived apps. Just like with views, the Town6 layouts will take precedence when registered for the same model in a Town6 app.

Comment on lines +313 to +320
layout_registry: dict[type, Layout]
) -> str:
return str(self.model)

def perform( # type:ignore[override]
self,
layout: Layout,
layout_registry: dict[type, Layout]
Copy link
Member

Choose a reason for hiding this comment

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

I cleaned up the type annotations to still include Layout, but otherwise a correct suggestion.

Comment on lines +288 to +304
def get_layout(self, model: object) -> Layout | DefaultLayout:
"""
Get the registered layout for a model instance.
"""
layout_registry = self.app.config.layout_registry
model_type = model if isinstance(model, type) else type(model)

layout_class = None
for cls in model_type.mro():
layout_class = layout_registry.get(cls)
if layout_class:
break

if layout_class is None:
layout_class = DefaultLayout

return layout_class(model, self)
Copy link
Member

@Daverball Daverball Jan 14, 2026

Choose a reason for hiding this comment

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

You could probably simplify this by instead relying on a dispatch_method on Framework, which could look something like this:

    @dispatch_method()
    def get_layout_class(self, model: object) -> type[Layout] | None:
        return None

...

@Framework.predicate(Framework.get_layout_class, name="model", default=None, index=ClassIndex)
def model_predicate(self, model: object) -> type:
    return model if isinstance(model, type) else model.__class__

which simplifies get_layout to

    def get_layout(self, model: object) -> Layout:
        """
        Get the registered layout for a model instance.
        """
        layout_class = self.app.get_layout_class(model)

        if layout_class is None:
            layout_class = DefaultLayout

        return layout_class(model, self)

You could also register a predicate_fallback, so get_layout_class returns DefaultLayout if the predicate doesn't match:

@OrgApp.predicate_fallback(OrgApp.get_view, model_predicate)
def model_not_found(self, model: object) -> type[Layout]:
    return DefaultLayout

which simplifies get_layout to

    def get_layout(self, model: object) -> Layout:
        """
        Get the registered layout for a model instance.
        """
        layout_class = self.app.get_layout_class(model)
        assert layout_class is not None
        return layout_class(model, self)

and lets you override the fallback in TownApp with its own DefaultLayout.

You then also no longer need a registry for the LayoutAction, you instead register with the dispatch method:

    def perform(self, layout: type[Layout], app_class: type[Framework]) -> None:
        app_class.get_layout_class.register(layout, model=self.model)

@Tschuppi81
Copy link
Contributor Author

Thank your for your feedback @BreathingFlesh regarding styling. Latest look

image

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.

4 participants