tasks ready

This commit is contained in:
2026-03-04 19:42:17 +03:00
parent 2820e491d5
commit 6a68770a8e
11 changed files with 3410 additions and 92263 deletions

View File

@@ -0,0 +1,52 @@
# Specification Quality Checklist: User Profile Dashboard Filter
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-03-04
**Feature**: [spec.md](../spec.md)
## Content Quality
- [ ] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed
## UX Consistency
- [x] Functional requirements fully support the 'Happy Path' in ux_reference.md
- [x] Error handling requirements match the 'Error Experience' in ux_reference.md
- [x] No requirements contradict the defined User Persona or Context
## Requirement Completeness
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [ ] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified
## Feature Readiness
- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [ ] No implementation details leak into specification
## Notes
### Failing items & evidence
1) **No implementation details (languages, frameworks, APIs)****FAIL**
Evidence in [spec.md](../spec.md), **Assumptions**:
- "Apache Superset API provides access to dashboard metadata including owner and modified_by fields"
2) **Success criteria are technology-agnostic (no implementation details)****FAIL**
Evidence in [spec.md](../spec.md), **Success Criteria**:
- "SC-002: Dashboard list loads filtered results within 2 seconds after preference is saved"
(This is acceptable as a user-facing performance target, but the current wording implies system internals; should be phrased as a user-perceived outcome.)
3) **No implementation details leak into specification****FAIL**
Root cause: same as (1) and (2). Fix by rewriting assumptions and SC-002 to be implementation-agnostic and user-perceived.

View File

@@ -0,0 +1,396 @@
openapi: 3.1.0
info:
title: User Profile Dashboard Filter API
version: "1.0.0"
description: >
API contract for user profile dashboard filtering with Superset account lookup
by selected environment and default "my dashboards" behavior on the main dashboards page.
servers:
- url: /api
security:
- bearerAuth: []
paths:
/profile/preferences:
get:
summary: Get current user's dashboard filter preferences
operationId: getMyDashboardPreferences
tags: [profile]
responses:
"200":
description: Preference payload for authenticated user
content:
application/json:
schema:
$ref: "#/components/schemas/ProfilePreferenceResponse"
"401":
description: Unauthorized
patch:
summary: Update current user's dashboard filter preferences
operationId: updateMyDashboardPreferences
tags: [profile]
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ProfilePreferenceUpdateRequest"
examples:
enableFilter:
value:
superset_username: "john_doe"
show_only_my_dashboards: true
disableFilter:
value:
superset_username: "john_doe"
show_only_my_dashboards: false
responses:
"200":
description: Preference saved
content:
application/json:
schema:
$ref: "#/components/schemas/ProfilePreferenceResponse"
"401":
description: Unauthorized
"422":
description: Validation failed
/profile/superset-accounts:
get:
summary: Lookup Superset accounts in selected environment for profile binding
operationId: lookupSupersetAccounts
tags: [profile, lookup]
parameters:
- name: environment_id
in: query
required: true
schema:
type: string
- name: search
in: query
required: false
schema:
type: string
- name: page_index
in: query
required: false
schema:
type: integer
minimum: 0
default: 0
- name: page_size
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 100
default: 20
- name: sort_column
in: query
required: false
schema:
type: string
enum: [username, first_name, last_name, email]
default: username
- name: sort_order
in: query
required: false
schema:
type: string
enum: [asc, desc]
default: desc
responses:
"200":
description: Lookup result (success or degraded with warning)
content:
application/json:
schema:
$ref: "#/components/schemas/SupersetAccountLookupResponse"
examples:
success:
value:
status: success
environment_id: dev
page_index: 0
page_size: 20
total: 2
items:
- environment_id: dev
username: john_doe
display_name: John Doe
email: john@example.local
is_active: true
- environment_id: dev
username: jane_admin
display_name: Jane Admin
email: jane@example.local
is_active: true
degraded:
value:
status: degraded
environment_id: dev
page_index: 0
page_size: 20
total: 0
warning: Cannot load Superset accounts for this environment right now. You can enter username manually.
items: []
"401":
description: Unauthorized
"404":
description: Environment not found
/dashboards:
get:
summary: Get dashboards list with optional profile-default filtering context
operationId: getDashboards
tags: [dashboards]
parameters:
- name: env_id
in: query
required: true
schema:
type: string
- name: search
in: query
required: false
schema:
type: string
- name: page
in: query
required: false
schema:
type: integer
minimum: 1
default: 1
- name: page_size
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 100
default: 10
- name: page_context
in: query
required: false
schema:
type: string
enum: [dashboards_main, other]
default: dashboards_main
- name: apply_profile_default
in: query
required: false
schema:
type: boolean
default: true
- name: override_show_all
in: query
required: false
schema:
type: boolean
default: false
responses:
"200":
description: Dashboards page with effective profile filter metadata
content:
application/json:
schema:
$ref: "#/components/schemas/DashboardsResponse"
"400":
description: Invalid pagination or query parameters
"401":
description: Unauthorized
"404":
description: Environment not found
"503":
description: Superset backend unavailable
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
ProfilePreference:
type: object
required:
- user_id
- show_only_my_dashboards
- created_at
- updated_at
properties:
user_id:
type: string
superset_username:
type: string
nullable: true
superset_username_normalized:
type: string
nullable: true
show_only_my_dashboards:
type: boolean
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
ProfilePreferenceUpdateRequest:
type: object
required:
- show_only_my_dashboards
properties:
superset_username:
type: string
nullable: true
show_only_my_dashboards:
type: boolean
ProfilePreferenceResponse:
type: object
required:
- status
- preference
properties:
status:
type: string
enum: [success, error]
message:
type: string
validation_errors:
type: array
items:
type: string
preference:
$ref: "#/components/schemas/ProfilePreference"
SupersetAccountCandidate:
type: object
required:
- environment_id
- username
properties:
environment_id:
type: string
username:
type: string
display_name:
type: string
nullable: true
email:
type: string
nullable: true
is_active:
type: boolean
nullable: true
SupersetAccountLookupResponse:
type: object
required:
- status
- environment_id
- page_index
- page_size
- total
- items
properties:
status:
type: string
enum: [success, degraded]
environment_id:
type: string
page_index:
type: integer
minimum: 0
page_size:
type: integer
minimum: 1
maximum: 100
total:
type: integer
minimum: 0
warning:
type: string
items:
type: array
items:
$ref: "#/components/schemas/SupersetAccountCandidate"
DashboardItem:
type: object
required:
- id
- title
properties:
id:
type: integer
title:
type: string
slug:
type: string
nullable: true
last_modified:
type: string
nullable: true
modified_by:
type: string
nullable: true
owners:
type: array
items:
type: string
EffectiveProfileFilter:
type: object
required:
- applied
- source_page
- override_show_all
properties:
applied:
type: boolean
source_page:
type: string
enum: [dashboards_main, other]
override_show_all:
type: boolean
username:
type: string
nullable: true
match_logic:
type: string
enum: [owners_or_modified_by]
DashboardsResponse:
type: object
required:
- dashboards
- total
- page
- page_size
- total_pages
properties:
dashboards:
type: array
items:
$ref: "#/components/schemas/DashboardItem"
total:
type: integer
minimum: 0
page:
type: integer
minimum: 1
page_size:
type: integer
minimum: 1
total_pages:
type: integer
minimum: 1
effective_profile_filter:
$ref: "#/components/schemas/EffectiveProfileFilter"

View File

