mirror of https://github.com/stashapp/stash.git
Add studio performer count (#3362)
* Add studio performer count * Add mocks
This commit is contained in:
parent
c52d8c9314
commit
32e8496314
|
@ -19,6 +19,7 @@ fragment StudioData on Studio {
|
|||
scene_count
|
||||
image_count
|
||||
gallery_count
|
||||
performer_count
|
||||
movie_count
|
||||
stash_ids {
|
||||
stash_id
|
||||
|
|
|
@ -12,6 +12,7 @@ type Studio {
|
|||
scene_count: Int # Resolver
|
||||
image_count: Int # Resolver
|
||||
gallery_count: Int # Resolver
|
||||
performer_count: Int # Resolver
|
||||
stash_ids: [StashID!]!
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/stashapp/stash/pkg/gallery"
|
||||
"github.com/stashapp/stash/pkg/image"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/performer"
|
||||
)
|
||||
|
||||
func (r *studioResolver) Name(ctx context.Context, obj *models.Studio) (string, error) {
|
||||
|
@ -93,6 +94,18 @@ func (r *studioResolver) GalleryCount(ctx context.Context, obj *models.Studio) (
|
|||
return &res, nil
|
||||
}
|
||||
|
||||
func (r *studioResolver) PerformerCount(ctx context.Context, obj *models.Studio) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
res, err = performer.CountByStudioID(ctx, r.repository.Performer, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (r *studioResolver) ParentStudio(ctx context.Context, obj *models.Studio) (ret *models.Studio, err error) {
|
||||
if !obj.ParentID.Valid {
|
||||
return nil, nil
|
||||
|
|
|
@ -427,6 +427,27 @@ func (_m *PerformerReaderWriter) Query(ctx context.Context, performerFilter *mod
|
|||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// QueryCount provides a mock function with given fields: ctx, galleryFilter, findFilter
|
||||
func (_m *PerformerReaderWriter) QueryCount(ctx context.Context, galleryFilter *models.PerformerFilterType, findFilter *models.FindFilterType) (int, error) {
|
||||
ret := _m.Called(ctx, galleryFilter, findFilter)
|
||||
|
||||
var r0 int
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.PerformerFilterType, *models.FindFilterType) int); ok {
|
||||
r0 = rf(ctx, galleryFilter, findFilter)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *models.PerformerFilterType, *models.FindFilterType) error); ok {
|
||||
r1 = rf(ctx, galleryFilter, findFilter)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// QueryForAutoTag provides a mock function with given fields: ctx, words
|
||||
func (_m *PerformerReaderWriter) QueryForAutoTag(ctx context.Context, words []string) ([]*models.Performer, error) {
|
||||
ret := _m.Called(ctx, words)
|
||||
|
|
|
@ -160,6 +160,7 @@ type PerformerReader interface {
|
|||
// support the query needed
|
||||
QueryForAutoTag(ctx context.Context, words []string) ([]*Performer, error)
|
||||
Query(ctx context.Context, performerFilter *PerformerFilterType, findFilter *FindFilterType) ([]*Performer, int, error)
|
||||
QueryCount(ctx context.Context, galleryFilter *PerformerFilterType, findFilter *FindFilterType) (int, error)
|
||||
AliasLoader
|
||||
GetImage(ctx context.Context, performerID int) ([]byte, error)
|
||||
StashIDLoader
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package performer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
type Queryer interface {
|
||||
Query(ctx context.Context, performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) ([]*models.Performer, int, error)
|
||||
}
|
||||
|
||||
type CountQueryer interface {
|
||||
QueryCount(ctx context.Context, galleryFilter *models.PerformerFilterType, findFilter *models.FindFilterType) (int, error)
|
||||
}
|
||||
|
||||
func CountByStudioID(ctx context.Context, r CountQueryer, id int) (int, error) {
|
||||
filter := &models.PerformerFilterType{
|
||||
Studios: &models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{strconv.Itoa(id)},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
}
|
||||
|
||||
return r.QueryCount(ctx, filter, nil)
|
||||
}
|
|
@ -617,7 +617,7 @@ func (qb *PerformerStore) makeFilter(ctx context.Context, filter *models.Perform
|
|||
return query
|
||||
}
|
||||
|
||||
func (qb *PerformerStore) Query(ctx context.Context, performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) ([]*models.Performer, int, error) {
|
||||
func (qb *PerformerStore) makeQuery(ctx context.Context, performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) (*queryBuilder, error) {
|
||||
if performerFilter == nil {
|
||||
performerFilter = &models.PerformerFilterType{}
|
||||
}
|
||||
|
@ -635,13 +635,23 @@ func (qb *PerformerStore) Query(ctx context.Context, performerFilter *models.Per
|
|||
}
|
||||
|
||||
if err := qb.validateFilter(performerFilter); err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
filter := qb.makeFilter(ctx, performerFilter)
|
||||
|
||||
query.addFilter(filter)
|
||||
|
||||
query.sortAndPagination = qb.getPerformerSort(findFilter) + getPagination(findFilter)
|
||||
|
||||
return &query, nil
|
||||
}
|
||||
|
||||
func (qb *PerformerStore) Query(ctx context.Context, performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) ([]*models.Performer, int, error) {
|
||||
query, err := qb.makeQuery(ctx, performerFilter, findFilter)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
idsResult, countResult, err := query.executeFind(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
@ -655,6 +665,15 @@ func (qb *PerformerStore) Query(ctx context.Context, performerFilter *models.Per
|
|||
return performers, countResult, nil
|
||||
}
|
||||
|
||||
func (qb *PerformerStore) QueryCount(ctx context.Context, performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) (int, error) {
|
||||
query, err := qb.makeQuery(ctx, performerFilter, findFilter)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return query.executeCount(ctx)
|
||||
}
|
||||
|
||||
func performerIsMissingCriterionHandler(qb *PerformerStore, isMissing *string) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if isMissing != nil && *isMissing != "" {
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
faImage,
|
||||
faImages,
|
||||
faPlayCircle,
|
||||
faUser,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import React, { useMemo } from "react";
|
||||
import { Button } from "react-bootstrap";
|
||||
|
@ -13,7 +14,7 @@ import { ConfigurationContext } from "src/hooks/Config";
|
|||
import { TextUtils } from "src/utils";
|
||||
import Icon from "./Icon";
|
||||
|
||||
type PopoverLinkType = "scene" | "image" | "gallery" | "movie";
|
||||
type PopoverLinkType = "scene" | "image" | "gallery" | "movie" | "performer";
|
||||
|
||||
interface IProps {
|
||||
className?: string;
|
||||
|
@ -44,6 +45,8 @@ export const PopoverCountButton: React.FC<IProps> = ({
|
|||
return faImages;
|
||||
case "movie":
|
||||
return faFilm;
|
||||
case "performer":
|
||||
return faUser;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,6 +72,11 @@ export const PopoverCountButton: React.FC<IProps> = ({
|
|||
one: "movie",
|
||||
other: "movies",
|
||||
};
|
||||
case "performer":
|
||||
return {
|
||||
one: "performer",
|
||||
other: "performers",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -116,12 +116,26 @@ export const StudioCard: React.FC<IProps> = ({
|
|||
);
|
||||
}
|
||||
|
||||
function maybeRenderPerformersPopoverButton() {
|
||||
if (!studio.performer_count) return;
|
||||
|
||||
return (
|
||||
<PopoverCountButton
|
||||
className="performer-count"
|
||||
type="performer"
|
||||
count={studio.performer_count}
|
||||
url={NavUtils.makeStudioPerformersUrl(studio)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function maybeRenderPopoverButtonGroup() {
|
||||
if (
|
||||
studio.scene_count ||
|
||||
studio.image_count ||
|
||||
studio.gallery_count ||
|
||||
studio.movie_count
|
||||
studio.movie_count ||
|
||||
studio.performer_count
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
|
@ -131,6 +145,7 @@ export const StudioCard: React.FC<IProps> = ({
|
|||
{maybeRenderMoviesPopoverButton()}
|
||||
{maybeRenderImagesPopoverButton()}
|
||||
{maybeRenderGalleriesPopoverButton()}
|
||||
{maybeRenderPerformersPopoverButton()}
|
||||
</ButtonGroup>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -269,7 +269,15 @@ const StudioPage: React.FC<IProps> = ({ studio }) => {
|
|||
</Tab>
|
||||
<Tab
|
||||
eventKey="performers"
|
||||
title={intl.formatMessage({ id: "performers" })}
|
||||
title={
|
||||
<React.Fragment>
|
||||
{intl.formatMessage({ id: "performers" })}
|
||||
<Counter
|
||||
abbreviateCounter={abbreviateCounter}
|
||||
count={studio.performer_count ?? 0}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
>
|
||||
<StudioPerformersPanel studio={studio} />
|
||||
</Tab>
|
||||
|
|
|
@ -148,6 +148,18 @@ const makeStudioMoviesUrl = (studio: Partial<GQL.StudioDataFragment>) => {
|
|||
return `/movies?${filter.makeQueryParameters()}`;
|
||||
};
|
||||
|
||||
const makeStudioPerformersUrl = (studio: Partial<GQL.StudioDataFragment>) => {
|
||||
if (!studio.id) return "#";
|
||||
const filter = new ListFilterModel(GQL.FilterMode.Performers, undefined);
|
||||
const criterion = new StudiosCriterion();
|
||||
criterion.value = {
|
||||
items: [{ id: studio.id, label: studio.name || `Studio ${studio.id}` }],
|
||||
depth: 0,
|
||||
};
|
||||
filter.criteria.push(criterion);
|
||||
return `/performers?${filter.makeQueryParameters()}`;
|
||||
};
|
||||
|
||||
const makeChildStudiosUrl = (studio: Partial<GQL.StudioDataFragment>) => {
|
||||
if (!studio.id) return "#";
|
||||
const filter = new ListFilterModel(GQL.FilterMode.Studios, undefined);
|
||||
|
@ -307,6 +319,7 @@ export default {
|
|||
makeStudioImagesUrl,
|
||||
makeStudioGalleriesUrl,
|
||||
makeStudioMoviesUrl,
|
||||
makeStudioPerformersUrl,
|
||||
makeParentTagsUrl,
|
||||
makeChildTagsUrl,
|
||||
makeTagSceneMarkersUrl,
|
||||
|
|
Loading…
Reference in New Issue