fix: remove scoring streaming

This commit is contained in:
Sam Chau
2025-11-09 07:41:20 +11:00
parent 1181729da5
commit ec7616c7a1
5 changed files with 56 additions and 108 deletions

View File

@@ -22,9 +22,8 @@
},
"tasks": {
"dev": "APP_BASE_PATH=./dist/dev deno run -A npm:vite dev",
"build": "APP_BASE_PATH=./dist/build deno run -A npm:vite build",
"build": "APP_BASE_PATH=./dist/build deno run -A npm:vite build && deno compile --no-check --allow-net --allow-read --allow-write --allow-env --allow-ffi --allow-run --target x86_64-unknown-linux-gnu --output dist/build/profilarr dist/build/mod.ts",
"preview": "APP_BASE_PATH=./dist/data ./dist/build/profilarr",
"compile": "deno compile --no-check --allow-net --allow-read --allow-write --allow-env --allow-ffi --allow-run --target x86_64-unknown-linux-gnu --output dist/build/profilarr dist/build/mod.ts",
"format": "prettier --write .",
"lint": "prettier --check . && eslint .",
"test": "APP_BASE_PATH=./dist/test deno test src/tests --allow-read --allow-write --allow-env",

View File

@@ -45,7 +45,8 @@ export async function list(cache: PCDCache): Promise<QualityProfileTableRow[]> {
't.created_at as tag_created_at'
])
.where('qpt.quality_profile_id', 'in', profileIds)
.orderBy(['qpt.quality_profile_id', 't.name'])
.orderBy('qpt.quality_profile_id')
.orderBy('t.name')
.execute();
// 3. Get custom format counts grouped by arr_type
@@ -72,7 +73,8 @@ export async function list(cache: PCDCache): Promise<QualityProfileTableRow[]> {
'qg.name as group_name'
])
.where('qpq.quality_profile_id', 'in', profileIds)
.orderBy(['qpq.quality_profile_id', 'qpq.position'])
.orderBy('qpq.quality_profile_id')
.orderBy('qpq.position')
.execute();
// 5. Get languages for all profiles

View File

