Skip to content

🚧 Proof of concept for arbitrary media groupings#976

Draft
cparadis777 wants to merge 2 commits intostumpapp:experimentalfrom
cparadis777:725-arbitrary-groupings
Draft

🚧 Proof of concept for arbitrary media groupings#976
cparadis777 wants to merge 2 commits intostumpapp:experimentalfrom
cparadis777:725-arbitrary-groupings

Conversation

@cparadis777
Copy link
Copy Markdown

An attemp to implement arbitrary groupings for media records. Related to #725.

Currently, allows to group by media field and mediaMetadata field.
This is a very rough PoC, everything can be changed.
I aimed to make it as transparent to other features and the existing codebase.
Example query to get media grouped by writer, then publisher.

query {
  groupedMedia(
    groupBy: {
      levels: [
        { mediaMetadata: { field: WRITERS } },
        { mediaMetadata: { field: PUBLISHER } }
      ]
    }
  ) {
    ... on SmartListGrouped {
      items {
        entity { ... on GenericGroupingValue { key } }
        subgroups { 
          entity { ... on GenericGroupingValue { key } }
          books { 
            id
            name
            pages
          }
        }
      }
    }
  }
}

Copy link
Copy Markdown
Collaborator

@aaronleopold aaronleopold left a comment

Choose a reason for hiding this comment

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

Alright I finally had a moment to take a look! I had a few comments, but I wasn't able to finish reviewing everything (I had to stop at the macro). I'll try to take another look sometime during the week 🙂

#[graphql(default)] filter: MediaFilterInput,
#[graphql(default)] group_by: GroupingPathInput,
#[graphql(default)] limit: Option<u64>,
) -> Result<crate::object::smart_list_item::SmartListItems> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think I understand why we are re-using this object for this purpose, however semantically I don't love it. If I were to make a grouped query totally detached from smart lists, it feels strange to have to declare an inline fragment named for smart lists (i.e., SmartListGrouped).

We don't need total duplication, so perhaps renaming SmartListGrouped to something generic? That way it doesn't feel as awkward, maybe?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I was undecided between creating a completely new query or trying to add groupings to the existing ones in a transparent manner.
Smart lists are still a bit nebulous to me, but I understood them as a generic grouping of media that was being reused across the code base.
I'm open to anything you feel is more idiomatic and fits better in the overall app!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Smart lists are still a bit nebulous to me, but I understood them as a generic grouping of media that was being reused across the code base

I don't think they were being reused, but if they were that should be cleaned up. Smart lists are a feature where you can configure more complex filters and save them as a "smart list" which effectively just takes that saved filter and returns the matching books (ungrouped, or grouped by library/series).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Then it makes way more sense to build something standalone. But this might be going into a more architectural direction about where and how we want to use groups. I'm trying to figure a way to not create a whole bunch of boilerplate code for each model and query. I'll have to admit I've barely worked with GraphQL, so I'm out of my depths on this side.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

But this might be going into a more architectural direction about where and how we want to use groups. I'm trying to figure a way to not create a whole bunch of boilerplate code for each model and query

Yeah that's totally fair! In my head it could be as simple as just generalizing the structs/enums (e.g., renaming to GroupedBooks or something) and using that in smart lists in addition to here.

Comment on lines +158 to +165
books: if is_last_level {
updated_subgroups
.iter()
.flat_map(|sg| sg.books.clone())
.collect()
} else {
vec![]
},
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This one took me a second to conceptualize, but I think I understand why it was done this way. I can't think of an alternative that would definitely be better, but I'd like to consider it a bit more.

The part I'm not 100% on is that implicit bit where books will only be populated at the last level, I'd rather lean into unioned types to make it more explicit. I could be over thinking it too, though.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I guess it depends on how we want to handle books that are missing the attribute we're grouping on?
We'd either create an empty group and put them there, ensuring all books in the query are grouped on the same terminal level or we leave them higher up the grouping chain?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ohhh yeah great point, I missed that in my initial review. Supporting multiple levels of grouping does also complicate how to handle it. I don't have an immediate answer, unfortunately. I think as a POC this is okay as-is, I just think implicit things are harder to grasp at a glance sometimes.

A related thought that has been bubbling though since looking at this PR: when I eventually expose any kind of unfiltered, grouped functionality on the client we need pagination. Smart lists kinda get away with it (at least temporarily, eventually I have plans to "fix" it) because of the somewhat explicit requirement of filtering. But an unfiltered set of books could produce way too much, there are folks with 100k+ files in their server. Again, it is a really hard problem to solve though since we are mixing SQL and programmatic grouping

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Pagination, that completely slipped my mind too. Again, I don't think it's in scope for the PR, but clearly a problem that will need solving.
It also brings up the question of performance. I'm doing everything in memory, but I wonder if trying to build DB queries and building the groups directly instead of propagation down the groupings tree would be possible. It would probably be more performant, but maybe need some prepared queries or constructing raw SQL instead of using the ORM.

Copy link
Copy Markdown
Collaborator

@aaronleopold aaronleopold Mar 23, 2026

Choose a reason for hiding this comment

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

Again, I don't think it's in scope for the PR, but clearly a problem that will need solving

Yeah, I agree. I understand this is a POC

It also brings up the question of performance. I'm doing everything in memory, but I wonder if trying to build DB queries and building the groups directly instead of propagation down the groupings tree would be possible

I think it should be possible, yeah. The other bit wrt pagination of grouped items is that to have any decent client would mean also collecting the group headers and associated counts ahead of time. Perhaps for a first iteration it might be acceptable to cap the grouping to one level at first until performance is improved.

I honestly don't get much feedback in general for Stump, the recent visibility bump from the Booklore debacle excluded since I expect it to die down a bit, and so this is not something I've personally prioritized solving yet. You're welcome to continue down this route if it is an area of interest, that's what I generally encourage folks to do, but I think once this POC is more generalized away from some of the smart list bits a better immediate use of time could be improving the ordering capabilities which would fix things like this #482 (comment)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I unfortunately won't be able to get to this one today, but I read the doc string and I think the approach outlined there so far is sound.

@cparadis777 cparadis777 force-pushed the 725-arbitrary-groupings branch from 2af428c to 0dce180 Compare March 22, 2026 22:01
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