Skip to content

Commit

Permalink
feat: Added the ability to test media items against a rule, returning…
Browse files Browse the repository at this point in the history
… a detailed execution breakdown

* chore: Add backend code for rule statistics. Also add an API endpoint to test a single media item

* refactor: Add application to rule statistics output name

* chore: Add UI modal

* chore: UI - button margin

* refactor: Fix broken seasons / episodes after rule comparator refactor

* refactor: Add a global statistic result indicating if the item matched the complete rule

* refactor: add info header

* refactor: Added validation for media type & improved info messages

* refactor: button caps

* refactor: import/export : fetch identifier from id instead of array location

* refactor: Responsive monaco editor height

* fix(collection): Clicking on the card will now navigate to the details page
  • Loading branch information
jorenn92 authored Jan 15, 2024
1 parent 97c52d4 commit 72cf392
Show file tree
Hide file tree
Showing 28 changed files with 1,589 additions and 722 deletions.
2 changes: 1 addition & 1 deletion docs/3-using-maintainerr/1-rules/Glossary.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Rule glossary

This glossary describes the available rules that can be used in the maintainerr.
This glossary describes the available rules that can be used in maintainerr.
Each rule contains the media type it applies to, the key and the data type of the returned value.

The key is used for identification in Yaml rule files.
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"plex-api": "^5.3.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-select": "^5.8.0",
"react-toast-notifications": "^2.5.1",
"react-transition-group": "^4.4.5",
"reflect-metadata": "^0.1.13",
Expand Down Expand Up @@ -96,7 +97,6 @@
"babel-plugin-react-intl-auto": "^3.3.0",
"clean-jsdoc-theme": "^4.2.14",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-next": "14.0.4",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.0.1",
Expand Down Expand Up @@ -171,4 +171,4 @@
"@semantic-release/github"
]
}
}
}
13 changes: 11 additions & 2 deletions server/src/modules/api/plex-api/interfaces/media.interface.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { PlexActor, PlexGenre } from './library.interfaces';

export interface PlexMetadata {
ratingKey: string;
parentRatingKey?: string;
guid: string;
type: 'movie' | 'show' | 'season';
type: 'movie' | 'show' | 'season' | 'episode' | 'collection';
title: string;
Guid: {
id: string;
Expand All @@ -15,13 +17,20 @@ export interface PlexMetadata {
parentIndex?: number;
Collection?: { tag: string }[];
leafCount: number;
grandparentRatingKey?: number;
grandparentRatingKey?: string;
viewedLeafCount: number;
addedAt: number;
updatedAt: number;
media: Media[];
parentData?: PlexMetadata;
Label?: { tag: string }[];
rating?: number;
audienceRating?: number;
userRating?: number;
Role?: PlexActor[];
originallyAvailableAt: string;
Media: Media[];
Genre?: PlexGenre[];
}

export interface Media {
Expand Down
128 changes: 128 additions & 0 deletions server/src/modules/rules/constants/constants.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { Injectable } from '@nestjs/common';
import { RuleConstants, RuleType } from './rules.constants';

export interface ICustomIdentifier {
type: string;
value: string | number;
}

@Injectable()
export class RuleConstanstService {
ruleConstants: RuleConstants;

constructor() {
this.ruleConstants = new RuleConstants();
}

public getRuleConstants() {
return this.ruleConstants;
}

public getValueIdentifier(location: [number, number]) {
const application = this.ruleConstants.applications.find(
(el) => el.id === location[0],
)?.name;

const rule = this.ruleConstants.applications
.find((el) => el.id === location[0])
?.props.find((el) => el.id === location[1])?.name;

return application + '.' + rule;
}

public getValueHumanName(location: [number, number]) {
return `${this.ruleConstants.applications.find(
(el) => el.id === location[0],
)?.name} - ${this.ruleConstants.applications
.find((el) => el.id === location[0])
?.props.find((el) => el.id === location[1])?.humanName}`;
}

public getValueFromIdentifier(identifier: string): [number, number] {
const application = identifier.split('.')[0];
const rule = identifier.split('.')[1];

const applicationConstant = this.ruleConstants.applications.find(
(el) => el.name.toLowerCase() === application.toLowerCase(),
);

const ruleConstant = applicationConstant.props.find(
(el) => el.name.toLowerCase() === rule.toLowerCase(),
);
return [applicationConstant.id, ruleConstant.id];
}

public getCustomValueIdentifier(customValue: {
ruleTypeId: number;
value: string;
}): ICustomIdentifier {
let ruleType: RuleType;
let value: string | number;
switch (customValue.ruleTypeId) {
case 0:
if (+customValue.value % 86400 === 0 && +customValue.value != 0) {
// when it's custom_days, translate to custom_days
ruleType = new RuleType('4', [], 'custom_days');
value = (+customValue.value / 86400).toString();
} else {
// otherwise, it's a normal number
ruleType = RuleType.NUMBER;
value = +customValue.value;
}
break;
case 1:
ruleType = RuleType.DATE;
value = customValue.value;
break;
case 2:
ruleType = RuleType.TEXT;
value = customValue.value;
break;
case 3:
ruleType = RuleType.BOOL;
value = customValue.value == '1' ? 'true' : 'false';
break;
}

return { type: ruleType.humanName, value: value };
}

public getCustomValueFromIdentifier(identifier: ICustomIdentifier): {
ruleTypeId: number;
value: string;
} {
let ruleType: RuleType;
let value: string;

switch (identifier.type.toUpperCase()) {
case 'NUMBER':
ruleType = RuleType.NUMBER;
value = identifier.value.toString();
break;
case 'DATE':
ruleType = RuleType.DATE;
value = identifier.value.toString();
break;
case 'TEXT':
ruleType = RuleType.TEXT;
value = identifier.value.toString();
break;
case 'BOOLEAN':
ruleType = RuleType.BOOL;
value = identifier.value == 'true' ? '1' : '0';
break;
case 'BOOL':
ruleType = RuleType.BOOL;
value = identifier.value == 'true' ? '1' : '0';
break;
case 'CUSTOM_DAYS':
ruleType = RuleType.NUMBER;
value = (+identifier.value * 86400).toString();
}

return {
ruleTypeId: +ruleType.toString(), // tostring returns the key
value: value,
};
}
}
4 changes: 2 additions & 2 deletions server/src/modules/rules/getter/getter.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { OverseerrGetterService } from './overseerr-getter.service';
import { PlexGetterService } from './plex-getter.service';
import { RadarrGetterService } from './radarr-getter.service';
import { SonarrGetterService } from './sonarr-getter.service';
import { EPlexDataType } from '../../api/plex-api/enums/plex-data-type-enum';
import { RulesDto } from '../dtos/rules.dto';
import { EPlexDataType } from 'src/modules/api/plex-api/enums/plex-data-type-enum';

@Injectable()
export class ValueGetterService {
Expand All @@ -20,8 +20,8 @@ export class ValueGetterService {
async get(
[val1, val2]: [number, number],
libItem: PlexLibraryItem,
dataType?: EPlexDataType,
ruleGroup?: RulesDto,
dataType?: EPlexDataType,
) {
switch (val1) {
case Application.PLEX: {
Expand Down
Loading

0 comments on commit 72cf392

Please sign in to comment.