Files
b5daafd264 feat(studio): add health category to the advisor panel (#49662)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Feature

## Summary

- Replace advisor panel tabs with multi-select category filters,
including Health
- Load health lints in the advisor panel (without blocking other
categories on the slower health request)
- Rename item `tab` to `category` and add empty-state copy for health

Stacked on #49661.

## To test

1. Open any project in Studio.
2. Open Advisor Center from the toolbar (the advisor / lightbulb
control).
3. Confirm the old All / Security / Performance / Messages **tabs are
gone**. You should see **Category**, **Status**, and **Severity**
filters instead.
4. Open Category and confirm **Health** is in the list with Security,
Performance, and Messages.
5. Select only **Health**:
- If the project is healthy: empty state “No health issues detected” /
“Your database, instance and services are all responding normally”.
   - If it is not: only health issues in the list.
6. Clear Health, then filter **Security** and **Performance**
separately. Those lists should still match what you expect from before.
7. With Health selected, also filter Severity to **Info** only. If
nothing matches, you should get “No items found” and a way to clear
filters — not a false “no health issues” message.
8. From project home, click an advisor card. Advisor Center should still
open on that same item.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Added category-based filtering for Advisor recommendations, including
Security, Performance, Health, and Messages.
- Added Health issue recommendations and category-specific icons,
labels, and empty-state messaging.
  - Advisor results now load according to the selected categories.
- Added clearer project requirements and hidden-item controls for
filtered results.

- **Bug Fixes**
  - Invalid category and severity filter values are safely ignored.
- Improved categorization and telemetry for Advisor items, including
health and security recommendations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 11:22:35 +02:00

73 lines
2.3 KiB
TypeScript

import { TextSearch } from 'lucide-react'
import { Button } from 'ui'
import type { AdvisorCategory } from '@/state/advisor-state'
const emptyCopyByCategory: Record<AdvisorCategory, { heading: string; message: string }> = {
security: {
heading: 'No security issues detected',
message: 'Congrats! There are no security issues detected for this project',
},
performance: {
heading: 'No performance issues detected',
message: 'Congrats! There are no performance issues detected for this project',
},
health: {
heading: 'No health issues detected',
message: 'Your database, instance and services are all responding normally',
},
messages: {
heading: 'No messages',
message: 'Messages alert you of upcoming changes or potential issues with your project',
},
}
interface EmptyAdvisorProps {
categoryFilters: AdvisorCategory[]
/**
* Whether severity or status filters are narrowing the list. Unlike the category filter,
* these can hide items the user would otherwise see, so we can't claim nothing was found.
*/
hasFilters: boolean
onClearFilters: () => void
}
export const EmptyAdvisor = ({
categoryFilters,
hasFilters,
onClearFilters,
}: EmptyAdvisorProps) => {
const singleCategory = categoryFilters.length === 1 ? categoryFilters[0] : undefined
const canClearFilters = hasFilters || categoryFilters.length > 0
const getCopy = () => {
if (hasFilters) {
return {
heading: 'No items found',
message: 'No advisor items match your current filters',
}
}
if (singleCategory) return emptyCopyByCategory[singleCategory]
return { heading: 'No issues detected', message: 'Congrats! There are no issues detected' }
}
const { heading, message } = getCopy()
return (
<div className="h-full px-6 flex flex-col items-center justify-center w-full gap-y-2">
<TextSearch className="text-foreground-muted" strokeWidth={1} />
<div className="flex flex-col items-center gap-y-0.5 text-center">
<h3 className="heading-default">{heading}</h3>
<p className="text-foreground-light text-sm text-balance">{message}</p>
</div>
{canClearFilters && (
<Button variant="outline" onClick={onClearFilters}>
Clear filters
</Button>
)}
</div>
)
}