@@ -0,0 +1,219 @@
# Module Contracts: User Profile Dashboard Filter
## Contract Scope
This document defines semantic contracts for modules introduced or extended by this feature.
Contracts are aligned with:
- feature requirements in [`spec.md`](../spec.md),
- UX states in [`ux_reference.md`](../ux_reference.md),
- entities in [`data-model.md`](../data-model.md).
---
# [DEF:backend.src.models.profile:Module]
# @TIER: STANDARD
# @SEMANTICS: profile, preferences, persistence, user, dashboard-filter
# @PURPOSE: Persist one dashboard filter preference record per authenticated user.
# @LAYER: Domain
# @RELATION: DEPENDS_ON -> backend.src.models.auth
# @INVARIANT: Exactly one preference record exists per user_id.
class UserDashboardPreference:
# @PURPOSE: Store bound Superset username and default filter toggle.
# @PRE: user_id references existing user.
# @POST: Record enforces one-to-one relation with user.
def __init__(self): ...
# [/DEF:backend.src.models.profile:Module]
---
# [DEF:backend.src.core.superset_client.profile_lookup_extension:Module]
# @TIER: STANDARD
# @SEMANTICS: superset, users, lookup, environment, normalization
# @PURPOSE: Query account candidates from selected Superset environment for profile binding.
# @LAYER: Infrastructure
# @RELATION: DEPENDS_ON -> backend.src.core.superset_client
# @RELATION: CALLS -> backend.src.core.utils.network.APIClient.request
# @INVARIANT: Account lookup never exposes upstream raw payload directly; output is normalized.
class SupersetAccountLookupAdapter:
# @PURPOSE: Query Superset users list endpoint with pagination/sort options.
# @PRE: Environment credentials are valid and authenticated.
# @POST: Returns normalized account candidate page.
def get_users_page(self): ...
# @PURPOSE: Normalize Superset user payload to canonical candidate shape.
# @PRE: raw payload may differ across Superset versions.
# @POST: Returns stable fields {username, display_name, email, is_active}.
def normalize_user_payload(self): ...
# [/DEF:backend.src.core.superset_client.profile_lookup_extension:Module]
---
# [DEF:backend.src.services.profile_service:Module]
# @TIER: CRITICAL
# @SEMANTICS: profile, service, validation, ownership, filtering
# @PURPOSE: Enforce profile preference rules, account lookup orchestration, and actor matching semantics.
# @LAYER: Domain
# @RELATION: DEPENDS_ON -> backend.src.models.profile
# @RELATION: DEPENDS_ON -> backend.src.core.superset_client.profile_lookup_extension
# @RELATION: DEPENDS_ON -> backend.src.models.auth
# @INVARIANT: User can mutate only own preference record; matching uses trim+case-insensitive logic.
# @TEST_CONTRACT: ProfilePreferenceUpdateRequest -> ProfilePreferenceResponse
# @TEST_FIXTURE: valid_profile_update -> {"user_id":"u-1","superset_username":"John_Doe","show_only_my_dashboards":true}
# @TEST_EDGE: enable_without_username -> toggle=true with empty username returns validation error
# @TEST_EDGE: cross_user_mutation -> attempt to update another user preference returns forbidden
# @TEST_EDGE: lookup_env_not_found -> unknown environment_id returns not found
# @TEST_INVARIANT: normalization_consistency -> VERIFIED_BY: [valid_profile_update, enable_without_username]
class ProfileService:
# @PURPOSE: Read authenticated user's current preference.
# @PRE: current_user is authenticated.
# @POST: Returns existing preference or default non-configured view.
def get_my_preference(self): ...
# @PURPOSE: Save authenticated user's preference with validation and normalization.
# @PRE: current_user is authenticated; request payload is provided.
# @POST: Preference persisted for current user only.
def update_my_preference(self): ...
# @PURPOSE: Lookup Superset account candidates in selected environment.
# @PRE: environment_id exists and is reachable.
# @POST: Returns normalized candidate list and paging metadata.
def lookup_superset_accounts(self): ...
# @PURPOSE: Determine if dashboard row matches bound username.
# @PRE: owners and modified_by values are available from dashboard projection.
# @POST: True when normalized username matches owners OR modified_by.
def matches_dashboard_actor(self): ...
# [/DEF:backend.src.services.profile_service:Module]
---
# [DEF:backend.src.api.routes.profile:Module]
# @TIER: CRITICAL
# @SEMANTICS: api, profile, preferences, self-service, account-lookup
# @PURPOSE: Expose self-scoped profile preference read/update and environment-based Superset account lookup.
# @LAYER: API
# @RELATION: DEPENDS_ON -> backend.src.services.profile_service
# @RELATION: DEPENDS_ON -> backend.src.dependencies.get_current_user
# @INVARIANT: Endpoints are self-scoped; no target-user mutation path is accepted.
# @TEST_CONTRACT: ProfileRouteRequest -> ProfileRouteResponse
# @TEST_FIXTURE: get_my_preference_ok -> {"auth_user":"u-1","expected_status":200}
# @TEST_EDGE: unauthorized_request -> missing token returns 401
# @TEST_EDGE: invalid_username_payload -> returns 422 or domain validation error
# @TEST_EDGE: superset_lookup_upstream_error -> returns non-blocking lookup error payload
# @TEST_INVARIANT: self_scope_enforced -> VERIFIED_BY: [get_my_preference_ok, unauthorized_request]
module ProfileRouter:
# @PURPOSE: GET own profile preference.
# @PRE: Auth token valid.
# @POST: Returns preference for authenticated user.
def get_preferences(self): ...
# @PURPOSE: PATCH own profile preference.
# @PRE: Auth token valid and payload provided.
# @POST: Persists preference and returns normalized result.
def update_preferences(self): ...
# @PURPOSE: GET Superset account candidates by selected environment.
# @PRE: Auth token valid and environment_id provided.
# @POST: Returns normalized account candidates or lookup warning metadata.
def lookup_superset_accounts(self): ...
# [/DEF:backend.src.api.routes.profile:Module]
---
# [DEF:backend.src.api.routes.dashboards.profile_filter_extension:Module]
# @TIER: CRITICAL
# @SEMANTICS: dashboards, filter, profile-default, override
# @PURPOSE: Extend dashboards list contract with profile-default filter controls and effective filter metadata.
# @LAYER: API
# @RELATION: DEPENDS_ON -> backend.src.api.routes.dashboards
# @RELATION: CALLS -> backend.src.services.profile_service.matches_dashboard_actor
# @INVARIANT: Profile default filter auto-apply is allowed only for main dashboards list context.
# @TEST_CONTRACT: DashboardsListRequest -> DashboardsListResponse
# @TEST_FIXTURE: profile_filter_applied -> {"apply_profile_default":true,"override_show_all":false}
# @TEST_EDGE: override_show_all -> effective_filter_applied=false even when saved toggle=true
# @TEST_EDGE: no_match_results -> returns empty dashboards with valid pagination metadata
# @TEST_INVARIANT: pagination_integrity_under_filter -> VERIFIED_BY: [profile_filter_applied, no_match_results]
module DashboardProfileFilterExtension:
# @PURPOSE: Parse profile-default query controls from request.
# @PRE: Request query is provided.
# @POST: Produces deterministic filter context.
def parse_profile_filter_context(self): ...
# @PURPOSE: Apply OR-based actor matching over owners and modified_by.
# @PRE: Dashboard projection includes owners and modified_by fields.
# @POST: Response list and totals reflect effective filter.
def apply_profile_filter(self): ...
# [/DEF:backend.src.api.routes.dashboards.profile_filter_extension:Module]
---
<!-- [DEF:frontend.src.routes.profile.+page:Module] -->
<!--
@TIER: CRITICAL
@SEMANTICS: profile-page, preferences, lookup, save, ux-states
@PURPOSE: Provide profile UI for binding Superset account and saving default "my dashboards" preference.
@LAYER: UI
@RELATION: BINDS_TO -> frontend.src.lib.api
@RELATION: BINDS_TO -> frontend.src.lib.i18n
@INVARIANT: Save action never updates another user's preference.
@UX_STATE: Default -> Current preference shown with environment selector, account input, and toggle.
@UX_STATE: LookupLoading -> Account candidate list shows loading indicator for selected environment.
@UX_STATE: LookupError -> Inline warning shown; manual username entry remains enabled.
@UX_STATE: Saving -> Save button disabled and shows progress.
@UX_STATE: SaveSuccess -> Success toast appears and form reflects persisted values.
@UX_STATE: SaveError -> Error feedback shown without losing user input.
@UX_FEEDBACK: Toasts for save result, inline validation and lookup fallback messaging.
@UX_RECOVERY: User can retry lookup, manually enter username, or disable toggle to continue.
@TEST_CONTRACT: ProfilePageInteraction -> PreferencePersisted
@TEST_FIXTURE: bind_account_happy_path -> {"env_id":"dev","candidate":"j.doe","toggle":true}
@TEST_EDGE: lookup_failed_manual_fallback -> save still possible with manually entered username
@TEST_EDGE: invalid_username -> save blocked with inline validation error
@TEST_EDGE: cancel_changes -> persisted state remains unchanged
@TEST_INVARIANT: lookup_does_not_block_manual_save -> VERIFIED_BY: [lookup_failed_manual_fallback, bind_account_happy_path]
-->
module ProfilePage:
/** @PURPOSE Render profile preference form and orchestrate lookup/save flows. */
function renderAndHandleState() {}
<!-- [/DEF:frontend.src.routes.profile.+page:Module] -->
---
<!-- [DEF:frontend.src.routes.dashboards.+page.profile_filter_ui_extension:Module] -->
<!--
@TIER: CRITICAL
@SEMANTICS: dashboards-page, profile-filter-indicator, override, empty-state
@PURPOSE: Apply profile-default filter context on main dashboards page and provide temporary "show all" override.
@LAYER: UI
@RELATION: BINDS_TO -> frontend.src.lib.api
@RELATION: BINDS_TO -> frontend.src.routes.profile.+page
@INVARIANT: Temporary override never mutates saved profile preference.
@UX_STATE: FilterActive -> Visible badge "My Dashboards Only" with clear action.
@UX_STATE: OverrideActive -> Full list shown with hint that preference remains saved.
@UX_STATE: EmptyFiltered -> Friendly no-match message with path to profile adjustment.
@UX_FEEDBACK: Immediate list refresh after profile save and after override toggle.
@UX_RECOVERY: User can clear override or edit profile binding and reload list.
@TEST_CONTRACT: DashboardsFilterUiAction -> DashboardsGridState
@TEST_FIXTURE: default_profile_filter_applied -> {"saved_toggle":true,"username":"j.doe"}
@TEST_EDGE: no_matching_dashboards -> EmptyFiltered state rendered
@TEST_EDGE: temporary_show_all -> list restored while saved preference preserved
@TEST_EDGE: return_to_page -> FilterActive restored from saved preference
@TEST_INVARIANT: override_non_persistent -> VERIFIED_BY: [temporary_show_all, return_to_page]
-->
module DashboardsProfileFilterUI:
/** @PURPOSE Render active-filter indicator and manage temporary override state. */
function renderFilterIndicatorAndOverride() {}
<!-- [/DEF:frontend.src.routes.dashboards.+page.profile_filter_ui_extension:Module] -->
---
## Contract Trace (Key Scenario)
Scenario: User binds Superset account from environment and sees filtered dashboards by default.
1. [`frontend.src.routes.profile.+page`](#deffrontendsrcroutesprofilepagemodule) requests account candidates for selected environment.
2. [`backend.src.api.routes.profile`](#defbackendsrcapiroutesprofilemodule) validates self-scope and forwards lookup request.
3. [`backend.src.services.profile_service`](#defbackendsrcservicesprofile_servicemodule) orchestrates lookup via [`backend.src.core.superset_client.profile_lookup_extension`](#defbackendsrccoresuperset_clientprofile_lookup_extensionmodule).
4. User saves preference; profile service normalizes username and persists [`backend.src.models.profile`](#defbackendsrcmodelsprofilemodule).
5. On `/dashboards`, [`backend.src.api.routes.dashboards.profile_filter_extension`](#defbackendsrcapiroutesdashboardsprofile_filter_extensionmodule) applies `owners OR modified_by` matching and returns effective filter metadata.
6. [`frontend.src.routes.dashboards.+page.profile_filter_ui_extension`](#deffrontendsrcroutesdashboardspageprofile_filter_ui_extensionmodule) shows active badge and supports temporary "show all" override.

View File

@@ -0,0 +1,178 @@
# Data Model: User Profile Dashboard Filter
## Overview
This model defines persistent and transient entities needed to:
1. store per-user dashboard filter preferences,
2. support Superset account lookup by selected environment during profile binding,
3. apply deterministic dashboard filtering on the main dashboards list page.
---
## 1) UserDashboardPreference (persistent)
**Purpose**: Stores the authenticated users saved profile settings for dashboard filtering.
### Fields
- `preference_id` (string/uuid, required, unique)
- `user_id` (string, required, unique, FK -> `users.id`)
- `superset_username` (string, optional)
- `superset_username_normalized` (string, optional)
- `show_only_my_dashboards` (boolean, required, default `false`)
- `created_at` (datetime, required)
- `updated_at` (datetime, required)
### Validation Rules
- `user_id` must be unique (one preference record per user).
- If `show_only_my_dashboards = true`, `superset_username` must be non-empty and valid.
- `superset_username_normalized` must be `trim(lower(superset_username))` when username exists.
- A user can mutate only their own preference record.
### Lifecycle Notes
- `unconfigured` -> `configured_disabled` (username optional, toggle off)
- `configured_disabled` -> `configured_enabled` (valid username + toggle on)
- `configured_enabled` -> `configured_disabled` (toggle off, username may be retained)
---
## 2) SupersetAccountLookupRequest (transient)
**Purpose**: Represents a profile-page request to fetch account candidates from a selected Superset environment.
### Fields
- `requester_user_id` (string, required)
- `environment_id` (string, required)
- `search` (string, optional)
- `page_index` (integer, required, default `0`, min `0`)
- `page_size` (integer, required, default `20`, min `1`, max `100`)
- `sort_column` (enum, required, default `username`)
Allowed: `username`, `first_name`, `last_name`, `email`
- `sort_order` (enum, required, default `desc`)
Allowed: `asc`, `desc`
### Validation Rules
- `requester_user_id` must be authenticated user identity.
- `environment_id` must exist in configured environments.
- Pagination and sort values must respect allowed bounds/enums.
### Integration Note
Backend may translate these fields into Superset-compatible query style (including `pageIndex`, `sortColumn`, `sortOrder`) when contacting selected environment.
---
## 3) SupersetAccountCandidate (transient)
**Purpose**: Canonical account option returned to frontend from selected Superset environment.
### Fields
- `environment_id` (string, required)
- `username` (string, required)
- `display_name` (string, optional)
- `email` (string, optional)
- `is_active` (boolean, optional)
### Validation Rules
- `username` must be non-empty.
- Candidate list should be deduplicated by normalized username per environment response.
- Payload must remain stable even if upstream Superset shape differs.
---
## 4) DashboardActorProjection (derived, transient)
**Purpose**: Normalized actor fields used for deterministic filter matching.
### Fields
- `dashboard_id` (integer, required)
- `owners_tokens` (array[string], required)
- `modified_by_token` (string, optional)
- `match_reason` (enum, required)
Allowed: `owners`, `modified_by`, `none`
### Validation Rules
- `owners_tokens` must contain trimmed unique non-empty tokens.
- `modified_by_token` must be trimmed when present.
- Matching uses case-insensitive compare against `superset_username_normalized`.
---
## 5) DashboardProfileFilterContext (transient)
**Purpose**: Carries effective profile-filter state for dashboards list response.
### Fields
- `user_id` (string, required)
- `apply_profile_default` (boolean, required)
- `override_show_all` (boolean, required)
- `effective_filter_applied` (boolean, required)
- `effective_username` (string, optional)
- `source_page` (enum, required)
Allowed: `dashboards_main`, `other`
### Validation Rules
- Auto-apply is valid only when `source_page = dashboards_main`.
- If `override_show_all = true`, `effective_filter_applied` must be `false`.
- If preference toggle is disabled, `effective_filter_applied` must be `false`.
---
## 6) ProfileSaveResult (transient)
**Purpose**: Normalized save response used by profile page UX feedback.
### Fields
- `status` (enum, required)
Allowed: `success`, `error`
- `message` (string, optional)
- `validation_errors` (array[string], optional)
- `saved_preference` (`UserDashboardPreference`, optional)
### Validation Rules
- `saved_preference` present only for successful save.
- `validation_errors` present only for invalid input scenarios.
---
## Relationships
1. `User` 1 — 1 `UserDashboardPreference`
2. `Environment` 1 — N `SupersetAccountLookupRequest` (runtime)
3. `SupersetAccountLookupRequest` 1 — N `SupersetAccountCandidate` (runtime)
4. `UserDashboardPreference` + `DashboardActorProjection` -> filtered dashboard list projection
5. `DashboardProfileFilterContext` decorates dashboards response behavior/state
---
## State Model Highlights
### Profile Binding Flow
`idle -> environment_selected -> lookup_loading -> lookup_loaded|lookup_failed -> save_loading -> save_success|save_error`
### Dashboards Filter Flow (main list page)
`default_apply -> active_filtered -> temporary_show_all -> leave_page -> default_apply_restored`
---
## Data Consistency & Security Invariants
- Preference mutations are always scoped to authenticated user identity.
- Bound username normalization is deterministic and identical across save and match paths.
- Temporary "show all" override does not mutate saved preference.
- Account lookup errors never force data loss; manual username fallback remains available.

View File

@@ -0,0 +1,220 @@
# Implementation Plan: User Profile Dashboard Filter
**Branch**: `024-user-dashboard-filter` | **Date**: 2026-03-04 | **Spec**: [`spec.md`](./spec.md)
**Input**: Feature specification from [`/specs/024-user-dashboard-filter/spec.md`](./spec.md)
## Summary
Add a dedicated user profile page where an authenticated user can:
1. select a Superset environment and lookup account candidates for binding,
2. save one global Apache Superset username (shared across all environments), and
3. save a default preference to show only dashboards related to that username.
The dashboard list page (`/dashboards`) applies this preference by default using the clarified rule:
- include dashboard when username matches `owners` **OR** `modified_by`;
- normalize with trim + case-insensitive comparison.
Account lookup failures are degraded-but-usable (manual username entry remains available).
The user can temporarily clear the filter from the dashboards page; preference remains saved and is applied again on next page visit.
## Technical Context
**Language/Version**: Python 3.9+ (backend), Node.js 18+ + SvelteKit (frontend)
**Primary Dependencies**: FastAPI, SQLAlchemy, Pydantic, existing auth stack (`get_current_user`), existing dashboards route/service, Svelte runes (`$state`, `$derived`, `$effect`), Tailwind CSS, frontend `api` wrapper
**Storage**: Existing auth database (`AUTH_DATABASE_URL`) with a dedicated per-user preference entity
**Testing**: `pytest` for backend routes/services, frontend integration/UX tests (existing JS/Svelte test stack)
**Target Platform**: Linux-hosted backend API + browser SPA frontend
**Project Type**: Web application (backend + frontend monorepo)
**Performance Goals**:
- Profile preferences read/save should complete in normal interactive latency (target p95 <= 300ms server-side in internal network).
- Dashboard list should reflect active default filter within one normal page reload cycle (target <= 2s for typical environments up to ~2k dashboards).
**Constraints**:
- Default filter auto-apply is allowed only on the main dashboards list page (`/dashboards`).
- Username is required when default filter is enabled.
- Username validation rejects whitespace-containing values and unsupported symbols.
- Matching must be case-insensitive and trim surrounding spaces on both saved username and dashboard actor fields.
- User may edit only own profile filter settings (self-service only, no cross-user edit API).
- Profile binding must support account lookup from selected Superset environment.
- Account lookup failure must be non-blocking and preserve manual username fallback.
**Scale/Scope**:
- 1 new backend profile API surface (read/update own settings + account lookup by selected environment).
- 1 new persistence model for user dashboard filter preferences.
- 1 Superset lookup adapter/proxy path for account candidate retrieval and normalization.
- 1 update to dashboards listing API query contract for deterministic “my dashboards” filtering.
- 1 new frontend profile page and 1 dashboards-page enhancement for active-filter indicator + temporary override.
- Localized copy updates (`en`, `ru`) and focused test additions.
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
- **I. Semantic Protocol Compliance**: PASS
New/updated modules will define `[DEF]` headers, `@TIER`, contracts, and proper closings. CRITICAL modules will include required testing tags and UX-state contracts.
- **II. Modular Plugin Architecture**: PASS
Feature is implemented via existing modular API/service/model architecture and centralized dependencies; no hardcoded environment/config bypasses are introduced.
- **III. Unified Frontend Experience**: PASS
UI work stays within SvelteKit + Tailwind conventions, all user-facing strings go through i18n locale files, API calls use existing `requestApi`/`fetchApi` wrappers.
- **IV. Security & RBAC**: PASS
Profile preferences API is self-scoped through authenticated identity (`get_current_user`), preventing cross-user updates. No permission escalation path is introduced.
- **V. Independent Testability**: PASS
Spec already defines independently testable user stories; design artifacts preserve isolated verification for profile save, default filtering, and temporary override behavior.
- **VI. Asynchronous Execution**: PASS
Feature introduces no long-running operations; existing async API patterns remain unchanged and compliant.
**Gate Result (pre-research)**: PASS
## Project Structure
### Documentation (this feature)
```text
specs/024-user-dashboard-filter/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│ ├── modules.md
│ └── api.yaml
└── tasks.md
```
### Source Code (repository root)
```text
backend/
├── src/
│ ├── api/
│ │ └── routes/
│ │ ├── dashboards.py # update: default my-dashboards filtering inputs
│ │ └── profile.py # new: own profile preference endpoints
│ ├── models/
│ │ └── profile.py # new: user dashboard preference persistence entity
│ ├── schemas/
│ │ └── profile.py # new: request/response schemas for profile prefs
│ └── services/
│ └── profile_service.py # new: preference read/write + validation orchestration
└── tests/
└── ... # new/updated route + service tests
frontend/
└── src/
├── lib/
│ ├── api.js # update: profile preference methods and dashboard query params
│ ├── i18n/
│ │ └── locales/
│ │ ├── en.json # update: profile/filter labels and errors
│ │ └── ru.json # update: profile/filter labels and errors
│ └── components/layout/
│ └── Sidebar.svelte # update: navigation link to profile page
└── routes/
├── profile/
│ └── +page.svelte # new: profile preferences page
└── dashboards/
└── +page.svelte # update: apply/override default my-dashboards filter
```
**Structure Decision**: Use existing web-application layout (backend + frontend) and add a focused profile subdomain (model/service/schema/route + frontend page), while extending current dashboards flow without introducing a new standalone subsystem.
## Phase 0 — Outline & Research Plan
Research focus areas:
1. **Preference persistence location and shape**
Evaluate safest persistence approach compatible with current auth/session model and existing DB lifecycle.
2. **Filtering locus and pagination correctness**
Decide where to apply “my dashboards” filtering so pagination/total counts stay deterministic.
3. **Username normalization/validation policy**
Formalize trim + case-insensitive matching and input validation boundaries.
4. **Temporary override semantics**
Ensure override is page-scoped and does not mutate saved preference.
5. **UX-state compatibility check**
Verify the plan preserves loading/success/error UX states defined in [`ux_reference.md`](./ux_reference.md).
Output: fully resolved [`research.md`](./research.md) with no unresolved clarifications.
## Phase 1 — Design & Contracts Plan
1. **Validate design against UX reference**
Map profile save states and dashboards filter indicator/empty-state flows to explicit contracts.
If architecture prevents these UX states, stop and raise risk before implementation.
2. **Data model extraction**
Define entities and relationships in [`data-model.md`](./data-model.md) for profile preferences and dashboard filter context.
3. **Semantic contracts**
Define/verify module contracts in [`contracts/modules.md`](./contracts/modules.md), including CRITICAL test tags and UX state mappings where required.
4. **API contract generation**
Produce backend/frontend synchronization contract in [`contracts/api.yaml`](./contracts/api.yaml).
5. **Operational usage flow**
Document end-to-end setup and verification in [`quickstart.md`](./quickstart.md).
6. **Agent context update**
Run agent context refresh script and capture result in planning output trace.
## Agent Context Update Record
- Command: `.specify/scripts/bash/update-agent-context.sh kilocode`
- Result: success (exit code 0)
- Updated file: `.kilocode/rules/specify-rules.md`
- Added context entries:
- Language: Python 3.9+ (backend), Node.js 18+ + SvelteKit (frontend)
- Framework: FastAPI, SQLAlchemy, Pydantic, auth stack, dashboards route/service, Svelte runes, Tailwind, frontend api wrapper
- Database: existing auth DB with dedicated per-user preference entity
## Phase 2 — Task Planning Approach
At `/speckit.tasks`, work will be decomposed into independent stories:
- **US1 (P1)**: Profile page with environment-based account lookup and saving Superset username + default toggle.
- **US2 (P1)**: Default “my dashboards” filtering on `/dashboards` with owners OR modified_by matching.
- **US3 (P2)**: Temporary filter override on dashboards page + restore-on-return behavior.
- **US4 (P2)**: Validation, UX feedback, and degraded lookup fallback handling (invalid username/save failure/lookup failure/empty state).
- **US5 (P3)**: Localization and test coverage updates.
Each story will include independent acceptance tests and explicit completion evidence.
## Post-Design Constitution Re-Check
(Performed after generating [`research.md`](./research.md), [`data-model.md`](./data-model.md), [`contracts/modules.md`](./contracts/modules.md), [`contracts/api.yaml`](./contracts/api.yaml), and [`quickstart.md`](./quickstart.md).)
- **I. Semantic Protocol Compliance**: PASS
Planned modules include explicit semantic contract envelopes and tiering in design artifacts.
- **II. Modular Plugin Architecture**: PASS
Design stays inside existing modular backend/frontend boundaries; no config hardcoding patterns required.
- **III. Unified Frontend Experience**: PASS
UX implementation relies on Tailwind + i18n + shared API wrappers, consistent with repository standards.
- **IV. Security & RBAC**: PASS
Self-service only profile preference operations; no cross-user mutation paths exposed by contract.
- **V. Independent Testability**: PASS
Contract and quickstart include independent checks for save, auto-filter, override, and recovery flows.
- **VI. Asynchronous Execution**: PASS
No new long-running jobs introduced; existing async API model remains intact.
**Gate Result (post-design)**: PASS
## Complexity Tracking
> Fill ONLY if Constitution Check has violations that must be justified
| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| None at planning stage | N/A | N/A |

View File

@@ -0,0 +1,206 @@
# Quickstart: User Profile Dashboard Filter
## Purpose
This quickstart describes how to validate the full feature flow:
1. bind Superset account on profile page with environment-based lookup,
2. save default "show only my dashboards" preference,
3. verify default filtering on `/dashboards`,
4. verify temporary override and restoration behavior.
---
## Prerequisites
1. App is running with backend + frontend in a reachable local environment.
2. At least one Superset environment is configured in settings.
3. Test user can authenticate into the app.
4. Selected Superset environment has reachable users endpoint and dashboard metadata containing `owners` and `modified_by`.
5. Test data includes at least:
- one dashboard matching test user by `owners` or `modified_by`,
- one dashboard not matching test user.
---
## Step 1 — Open Profile Page
1. Log in as test user.
2. Navigate to profile page (new navigation entry).
Expected:
- Profile form shows:
- Superset environment selector,
- account input (manual),
- "Show only my dashboards by default" toggle,
- Save/Cancel actions.
- Existing saved values (if any) are preloaded.
---
## Step 2 — Environment-Based Account Lookup
1. Select a configured Superset environment.
2. Wait for account lookup request to complete.
Expected (success path):
- Account candidate list appears.
- Candidate includes recognizable `username`/`display_name`.
- User can pick account candidate to fill binding field.
Expected (degraded path):
- If lookup fails, inline warning appears:
- "Cannot load Superset accounts for this environment right now. You can enter username manually."
- Manual username input remains enabled.
- Save remains available.
---
## Step 3 — Save Preference
1. Choose account candidate **or** manually enter valid username.
2. Enable `Show only my dashboards by default`.
3. Click Save.
Expected:
- Save enters loading state.
- Success toast confirms persistence.
- Reloading profile page shows the same saved values.
Validation checks:
- If username invalid (e.g., contains spaces), save is blocked with validation message.
- If toggle enabled and username empty, save is blocked.
---
## Step 4 — Verify Default Filter on Dashboards Main Page
1. Navigate to `/dashboards`.
2. Ensure main list context applies profile default.
Expected:
- Active indicator (e.g., "My Dashboards Only") is visible.
- Returned list includes dashboards where normalized username matches:
- any `owners` entry **OR**
- `modified_by`.
- Non-matching dashboards are excluded.
- Pagination totals reflect filtered results (no client-side mismatch).
---
## Step 5 — Verify Temporary Override
1. Click active filter indicator clear action ("show all").
2. Observe list behavior.
Expected:
- All dashboards become visible for this page session/context.
- Saved profile preference is **not** changed.
- Indicator reflects override state.
3. Leave dashboards page and return to `/dashboards`.
Expected:
- Default profile filter is auto-applied again.
- Indicator returns to active filtered state.
---
## Step 6 — Verify Recovery / Error UX
### 6A. Lookup Failure Recovery
1. Simulate Superset lookup failure (temporary env outage or invalid env).
2. Open profile page and select environment.
Expected:
- Non-blocking warning shown.
- Manual username fallback remains possible.
- Save succeeds with valid manual username.
### 6B. No Match Empty State
1. Save valid username that matches no dashboards.
2. Open `/dashboards`.
Expected:
- Friendly empty state message is displayed.
- User can clear filter override to see all dashboards.
---
## Backend API Smoke Checklist
### Profile endpoints
- `GET /api/profile/preferences` returns authenticated user's preference only.
- `PATCH /api/profile/preferences` persists normalized username + toggle.
- `GET /api/profile/superset-accounts?environment_id=<id>` returns:
- success payload with candidates, or
- degraded payload with warning + empty list.
### Dashboards endpoint
- `GET /api/dashboards?...&page_context=dashboards_main&apply_profile_default=true&override_show_all=false`
returns filtered result + effective filter metadata.
- Same call with `override_show_all=true` returns unfiltered list and marks effective filter as not applied.
---
## Acceptance Checklist (Operator)
- [ ] Profile page visible and reachable from navigation.
- [ ] Environment-based account lookup works for binding.
- [ ] Lookup failure path allows manual entry (non-blocking).
- [ ] Preference save persists across reload/session.
- [ ] `/dashboards` applies default filter by `owners OR modified_by`.
- [ ] Temporary clear shows all dashboards without changing saved preference.
- [ ] Re-entering `/dashboards` restores default filtered behavior.
- [ ] i18n texts are present for new profile/lookup/filter states (EN + RU).
---
## Troubleshooting Matrix
| Category | Symptom | Likely Cause | Recovery |
|---|---|---|---|
| `lookup` | Account list does not load | Superset env unavailable or upstream endpoint issue | Show warning, use manual username, retry later |
| `validation` | Save blocked unexpectedly | Invalid username format or empty username with toggle on | Correct username, re-save |
| `filtering` | Unexpected dashboards included/excluded | Mismatch in normalization or actor fields | Confirm trim/case-insensitive compare and `owners OR modified_by` logic |
| `override` | Temporary clear persists incorrectly | Override state leaking into saved preference | Verify override is page-scoped and non-persistent |
---
## Suggested Test Commands (Backend)
Use project venv and `python3` as required.
```bash
cd backend && .venv/bin/python3 -m pytest tests -k "profile or dashboards" -q
```
If adding dedicated route tests for this feature:
```bash
cd backend && .venv/bin/python3 -m pytest src/api/routes/__tests__ -k "profile or dashboards" -q
```
---
## Exit Criteria
Feature is considered ready for implementation tasking when:
1. All acceptance checklist items above are reproducibly passable.
2. Contracts in [`contracts/modules.md`](./contracts/modules.md) and [`contracts/api.yaml`](./contracts/api.yaml) cover all required paths (happy + degraded + edge).
3. No unresolved clarification remains in [`research.md`](./research.md).

View File

@@ -0,0 +1,197 @@
# Phase 0 Research: User Profile Dashboard Filter
## Decision 1: Profile binding includes Superset account lookup by selected environment
**Decision**
During account binding on profile page, user selects a Superset environment and the system requests account candidates from that environment (as requested by stakeholder, including compatibility with Superset users-list style such as `/users/?pageIndex=...&sortColumn=...&sortOrder=...`).
**Rationale**
The new requirement explicitly asks for environment-based account querying during binding. This improves correctness and reduces manual typos in usernames.
**Alternatives considered**
- **Manual username entry only**: rejected (does not satisfy requested behavior).
- **Global account lookup without environment context**: rejected (incorrect in multi-environment setups).
- **Browser direct call to Superset from frontend**: rejected (CORS/session fragility and credential exposure risks).
---
## Decision 2: Account lookup is executed via backend proxy, not direct frontend-to-Superset fetch
**Decision**
Frontend calls internal API; backend queries selected Superset environment using existing server-side environment credentials/session flow and returns normalized account candidates.
**Rationale**
Current backend already handles Superset auth/session (`security/login` + CSRF) through existing network client. Reusing that channel is safer and operationally stable.
**Alternatives considered**
- **Frontend fetch with `credentials: include` directly to Superset**: rejected for cross-origin and cookie-domain uncertainty; also weak security boundary.
- **Store Superset cookies/tokens in frontend state**: rejected as security anti-pattern.
- **Separate external integration service**: rejected as unnecessary complexity for current scope.
---
## Decision 3: Preserve one global bound Superset username per app user
**Decision**
Even with environment-based lookup for convenience, saved preference remains a single global Superset username per application user (already clarified), not per-environment mapping.
**Rationale**
This preserves previously accepted scope and avoids expanding feature into multi-mapping profile management.
**Alternatives considered**
- **Per-environment username mapping**: rejected (scope increase, additional UX complexity).
- **Multiple candidate bindings at once**: rejected (not required and harder to validate).
---
## Decision 4: Use deterministic normalization and validation for bound username
**Decision**
Apply consistent policy:
1. trim leading/trailing spaces,
2. compare case-insensitively,
3. block save when filter is enabled and username is empty,
4. reject unsupported format (including spaces inside username),
5. store normalized companion value for comparison.
**Rationale**
Matches accepted clarifications and keeps filter behavior deterministic across inconsistent source formatting.
**Alternatives considered**
- **Case-sensitive match**: rejected (contradicts accepted clarification).
- **Frontend-only validation**: rejected (backend must remain source of truth).
- **No format validation**: rejected (conflicts with spec acceptance criteria).
---
## Decision 5: Apply profile filter on backend dashboards listing path for pagination correctness
**Decision**
Main dashboards list endpoint applies profile-filter logic server-side when requested by `/dashboards` page context. Matching rule is `owners OR modified_by` against normalized bound username.
**Rationale**
Server-side filtering keeps `total`, `total_pages`, and page slices consistent. Frontend-only filtering after pagination would produce misleading counts.
**Alternatives considered**
- **Frontend-only filtering**: rejected (breaks pagination semantics).
- **Separate “my dashboards” endpoint**: rejected (unnecessary API fragmentation).
- **Always-on server auto-filter for all dashboard endpoints**: rejected (clarified scope says only main list page auto-apply).
---
## Decision 6: Temporary “show all” override is page-scoped and non-persistent
**Decision**
Dashboards page supports temporary clear of active profile filter without changing saved profile preference; re-entering page restores default behavior.
**Rationale**
Directly satisfies FR-011/FR-012 semantics and avoids accidental preference mutation.
**Alternatives considered**
- **Persist override as profile update**: rejected (not temporary).
- **Global override across app routes**: rejected (scope leak and confusing UX).
- **No override**: rejected (explicit requirement exists).
---
## Decision 7: Account lookup failure is non-blocking with manual fallback
**Decision**
If environment account lookup fails or is unavailable, system shows warning and keeps manual username entry enabled; save remains possible.
**Rationale**
UX reference requires guiding recovery and avoiding dead-end errors. Binding should not become impossible due to transient connectivity.
**Alternatives considered**
- **Hard-block save on lookup failure**: rejected (poor resilience, unnecessary blocker).
- **Silent failure**: rejected (bad transparency and operator confidence).
- **Auto-disable filter on lookup failure**: rejected (unexpected side effects).
---
## Decision 8: Normalize account candidate payload from Superset into stable UI contract
**Decision**
Backend maps environment-specific Superset response into canonical candidate shape:
- `username` (required),
- `display_name` (preferred),
- `email` (optional),
- `environment_id` (echo context).
**Rationale**
Superset deployments may differ in fields and endpoint format; normalized response shields frontend from version-specific payload drift.
**Alternatives considered**
- **Expose raw Superset payload directly**: rejected (tight coupling and brittle UI).
- **Single-string list only**: rejected (insufficient context for user choice).
- **Environment-agnostic cache-only list**: rejected (staleness risk).
---
## Decision 9: Navigation and i18n must reflect new profile flow and lookup states
**Decision**
Add profile entry in app navigation and introduce localized text for:
- environment selector label,
- account lookup loading/empty/error states,
- manual fallback hint,
- active filter indicator and clear action.
**Rationale**
Constitution requires i18n for all user-facing text and unified UX conventions.
**Alternatives considered**
- **Hardcoded inline strings**: rejected (constitutional violation).
- **Reuse unrelated settings labels**: rejected (ambiguous UX).
- **No nav entry (deep-link only)**: rejected (discoverability issue).
---
## Decision 10: Testing strategy covers binding lookup + filtering + recovery independently
**Decision**
Define independent checks for:
1. profile preference read/update ownership rules,
2. account lookup by selected environment (success + failure fallback),
3. dashboards `owners OR modified_by` filtering correctness,
4. temporary override restore behavior,
5. localized UX feedback states.
**Rationale**
Supports constitution independent-testability and reduces regression ambiguity.
**Alternatives considered**
- **Only e2e flow tests**: rejected (debugging becomes expensive).
- **Only unit tests**: rejected (insufficient contract confidence).
- **Manual QA only**: rejected (non-repeatable quality gate).
---
## UX Feasibility Check (Phase 0 -> Phase 1)
Architecture remains compatible with UX reference after requirement expansion:
- Profile page can represent additional **Lookup Loading** / **Lookup Error** states without breaking existing save states.
- Manual username fallback preserves uninterrupted happy-path completion.
- Dashboards page active filter badge + temporary clear remains intact with server-side filtering.
**Result**: No blocking UX/architecture conflict. Phase 1 design can proceed.
---
## Open Clarifications Status
All active clarifications are resolved, including the newly introduced one:
1. Global Superset username across environments — resolved.
2. Include when `owners OR modified_by` — resolved.
3. Case-insensitive + trim matching — resolved.
4. Own-settings-only edit rule — resolved.
5. Auto-apply only on main dashboards list page — resolved.
6. During binding, query Superset accounts from selected environment — resolved.
No unresolved `NEEDS CLARIFICATION` markers remain.

View File

@@ -0,0 +1,135 @@
# Feature Specification: User Profile Dashboard Filter
**Feature Branch**: `024-user-dashboard-filter`
**Created**: 2026-03-04
**Status**: Draft
**Input**: User description: "Необходимо добавить страницу пользовательского профиля. В странице стоит указать следующие настройки - показывать по умолчанию только мои дашборды. Для этого в настройках следует указать поле для аккаунта Apache Superset, и дальше фильтровать все дашборды по имени owners | modified_by"
## Clarifications
### Session 2026-03-04
- Q: How should username mapping work if users switch between different Superset environments? → A: One global Superset username for all environments.
- Q: What exact filter logic should be used for owners and modified_by? → A: Include dashboard if username matches owners OR modified_by.
- Q: How should username matching work between saved profile username and dashboard fields? → A: Compare case-insensitively and trim surrounding spaces.
- Q: Who is allowed to edit the dashboard filter settings on the profile page? → A: Only the logged-in user can edit their own profile settings.
- Q: Where should the default "my dashboards" filter be applied in the product? → A: Only on the main dashboards list page.
- Q: Should profile binding support querying Apache Superset accounts from a selected environment? → A: Yes, provide account lookup from Superset environment during account binding.
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Configure Dashboard Filter Preference (Priority: P1)
As a data analyst working with Apache Superset dashboards, I want to configure my profile to show only dashboards I own or have modified, so that I can quickly access my work without scrolling through irrelevant dashboards.
**Why this priority**: This is the core value of the feature - personalization of the dashboard view to improve user productivity. Without this, the feature provides no value.
**Independent Test**: Can be fully tested by navigating to the profile page, entering a Superset username, enabling the filter, saving preferences, and verifying that only matching dashboards appear in the dashboard list.
**Acceptance Scenarios**:
1. **Given** the user is on their profile page, **When** they enter a valid Apache Superset username and enable "Show only my dashboards", **Then** the system saves their preference and applies the filter to the dashboard list
2. **Given** the user has saved their filter preference, **When** they navigate to the dashboard list, **Then** only dashboards where they are listed as owner or modifier are displayed
3. **Given** the user has enabled the filter, **When** they disable it and save, **Then** the dashboard list shows all available dashboards again
4. **Given** the user is binding their Apache Superset account, **When** they select a Superset environment in profile settings, **Then** the system loads available Superset accounts and allows selecting one for binding
5. **Given** the user enters an invalid username format, **When** they attempt to save, **Then** the system displays a validation error and prevents saving
---
### User Story 2 - Manage Profile Settings (Priority: P2)
As a user, I want to access and modify my profile settings from a dedicated page, so that I can control my dashboard viewing preferences at any time.
**Why this priority**: While essential for the feature to work, this is a supporting capability that enables the core filtering functionality. It can be implemented alongside the filtering logic.
**Independent Test**: Can be tested by navigating to the profile page, viewing current settings, making changes, and verifying persistence across sessions.
**Acceptance Scenarios**:
1. **Given** the user has previously saved their preferences, **When** they navigate to the profile page, **Then** their current settings are displayed (username and filter toggle state)
2. **Given** the user is on the profile page, **When** they click "Cancel" without saving changes, **Then** their previous settings remain unchanged
3. **Given** the user modifies their settings, **When** they click "Save", **Then** the changes persist and are reflected immediately in the dashboard list
4. **Given** a user attempts to edit another user's profile settings, **When** they submit changes, **Then** the system denies the action and keeps the target user's settings unchanged
---
### User Story 3 - Override Dashboard Filter (Priority: P3)
As a user, I want to temporarily view all dashboards even when my filter is enabled, so that I can explore dashboards owned by others when needed.
**Why this priority**: This enhances usability by providing flexibility. Users may occasionally need to see all dashboards without permanently changing their preference.
**Independent Test**: Can be tested by having a filter enabled, then using a clear/remove filter option on the dashboard list to temporarily show all dashboards.
**Acceptance Scenarios**:
1. **Given** the user has enabled "Show only my dashboards", **When** they click the filter indicator on the dashboard list, **Then** all dashboards are displayed and the filter is temporarily disabled
2. **Given** the user has temporarily disabled the filter, **When** they navigate away and return to the dashboard list, **Then** their original filter preference is still applied
---
### Edge Cases
- What happens when the user's Superset username is changed in Apache Superset after they've configured their filter?
- How does the system handle a user who has no dashboards as owner or modifier?
- What happens when the Apache Superset connection is unavailable when the user tries to save preferences?
- How does the system handle special characters or very long usernames?
- What happens when one user has different usernames across Superset environments?
- How does the system behave if the Superset account lookup request fails or returns partial data?
- How does the system behave if the user has never configured their profile settings?
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: System MUST provide a user profile page accessible from the main application interface
- **FR-002**: Profile page MUST include a text input field for Apache Superset username
- **FR-003**: Profile page MUST include a toggle control for "Show only my dashboards by default" setting
- **FR-004**: System MUST validate that the username field is not empty when the filter toggle is enabled
- **FR-005**: System MUST validate that the username does not contain spaces or invalid characters
- **FR-006**: System MUST persist user profile settings across sessions
- **FR-007**: When the filter is enabled, dashboard list MUST include a dashboard if the user's username matches the owners field OR the modified_by field
- **FR-008**: When the filter is enabled, dashboard list MUST exclude dashboards where the user's username matches neither owners nor modified_by
- **FR-009**: System MUST apply the filter immediately after saving profile preferences on the main dashboards list page
- **FR-010**: Dashboard list MUST display a visual indicator when a filter is active
- **FR-011**: System MUST allow users to temporarily clear the filter from the dashboard list view
- **FR-012**: System MUST restore the user's filter preference when returning to the dashboard list after temporarily clearing it
- **FR-013**: System MUST display appropriate error messages when settings cannot be saved
- **FR-014**: System MUST display a success confirmation when settings are saved successfully
- **FR-015**: System MUST handle the case where no dashboards match the filter criteria with an appropriate empty state message
- **FR-016**: System MUST use one global Apache Superset username per user profile across all Superset environments
- **FR-017**: System MUST match usernames using case-insensitive comparison after trimming leading and trailing spaces in both saved username and dashboard fields
- **FR-018**: System MUST allow users to edit only their own dashboard filter settings and MUST prevent edits to other users' settings
- **FR-019**: System MUST NOT auto-apply this default filter outside the main dashboards list page
- **FR-020**: While binding Apache Superset account in profile settings, system MUST allow selecting a Superset environment for account lookup
- **FR-021**: For selected environment, system MUST request and display available Superset accounts for user selection during binding
- **FR-022**: If Superset account lookup is unavailable, system MUST show a non-blocking error and allow manual username entry
### Key Entities
- **User Profile**: Stores user preferences including one global Apache Superset username and dashboard filter setting. Key attributes: username (text, global across environments), show_only_my_dashboards (boolean)
- **Dashboard**: Represents an Apache Superset dashboard. Key attributes: dashboard_id, title, owner (username), modified_by (username), last_modified_date
- **Filter Preference**: Represents the user's saved filtering behavior. Key attributes: enabled (boolean), username (text), filter_type (owners_or_modified_by)
- **Superset Account Candidate**: Represents an account returned from selected Superset environment lookup for profile binding. Key attributes: environment_id, username, display_name
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: Users can configure their dashboard filter preference in under 30 seconds
- **SC-002**: After saving preferences, users see the dashboard list update to the filtered view within 2 seconds
- **SC-003**: 95% of users successfully save their profile settings on first attempt
- **SC-004**: Users report a 50% reduction in time spent finding their dashboards in surveys
- **SC-005**: During peak usage, users can save preferences reliably without noticeable slowdowns
- **SC-006**: Filter correctly identifies user-owned dashboards with 99% accuracy based on owner and modified_by fields
- **SC-007**: Profile settings persist correctly across 100% of user sessions
## Assumptions
- Users have an existing Apache Superset account with a unique username
- The application can access dashboard metadata that includes owner and modified_by identifiers
- The application has an existing user authentication system that can be extended with profile settings
- Dashboard list page exists and can be modified to support filtering
- Apache Superset username format follows standard conventions (alphanumeric, underscores, hyphens allowed)
- Each user has one global Superset username that is used in all environments
- Configured Superset environments expose account listing data that can be queried during profile binding

View File

@@ -0,0 +1,204 @@
# Tasks: User Profile Dashboard Filter
**Input**: Design documents from `/specs/024-user-dashboard-filter/`
**Prerequisites**: `plan.md`, `spec.md`, `ux_reference.md`, `research.md`, `data-model.md`, `contracts/`, `quickstart.md`
**Tests**: Include backend contract/integration tests and frontend integration tests because the specification and quickstart define independently testable scenarios and recovery behavior.
**Organization**: Tasks are grouped by user story to enable independent implementation and testing.
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: User story label (`[US1]`, `[US2]`, `[US3]`) for story-phase tasks only
- Every task includes exact file path(s)
---
## Phase 1: Setup (Shared Infrastructure)
**Purpose**: Initialize scaffolding and shared assets for profile preferences and dashboards filter UX.
- [ ] T001 Create feature scaffolding files in `backend/src/models/profile.py`, `backend/src/schemas/profile.py`, `backend/src/services/profile_service.py`, `backend/src/api/routes/profile.py`, and `frontend/src/routes/profile/+page.svelte`
- [ ] T002 [P] Add shared fixture placeholders for profile/filter flows in `backend/tests/fixtures/profile/fixtures_profile_filter.json` and `frontend/src/routes/profile/__tests__/fixtures/profile.fixtures.js`
- [ ] T003 [P] Add i18n placeholder keys for profile lookup/filter states in `frontend/src/lib/i18n/locales/en.json` and `frontend/src/lib/i18n/locales/ru.json`
- [ ] T004 [P] Add profile navigation placeholder entry in `frontend/src/lib/components/layout/Sidebar.svelte`
---
## Phase 2: Foundational (Blocking Prerequisites)
**Purpose**: Implement core persistence, contracts, and API/client foundations required by all user stories.
**⚠️ CRITICAL**: No user story work starts before this phase is complete.
- [ ] T005 Implement persistent `UserDashboardPreference` entity and repository access in `backend/src/models/profile.py` and `backend/src/core/auth/repository.py`
- [ ] T006 [P] Implement profile preference and Superset lookup schemas in `backend/src/schemas/profile.py`
- [ ] T007 [P] Implement Superset account lookup adapter for selected environment (paging/sort passthrough and normalization) in `backend/src/core/superset_client.py`
- [ ] T008 Implement profile domain orchestration in `backend/src/services/profile_service.py` (CRITICAL: PRE authenticated user + payload/environment; POST self-scoped normalized preference save and deterministic actor matching helper; UX_STATE backend service, no direct UI states; TEST_FIXTURE `valid_profile_update`; TEST_EDGE `enable_without_username`, `cross_user_mutation`, `lookup_env_not_found`)
- [ ] T009 Implement profile API endpoints in `backend/src/api/routes/profile.py` (CRITICAL: PRE valid auth token, self context, `environment_id` for lookup; POST returns self-only preference payload or degraded lookup warning payload; UX_STATE supports profile page save/lookup state mapping through stable response shape; TEST_FIXTURE `get_my_preference_ok`; TEST_EDGE `unauthorized_request`, `invalid_username_payload`, `superset_lookup_upstream_error`)
- [ ] T010 Wire profile router registration in `backend/src/api/routes/__init__.py` and `backend/src/app.py`
- [ ] T011 [P] Extend frontend API methods for profile preferences, Superset account lookup, and dashboards profile filter params in `frontend/src/lib/api.js`
- [ ] T012 [P] Create backend test skeletons for profile and dashboards filter contracts in `backend/src/api/routes/__tests__/test_profile_api.py` and `backend/src/api/routes/__tests__/test_dashboards.py`
**Checkpoint**: Foundation ready; user stories can now be implemented and validated independently.
---
## Phase 3: User Story 1 - Configure Dashboard Filter Preference (Priority: P1) 🎯 MVP
**Goal**: Let user bind Superset account (with environment-based lookup), save global username + default toggle, and see filtered dashboards by default.
**Independent Test**: On profile page select environment, load/select account (or manual fallback), save enabled preference, then open `/dashboards` and verify only matching dashboards (`owners OR modified_by`) are shown.
### Tests for User Story 1
- [ ] T013 [P] [US1] Add contract tests for `GET/PATCH /api/profile/preferences` validation and persistence rules in `backend/src/api/routes/__tests__/test_profile_api.py`
- [ ] T014 [P] [US1] Add dashboards filter contract tests for `owners OR modified_by` with trim + case-insensitive matching in `backend/src/api/routes/__tests__/test_dashboards.py`
- [ ] T015 [P] [US1] Add frontend integration test for profile binding happy path (lookup success and manual fallback save) in `frontend/src/routes/profile/__tests__/profile-preferences.integration.test.js`
### Implementation for User Story 1
- [ ] T016 [US1] Implement lookup + save rules (global username, normalization, non-blocking degraded lookup) in `backend/src/services/profile_service.py` (CRITICAL: PRE authenticated user and selected environment for lookup; POST persisted normalized global username and lookup fallback remains save-capable; UX_STATE supports `LookupLoading/LookupError/Saving/SaveSuccess/SaveError` via service outcomes; TEST_FIXTURE `valid_profile_update`; TEST_EDGE `enable_without_username`, `lookup_env_not_found`)
- [ ] T017 [US1] Implement profile route request/response mapping for preference save/get + account lookup in `backend/src/api/routes/profile.py` (CRITICAL: PRE self-scoped authenticated request; POST stable `ProfilePreferenceResponse` and `SupersetAccountLookupResponse`; UX_STATE provides explicit degraded warning for profile UI recovery; TEST_FIXTURE `get_my_preference_ok`; TEST_EDGE `invalid_username_payload`, `superset_lookup_upstream_error`)
- [ ] T018 [US1] Implement default profile filtering on dashboards main list in `backend/src/api/routes/dashboards.py` (CRITICAL: PRE parsed `page_context/apply_profile_default/override_show_all` query context; POST response totals and pagination remain deterministic with `owners OR modified_by` matching; UX_STATE supports filtered list and empty-filtered state metadata; TEST_FIXTURE `profile_filter_applied`; TEST_EDGE `no_match_results`)
- [ ] T019 [P] [US1] Implement profile page form with environment selector, account suggestions, manual username field, toggle, and save/cancel actions in `frontend/src/routes/profile/+page.svelte` (CRITICAL: PRE authenticated user and initial preference load; POST persisted values reflected in form state; UX_STATE `Default`, `LookupLoading`, `LookupError`, `Saving`, `SaveSuccess`, `SaveError`; TEST_FIXTURE `bind_account_happy_path`; TEST_EDGE `lookup_failed_manual_fallback`, `invalid_username`, `cancel_changes`)
- [ ] T020 [P] [US1] Implement dashboards active-filter indicator and filtered empty-state rendering in `frontend/src/routes/dashboards/+page.svelte` (CRITICAL: PRE effective filter metadata returned by dashboards API; POST filtered context is visible and understandable; UX_STATE `FilterActive`, `EmptyFiltered`; TEST_FIXTURE `default_profile_filter_applied`; TEST_EDGE `no_matching_dashboards`)
- [ ] T021 [US1] Finalize localized copy for profile binding, lookup warning, and filter-active texts in `frontend/src/lib/i18n/locales/en.json` and `frontend/src/lib/i18n/locales/ru.json`
- [ ] T022 [US1] Verify implementation matches `specs/024-user-dashboard-filter/ux_reference.md` (Happy Path & Errors)
**Checkpoint**: US1 is independently functional and demo-ready as MVP.
---
## Phase 4: User Story 2 - Manage Profile Settings (Priority: P2)
**Goal**: Ensure profile settings are easy to access/manage over time, with self-only edit enforcement and predictable cancel/reload behavior.
**Independent Test**: Open profile page, confirm existing values preload, change then cancel to keep original values, save valid changes, and verify cross-user edits are rejected.
### Tests for User Story 2
- [ ] T023 [P] [US2] Add backend authorization tests for self-only preference mutation and cross-user rejection in `backend/src/api/routes/__tests__/test_profile_api.py`
- [ ] T024 [P] [US2] Add frontend tests for preload, cancel without persistence, and saved-state reload in `frontend/src/routes/profile/__tests__/profile-settings-state.integration.test.js`
### Implementation for User Story 2
- [ ] T025 [US2] Implement preload and cancel-to-last-saved behavior in `frontend/src/routes/profile/+page.svelte` (CRITICAL: PRE current preference loaded before editing; POST cancel does not mutate persisted data; UX_STATE `Default`, `Saving`, `SaveError` retain user context; TEST_EDGE `cancel_changes`)
- [ ] T026 [US2] Enforce self-scope mutation guard across service and route in `backend/src/services/profile_service.py` and `backend/src/api/routes/profile.py` (CRITICAL: PRE authenticated actor identity; POST attempts to mutate another user are denied; UX_STATE maps denial into actionable error feedback; TEST_EDGE `cross_user_mutation`)
- [ ] T027 [US2] Implement consistent save success/error/validation feedback mapping in `frontend/src/routes/profile/+page.svelte` and `frontend/src/lib/api.js` (CRITICAL: PRE response includes validation or error details; POST user gets clear recovery guidance without data loss; UX_STATE `SaveSuccess`, `SaveError`; TEST_EDGE `invalid_username`)
- [ ] T028 [US2] Verify implementation matches `specs/024-user-dashboard-filter/ux_reference.md` (Happy Path & Errors)
**Checkpoint**: US2 independently functional with robust state management and ownership guardrails.
---
## Phase 5: User Story 3 - Override Dashboard Filter (Priority: P3)
**Goal**: Allow temporary "show all dashboards" override on dashboards page without changing saved default preference.
**Independent Test**: With default filter enabled, clear filter on `/dashboards` to see all dashboards, then leave and return to `/dashboards` and verify default filter is restored automatically.
### Tests for User Story 3
- [ ] T029 [P] [US3] Add backend tests for `override_show_all`, `page_context`, and effective filter metadata semantics in `backend/src/api/routes/__tests__/test_dashboards.py`
- [ ] T030 [P] [US3] Add frontend integration test for temporary clear and restore-on-return flow in `frontend/src/routes/dashboards/__tests__/dashboard-profile-override.integration.test.js`
### Implementation for User Story 3
- [ ] T031 [US3] Implement override semantics and effective filter metadata in dashboards API response in `backend/src/api/routes/dashboards.py` (CRITICAL: PRE main-list context with override flag; POST `effective_profile_filter.applied=false` when override is active and persisted preference remains unchanged; UX_STATE enables clear distinction between active-filter and override states; TEST_FIXTURE `profile_filter_applied`; TEST_EDGE `override_show_all`, `no_match_results`)
- [ ] T032 [US3] Implement temporary show-all control and restore-on-return behavior in `frontend/src/routes/dashboards/+page.svelte` (CRITICAL: PRE active filtered state available from response metadata; POST override is page-scoped and non-persistent; UX_STATE `FilterActive`, `OverrideActive`, `EmptyFiltered`; TEST_FIXTURE `default_profile_filter_applied`; TEST_EDGE `temporary_show_all`, `return_to_page`)
- [ ] T033 [US3] Verify implementation matches `specs/024-user-dashboard-filter/ux_reference.md` (Happy Path & Errors)
**Checkpoint**: US3 independently functional with non-persistent override behavior.
---
## Phase 6: Polish & Cross-Cutting Concerns
**Purpose**: Align final contracts/docs and record verification evidence across all delivered stories.
- [ ] T034 [P] Reconcile OpenAPI contract with implemented payloads and query semantics in `specs/024-user-dashboard-filter/contracts/api.yaml`
- [ ] T035 [P] Reconcile CRITICAL module contract annotations and edge-case traces in `specs/024-user-dashboard-filter/contracts/modules.md`
- [ ] T036 [P] Execute quickstart validation and record outcomes in `specs/024-user-dashboard-filter/quickstart.md` and `specs/024-user-dashboard-filter/tests/coverage.md`
- [ ] T037 Document final implementation notes and operational constraints in `specs/024-user-dashboard-filter/plan.md`
---
## Dependencies & Execution Order
### Phase Dependencies
- **Phase 1 (Setup)**: no dependencies
- **Phase 2 (Foundational)**: depends on Phase 1; blocks all user stories
- **Phase 3 (US1)**: depends on Phase 2 and delivers MVP
- **Phase 4 (US2)**: depends on Phase 2 and integrates with US1 profile baseline
- **Phase 5 (US3)**: depends on Phase 2 and builds on US1 filtered dashboards baseline
- **Phase 6 (Polish)**: depends on completion of selected user stories
### User Story Dependency Graph
- **US1 (P1)**: first deliverable and base for profile + default filtering
- **US2 (P2)**: extends profile management behavior and ownership guarantees
- **US3 (P3)**: extends dashboards behavior with temporary override
Graph: `US1 -> {US2, US3}`
### Parallel Opportunities
- **Setup**: T002, T003, T004
- **Foundational**: T006, T007, T011, T012 after T005 baseline
- **US1**: T013, T014, T015 in parallel; T019 and T020 can proceed in parallel after backend contracts stabilize
- **US2**: T023 and T024 in parallel
- **US3**: T029 and T030 in parallel
- **Polish**: T034, T035, T036 in parallel
---
## Parallel Example: User Story 1
```bash
Task: "T013 [US1] Contract tests for profile preferences in backend/src/api/routes/__tests__/test_profile_api.py"
Task: "T014 [US1] Dashboards matching tests in backend/src/api/routes/__tests__/test_dashboards.py"
Task: "T015 [US1] Frontend profile binding integration test in frontend/src/routes/profile/__tests__/profile-preferences.integration.test.js"
Task: "T019 [US1] Profile page UI implementation in frontend/src/routes/profile/+page.svelte"
Task: "T020 [US1] Dashboards filter indicator implementation in frontend/src/routes/dashboards/+page.svelte"
```
## Parallel Example: User Story 2
```bash
Task: "T023 [US2] Backend self-scope auth tests in backend/src/api/routes/__tests__/test_profile_api.py"
Task: "T024 [US2] Frontend preload/cancel tests in frontend/src/routes/profile/__tests__/profile-settings-state.integration.test.js"
```
## Parallel Example: User Story 3
```bash
Task: "T029 [US3] Backend override semantics tests in backend/src/api/routes/__tests__/test_dashboards.py"
Task: "T030 [US3] Frontend override restore integration test in frontend/src/routes/dashboards/__tests__/dashboard-profile-override.integration.test.js"
```
---
## Implementation Strategy
### MVP First (Recommended)
1. Complete Phase 1 + Phase 2.
2. Complete Phase 3 (US1).
3. Validate US1 independent test criteria.
4. Demo/deploy MVP.
### Incremental Delivery
1. Deliver US1 (profile binding + default filtering).
2. Deliver US2 (profile management reliability + self-scope hardening).
3. Deliver US3 (temporary override behavior).
4. Run Phase 6 cross-cutting validation and contract/documentation alignment.
### UX Preservation Rule
No task in this plan intentionally degrades UX defined in `specs/024-user-dashboard-filter/ux_reference.md`.
Mandatory UX verification tasks are included at the end of each user story phase: **T022**, **T028**, **T033**.

View File

@@ -0,0 +1,90 @@
# UX Reference: User Profile Dashboard Filter
**Feature Branch**: `024-user-dashboard-filter`
**Created**: 2026-03-04
**Status**: Draft
## 1. User Persona & Context
* **Who is the user?**: Data Analyst or Dashboard Developer who works with Apache Superset dashboards
* **What is their goal?**: Quickly find and access only the dashboards they own or have modified, reducing clutter from irrelevant dashboards
* **Context**: Browsing the dashboard list in a web application, wanting to personalize their view to focus on their own work
## 2. The "Happy Path" Narrative
The user navigates to their profile page, sees a clean settings section with a clearly labeled field for their Apache Superset username, and chooses a Superset environment for account lookup. The system loads available Superset accounts, the user selects their account (or enters manually if needed), toggles "Show only my dashboards" to "On", and clicks Save. The system confirms the change with a brief success message. When they return to the dashboard list, they immediately see only their own dashboards - no clutter, no scrolling through hundreds of irrelevant items. The experience feels instant, intuitive, and personalized.
## 3. Interface Mockups
### UI Layout & Flow
**Screen**: User Profile Page
* **Layout**: Single-column layout with clear sections. Header shows user information, followed by a "Dashboard Preferences" section.
* **Key Elements**:
* **Section Title**: "Dashboard Preferences" - Bold, prominent
* **Superset Environment Selector**: Dropdown for selecting environment used for account lookup. Label: "Superset Environment"
* **Apache Superset Username Input**: Text field with placeholder "Enter your Apache Superset username". Label: "Your Apache Superset Account"
* **Superset Account Suggestions**: Searchable list loaded from selected environment, allowing account selection for binding
* **Toggle Switch**: "Show only my dashboards by default" - Large, easy to tap/click. Default: Off
* **Save Button**: Primary action. Color: Blue. Text: "Save Preferences"
* **Cancel Button**: Secondary action. Text: "Cancel"
* **States**:
* **Default**: Fields populated with current settings (if any), Save button enabled
* **Lookup Loading**: Account suggestions show loading indicator after environment selection
* **Loading**: Save button shows spinner, text changes to "Saving..."
* **Success**: Toast notification appears top-right: "✓ Preferences saved!" (Green). Button returns to normal state
* **Error**: Toast notification appears top-right: "✗ Failed to save. Please try again." (Red)
**Screen**: Dashboard List (After Filter Applied)
* **Layout**: Grid or list view of dashboards
* **Key Elements**:
* **Filter Indicator**: Small badge showing "My Dashboards Only" with an "X" to clear filter
* **Dashboard Cards**: Each shows dashboard name, owner, last modified date
* **Empty State**: If no dashboards match, shows friendly message: "No dashboards found for your account. Try adjusting your filter settings."
## 4. The "Error" Experience
**Philosophy**: Don't just report the error; guide the user to the fix.
### Scenario A: Invalid Username Format
* **User Action**: Enters "John Doe" (with spaces) in the username field
* **System Response**:
* Input border turns Red. Message below input: "Username should not contain spaces. Please enter a valid Apache Superset username."
* Save button becomes disabled until error is resolved
* **Recovery**: User can immediately correct the username without page refresh
### Scenario B: Username Not Found in Superset
* **User Action**: Enters username that doesn't exist in connected Apache Superset instance
* **System Response**:
* Toast notification: "⚠ Username 'johndoe' not found in Apache Superset. Please verify your username."
* Settings are still saved, but filter may return no results
* **Recovery**: User can correct the username and try again, or disable the filter
### Scenario C: Connection Error to Superset
* **User Action**: Clicks Save while system cannot connect to Apache Superset
* **System Response**:
* Toast notification: "⚠ Cannot connect to Apache Superset. Your preferences were saved, but dashboard filtering may not work until connection is restored."
* Button returns to normal state
* **Recovery**: User can retry saving later, or contact administrator
### Scenario D: Account Lookup Failed for Selected Environment
* **User Action**: Selects a Superset environment, but account lookup request fails
* **System Response**:
* Inline warning near account field: "Cannot load Superset accounts for this environment right now. You can enter username manually."
* Username input stays editable, Save remains available
* **Recovery**: User enters username manually or retries environment lookup
## 5. Tone & Voice
* **Style**: Concise, Professional, User-friendly
* **Terminology**:
* Use "Apache Superset" (full name) on first mention, "Superset" thereafter
* Use "Dashboard" not "Dash" or "Board"
* Use "Username" not "User ID" or "Account Name"
* Use "Filter" not "Filtering" when referring to the feature