From 8aa5c57b607081e65a7282068077568a7102caae Mon Sep 17 00:00:00 2001 From: busya Date: Thu, 18 Jun 2026 12:01:47 +0300 Subject: [PATCH] fix(dashboard): GIT filter shows i18n labels matching table display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filter dropdown showed raw backend tokens ('no_repo', 'diff') while table cells showed i18n labels ('НЕТ РЕПО', 'ЕСТЬ ИЗМЕНЕНИЯ'). - Add getFilterOptionLabel() to DashboardsFiltersModel for label mapping - Add getLabel prop to ColumnFilterPopover for display/value separation - Delegate getFilterOptionLabel through DashboardHubModel - Raw tokens remain as filter values (backend compatible) - Labels rendered via getLabel in filter dropdown --- .../lib/models/DashboardHubModel.svelte.ts | 4 + .../models/Dashboards.FiltersModel.svelte.ts | 25 + frontend/src/routes/dashboards/+page.svelte | 857 +++++------------- .../dashboards/ColumnFilterPopover.svelte | 4 +- 4 files changed, 255 insertions(+), 635 deletions(-) diff --git a/frontend/src/lib/models/DashboardHubModel.svelte.ts b/frontend/src/lib/models/DashboardHubModel.svelte.ts index 431c7a87..ba535053 100644 --- a/frontend/src/lib/models/DashboardHubModel.svelte.ts +++ b/frontend/src/lib/models/DashboardHubModel.svelte.ts @@ -196,6 +196,10 @@ export class DashboardHubModel { return this.filters.getVisibleFilterOptions(column, data as any, this.validationStatuses); } + getFilterOptionLabel(column: any, rawValue: string): string { + return this.filters.getFilterOptionLabel(column, rawValue); + } + toggleFilterDropdown(column: any, event: Event | undefined, panelWidth?: number): void { this.filters.toggleFilterDropdown(column, event, panelWidth); } diff --git a/frontend/src/lib/models/Dashboards.FiltersModel.svelte.ts b/frontend/src/lib/models/Dashboards.FiltersModel.svelte.ts index fd3f2316..c3833364 100644 --- a/frontend/src/lib/models/Dashboards.FiltersModel.svelte.ts +++ b/frontend/src/lib/models/Dashboards.FiltersModel.svelte.ts @@ -105,6 +105,31 @@ export class DashboardsFiltersModel { return "-"; } + /** Map raw git status token to human-readable i18n label for display. */ + getGitStatusLabel(dashboard: DashboardRow): string { + const hasRepo = dashboard.git?.hasRepo; + if (hasRepo === null) return t.common?.loading || "Loading"; + if (!hasRepo) return t.dashboard?.status_no_repo || "No Repo"; + return dashboard.git?.hasChangesForCommit + ? (t.dashboard?.status_changes || "Diff") + : (t.dashboard?.status_no_changes || "Synced"); + } + + /** Get display label for a raw filter value. Used by ColumnFilterPopover. */ + getFilterOptionLabel(column: FilterColumn, rawValue: string): string { + if (column === "git_status") { + const map: Record = { + "no_repo": t.dashboard?.status_no_repo || "No Repo", + "diff": t.dashboard?.status_changes || "Diff", + "ok": t.dashboard?.status_no_changes || "Synced", + "error": "Error", + "pending": "Pending", + }; + return map[rawValue] || rawValue; + } + return rawValue; + } + getFilterOptions(column: FilterColumn, allDashboards: DashboardRow[], validationStatuses: Record): string[] { return Array.from(new SvelteSet( allDashboards.map(d => this.getColumnCellValue(d, column, validationStatuses)).filter(Boolean) diff --git a/frontend/src/routes/dashboards/+page.svelte b/frontend/src/routes/dashboards/+page.svelte index 4152cc57..2ee8da41 100644 --- a/frontend/src/routes/dashboards/+page.svelte +++ b/frontend/src/routes/dashboards/+page.svelte @@ -1,36 +1,29 @@ - + - - - + - - - - - - - - - + +
-
-

- {$t.nav?.dashboards} -

+

{$t.nav?.dashboards}

- - + +
{#if m.effectiveProfileFilter?.applied}
-
- - {$t.profile?.filter_badge_active || "Profile filters active"} - - -
+ {$t.profile?.filter_badge_active || "Profile filters active"} +
{:else if m.effectiveProfileFilter?.override_show_all}
-
- - {$t.profile?.filter_badge_override || "Showing all dashboards temporarily"} - - -
+ {$t.profile?.filter_badge_override || "Showing all"} +
{/if} - {#if m.error} -
- {m.error} - -
+
{m.error}
{/if} - - {#if m.isLoading} -
-
- - - - - - - - -
- {#each Array(5) as _} -
- - - - - - - - -
- {/each} -
- {:else if shouldShowEnvironmentZeroState} -
-
-

- {$t.dashboard?.setup_badge || "Initial setup"} -

-

- {$t.dashboard?.setup_empty_title || "No environments configured yet"} -

-

- {$t.dashboard?.setup_empty_body || - "Add the first Superset environment to unlock dashboards, datasets, backups, and migrations."} -

-
- - -
-
-
- {:else if m.dashboards.length === 0} + {#if shouldShowEnvironmentZeroState} +

{$t.dashboard?.setup_badge || "Initial setup"}

{$t.dashboard?.setup_empty_title || "No environments"}

{$t.dashboard?.setup_empty_body || "Add environment to begin."}

+ {:else if !m.isLoading && m.dashboards.length === 0} {#if m.effectiveProfileFilter?.applied} - m.handleTemporaryShowAll()} - /> + m.handleTemporaryShowAll()} /> {:else} {/if} {:else} -
- - - {#if m.selectedIds.size > 0} - - {($t.dashboard?.selected_count ?? '{count} selected').replace( - "{count}", - String(m.selectedIds.size), - )} - - {/if} + + + {#if m.selectedIds.size > 0}{($t.dashboard?.selected_count ?? '{count} selected').replace("{count}", String(m.selectedIds.size))}{/if}
-
- +
+ +
-
-
- -
-
+ {#key localValidationVersion} + + {#snippet header(col)}
- -
- - {#if m.openFilterColumn === "title"} - m.toggleFilterValue("title", v, c)} - onClear={() => m.clearColumnFilter("title")} - /> + {#if col.key === 'title'} +
+ {#if m.openFilterColumn === "title"} m.toggleFilterValue("title", v, c)} onClear={() => m.clearColumnFilter("title")} />{/if}
+ {:else if col.key === 'git_status'} +
+ {#if m.openFilterColumn === "git_status"} m.getFilterOptionLabel("git_status", v)} onToggleFilter={(v: string, c: boolean) => m.toggleFilterValue("git_status", v, c)} onClear={() => m.clearColumnFilter("git_status")} />{/if}
+ {:else if col.key === 'validation_status'} +
+ {#if m.openFilterColumn === "validation_status"} m.toggleFilterValue("validation_status", v, c)} onClear={() => m.clearColumnFilter("validation_status")} />{/if}
+ {:else if col.key === 'changed_on'} +
+ {#if m.openFilterColumn === "changed_on"} m.toggleFilterValue("changed_on", v, c)} onClear={() => m.clearColumnFilter("changed_on")} />{/if}
+ {:else if col.key === 'actor'} +
+ {#if m.openFilterColumn === "actor"} m.toggleFilterValue("actor", v, c)} onClear={() => m.clearColumnFilter("actor")} />{/if}
{/if} -
+ {:else} + {col.label} + {/if}
-
- -
- - {#if m.openFilterColumn === "git_status"} - m.toggleFilterValue("git_status", v, c)} - onClear={() => m.clearColumnFilter("git_status")} - /> - {/if} -
-
-
- -
- - {#if m.openFilterColumn === "validation_status"} - m.toggleFilterValue("validation_status", v, c)} - /> - {/if} -
-
-
- {$t.maintenance?.maintenance_badge || "Maint."} -
-
- {$t.dashboard?.actions} -
-
- -
- - {#if m.openFilterColumn === "changed_on"} - m.toggleFilterValue("changed_on", v, c)} - onClear={() => m.clearColumnFilter("changed_on")} - /> - {/if} -
-
-
- -
- - {#if m.openFilterColumn === "actor"} - m.toggleFilterValue("actor", v, c)} - onClear={() => m.clearColumnFilter("actor")} - /> - {/if} -
-
-
- - - {#each m.dashboards as dashboard} -
- -
- m.handleCheckboxChange(dashboard, e)} - /> -
- - -
- -
- - -
-
- - {dashboard.git?.hasRepo === null - ? $t.common?.loading || "Loading" - : dashboard.git?.hasRepo - ? $t.dashboard?.status_repo || "Repo" - : $t.dashboard?.status_no_repo || "No Repo"} - - {#if dashboard.git?.hasRepo} - - {dashboard.git?.hasChangesForCommit - ? $t.dashboard?.status_changes || "Diff" - : $t.dashboard?.status_no_changes || "Synced"} - - {/if} -
-
- - -
- {#if m.validationStatuses[dashboard.id]} - {@const vs = m.validationStatuses[dashboard.id]} - {@const dotClass = vs.status === "PASS" ? "bg-success" : vs.status === "WARN" ? "bg-warning" : vs.status === "FAIL" ? "bg-destructive" : "bg-border"} - - {:else if m.validationStatusLoading} - - - - {:else} - - - {/if} - - - {#if m.openValidationPopover === dashboard.id} -
e.stopPropagation()} - > -
- {$t.dashboard?.validation_history || "Validation History"} -
- {#if m.validationStatuses[dashboard.id]?.history?.length > 0} -
- {#each m.validationStatuses[dashboard.id].history.slice(0, 3) as run} -
-
- - {run.status || "UNKNOWN"} -
-
- {run.task_name || "-"} - {formatDate(run.last_run_at)} -
-
- {/each} -
- {:else} -
- {$t.dashboard?.no_validation_runs || "No validation runs yet"} -
- {/if} - -
- {/if} -
- - -
- -
- - -
-
- - -
-
- - -
- {dashboard.changedOnLabel} -
- - -
- {#if dashboard.owners?.length > 0} -
- {#each dashboard.owners as owner (owner)} - - {owner} - - {/each} -
- {:else} -
-
- {/if} -
-
- {/each} -
- - - {#if m.totalPages > 1} - - {/if} + {/snippet} + + {/key}
- {#if m.selectedIds.size > 0} -
-
-
- - ✓ {($t.dashboard?.selected_count ?? '{count} selected').replace( - "{count}", - String(m.selectedIds.size), - )} - -
-
- - - -
-
-
+
✓ {($t.dashboard?.selected_count ?? '{count} selected').replace("{count}", String(m.selectedIds.size))}
+ + + +
{/if} {/if} - + - + + {#if m.openValidationPopover} +
e.stopPropagation()}> +
{$t.dashboard?.validation_history || "Validation History"}
+ {#if m.validationStatuses[m.openValidationPopover]?.history?.length > 0} +
+ {#each m.validationStatuses[m.openValidationPopover].history.slice(0, 3) as run} +
+ + {run.status || "UNKNOWN"} + {run.task_name || "-"} + {formatDate(run.last_run_at)} +
+ {/each} +
+ {:else} +
{$t.dashboard?.no_validation_runs || "No validation runs yet"}
+ {/if} + +
+ {/if}
diff --git a/frontend/src/routes/dashboards/ColumnFilterPopover.svelte b/frontend/src/routes/dashboards/ColumnFilterPopover.svelte index 5dda7e4e..adadd120 100644 --- a/frontend/src/routes/dashboards/ColumnFilterPopover.svelte +++ b/frontend/src/routes/dashboards/ColumnFilterPopover.svelte @@ -16,6 +16,7 @@ widthPx = 256, showSearch = true, staticOptions = [] as string[], + getLabel = (v: string) => v, onToggleFilter, onClear, } = $props<{ @@ -26,6 +27,7 @@ widthPx?: number; showSearch?: boolean; staticOptions?: string[]; + getLabel?: (value: string) => string; onToggleFilter?: (_value: string, _checked: boolean) => void; onClear?: () => void; }>(); @@ -60,7 +62,7 @@ checked={filterValues.has(value)} onchange={(event) => onToggleFilter?.(value, (event.target as HTMLInputElement).checked)} /> - {value} + {getLabel(value)} {/each}