Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Extend smartlist filters for series metadata #475

Merged
merged 1 commit into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions core/src/db/filter/smart_filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,25 +236,45 @@ pub enum LibrarySmartFilter {
#[serde(untagged)]
#[prisma_table("series_metadata")]
pub enum SeriesMetadataSmartFilter {
#[is_optional]
AgeRating {
age_rating: i32,
},
MetaType {
meta_type: String,
},
#[is_optional]
Title {
title: String,
},
#[is_optional]
Summary {
summary: String,
},
#[is_optional]
Publisher {
publisher: String,
},
#[is_optional]
Status {
status: String,
Imprint {
imprint: String,
},
#[is_optional]
AgeRating {
age_rating: i32,
ComicId {
comicid: i32,
},
#[is_optional]
BookType {
booktype: String,
},
#[is_optional]
Volume {
volume: i32,
},
#[is_optional]
Status {
status: String,
},
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Type, ToSchema)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,40 @@ describe('schema', () => {
value: Object.values(filter)[0],
})
}
})

it('should convert smart filter with series meta into form filter', () => {
for (const filter of stringFilters) {
expect(
intoFormFilter({
series: {
metadata: {
title: filter,
},
},
} satisfies MediaSmartFilter),
).toEqual({
field: 'title',
operation: Object.keys(filter)[0],
source: 'series_meta',
value: Object.values(filter)[0],
})
}

// TODO: support series metadata
// TODO: add numeric filters for series?
for (const filter of numericFilters) {
const operation = 'from' in filter ? 'range' : Object.keys(filter)[0]
const value = 'from' in filter ? filter : Object.values(filter)[0]
expect(
intoFormFilter({
series: { metadata: { age_rating: filter } },
} satisfies MediaSmartFilter),
).toEqual({
field: 'age_rating',
operation,
source: 'series_meta',
value,
})
}
})

it('should convert smart filter with library into form filter', () => {
Expand Down Expand Up @@ -304,6 +335,68 @@ describe('schema', () => {
})
})

it('should convert smart filter form with series meta into API filter', () => {
// String filter
expect(
intoAPIFilter({
field: 'title',
operation: 'any',
source: 'series_meta',
value: ['foo', 'shmoo'],
}),
).toEqual({
series: {
metadata: {
title: {
any: ['foo', 'shmoo'],
},
},
},
})

// Numeric filter (basic)
expect(
intoAPIFilter({
field: 'age_rating',
operation: 'gte',
source: 'series_meta',
value: 42,
}),
).toEqual({
series: {
metadata: {
age_rating: {
gte: 42,
},
},
},
})

// Numeric filter (complex)
expect(
intoAPIFilter({
field: 'age_rating',
operation: 'range',
source: 'series_meta',
value: {
from: 42,
inclusive: true,
to: 69,
},
}),
).toEqual({
series: {
metadata: {
age_rating: {
from: 42,
inclusive: true,
to: 69,
},
},
},
})
})

it('should convert smart filter form with library into API filter', () => {
// String filter
expect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ export function FieldSelector({ idx }: Props) {
{t(getSourceKey('series', 'label'))}
<ArrowRight className="ml-2 h-4 w-4 text-foreground-muted" />
</Command.Item>

<Command.Item
onSelect={() => setSource('series_meta')}
className="flex items-center justify-between"
>
{t(getSourceKey('series_meta', 'label'))}
<ArrowRight className="ml-2 h-4 w-4 text-foreground-muted" />
</Command.Item>

<Command.Item
onSelect={() => setSource('library')}
className="flex items-center justify-between"
Expand Down Expand Up @@ -189,6 +198,18 @@ const sourceOptions: Record<FilterSource, { value: string }[]> = {
],
library: [{ value: 'name' }, { value: 'path' }],
series: [{ value: 'name' }, { value: 'path' }],
series_meta: [
{ value: 'age_rating' },
{ value: 'meta_type' },
{ value: 'title' },
{ value: 'summary' },
{ value: 'publisher' },
{ value: 'imprint' },
{ value: 'comicid' },
{ value: 'booktype' },
{ value: 'status' },
{ value: 'volume' },
],
}

// TODO: series_meta: [meta_type, publisher, status, age_rating, volume]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export default function OperatorSelect({ idx }: Props) {
(field) => isNumberField(field) || isDateField(field),
() => ['gt', 'gte', 'lt', 'lte', 'not', 'equals', 'range'] as NumberOperation[],
)
.otherwise(() => [] as Operation[]),
.otherwise(() => ['not', 'equals'] as Operation[]),
[fieldDef],
)

Expand All @@ -79,7 +79,7 @@ export default function OperatorSelect({ idx }: Props) {
},
]
: []),
]
].filter(({ operators }) => operators.length)
}, [operators, fieldDef])

