diff --git a/pkg/sqlite/group.go b/pkg/sqlite/group.go index 13a6905a5..666ff2e84 100644 --- a/pkg/sqlite/group.go +++ b/pkg/sqlite/group.go @@ -500,6 +500,7 @@ var groupSortOptions = sortOptions{ "rating", "scenes_count", "o_counter", + "sub_group_description", "sub_group_order", "tag_count", "updated_at", @@ -532,6 +533,14 @@ func (qb *GroupStore) setGroupSort(query *queryBuilder, findFilter *models.FindF query.joinSort(groupRelationsTable, "", "groups.id = groups_relations.sub_id") query.sortAndPagination += getSort("order_index", direction, groupRelationsTable) } + case "sub_group_description": + // as above, we need to handle parent groups differently here + if query.hasJoin("groups_parents") { + query.sortAndPagination += getSort("description", direction, "groups_parents") + } else { + query.joinSort(groupRelationsTable, "", "groups.id = groups_relations.sub_id") + query.sortAndPagination += getSort("description", direction, groupRelationsTable) + } case "tag_count": query.sortAndPagination += getCountSort(groupTable, groupsTagsTable, groupIDColumn, direction) case "scenes_count": // generic getSort won't work for this diff --git a/pkg/sqlite/group_test.go b/pkg/sqlite/group_test.go index 22b551e02..dc47d6e0b 100644 --- a/pkg/sqlite/group_test.go +++ b/pkg/sqlite/group_test.go @@ -1124,6 +1124,90 @@ func TestGroupQuerySortOrderIndex(t *testing.T) { }) } +func TestGroupQuerySortSubGroupDescription(t *testing.T) { + runWithRollbackTxn(t, "sort subgroup description", func(t *testing.T, ctx context.Context) { + assert := assert.New(t) + + cEmpty := models.Group{Name: "sort-desc-child-empty"} + c01 := models.Group{Name: "sort-desc-child-01"} + c2 := models.Group{Name: "sort-desc-child-2"} + c10 := models.Group{Name: "sort-desc-child-10"} + assert.NoError(db.Group.Create(ctx, &cEmpty)) + assert.NoError(db.Group.Create(ctx, &c01)) + assert.NoError(db.Group.Create(ctx, &c2)) + assert.NoError(db.Group.Create(ctx, &c10)) + + parent := models.Group{ + Name: "sort-desc-parent", + SubGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{ + {GroupID: cEmpty.ID, Description: ""}, + {GroupID: c10.ID, Description: "10"}, + {GroupID: c2.ID, Description: "2"}, + {GroupID: c01.ID, Description: "01"}, + }), + } + assert.NoError(db.Group.Create(ctx, &parent)) + + sortKey := "sub_group_description" + dirAsc := models.SortDirectionEnumAsc + findFilter := models.FindFilterType{ + Sort: &sortKey, + Direction: &dirAsc, + } + groupFilter := models.GroupFilterType{ + ContainingGroups: &models.HierarchicalMultiCriterionInput{ + Value: []string{strconv.Itoa(parent.ID)}, + Modifier: models.CriterionModifierIncludes, + }, + } + + groups, _, err := db.Group.Query(ctx, &groupFilter, &findFilter) + assert.NoError(err) + assert.Len(groups, 4) + assert.Equal(cEmpty.ID, groups[0].ID) + assert.Equal(c01.ID, groups[1].ID) + assert.Equal(c2.ID, groups[2].ID) + assert.Equal(c10.ID, groups[3].ID) + + dirDesc := models.SortDirectionEnumDesc + findFilter.Direction = &dirDesc + groups, _, err = db.Group.Query(ctx, &groupFilter, &findFilter) + assert.NoError(err) + assert.Len(groups, 4) + assert.Equal(c10.ID, groups[0].ID) + assert.Equal(c2.ID, groups[1].ID) + assert.Equal(c01.ID, groups[2].ID) + assert.Equal(cEmpty.ID, groups[3].ID) + + // Exercise the non-groups_parents code path by filtering on name only. + nameCriterion := models.StringCriterionInput{ + Value: "sort-desc-child-", + Modifier: models.CriterionModifierIncludes, + } + nameFilter := models.GroupFilterType{ + Name: &nameCriterion, + } + + findFilter.Direction = &dirAsc + groups, _, err = db.Group.Query(ctx, &nameFilter, &findFilter) + assert.NoError(err) + assert.Len(groups, 4) + assert.Equal(cEmpty.ID, groups[0].ID) + assert.Equal(c01.ID, groups[1].ID) + assert.Equal(c2.ID, groups[2].ID) + assert.Equal(c10.ID, groups[3].ID) + + findFilter.Direction = &dirDesc + groups, _, err = db.Group.Query(ctx, &nameFilter, &findFilter) + assert.NoError(err) + assert.Len(groups, 4) + assert.Equal(c10.ID, groups[0].ID) + assert.Equal(c2.ID, groups[1].ID) + assert.Equal(c01.ID, groups[2].ID) + assert.Equal(cEmpty.ID, groups[3].ID) + }) +} + func TestGroupUpdateFrontImage(t *testing.T) { if err := withRollbackTxn(func(ctx context.Context) error { qb := db.Group diff --git a/pkg/sqlite/sql.go b/pkg/sqlite/sql.go index 87376c2c1..13156b772 100644 --- a/pkg/sqlite/sql.go +++ b/pkg/sqlite/sql.go @@ -88,6 +88,23 @@ func getSortDirection(direction string) string { return direction } } + +func isNaturalSort(sort string) bool { + switch sort { + case "name", "title", "description": + return true + default: + return false + } +} + +func isCoalesceSort(column, sort string) string { + if sort == "description" { + return coalesce(column) + } + return column +} + func getSort(sort string, direction string, tableName string) string { direction = getSortDirection(direction) @@ -115,11 +132,8 @@ func getSort(sort string, direction string, tableName string) string { if strings.Contains(sort, ".") { colName = sort } - if strings.Compare(sort, "name") == 0 { - return " ORDER BY " + colName + " COLLATE NATURAL_CI " + direction - } - if strings.Compare(sort, "title") == 0 { - return " ORDER BY " + colName + " COLLATE NATURAL_CI " + direction + if isNaturalSort(sort) { + return " ORDER BY " + isCoalesceSort(colName, sort) + " COLLATE NATURAL_CI " + direction } return " ORDER BY " + colName + " " + direction diff --git a/ui/v2.5/src/components/Groups/GroupDetails/GroupSubGroupsPanel.tsx b/ui/v2.5/src/components/Groups/GroupDetails/GroupSubGroupsPanel.tsx index 6a11f7004..5dfba0569 100644 --- a/ui/v2.5/src/components/Groups/GroupDetails/GroupSubGroupsPanel.tsx +++ b/ui/v2.5/src/components/Groups/GroupDetails/GroupSubGroupsPanel.tsx @@ -54,9 +54,6 @@ const useContainingGroupFilterHook = ( filter.criteria.push(groupCriterion); } - filter.sortBy = "sub_group_order"; - filter.sortDirection = GQL.SortDirectionEnum.Asc; - return filter; }; }; @@ -67,18 +64,6 @@ interface IGroupSubGroupsPanel { extraOperations?: IItemListOperation[]; } -const defaultFilter = (() => { - const sortBy = "sub_group_order"; - const ret = new ListFilterModel(GQL.FilterMode.Groups, undefined, { - defaultSortBy: sortBy, - }); - - // unset the sort by so that its not included in the URL - ret.sortBy = undefined; - - return ret; -})(); - export const GroupSubGroupsPanel: React.FC = PatchComponent( "GroupSubGroupsPanel", @@ -163,7 +148,6 @@ export const GroupSubGroupsPanel: React.FC = <> {modal} ListFilterModel; - defaultFilter?: ListFilterModel; view?: View; alterQuery?: boolean; } @@ -210,13 +209,8 @@ export const FilteredGroupList = PatchComponent( onMove, fromGroupId, otherOperations: providedOperations = [], - defaultFilter, } = props; - const withSidebar = view !== View.GroupSubGroups; - const filterable = view !== View.GroupSubGroups; - const sortable = view !== View.GroupSubGroups; - // States const { showSidebar, @@ -230,7 +224,6 @@ export const FilteredGroupList = PatchComponent( useFilteredItemList({ filterStateProps: { filterMode: GQL.FilterMode.Groups, - defaultFilter, view, useURL: alterQuery, }, @@ -402,8 +395,6 @@ export const FilteredGroupList = PatchComponent( operationComponent={operations} view={view} zoomable - filterable={filterable} - sortable={sortable} /> ); - if (!withSidebar) { - return content; - } - return (
= ({ sortable = true, }) => { const filterOptions = filter.options; + // Something in the popper layout for groups.sub-groups tab to double calculates the offset + // causing the dropdown to be misaligned. Portal to document.body to fix this. + const menuPortalTarget = + typeof document !== "undefined" ? document.body : undefined; const { setDisplayMode, setZoom } = useFilterOperations({ filter, setFilter, @@ -142,6 +146,7 @@ export const FilteredListToolbar: React.FC = ({ filter={filter} onSetFilter={setFilter} view={view} + menuPortalTarget={menuPortalTarget} /> showEditFilter()} diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index 4974c06ca..25dbb9342 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -1611,6 +1611,7 @@ "sub_group_count": "Sub-Group Count", "sub_group_of": "Sub-group of {parent}", "sub_group_order": "Sub-Group Order", + "sub_group_description": "Sub-Group Description", "sub_groups": "Sub-Groups", "sub_tag_count": "Sub-Tag Count", "sub_tag_of": "Sub-tag of {parent}", diff --git a/ui/v2.5/src/models/list-filter/groups.ts b/ui/v2.5/src/models/list-filter/groups.ts index 9c5b3f2d4..899d4d0f9 100644 --- a/ui/v2.5/src/models/list-filter/groups.ts +++ b/ui/v2.5/src/models/list-filter/groups.ts @@ -28,6 +28,7 @@ const sortByOptions = [ "duration", "rating", "tag_count", + "sub_group_description", "sub_group_order", ] .map(ListFilterOptions.createSortBy)