This commit is contained in:
Stash-KennyG 2026-05-08 19:42:44 -04:00 committed by GitHub
commit 4b8b01d657
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 119 additions and 34 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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<GQL.FindGroupsQueryResult>[];
}
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<IGroupSubGroupsPanel> =
PatchComponent(
"GroupSubGroupsPanel",
@ -163,7 +148,6 @@ export const GroupSubGroupsPanel: React.FC<IGroupSubGroupsPanel> =
<>
{modal}
<FilteredGroupList
defaultFilter={defaultFilter}
filterHook={filterHook}
alterQuery={active}
fromGroupId={group.id}

View file

@ -147,7 +147,6 @@ const SidebarContent: React.FC<{
interface IGroupListContext {
filterHook?: (filter: ListFilterModel) => 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}
/>
<FilterTags
@ -455,10 +446,6 @@ export const FilteredGroupList = PatchComponent(
</>
);
if (!withSidebar) {
return content;
}
return (
<div
className={cx("item-list-container group-list", {

View file

@ -99,6 +99,10 @@ export const FilteredListToolbar: React.FC<IFilteredListToolbar> = ({
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<IFilteredListToolbar> = ({
filter={filter}
onSetFilter={setFilter}
view={view}
menuPortalTarget={menuPortalTarget}
/>
<FilterButton
onClick={() => showEditFilter()}

View file

@ -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}",

View file

@ -28,6 +28,7 @@ const sortByOptions = [
"duration",
"rating",
"tag_count",
"sub_group_description",
"sub_group_order",
]
.map(ListFilterOptions.createSortBy)