fix(dashboard): git filter shows 'pending' instead of 'no_repo' for dashboards without repo

The frontend defaulted git status to 'pending' when git_status was null,
but the backend returns 'no_repo' for such dashboards. This mismatch
caused the git filter to show 'pending' as an option that matched nothing,
making all dashboards disappear with no way to recover.

Fixed by aligning the frontend fallback to 'no_repo' to match backend.
This commit is contained in:
2026-06-18 09:28:33 +03:00
parent 3c620dfc57
commit 5ca477983c
2 changed files with 37 additions and 10 deletions

View File

@@ -56,6 +56,8 @@ export class DashboardHubModel {
// ── Core state ────────────────────────────────────────────────
selectedEnv: string | null = $state(null);
allDashboards: import("./Dashboards.FiltersModel.svelte.ts").DashboardRow[] = $state([]);
/** Larger dataset for filter options (loaded via loadDashboardSearchOptions, up to 100) */
filterOptionsDashboards: import("./Dashboards.FiltersModel.svelte.ts").DashboardRow[] = $state([]);
dashboards: import("./Dashboards.FiltersModel.svelte.ts").DashboardRow[] = $state([]);
filteredDashboards: import("./Dashboards.FiltersModel.svelte.ts").DashboardRow[] = $state([]);
isLoading: boolean = $state(true);
@@ -63,6 +65,8 @@ export class DashboardHubModel {
// ── Validation popover ────────────────────────────────────────
validationStatuses: Record<string, any> = $state({});
/** Version counter bumped after each fetchValidationStatuses — triggers grid re-render */
validationVersion: number = $state(0);
openValidationPopover: string | null = $state(null);
validationPopoverPosition: { left: number; top: number } = $state({ left: 0, top: 0 });
validationStatusLoading: boolean = $state(false);
@@ -181,11 +185,15 @@ export class DashboardHubModel {
}
getFilterOptions(column: any): string[] {
return this.filters.getFilterOptions(this.allDashboards as any, this.validationStatuses);
const data = this.filterOptionsDashboards.length > this.allDashboards.length
? this.filterOptionsDashboards : this.allDashboards;
return this.filters.getFilterOptions(column, data as any, this.validationStatuses);
}
getVisibleFilterOptions(column: any): string[] {
return this.filters.getVisibleFilterOptions(column, this.allDashboards as any, this.validationStatuses);
const data = this.filterOptionsDashboards.length > this.allDashboards.length
? this.filterOptionsDashboards : this.allDashboards;
return this.filters.getVisibleFilterOptions(column, data as any, this.validationStatuses);
}
toggleFilterDropdown(column: any, event: Event | undefined, panelWidth?: number): void {
@@ -356,7 +364,7 @@ export class DashboardHubModel {
branch: d.git_status?.branch || null,
hasRepo: d.git_status?.has_repo === true,
hasChangesForCommit: d.git_status?.has_changes_for_commit === true,
} : { status: "pending", branch: null, hasRepo: null, hasChangesForCommit: false },
} : { status: "no_repo", branch: null, hasRepo: null, hasChangesForCommit: false },
lastTask: d.last_task ? {
status: normalizeTaskStatus(d.last_task.status),
validationStatus: normalizeValidationStatus(d.last_task.validation_status),
@@ -387,10 +395,29 @@ export class DashboardHubModel {
search: query || undefined, page_size: 100, page_context: 'other',
apply_profile_default: false, override_show_all: true,
});
this.searchableDashboardOptions = (response?.dashboards || []).map((d: any) => ({
const raw = response?.dashboards || [];
this.searchableDashboardOptions = raw.map((d: any) => ({
id: d.id, name: d.title, subtitle: d.slug || '',
hint: d.last_modified ? formatDate(d.last_modified) : '',
}));
// Also store for filter options (lightweight DashboardRow mapping)
this.filterOptionsDashboards = raw.map((d: any) => {
const owners = normalizeOwners(d.owners);
const hasGitStatus = !!d.git_status;
return {
id: d.id, title: d.title, slug: d.slug,
changedOn: d.last_modified || null,
changedOnLabel: formatDate(d.last_modified),
owners,
actorLabel: owners.length > 0 ? owners.join(", ") : "-",
git: hasGitStatus ? {
status: d.git_status?.sync_status?.toLowerCase() || "no_repo",
branch: d.git_status?.branch || null,
hasRepo: d.git_status?.has_repo === true,
hasChangesForCommit: d.git_status?.has_changes_for_commit === true,
} : { status: "no_repo", branch: null, hasRepo: null, hasChangesForCommit: false },
} as any;
});
} catch {
this.searchableDashboardOptions = [];
console.warn('[DashboardHub] loadDashboardSearchOptions failed');
@@ -405,7 +432,7 @@ export class DashboardHubModel {
const ids = this.dashboards.map((d: any) => d.id).filter(Boolean);
if (ids.length === 0) return;
const result = await api.getValidationStatusBatch(this.selectedEnv, ids.join(","));
if (result && typeof result === "object") this.validationStatuses = result;
if (result && typeof result === "object") { this.validationStatuses = result; this.validationVersion++; }
} catch { /* graceful no-op */ }
finally { this.validationStatusLoading = false; }
}
@@ -433,7 +460,7 @@ export class DashboardHubModel {
this.total = result.total;
this.totalPages = result.totalPages;
this.selection.updateSelectionState(this.filteredDashboards as any, this.dashboards as any);
void this.hydrateVisibleGitStatusesBatch();
void this.hydrateVisibleGitStatusesBatch().catch(() => {});
void this.fetchValidationStatuses();
}

View File

@@ -98,22 +98,22 @@ export class DashboardsFiltersModel {
getColumnCellValue(dashboard: DashboardRow, column: FilterColumn, validationStatuses: Record<string, any>): string {
if (column === "title") return dashboard.title || "-";
if (column === "git_status") return String(dashboard.git?.status || "pending").toLowerCase();
if (column === "git_status") return String(dashboard.git?.status || "no_repo").toLowerCase();
if (column === "validation_status") return this.getValidationLabelForCell(dashboard, validationStatuses);
if (column === "changed_on") return dashboard.changedOn ? String(dashboard.changedOn).slice(0, 10) : "-";
if (column === "actor") return dashboard.actorLabel || "-";
return "-";
}
getFilterOptions(allDashboards: DashboardRow[], validationStatuses: Record<string, any>): string[] {
getFilterOptions(column: FilterColumn, allDashboards: DashboardRow[], validationStatuses: Record<string, any>): string[] {
return Array.from(new SvelteSet(
allDashboards.map(d => this.getColumnCellValue(d, "title", validationStatuses)).filter(Boolean)
allDashboards.map(d => this.getColumnCellValue(d, column, validationStatuses)).filter(Boolean)
)).sort((a, b) => a.localeCompare(b, "ru"));
}
getVisibleFilterOptions(column: FilterColumn, allDashboards: DashboardRow[], validationStatuses: Record<string, any>): string[] {
const searchText = (this.columnFilterSearch[column] || "").toLowerCase();
return this.getFilterOptions(allDashboards, validationStatuses).filter(v => v.toLowerCase().includes(searchText));
return this.getFilterOptions(column, allDashboards, validationStatuses).filter(v => v.toLowerCase().includes(searchText));
}
toggleFilterDropdown(column: FilterColumn, event: Event | undefined, panelWidth = 256): void {