AdvisorPanel.utils.ts186 lines · main
| 1 | import dayjs from 'dayjs' |
| 2 | import { Gauge, Inbox, Shield } from 'lucide-react' |
| 3 | import type { ElementType } from 'react' |
| 4 | |
| 5 | import type { AdvisorItem, AdvisorLintItem, AdvisorNotificationItem } from './AdvisorPanel.types' |
| 6 | import { lintInfoMap } from '@/components/interfaces/Linter/Linter.utils' |
| 7 | import type { Lint } from '@/data/lint/lint-query' |
| 8 | import type { Notification, NotificationData } from '@/data/notifications/notifications-v2-query' |
| 9 | import type { AdvisorSeverity, AdvisorTab } from '@/state/advisor-state' |
| 10 | |
| 11 | export const MAX_HOMEPAGE_ADVISOR_ITEMS = 4 |
| 12 | |
| 13 | export const severityOrder: Record<AdvisorSeverity, number> = { |
| 14 | critical: 0, |
| 15 | warning: 1, |
| 16 | info: 2, |
| 17 | } |
| 18 | |
| 19 | export 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 | |
| 30 | export 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 | |
| 43 | export 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 | |
| 70 | export 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 | |
| 90 | export 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 | |
| 102 | export 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 | |
| 109 | export 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 | |
| 124 | export const getAdvisorPanelItemDisplayTitle = (item: AdvisorItem): string => { |
| 125 | if (item.source === 'signal') { |
| 126 | return item.title |
| 127 | } |
| 128 | |
| 129 | return getAdvisorItemDisplayTitle(item) |
| 130 | } |
| 131 | |
| 132 | export 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 | |
| 144 | export const tabIconMap: Record<Exclude<AdvisorTab, 'all'>, ElementType> = { |
| 145 | security: Shield, |
| 146 | performance: Gauge, |
| 147 | messages: Inbox, |
| 148 | } |
| 149 | |
| 150 | export const severityColorClasses: Record<AdvisorSeverity, string> = { |
| 151 | critical: 'text-destructive', |
| 152 | warning: 'text-warning', |
| 153 | info: 'text-foreground-light', |
| 154 | } |
| 155 | |
| 156 | export const severityBadgeVariants: Record<AdvisorSeverity, 'destructive' | 'warning' | 'default'> = |
| 157 | { |
| 158 | critical: 'destructive', |
| 159 | warning: 'warning', |
| 160 | info: 'default', |
| 161 | } |
| 162 | |
| 163 | export const severityLabels: Record<AdvisorSeverity, string> = { |
| 164 | critical: 'Critical', |
| 165 | warning: 'Warning', |
| 166 | info: 'Info', |
| 167 | } |
| 168 | |
| 169 | export 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 | } |