Optimize Tag List Page Performance (#6398)

This commit is contained in:
CJ
2025-12-10 20:59:19 -06:00
committed by GitHub
parent ebfe5c4b5c
commit f1e54bfc73
8 changed files with 77 additions and 19 deletions

View File

@@ -57,3 +57,31 @@ fragment SelectTagData on Tag {
updated_at
}
}
# Optimized fragment for tag list page - excludes expensive recursive *_count_all fields
fragment TagListData on Tag {
id
name
sort_name
description
aliases
ignore_auto_tag
favorite
image_path
# Direct counts only - no recursive depth queries
scene_count
scene_marker_count
image_count
gallery_count
performer_count
studio_count
group_count
parents {
...SlimTagData
}
children {
...SlimTagData
}
}

View File

@@ -25,3 +25,13 @@ query FindTagsForSelect(
}
}
}
# Optimized query for tag list page - uses TagListData fragment without recursive counts
query FindTagsForList($filter: FindFilterType, $tag_filter: TagFilterType) {
findTags(filter: $filter, tag_filter: $tag_filter) {
count
tags {
...TagListData
}
}
}

View File

@@ -55,7 +55,7 @@ function Tags(props: {
}
interface IListOperationProps {
selected: GQL.TagDataFragment[];
selected: (GQL.TagDataFragment | GQL.TagListDataFragment)[];
onClose: (applied: boolean) => void;
}
@@ -134,7 +134,7 @@ export const EditTagsDialog: React.FC<IListOperationProps> = (
let updateChildTagIds: string[] = [];
let first = true;
state.forEach((tag: GQL.TagDataFragment) => {
state.forEach((tag: GQL.TagDataFragment | GQL.TagListDataFragment) => {
getAggregateStateObject(updateState, tag, tagFields, first);
const thisParents = (tag.parents ?? []).map((t) => t.id).sort();

View File

@@ -14,7 +14,7 @@ import cx from "classnames";
import { useTagUpdate } from "src/core/StashService";
interface IProps {
tag: GQL.TagDataFragment;
tag: GQL.TagDataFragment | GQL.TagListDataFragment;
cardWidth?: number;
zoomIndex: number;
selecting?: boolean;

View File

@@ -7,7 +7,7 @@ import {
import { TagCard } from "./TagCard";
interface ITagCardGrid {
tags: GQL.TagDataFragment[];
tags: (GQL.TagDataFragment | GQL.TagListDataFragment)[];
selectedIds: Set<string>;
zoomIndex: number;
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;

View File

@@ -8,9 +8,9 @@ import { Button } from "react-bootstrap";
import { Link, useHistory } from "react-router-dom";
import * as GQL from "src/core/generated-graphql";
import {
queryFindTags,
queryFindTagsForList,
mutateMetadataAutoTag,
useFindTags,
useFindTagsForList,
useTagDestroy,
useTagsDestroy,
} from "src/core/StashService";
@@ -27,11 +27,11 @@ import { TagCardGrid } from "./TagCardGrid";
import { EditTagsDialog } from "./EditTagsDialog";
import { View } from "../List/views";
function getItems(result: GQL.FindTagsQueryResult) {
function getItems(result: GQL.FindTagsForListQueryResult) {
return result?.data?.findTags?.tags ?? [];
}
function getCount(result: GQL.FindTagsQueryResult) {
function getCount(result: GQL.FindTagsForListQueryResult) {
return result?.data?.findTags?.count ?? 0;
}
@@ -43,7 +43,7 @@ interface ITagList {
export const TagList: React.FC<ITagList> = ({ filterHook, alterQuery }) => {
const Toast = useToast();
const [deletingTag, setDeletingTag] =
useState<Partial<GQL.TagDataFragment> | null>(null);
useState<Partial<GQL.TagListDataFragment> | null>(null);
const filterMode = GQL.FilterMode.Tags;
const view = View.Tags;
@@ -79,7 +79,7 @@ export const TagList: React.FC<ITagList> = ({ filterHook, alterQuery }) => {
];
function addKeybinds(
result: GQL.FindTagsQueryResult,
result: GQL.FindTagsForListQueryResult,
filter: ListFilterModel
) {
Mousetrap.bind("p r", () => {
@@ -92,7 +92,7 @@ export const TagList: React.FC<ITagList> = ({ filterHook, alterQuery }) => {
}
async function viewRandom(
result: GQL.FindTagsQueryResult,
result: GQL.FindTagsForListQueryResult,
filter: ListFilterModel
) {
// query for a random tag
@@ -103,7 +103,7 @@ export const TagList: React.FC<ITagList> = ({ filterHook, alterQuery }) => {
const filterCopy = cloneDeep(filter);
filterCopy.itemsPerPage = 1;
filterCopy.currentPage = index + 1;
const singleResult = await queryFindTags(filterCopy);
const singleResult = await queryFindTagsForList(filterCopy);
if (singleResult.data.findTags.tags.length === 1) {
const { id } = singleResult.data.findTags.tags[0];
// navigate to the tag page
@@ -122,7 +122,7 @@ export const TagList: React.FC<ITagList> = ({ filterHook, alterQuery }) => {
setIsExportDialogOpen(true);
}
async function onAutoTag(tag: GQL.TagDataFragment) {
async function onAutoTag(tag: GQL.TagListDataFragment) {
if (!tag) return;
try {
await mutateMetadataAutoTag({ tags: [tag.id] });
@@ -139,7 +139,7 @@ export const TagList: React.FC<ITagList> = ({ filterHook, alterQuery }) => {
children: deletingTag?.children ?? [],
};
await deleteTag();
tagRelationHook(deletingTag as GQL.TagDataFragment, oldRelations, {
tagRelationHook(deletingTag as GQL.TagListDataFragment, oldRelations, {
parents: [],
children: [],
});
@@ -160,7 +160,7 @@ export const TagList: React.FC<ITagList> = ({ filterHook, alterQuery }) => {
}
function renderContent(
result: GQL.FindTagsQueryResult,
result: GQL.FindTagsForListQueryResult,
filter: ListFilterModel,
selectedIds: Set<string>,
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void
@@ -324,14 +324,14 @@ export const TagList: React.FC<ITagList> = ({ filterHook, alterQuery }) => {
}
function renderEditDialog(
selectedTags: GQL.TagDataFragment[],
selectedTags: GQL.TagListDataFragment[],
onClose: (confirmed: boolean) => void
) {
return <EditTagsDialog selected={selectedTags} onClose={onClose} />;
}
function renderDeleteDialog(
selectedTags: GQL.TagDataFragment[],
selectedTags: GQL.TagListDataFragment[],
onClose: (confirmed: boolean) => void
) {
return (
@@ -357,7 +357,7 @@ export const TagList: React.FC<ITagList> = ({ filterHook, alterQuery }) => {
return (
<ItemListContext
filterMode={filterMode}
useResult={useFindTags}
useResult={useFindTagsForList}
getItems={getItems}
getCount={getCount}
alterQuery={alterQuery}

View File

@@ -429,6 +429,16 @@ export const useFindTags = (filter?: ListFilterModel) =>
},
});
// Optimized query for tag list page - excludes expensive recursive *_count_all fields
export const useFindTagsForList = (filter?: ListFilterModel) =>
GQL.useFindTagsForListQuery({
skip: filter === undefined,
variables: {
filter: filter?.makeFindFilter(),
tag_filter: filter?.makeFilter(),
},
});
export const queryFindTags = (filter: ListFilterModel) =>
client.query<GQL.FindTagsQuery>({
query: GQL.FindTagsDocument,
@@ -438,6 +448,16 @@ export const queryFindTags = (filter: ListFilterModel) =>
},
});
// Optimized query for tag list page
export const queryFindTagsForList = (filter: ListFilterModel) =>
client.query<GQL.FindTagsForListQuery>({
query: GQL.FindTagsForListDocument,
variables: {
filter: filter.makeFindFilter(),
tag_filter: filter.makeFilter(),
},
});
export const queryFindTagsByIDForSelect = (tagIDs: string[]) =>
client.query<GQL.FindTagsForSelectQuery>({
query: GQL.FindTagsForSelectDocument,

View File

@@ -58,7 +58,7 @@ interface ITagRelationTuple {
}
export const tagRelationHook = (
tag: GQL.SlimTagDataFragment | GQL.TagDataFragment,
tag: GQL.SlimTagDataFragment | GQL.TagDataFragment | GQL.TagListDataFragment,
old: ITagRelationTuple,
updated: ITagRelationTuple
) => {