AdvisorPanel.utils.ts186 lines · main
1import dayjs from 'dayjs'
2import { Gauge, Inbox, Shield } from 'lucide-react'
3import type { ElementType } from 'react'
4
5import type { AdvisorItem, AdvisorLintItem, AdvisorNotificationItem } from './AdvisorPanel.types'
6import { lintInfoMap } from '@/components/interfaces/Linter/Linter.utils'
7import type { Lint } from '@/data/lint/lint-query'
8import type { Notification, NotificationData } from '@/data/notifications/notifications-v2-query'
9import type { AdvisorSeverity, AdvisorTab } from '@/state/advisor-state'
10
11export const MAX_HOMEPAGE_ADVISOR_ITEMS = 4
12
13export const severityOrder: Record<AdvisorSeverity, number> = {
14 critical: 0,
15 warning: 1,
16 info: 2,
17}
18
19export const lintLevelToSeverity = (level: Lint['level']): AdvisorSeverity => {
20 switch (level) {
21 case 'ERROR':
22 return 'critical'
23 case 'WARN':
24 return 'warning'
25 default:
26 return 'info'
27 }
28}
29
30export const notificationPriorityToSeverity = (
31 priority: string | null | undefined
32): AdvisorSeverity => {
33 switch (priority) {
34 case 'Critical':
35 return 'critical'
36 case 'Warning':
37 return 'warning'
38 default:
39 return 'info'
40 }
41}
42
43export const createAdvisorLintItems = (lintData?: Lint[]): AdvisorLintItem[] => {
44 if (!lintData) return []
45
46 return lintData
47 .map((lint): AdvisorLintItem | null => {
48 const categories = lint.categories || []
49 const tab = categories.includes('SECURITY')
50 ? ('security' as const)
51 : categories.includes('PERFORMANCE')
52 ? ('performance' as const)
53 : undefined
54
55 if (!tab) return null
56
57 return {
58 id: lint.cache_key,
59 title: lint.detail,
60 severity: lintLevelToSeverity(lint.level),
61 createdAt: undefined,
62 tab,
63 source: 'lint',
64 original: lint,
65 }
66 })
67 .filter((item): item is AdvisorLintItem => item !== null)
68}
69
70export const createAdvisorNotificationItems = (
71 notifications?: Notification[]
72): AdvisorNotificationItem[] => {
73 if (!notifications) return []
74
75 return notifications.map((notification) => {
76 const data = notification.data as NotificationData
77
78 return {
79 id: notification.id,
80 title: data.title,
81 severity: notificationPriorityToSeverity(notification.priority),
82 createdAt: dayjs(notification.inserted_at).valueOf(),
83 tab: 'messages' as const,
84 source: 'notification' as const,
85 original: notification,
86 }
87 })
88}
89
90export const sortAdvisorItems = <T extends AdvisorItem>(items: T[]) => {
91 return [...items].sort((a, b) => {
92 const severityDiff = severityOrder[a.severity] - severityOrder[b.severity]
93 if (severityDiff !== 0) return severityDiff
94
95 const createdDiff = (b.createdAt ?? 0) - (a.createdAt ?? 0)
96 if (createdDiff !== 0) return createdDiff
97
98 return getAdvisorItemDisplayTitle(a).localeCompare(getAdvisorItemDisplayTitle(b))
99 })
100}
101
102export const formatItemDate = (timestamp: number): string => {
103 const daysFromNow = dayjs().diff(dayjs(timestamp), 'day')
104 const formattedTimeFromNow = dayjs(timestamp).fromNow()
105 const formattedInsertedAt = dayjs(timestamp).format('MMM DD, YYYY')
106 return daysFromNow > 1 ? formattedInsertedAt : formattedTimeFromNow
107}
108
109export const getAdvisorItemDisplayTitle = (item: AdvisorItem): string => {
110 if (item.source === 'lint') {
111 return (
112 lintInfoMap.find((info) => info.name === item.original.name)?.title ||
113 item.title.replace(/[`\\]/g, '')
114 )
115 }
116
117 if (item.source === 'signal') {
118 return `${item.title}`
119 }
120
121 return item.title.replace(/[`\\]/g, '')
122}
123
124export const getAdvisorPanelItemDisplayTitle = (item: AdvisorItem): string => {
125 if (item.source === 'signal') {
126 return item.title
127 }
128
129 return getAdvisorItemDisplayTitle(item)
130}
131
132export const getAdvisorItemSecondaryText = (item: AdvisorItem): string | undefined => {
133 if (item.source === 'lint') {
134 return getLintEntityString(item.original)
135 }
136
137 if (item.source === 'signal') {
138 return `Database · ${item.sourceData.ip}`
139 }
140
141 return undefined
142}
143
144export const tabIconMap: Record<Exclude<AdvisorTab, 'all'>, ElementType> = {
145 security: Shield,
146 performance: Gauge,
147 messages: Inbox,
148}
149
150export const severityColorClasses: Record<AdvisorSeverity, string> = {
151 critical: 'text-destructive',
152 warning: 'text-warning',
153 info: 'text-foreground-light',
154}
155
156export const severityBadgeVariants: Record<AdvisorSeverity, 'destructive' | 'warning' | 'default'> =
157 {
158 critical: 'destructive',
159 warning: 'warning',
160 info: 'default',
161 }
162
163export const severityLabels: Record<AdvisorSeverity, string> = {
164 critical: 'Critical',
165 warning: 'Warning',
166 info: 'Info',
167}
168
169export const getLintEntityString = (lint: Lint | null): string | undefined => {
170 if (!lint?.metadata) {
171 return undefined
172 }
173
174 if (lint.metadata.entity) {
175 return lint.metadata.entity
176 }
177
178 if (lint.metadata.schema && lint.metadata.name) {
179 const extendedMetadata = lint.metadata as typeof lint.metadata & { arguments?: string }
180 const args =
181 typeof extendedMetadata.arguments === 'string' ? extendedMetadata.arguments : undefined
182 return `${lint.metadata.schema}.${lint.metadata.name}${args !== undefined ? `(${args})` : ''}`
183 }
184
185 return undefined
186}