useEffect(() => {
Expand Down
43 changes: 40 additions & 3 deletions packages/browser/src/components/smartList/createOrUpdate/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
LibrarySmartFilter,
MediaMetadataSmartFilter,
MediaSmartFilter,
SeriesMetadataSmartFilter,
SeriesSmartFilter,
SmartFilter,
SmartList,
Expand Down Expand Up @@ -56,6 +57,10 @@ export const stringField = z.enum([
'links',
'characters',
'teams',
'comicid',
'booktype',
'status',
'meta_type',
])
export type StringField = z.infer<typeof stringField>
export const isStringField = (field: string): field is StringField =>
Expand All @@ -69,6 +74,7 @@ export const numberField = z.enum([
'pages',
'page_count',
'size',
'volume',
])
export type NumberField = z.infer<typeof numberField>
export const isNumberField = (field: string): field is NumberField =>
Expand All @@ -82,7 +88,7 @@ export const filter = z
.object({
field: z.string(),
operation,
source: z.enum(['book', 'book_meta', 'series', 'library']),
source: z.enum(['book', 'book_meta', 'series', 'series_meta', 'library']),
value: z.union([
z.string(),
z.string().array(),
Expand Down Expand Up @@ -129,6 +135,13 @@ export const intoAPIFilter = (input: z.infer<typeof filter>): MediaSmartFilter =
[input.field]: fieldValue,
} as SeriesSmartFilter,
}))
.with('series_meta', () => ({
series: {
metadata: {
[input.field]: fieldValue,
},
} as SeriesSmartFilter,
}))
.with('library', () => ({
series: {
library: {
Expand All @@ -152,6 +165,10 @@ export const intoFormFilter = (input: MediaSmartFilter): z.infer<typeof filter>
(x) => 'series' in x && 'library' in x.series,
() => 'library' as const,
)
.when(
(x) => 'series' in x && 'metadata' in x.series,
() => 'series_meta' as const,
)
.when(
(x) => 'series' in x,
() => 'series' as const,
Expand All @@ -165,6 +182,13 @@ export const intoFormFilter = (input: MediaSmartFilter): z.infer<typeof filter>
() => Object.keys((input as { metadata: MediaMetadataSmartFilter }).metadata)[0],
)
.with('series', () => Object.keys((input as { series: SeriesSmartFilter }).series)[0])
.with(
'series_meta',
() =>
Object.keys(
(input as { series: { metadata: SeriesMetadataSmartFilter } }).series.metadata,
)[0],
)
.with(
'library',
() => Object.keys((input as { series: { library: LibrarySmartFilter } }).series.library)[0],
Expand Down Expand Up @@ -217,6 +241,21 @@ export const intoFormFilter = (input: MediaSmartFilter): z.infer<typeof filter>
value,
}
})
.with('series_meta', () => {
const castedInput = input as { series: { metadata: SeriesMetadataSmartFilter } } // { series: { metadata: { [field]: { [operation]: value } } } }
const filterValue = getProperty(castedInput.series.metadata, field || '') // { [operation]: value }
const operation = 'from' in filterValue ? 'range' : Object.keys(filterValue || {})[0]
const value = match(operation)
.with('range', () => filterValue)
.otherwise(() => getProperty(filterValue, operation || ''))

return {
field,
operation,
source,
value,
}
})
.with('library', () => {
const castedInput = input as { series: { library: LibrarySmartFilter } } // { series: { library: { [field]: { [operation]: value } } } }
const filterValue = getProperty(castedInput.series.library, field || '') // { [operation]: value }
Expand Down Expand Up @@ -361,8 +400,6 @@ export const intoAPI = ({

export const intoAPIFilters = ({
groups,
joiner,
}: Pick<SmartListFormSchema, 'filters'>['filters']): SmartFilter<MediaSmartFilter> => ({
groups: groups.map(intoAPIGroup),
joiner: joiner.toUpperCase() as 'AND' | 'OR',
})
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ export default function FilterConfigJSON() {
const [joiner] = form.watch(['filters.joiner'])

const groups = useMemo(() => (filters?.groups ?? []) as FilterGroupSchema[], [filters?.groups])
// FIXME: this errors lol
const apiFilters = useMemo(
() =>
intoAPIFilters({
groups,
joiner: joiner ?? 'AND',
joiner,
}),
[groups, joiner],
)
Expand Down
4 changes: 2 additions & 2 deletions packages/components/src/select/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ export function ComboBox({
<Button
className="h-[unset] shrink-0 text-ellipsis text-wrap break-all text-brand"
onClick={() => {
onAddOption({ label: filter, value: filter.toLowerCase() })
handleChange(filter.toLowerCase())
onAddOption({ label: filter, value: filter })
handleChange(filter)
setFilter('')
}}
>
Expand Down
7 changes: 6 additions & 1 deletion packages/i18n/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -734,10 +734,15 @@
"series_meta": {
"label": "Series Meta",
"attributes": {
"age_rating": "Age rating",
"title": "Title",
"summary": "Summary",
"meta_type": "Type",
"publisher": "Publisher",
"imprint": "Imprint",
"comicid": "Comic ID",
"booktype": "Book type",
"status": "Status",
"age_rating": "Age rating",
"volume": "Volume"
}
},
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/src/types/generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export type MediaSmartFilter = { name: Filter<string> } | { size: Filter<number>

export type MediaMetadataSmartFilter = { publisher: Filter<string> } | { genre: Filter<string> } | { characters: Filter<string> } | { colorists: Filter<string> } | { writers: Filter<string> } | { pencillers: Filter<string> } | { letterers: Filter<string> } | { inkers: Filter<string> } | { editors: Filter<string> } | { age_rating: Filter<number> } | { year: Filter<number> } | { month: Filter<number> } | { day: Filter<number> }

export type SeriesMetadataSmartFilter = { meta_type: Filter<string> } | { publisher: Filter<string> } | { status: Filter<string> } | { age_rating: Filter<number> } | { volume: Filter<number> }
export type SeriesMetadataSmartFilter = { age_rating: Filter<number> } | { meta_type: Filter<string> } | { title: Filter<string> } | { summary: Filter<string> } | { publisher: Filter<string> } | { imprint: Filter<string> } | { comicid: Filter<number> } | { booktype: Filter<string> } | { volume: Filter<number> } | { status: Filter<string> }

export type SeriesSmartFilter = { name: Filter<string> } | { path: Filter<string> } | { metadata: SeriesMetadataSmartFilter } | { library: LibrarySmartFilter }

Expand Down
Loading