@@ -4,12 +4,14 @@
import type { PCDCache } from '../../cache.ts';
import type { QualityProfileScoring } from '../../types.ts';
import { logger } from '$logger/logger.ts';
/**
* Get quality profile scoring data
* Returns all custom formats with their scores per arr type
*/
export async function scoring(cache: PCDCache, databaseId: number, profileId: number): Promise<QualityProfileScoring> {
await logger.debug('scoring query called', { source: 'scoring', meta: { databaseId, profileId } });
const db = cache.kb;
// 1. Get profile settings
@@ -88,7 +90,7 @@ export async function scoring(cache: PCDCache, databaseId: number, profileId: nu
};
});
return {
const result = {
databaseId,
arrTypes,
customFormats: customFormatScoring,
@@ -96,4 +98,8 @@ export async function scoring(cache: PCDCache, databaseId: number, profileId: nu
upgrade_until_score: profile.upgrade_until_score,
upgrade_score_increment: profile.upgrade_score_increment
};
await logger.debug('scoring query completed', { source: 'scoring', meta: { customFormatCount: customFormatScoring.length } });
return result;
}

View File

@@ -3,7 +3,7 @@ import type { ServerLoad } from '@sveltejs/kit';
import { pcdManager } from '$pcd/pcd.ts';
import * as qualityProfileQueries from '$pcd/queries/qualityProfiles/index.ts';
export const load: ServerLoad = async ({ params, isDataRequest }) => {
export const load: ServerLoad = async ({ params }) => {
const { databaseId, id } = params;
// Validate params exist
@@ -29,23 +29,9 @@ export const load: ServerLoad = async ({ params, isDataRequest }) => {
throw error(500, 'Database cache not available');
}
// Always return synchronous data at top level, stream the heavy data
if (isDataRequest) {
// Client-side navigation - stream the data
return {
loaded: true, // Synchronous data to enable instant navigation
streamed: {
scoring: qualityProfileQueries.scoring(cache, currentDatabaseId, profileId)
}
};
} else {
// Initial page load - await the data for SEO
const scoringData = await qualityProfileQueries.scoring(cache, currentDatabaseId, profileId);
return {
loaded: true,
streamed: {
scoring: Promise.resolve(scoringData)
}
};
}
const scoringData = await qualityProfileQueries.scoring(cache, currentDatabaseId, profileId);
return {
scoring: scoringData
};
};

View File

@@ -30,7 +30,6 @@
let upgradeScoreIncrement = 0;
let customFormatScores: Record<number, Record<string, number | null>> = {};
let customFormatEnabled: Record<number, Record<string, boolean>> = {};
let scoringData: any = null;
// Track initial values
let initialMinimumScore = 0;
@@ -306,25 +305,51 @@
return arrTypeColors[arrType] || '#3b82f6'; // default to blue
}
$: scoring = data.scoring;
// Compute filtered and sorted formats
$: searchQuery = ($searchStore.query ?? '').trim().toLowerCase();
$: filteredCustomFormats = scoring?.customFormats.filter((format) => {
if (searchQuery && !format.name?.toLowerCase().includes(searchQuery)) {
return false;
}
if (hideUnscoredFormats) {
const hasAnyScore = scoring.arrTypes.some((arrType) => format.scores[arrType] !== null);
if (!hasAnyScore) return false;
}
return true;
}) || [];
$: sortedCustomFormats = sortFormats(filteredCustomFormats, state, sortState);
$: groupedFormats = groupFormats(sortedCustomFormats, selectedGroups);
// Apply default sort
$: if (scoring && !sortState) {
const defaultSortKey = (scoring.arrTypes.includes('radarr') ? 'radarr' : scoring.arrTypes[0]) as SortKey;
if (defaultSortKey) {
sortState = { key: defaultSortKey, direction: 'desc' };
}
}
// Reactive state - initialize from data
$: if (scoringData) {
minimumScore = scoringData.minimum_custom_format_score;
upgradeUntilScore = scoringData.upgrade_until_score;
upgradeScoreIncrement = scoringData.upgrade_score_increment;
$: if (scoring) {
minimumScore = scoring.minimum_custom_format_score;
upgradeUntilScore = scoring.upgrade_until_score;
upgradeScoreIncrement = scoring.upgrade_score_increment;
// Save initial values
initialMinimumScore = scoringData.minimum_custom_format_score;
initialUpgradeUntilScore = scoringData.upgrade_until_score;
initialUpgradeScoreIncrement = scoringData.upgrade_score_increment;
initialMinimumScore = scoring.minimum_custom_format_score;
initialUpgradeUntilScore = scoring.upgrade_until_score;
initialUpgradeScoreIncrement = scoring.upgrade_score_increment;
// Initialize scores and enabled state from data
const newScores: Record<number, Record<string, number | null>> = {};
const newEnabled: Record<number, Record<string, boolean>> = {};
scoringData.customFormats.forEach((cf: any) => {
scoring.customFormats.forEach((cf: any) => {
newScores[cf.id] = { ...cf.scores };
newEnabled[cf.id] = {};
scoringData.arrTypes.forEach((arrType: string) => {
scoring.arrTypes.forEach((arrType: string) => {
newEnabled[cf.id][arrType] = cf.scores[arrType] !== null;
});
});
@@ -437,78 +462,8 @@
<UnsavedChangesModal />
{#await data.streamed.scoring}
<!-- Loading skeleton -->
<div class="mt-6 space-y-6 animate-pulse">
<!-- Profile-level Score Settings Skeleton -->
<div class="grid grid-cols-1 gap-6 md:grid-cols-3">
{#each [1, 2, 3] as _}
<div class="space-y-2">
<div class="h-5 w-32 rounded bg-neutral-200 dark:bg-neutral-700"></div>
<div class="h-4 w-48 rounded bg-neutral-100 dark:bg-neutral-800"></div>
<div class="h-10 w-full rounded-lg bg-neutral-200 dark:bg-neutral-700"></div>
</div>
{/each}
</div>
<!-- Section Header Skeleton -->
<div class="flex items-start justify-between">
<div class="space-y-2">
<div class="h-5 w-48 rounded bg-neutral-200 dark:bg-neutral-700"></div>
<div class="h-4 w-64 rounded bg-neutral-100 dark:bg-neutral-800"></div>
</div>
<div class="h-8 w-16 rounded-lg bg-neutral-200 dark:bg-neutral-700"></div>
</div>
<!-- Table Skeleton -->
<div class="overflow-x-auto rounded-lg border border-neutral-200 dark:border-neutral-800">
<div class="w-full">
<div class="border-b border-neutral-200 bg-neutral-50 p-6 dark:border-neutral-800 dark:bg-neutral-800">
<div class="flex gap-4">
<div class="h-4 w-48 rounded bg-neutral-300 dark:bg-neutral-600"></div>
<div class="h-4 w-24 rounded bg-neutral-300 dark:bg-neutral-600"></div>
<div class="h-4 w-24 rounded bg-neutral-300 dark:bg-neutral-600"></div>
</div>
</div>
<div class="space-y-2 p-6">
{#each [1, 2, 3, 4, 5] as _}
<div class="flex gap-4">
<div class="h-10 w-48 rounded bg-neutral-200 dark:bg-neutral-700"></div>
<div class="h-10 w-24 rounded bg-neutral-200 dark:bg-neutral-700"></div>
<div class="h-10 w-24 rounded bg-neutral-200 dark:bg-neutral-700"></div>
</div>
{/each}
</div>
</div>
</div>
</div>
{:then scoring}
{@const _ = (scoringData = scoring, null)}
{@const searchQuery = ($searchStore.query ?? '').trim().toLowerCase()}
{@const filteredCustomFormats = scoring.customFormats.filter((format) => {
// Filter by search
if (searchQuery && !format.name?.toLowerCase().includes(searchQuery)) {
return false;
}
// Filter unscored formats if option is enabled
if (hideUnscoredFormats) {
const hasAnyScore = scoring.arrTypes.some((arrType) => format.scores[arrType] !== null);
if (!hasAnyScore) return false;
}
return true;
})}
{@const defaultSortKey = (scoring.arrTypes.includes('radarr') ? 'radarr' : scoring.arrTypes[0]) as SortKey}
{@const _applyDefaultSort = (() => {
if (!sortState && defaultSortKey) {
sortState = { key: defaultSortKey, direction: 'desc' };
}
return null;
})()}
{@const sortedCustomFormats = sortFormats(filteredCustomFormats, state, sortState)}
{@const groupedFormats = groupFormats(sortedCustomFormats, selectedGroups)}
<div class="mt-6 space-y-6" on:change={() => unsavedChanges.markDirty()}>
{#if scoring}
<div class="mt-6 space-y-6" on:change={() => unsavedChanges.markDirty()}>
<!-- Profile-level Score Settings -->
<div class="grid grid-cols-1 gap-6 md:grid-cols-3">
<div class="space-y-2">
@@ -855,7 +810,7 @@
</div>
{/if}
</div>
{/await}
{/if}
<InfoModal bind:open={showInfoModal} header="Custom Format Scoring">
<div class="space-y-4 text-sm text-neutral-600 dark:text-neutral-400">