Use existing formats for saved filters (#5697)

* Use existing formats for saved filters
* Fix date criterion marshalling
This commit is contained in:
WithoutPants 2025-03-04 09:26:46 +11:00 committed by GitHub
parent ea5073fef4
commit fdb2dd9a8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 218 additions and 72 deletions

View file

@ -67,7 +67,7 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
mode: filter.mode,
name,
find_filter: filterCopy.makeFindFilter(),
object_filter: filterCopy.makeFilter(),
object_filter: filterCopy.makeSavedFilter(),
ui_options: filterCopy.makeSavedUIOptions(),
},
},
@ -142,7 +142,7 @@ export const SavedFilterList: React.FC<ISavedFilterListProps> = ({
value: {
mode: filter.mode,
find_filter: filterCopy.makeFindFilter(),
object_filter: filterCopy.makeFilter(),
object_filter: filterCopy.makeSavedFilter(),
ui_options: filterCopy.makeSavedUIOptions(),
},
},

View file

@ -5,9 +5,9 @@ import {
HierarchicalMultiCriterionInput,
IntCriterionInput,
MultiCriterionInput,
DateCriterionInput,
TimestampCriterionInput,
ConfigDataFragment,
DateCriterionInput,
} from "src/core/generated-graphql";
import TextUtils from "src/utils/text";
import {
@ -21,11 +21,13 @@ import {
ITimestampValue,
ILabeledValueListValue,
IPhashDistanceValue,
IRangeValue,
} from "../types";
export type Option = string | number | IOptionType;
export type CriterionValue =
| string
| boolean
| string[]
| ILabeledId[]
| IHierarchicalLabelValue
@ -36,7 +38,7 @@ export type CriterionValue =
| ITimestampValue
| IPhashDistanceValue;
export interface ISavedCriterion<T extends CriterionValue> {
export interface ISavedCriterion<T> {
modifier: CriterionModifier;
value: T | undefined;
}
@ -82,9 +84,15 @@ export abstract class Criterion {
return `${this.criterionOption.type}`;
}
public abstract toJSON(): string;
public abstract toQueryParams(): Record<string, unknown>;
// fromDecodedParams is used to set the criterion from the query string
// i is the decoded parameter object
public abstract fromDecodedParams(i: Record<string, unknown>): void;
public abstract applyToCriterionInput(input: Record<string, unknown>): void;
public abstract applyToSavedCriterion(input: Record<string, unknown>): void;
public abstract setFromSavedCriterion(criterion: unknown): void;
}
@ -164,38 +172,61 @@ export abstract class ModifierCriterion<
);
}
public toJSON() {
let encodedCriterion;
public toQueryParams(): Record<string, unknown> {
let encodedCriterion: Record<string, unknown> = {
type: this.criterionOption.type,
modifier: this.modifier,
};
if (
this.modifier === CriterionModifier.IsNull ||
this.modifier === CriterionModifier.NotNull
this.modifier !== CriterionModifier.IsNull &&
this.modifier !== CriterionModifier.NotNull
) {
encodedCriterion = {
type: this.criterionOption.type,
modifier: this.modifier,
};
} else {
encodedCriterion = {
type: this.criterionOption.type,
value: this.value,
modifier: this.modifier,
};
}
return JSON.stringify(encodedCriterion);
encodedCriterion.value = this.encodeValue();
}
public setFromSavedCriterion(criterion: ISavedCriterion<V>) {
if (criterion.value !== undefined && criterion.value !== null) {
this.value = criterion.value;
return encodedCriterion;
}
this.modifier = criterion.modifier;
protected encodeValue(): unknown {
return this.value;
}
protected decodeValue(v: unknown) {
if (v !== undefined && v !== null) {
this.value = v as V;
}
}
public fromDecodedParams(i: unknown): void {
// use same logic as from saved criterion by default
const c = i as ISavedCriterion<V>;
this.modifier = c.modifier;
this.decodeValue(c.value);
}
public setFromSavedCriterion(criterion: unknown) {
const c = criterion as ISavedCriterion<V>;
if (c.value !== undefined && c.value !== null) {
this.value = c.value;
}
this.modifier = c.modifier;
}
public applyToCriterionInput(input: Record<string, unknown>) {
input[this.criterionOption.type] = this.toCriterionInput();
}
public toCriterionInput(): unknown {
// TODO - saved criterion _should_ be criterion input
// kicking this can down the road a little further
public applyToSavedCriterion(input: Record<string, unknown>): void {
input[this.criterionOption.type] = {
value: this.value,
modifier: this.modifier,
};
}
protected toCriterionInput(): unknown {
return {
value: this.value,
modifier: this.modifier,
@ -755,6 +786,33 @@ export function createMandatoryNumberCriterionOption(
return new MandatoryNumberCriterionOption(messageID ?? value, value);
}
export function encodeRangeValue<V>(
modifier: CriterionModifier,
value: IRangeValue<V>
): unknown {
// only encode value2 if modifier is between/not between
if (
modifier === CriterionModifier.Between ||
modifier === CriterionModifier.NotBetween
) {
return { value: value.value, value2: value.value2 };
}
return { value: value.value };
}
export function decodeRangeValue<V>(v: {
value: V | IRangeValue<V>;
value2?: V;
}): IRangeValue<V> {
// handle backwards compatible value
if (typeof v.value === "object") {
return v.value as IRangeValue<V>;
} else {
return { value: v.value, value2: v.value2 };
}
}
export class NumberCriterion extends ModifierCriterion<INumberValue> {
constructor(type: ModifierCriterionOption) {
super(type, { value: undefined, value2: undefined });
@ -787,6 +845,19 @@ export class NumberCriterion extends ModifierCriterion<INumberValue> {
};
}
public setFromSavedCriterion(c: {
modifier: CriterionModifier;
value: number | INumberValue;
value2?: number;
}) {
super.setFromSavedCriterion(c);
// this.value = decodeRangeValue(c);
}
protected encodeValue(): unknown {
return encodeRangeValue(this.modifier, this.value);
}
protected getLabelValue(_intl: IntlShape) {
const { value, value2 } = this.value;
if (
@ -867,6 +938,19 @@ export class DurationCriterion extends ModifierCriterion<INumberValue> {
};
}
public setFromSavedCriterion(c: {
modifier: CriterionModifier;
value: number | INumberValue;
value2?: number;
}) {
super.setFromSavedCriterion(c);
// this.value = decodeRangeValue(c);
}
protected encodeValue(): unknown {
return encodeRangeValue(this.modifier, this.value);
}
protected getLabelValue(_intl: IntlShape) {
const value = TextUtils.secondsToTimestamp(this.value.value ?? 0);
const value2 = TextUtils.secondsToTimestamp(this.value.value2 ?? 0);
@ -940,17 +1024,23 @@ export class DateCriterion extends ModifierCriterion<IDateValue> {
this.value = { ...this.value };
}
public encodeValue() {
return {
value: this.value.value,
value2: this.value.value2,
};
public setFromSavedCriterion(c: {
modifier: CriterionModifier;
value: string | IDateValue;
value2?: string;
}) {
super.setFromSavedCriterion(c);
// this.value = decodeRangeValue(c);
}
protected encodeValue(): unknown {
return encodeRangeValue(this.modifier, this.value);
}
public toCriterionInput(): DateCriterionInput {
return {
modifier: this.modifier,
value: this.value?.value,
value: this.value?.value ?? "",
value2: this.value?.value2,
};
}
@ -1043,23 +1133,29 @@ export class TimestampCriterion extends ModifierCriterion<ITimestampValue> {
this.value = { ...this.value };
}
public encodeValue() {
return {
value: this.value?.value,
value2: this.value?.value2,
};
}
public toCriterionInput(): TimestampCriterionInput {
return {
modifier: this.modifier,
value: this.transformValueToInput(this.value.value),
value: this.transformValueToInput(this.value.value ?? ""),
value2: this.value.value2
? this.transformValueToInput(this.value.value2)
: null,
};
}
public setFromSavedCriterion(c: {
modifier: CriterionModifier;
value: string | ITimestampValue;
value2?: string;
}) {
this.setFromSavedCriterion(c);
// this.value = decodeRangeValue(c);
}
protected encodeValue(): unknown {
return encodeRangeValue(this.modifier, this.value);
}
protected getLabelValue() {
const { value } = this.value;
return this.modifier === CriterionModifier.Between ||

View file

@ -27,6 +27,10 @@ export class CustomFieldsCriterion extends Criterion {
input.custom_fields = cloneDeep(this.value);
}
public applyToSavedCriterion(input: Record<string, unknown>): void {
input.custom_fields = cloneDeep(this.value);
}
public getLabel(intl: IntlShape): string {
// show first criterion
if (this.value.length === 0) {
@ -91,19 +95,20 @@ export class CustomFieldsCriterion extends Criterion {
);
}
public toJSON(): string {
public toQueryParams(): Record<string, unknown> {
const encodedCriterion = {
type: this.criterionOption.type,
value: this.value,
};
return JSON.stringify(encodedCriterion);
return encodedCriterion;
}
public setFromSavedCriterion(criterion: {
type: string;
value: CustomFieldCriterionInput[];
}): void {
const { value } = criterion;
this.value = cloneDeep(value);
public fromDecodedParams(i: unknown): void {
const criterion = i as { value: CustomFieldCriterionInput[] };
this.value = cloneDeep(criterion.value);
}
public setFromSavedCriterion(input: CustomFieldCriterionInput[]): void {
this.value = cloneDeep(input);
}
}

View file

@ -10,7 +10,11 @@ import {
IntCriterionInput,
} from "src/core/generated-graphql";
import { INumberValue } from "../types";
import { ModifierCriterion, ModifierCriterionOption } from "./criterion";
import {
encodeRangeValue,
ModifierCriterion,
ModifierCriterionOption,
} from "./criterion";
const modifierOptions = [
CriterionModifier.Equals,
@ -72,6 +76,19 @@ export class RatingCriterion extends ModifierCriterion<INumberValue> {
};
}
public setFromSavedCriterion(c: {
modifier: CriterionModifier;
value: number | INumberValue;
value2?: number;
}) {
super.setFromSavedCriterion(c);
// this.value = decodeRangeValue(c);
}
protected encodeValue(): unknown {
return encodeRangeValue(this.modifier, this.value);
}
protected getLabelValue() {
const { value, value2 } = this.value;
if (

View file

@ -5,7 +5,11 @@ import {
StashIdCriterionInput,
} from "src/core/generated-graphql";
import { IStashIDValue } from "../types";
import { ModifierCriterion, ModifierCriterionOption } from "./criterion";
import {
ISavedCriterion,
ModifierCriterion,
ModifierCriterionOption,
} from "./criterion";
export const StashIDCriterionOption = new ModifierCriterionOption({
messageID: "stash_id",
@ -90,7 +94,29 @@ export class StashIDCriterion extends ModifierCriterion<IStashIDValue> {
return ret;
}
public toJSON() {
public setFromSavedCriterion(
criterion: StashIdCriterionInput | ISavedCriterion<StashIdCriterionInput>
) {
super.setFromSavedCriterion(criterion);
// const asStashIDValue = criterion as StashIdCriterionInput;
// const asSavedCriterion =
// criterion as ISavedCriterion<StashIdCriterionInput>;
// if (asStashIDValue.endpoint || asStashIDValue.stash_id) {
// this.value = {
// endpoint: asStashIDValue.endpoint ?? "",
// stashID: asStashIDValue.stash_id ?? "",
// };
// } else if (asSavedCriterion.value) {
// this.value = {
// endpoint: asSavedCriterion.value.endpoint ?? "",
// stashID: asSavedCriterion.value.stash_id ?? "",
// };
// }
}
public toQueryParams(): Record<string, unknown> {
super.toQueryParams();
let encodedCriterion;
if (
(this.modifier === CriterionModifier.IsNull ||
@ -108,7 +134,7 @@ export class StashIDCriterion extends ModifierCriterion<IStashIDValue> {
modifier: this.modifier,
};
}
return JSON.stringify(encodedCriterion);
return encodedCriterion;
}
public isValid(): boolean {

View file

@ -5,11 +5,7 @@ import {
SavedFilterDataFragment,
SortDirectionEnum,
} from "src/core/generated-graphql";
import {
Criterion,
CriterionValue,
ISavedCriterion,
} from "./criteria/criterion";
import { Criterion } from "./criteria/criterion";
import { getFilterOptions } from "./factory";
import { CriterionType, DisplayMode, SavedUIOptions } from "./types";
import { ListFilterOptions } from "./filter-options";
@ -159,7 +155,7 @@ export class ListFilterModel {
JSON.parse(jsonString);
const criterion = this.makeCriterion(criterionType);
criterion.setFromSavedCriterion(savedCriterion);
criterion.fromDecodedParams(savedCriterion);
this.criteria.push(criterion);
} catch (err) {
@ -304,7 +300,7 @@ export class ListFilterModel {
if (objectFilter) {
for (const [k, v] of Object.entries(objectFilter)) {
const criterion = this.makeCriterion(k as CriterionType);
criterion.setFromSavedCriterion(v as ISavedCriterion<CriterionValue>);
criterion.setFromSavedCriterion(v);
this.criteria.push(criterion);
}
}
@ -335,7 +331,11 @@ export class ListFilterModel {
// Returns query parameters with necessary parts URL-encoded
public getEncodedParams(): IEncodedParams {
const encodedCriteria: string[] = this.criteria.map((criterion) => {
let str = ListFilterModel.translateJSON(criterion.toJSON(), false);
const queryParams = criterion.toQueryParams();
let str = ListFilterModel.translateJSON(
JSON.stringify(queryParams),
false
);
// URL-encode other characters
str = encodeURI(str);
@ -447,6 +447,15 @@ export class ListFilterModel {
return output;
}
// TODO - this needs to just use makeFilter, but it needs a migration
public makeSavedFilter() {
const output: Record<string, unknown> = {};
for (const c of this.criteria) {
c.applyToSavedCriterion(output);
}
return output;
}
public makeSavedUIOptions(): SavedUIOptions {
return {
display_mode: this.displayMode,

View file

@ -39,11 +39,14 @@ export interface IHierarchicalLabelValue {
depth: number;
}
export interface INumberValue {
value: number | undefined;
value2: number | undefined;
export interface IRangeValue<V> {
value: V | undefined;
value2: V | undefined;
}
export type INumberValue = IRangeValue<number>;
export type IDateValue = IRangeValue<string>;
export type ITimestampValue = IRangeValue<string>;
export interface IPHashDuplicationValue {
duplicated: boolean;
distance?: number; // currently not implemented
@ -54,16 +57,6 @@ export interface IStashIDValue {
stashID: string;
}
export interface IDateValue {
value: string;
value2: string | undefined;
}
export interface ITimestampValue {
value: string;
value2: string | undefined;
}
export interface IPhashDistanceValue {
value: string;
distance?: number;