Skip to content

BACKEND ISSUE 20 — [FEATURE] Implement Global Dynamic Dropdown Filters API #38

Description

@LeDuyCoder

🎯 Goal

Implement API GET /dashboard/filters để trả về cấu hình dropdown filter dùng chung cho các dashboard screen.

API này phục vụ khung FILTERS Bar xuất hiện ở phần trên của các màn hình analytics, bao gồm:

  • Timeframe
  • Domain
  • Region

Mục tiêu là giúp FE render filter dropdown một cách dynamic, không hard-code trực tiếp trong UI.


🖼️ UI Mapping

UI Block

FILTERS Bar

Filter Items

Timeframe
Domain
Region

Ví dụ UI:

[Last 5 Years ▼] [Computer Science ▼] [Global Distribution ▼]

📡 Endpoint

GET /dashboard/filters

📥 Query Parameters

Phase 1 không bắt buộc query params.

No required query params

Optional future params:

Param Type Required Description
project_id string / number No Nếu muốn trả filter theo project scope
screen string No Nếu muốn trả filter theo từng màn hình dashboard
include_counts boolean No Nếu muốn trả số lượng item tương ứng với mỗi option

✅ Example Request

GET /dashboard/filters

🧾 Response Contract

{
  "code": 200,
  "message": "Fetch filter configurations successfully",
  "data": {
    "timeframes": [
      "Last 5 Years",
      "Last 10 Years",
      "All Time"
    ],
    "domains": [
      "Biological Sciences",
      "Computer Science",
      "Physical Sciences"
    ],
    "regions": [
      "Global Distribution",
      "North America",
      "Asia Pacific"
    ]
  }
}

📌 Response Field Explanation

Field Type Description
timeframes string[] Danh sách khoảng thời gian cho dashboard
domains string[] Danh sách lĩnh vực / subject area
regions string[] Danh sách vùng địa lý / region filter

🧠 Business Logic

1. Timeframe options

Phase 1 trả các option cố định:

Last 5 Years
Last 10 Years
All Time

Mapping gợi ý cho FE / BE khi gọi các API analytics khác:

UI Label Meaning
Last 5 Years from_year = currentYear - 4, to_year = currentYear
Last 10 Years from_year = currentYear - 9, to_year = currentYear
All Time Không truyền from_year, to_year hoặc lấy toàn bộ data

Ví dụ nếu năm hiện tại là 2026:

Last 5 Years  -> 2022-2026
Last 10 Years -> 2017-2026

2. Domain options

domains nên lấy dynamic từ bảng Subject Area.

Suggested source:

Subject_Area.display_name

Suggested filters:

is_deleted = false

Sort:

display_name ASC

Nếu chưa có dữ liệu dynamic hoặc phase 1 cần mock:

Biological Sciences
Computer Science
Physical Sciences

3. Region options

regions dùng để lọc dữ liệu địa lý trên các analytics screen.

Phase 1 có thể trả static options:

Global Distribution
North America
Asia Pacific
Europe
Middle East & Africa
Latin America

Nếu hệ thống đã có country / affiliation data, có thể build dynamic từ country group.

Suggested source:

Institution.country
Author_Affiliation.country
Article_Affiliation.country

Sau đó map country sang region.


4. Default selected values

FE có thể dùng item đầu tiên làm default:

timeframes[0] = Last 5 Years
domains[0] = Biological Sciences hoặc All Domains nếu có
regions[0] = Global Distribution

Suggested improvement:

Add "All Domains" vào domains nếu UI cần filter toàn bộ domain.

Tuy nhiên contract hiện tại chưa có All Domains, nên phase 1 giữ đúng danh sách theo mock.


5. Data normalization

API cần đảm bảo:

  • Không trả duplicate option
  • Không trả string rỗng
  • Không trả null
  • Sort options ổn định
  • Response shape luôn cố định

🔄 Filter Mapping To Analytics APIs

Các filter này sẽ được FE dùng để gọi các API analytics khác.

Timeframe mapping

Last 5 Years  -> from_year / to_year
Last 10 Years -> from_year / to_year
All Time      -> omit from_year / to_year

Domain mapping

Domain -> subject_area

Ví dụ:

GET /analytics/journals/ranking?project_id=1&subject_area=Computer Science

Region mapping

Region -> region / country_group

Ví dụ future API:

GET /analytics/geo-distribution?project_id=1&region=Asia Pacific

📦 Data Source

Phase 1

Có thể dùng hybrid approach:

timeframes: static
domains: dynamic from Subject_Area
regions: static

Suggested tables

Subject_Area
Institution
Author_Affiliation
Article_Affiliation
Country
Region

Relevant schema flow nếu có region/country:

Institution.country_code -> Country.country_code
Country.region_id -> Region.region_id

⚠️ Edge Cases

1. No subject areas found

Nếu không có domain trong database:

{
  "code": 200,
  "message": "Fetch filter configurations successfully",
  "data": {
    "timeframes": [
      "Last 5 Years",
      "Last 10 Years",
      "All Time"
    ],
    "domains": [],
    "regions": [
      "Global Distribution",
      "North America",
      "Asia Pacific"
    ]
  }
}

2. Database query failure for domains

Nếu query domain lỗi, API có thể:

Option A: Return error

{
  "code": 500,
  "message": "Failed to fetch filter configurations",
  "data": null
}

Option B: Fallback static domains

{
  "code": 200,
  "message": "Fetch filter configurations successfully",
  "data": {
    "timeframes": [
      "Last 5 Years",
      "Last 10 Years",
      "All Time"
    ],
    "domains": [
      "Biological Sciences",
      "Computer Science",
      "Physical Sciences"
    ],
    "regions": [
      "Global Distribution",
      "North America",
      "Asia Pacific"
    ]
  }
}

Suggested phase 1:

Use fallback static domains if dynamic source unavailable.

3. Duplicate domains

Nếu database có duplicate display name:

Return unique domain names only

4. Deleted subject areas

Nếu Subject_Area.is_deleted = true:

Do not include in domains

5. Empty strings or null values

Nếu option bị null hoặc empty string:

Ignore option

🧪 Acceptance Criteria

  • API GET /dashboard/filters hoạt động
  • Response có code, message, data
  • data.timeframes luôn là array
  • data.domains luôn là array
  • data.regions luôn là array
  • timeframesLast 5 Years
  • timeframesLast 10 Years
  • timeframesAll Time
  • domains lấy từ Subject_Area.display_name nếu có dữ liệu
  • domains không chứa null hoặc empty string
  • domains không duplicate
  • regionsGlobal Distribution
  • regionsNorth America
  • regionsAsia Pacific
  • Response shape luôn ổn định để FE render dropdown trực tiếp
  • Không có console/server error khi gọi API
  • Response time với dữ liệu nhỏ dưới 100ms

🧪 Test Cases

TC-01: Fetch filters successfully

Request

GET /dashboard/filters

Expected

{
  "code": 200,
  "message": "Fetch filter configurations successfully",
  "data": {
    "timeframes": [
      "Last 5 Years",
      "Last 10 Years",
      "All Time"
    ],
    "domains": [
      "Biological Sciences",
      "Computer Science",
      "Physical Sciences"
    ],
    "regions": [
      "Global Distribution",
      "North America",
      "Asia Pacific"
    ]
  }
}

TC-02: Domains are loaded from Subject_Area

Input

Database has:

Computer Science
Biological Sciences
Physical Sciences

Expected

Response contains:

{
  "domains": [
    "Biological Sciences",
    "Computer Science",
    "Physical Sciences"
  ]
}

Sorted alphabetically.


TC-03: Ignore deleted subject areas

Input

Computer Science, is_deleted = false
Outdated Domain, is_deleted = true

Expected

Computer Science is returned
Outdated Domain is not returned

TC-04: No domains found

Input

No active Subject_Area rows.

Expected

{
  "code": 200,
  "message": "Fetch filter configurations successfully",
  "data": {
    "timeframes": [
      "Last 5 Years",
      "Last 10 Years",
      "All Time"
    ],
    "domains": [],
    "regions": [
      "Global Distribution",
      "North America",
      "Asia Pacific"
    ]
  }
}

TC-05: Duplicate domain names

Input

Database has duplicate:

Computer Science
Computer Science

Expected

Response only includes one:

{
  "domains": [
    "Computer Science"
  ]
}

TC-06: FE can render dropdowns directly

Steps

  1. Call GET /dashboard/filters
  2. Pass timeframes to Timeframe dropdown
  3. Pass domains to Domain dropdown
  4. Pass regions to Region dropdown

Expected

  • Dropdowns render without additional transformation
  • No null option appears
  • No duplicate option appears

📌 Implementation Notes

Nên tách logic thành các phần riêng:

Controller
  -> call service
Service
  -> get timeframe options
  -> get domain options
  -> get region options
  -> normalize options
  -> build response contract

Gợi ý function:

getDashboardFilters()
getTimeframeOptions()
getDomainOptions()
getRegionOptions()
normalizeFilterOptions(options)
buildDashboardFiltersResponse(timeframes, domains, regions)

📦 Suggested Mock Data

const mockDashboardFilters = {
  timeframes: [
    "Last 5 Years",
    "Last 10 Years",
    "All Time"
  ],
  domains: [
    "Biological Sciences",
    "Computer Science",
    "Physical Sciences"
  ],
  regions: [
    "Global Distribution",
    "North America",
    "Asia Pacific"
  ]
};

🚀 Future Improvements

  • Return label and value instead of raw strings:
{
  "timeframes": [
    {
      "label": "Last 5 Years",
      "value": "last_5_years",
      "fromYear": 2022,
      "toYear": 2026
    }
  ]
}
  • Add counts per filter option:
{
  "label": "Computer Science",
  "value": "computer_science",
  "count": 1200
}
  • Add defaultValues:
{
  "defaultValues": {
    "timeframe": "Last 5 Years",
    "domain": "Computer Science",
    "region": "Global Distribution"
  }
}
  • Add project-aware filters:
GET /dashboard/filters?project_id=1
  • Add screen-specific filters:
GET /dashboard/filters?screen=journals
  • Add Redis caching:
dashboard:filters:v1
dashboard:filters:{project_id}:{screen}
  • Optimize query with indexes on:
    • subject_area_id
    • display_name
    • is_deleted

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions