Skip to content

Commit a894309

Browse files
committed
feat: update for new routes
1 parent c2433c2 commit a894309

File tree

5 files changed

+401
-189
lines changed

5 files changed

+401
-189
lines changed

apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ import { capitalizeString, formatProjectType, highlightCodeLines } from '@modrin
2424
import { computed, ref } from 'vue'
2525
2626
const props = defineProps<{
27-
item: Labrinth.TechReview.Internal.ProjectReview
27+
item: {
28+
project: Labrinth.Projects.v3.Project
29+
project_owner: Labrinth.TechReview.Internal.Ownership
30+
thread: Labrinth.TechReview.Internal.DBThread
31+
reports: Labrinth.TechReview.Internal.FileReport[]
32+
}
2833
}>()
2934
3035
const { addNotification } = injectNotificationManager()
@@ -66,19 +71,18 @@ type Tab = 'Thread' | 'Files'
6671
const tabs: readonly Tab[] = ['Thread', 'Files']
6772
const currentTab = ref<Tab>('Thread')
6873
69-
type SelectedFile = Labrinth.TechReview.Internal.FileReview | null
74+
type SelectedFile = Labrinth.TechReview.Internal.FileReport | null
7075
const selectedFile = ref<SelectedFile>(null)
7176
7277
const client = injectModrinthClient()
7378
7479
const allFiles = computed(() => {
75-
return props.item.reports.flatMap((report) => report.files)
80+
return props.item.reports
7681
})
7782
7883
const highestSeverity = computed(() => {
7984
const severities = props.item.reports
80-
.flatMap((r) => r.files)
81-
.flatMap((f) => f.issues)
85+
.flatMap((r) => r.issues)
8286
.flatMap((i) => i.details)
8387
.map((d) => d.severity)
8488
@@ -101,7 +105,7 @@ const severityColor = computed(() => {
101105
})
102106
103107
const formattedDate = computed(() => {
104-
const dates = props.item.reports.map((r) => new Date(r.created_at))
108+
const dates = props.item.reports.map((r) => new Date(r.created))
105109
const earliest = new Date(Math.min(...dates.map((d) => d.getTime())))
106110
const now = new Date()
107111
const diffDays = Math.floor((now.getTime() - earliest.getTime()) / (1000 * 60 * 60 * 24))
@@ -116,11 +120,11 @@ function formatFileSize(bytes: number): string {
116120
return `${(bytes / (1024 * 1024)).toFixed(2)} MiB`
117121
}
118122
119-
function viewFileFlags(file: Labrinth.TechReview.Internal.FileReview) {
123+
function viewFileFlags(file: Labrinth.TechReview.Internal.FileReport) {
120124
selectedFile.value = file
121125
// Automatically expand the first issue
122126
if (file.issues.length > 0) {
123-
expandedIssues.value.add(file.issues[0].issue_id)
127+
expandedIssues.value.add(file.issues[0].id)
124128
}
125129
}
126130
@@ -163,30 +167,30 @@ function toggleIssue(issueId: string) {
163167
}
164168
}
165169
166-
function getSeverityBreakdown(file: Labrinth.TechReview.Internal.FileReview) {
167-
const counts = {
168-
SEVERE: 0,
169-
HIGH: 0,
170-
MEDIUM: 0,
171-
LOW: 0,
172-
}
173-
174-
file.issues.forEach((issue) => {
175-
issue.details.forEach((detail) => {
176-
if (detail.severity in counts) {
177-
counts[detail.severity as keyof typeof counts]++
178-
}
179-
})
180-
})
181-
182-
const breakdown = []
183-
if (counts.SEVERE > 0) breakdown.push({ count: counts.SEVERE, severity: 'SEVERE' })
184-
if (counts.HIGH > 0) breakdown.push({ count: counts.HIGH, severity: 'HIGH' })
185-
if (counts.MEDIUM > 0) breakdown.push({ count: counts.MEDIUM, severity: 'MEDIUM' })
186-
if (counts.LOW > 0) breakdown.push({ count: counts.LOW, severity: 'LOW' })
187-
188-
return breakdown
189-
}
170+
// function getSeverityBreakdown(file: Labrinth.TechReview.Internal.FileReport) {
171+
// const counts = {
172+
// SEVERE: 0,
173+
// HIGH: 0,
174+
// MEDIUM: 0,
175+
// LOW: 0,
176+
// }
177+
178+
// file.issues.forEach((issue) => {
179+
// issue.details.forEach((detail) => {
180+
// if (detail.severity in counts) {
181+
// counts[detail.severity as keyof typeof counts]++
182+
// }
183+
// })
184+
// })
185+
186+
// const breakdown = []
187+
// if (counts.SEVERE > 0) breakdown.push({ count: counts.SEVERE, severity: 'SEVERE' })
188+
// if (counts.HIGH > 0) breakdown.push({ count: counts.HIGH, severity: 'HIGH' })
189+
// if (counts.MEDIUM > 0) breakdown.push({ count: counts.MEDIUM, severity: 'MEDIUM' })
190+
// if (counts.LOW > 0) breakdown.push({ count: counts.LOW, severity: 'LOW' })
191+
192+
// return breakdown
193+
// }
190194
</script>
191195

192196
<template>
@@ -374,7 +378,7 @@ function getSeverityBreakdown(file: Labrinth.TechReview.Internal.FileReview) {
374378
<div v-else-if="currentTab === 'Files' && selectedFile" class="flex flex-col">
375379
<div
376380
v-for="(issue, idx) in selectedFile.issues"
377-
:key="issue.issue_id"
381+
:key="issue.id"
378382
class="border-x border-b border-t-0 border-solid border-surface-3 bg-surface-2"
379383
:class="{ 'rounded-bl-2xl rounded-br-2xl': idx === selectedFile.issues.length - 1 }"
380384
>
@@ -383,15 +387,15 @@ function getSeverityBreakdown(file: Labrinth.TechReview.Internal.FileReview) {
383387
<ButtonStyled type="transparent" circular>
384388
<button
385389
class="transition-transform"
386-
:class="{ 'rotate-180': !expandedIssues.has(issue.issue_id) }"
387-
@click="toggleIssue(issue.issue_id)"
390+
:class="{ 'rotate-180': !expandedIssues.has(issue.id) }"
391+
@click="toggleIssue(issue.id)"
388392
>
389393
<ChevronDownIcon class="h-5 w-5 text-contrast" />
390394
</button>
391395
</ButtonStyled>
392396

393397
<span class="text-base font-semibold text-contrast">{{
394-
issue.kind.replace(/_/g, ' ')
398+
issue.issue_type.replace(/_/g, ' ')
395399
}}</span>
396400

397401
<div
@@ -415,26 +419,26 @@ function getSeverityBreakdown(file: Labrinth.TechReview.Internal.FileReview) {
415419

416420
<div class="flex items-center gap-2">
417421
<ButtonStyled color="brand" type="outlined">
418-
<button class="!border-[1px]" @click="updateIssueStatus(issue.issue_id, 'safe')">
422+
<button class="!border-[1px]" @click="updateIssueStatus(issue.id, 'safe')">
419423
Safe
420424
</button>
421425
</ButtonStyled>
422426

423427
<ButtonStyled color="red" type="outlined">
424-
<button class="!border-[1px]" @click="updateIssueStatus(issue.issue_id, 'unsafe')">
428+
<button class="!border-[1px]" @click="updateIssueStatus(issue.id, 'unsafe')">
425429
Malware
426430
</button>
427431
</ButtonStyled>
428432
</div>
429433
</div>
430434

431-
<div v-if="expandedIssues.has(issue.issue_id)" class="flex flex-col gap-4 px-4 pb-4">
435+
<div v-if="expandedIssues.has(issue.id)" class="flex flex-col gap-4 px-4 pb-4">
432436
<div
433437
v-for="(detail, detailIdx) in issue.details"
434438
:key="detailIdx"
435439
class="flex flex-col"
436440
>
437-
<p class="mt-0 pt-0 font-mono text-sm text-secondary">{{ detail.class_name }}</p>
441+
<p class="mt-0 pt-0 font-mono text-sm text-secondary">{{ detail.file_path }}</p>
438442

439443
<div
440444
v-if="detail.decompiled_source"
@@ -469,6 +473,9 @@ function getSeverityBreakdown(file: Labrinth.TechReview.Internal.FileReview) {
469473
</div>
470474
</div>
471475
</div>
476+
<div v-else class="rounded-lg border border-solid border-surface-5 bg-surface-3 p-4">
477+
<p class="text-sm text-secondary">Source code not available for this flag.</p>
478+
</div>
472479
</div>
473480
</div>
474481
</div>

apps/frontend/src/pages/moderation/technical-review.vue

Lines changed: 88 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ import { defineMessages, useVIntl } from '@vintl/vintl'
2121
import Fuse from 'fuse.js'
2222
2323
import ModerationTechRevCard from '~/components/ui/moderation/ModerationTechRevCard.vue'
24-
2524
// TEMPORARY: Mock data for development
26-
import { generateMockProjectReviews } from '~/utils/mockTechReviewData'
25+
// import { generateMockSearchResponse } from '~/utils/mockTechReviewData'
2726
2827
const client = injectModrinthClient()
2928
@@ -88,9 +87,8 @@ const filterTypes = computed<ComboboxOption<string>[]>(() => {
8887
const issueTypes = new Set(
8988
reviewItems.value
9089
.flatMap((review) => review.reports)
91-
.flatMap((report) => report.files)
92-
.flatMap((file) => file.issues)
93-
.map((issue) => issue.kind),
90+
.flatMap((report) => report.issues)
91+
.map((issue) => issue.issue_type),
9492
)
9593
9694
const sortedTypes = Array.from(issueTypes).sort()
@@ -112,8 +110,8 @@ const fuse = computed(() => {
112110
keys: [
113111
{ name: 'project.title', weight: 4 },
114112
{ name: 'project.slug', weight: 3 },
115-
{ name: 'reports.files.file_name', weight: 2 },
116-
{ name: 'reports.files.issues.kind', weight: 3 },
113+
{ name: 'reports.file_name', weight: 2 },
114+
{ name: 'reports.issues.issue_type', weight: 3 },
117115
{ name: 'project_owner.name', weight: 2 },
118116
],
119117
includeScore: true,
@@ -123,9 +121,7 @@ const fuse = computed(() => {
123121
124122
const searchResults = computed(() => {
125123
if (!query.value || !fuse.value) return null
126-
return fuse.value
127-
.search(query.value)
128-
.map((result) => result.item as Labrinth.TechReview.Internal.ProjectReview)
124+
return fuse.value.search(query.value).map((result) => result.item)
129125
})
130126
131127
const baseFiltered = computed(() => {
@@ -138,31 +134,32 @@ const typeFiltered = computed(() => {
138134
const type = currentFilterType.value
139135
140136
return baseFiltered.value.filter((review) => {
141-
return review.reports.some((report) =>
142-
report.files.some((file) => file.issues.some((issue) => issue.kind === type)),
137+
return review.reports.some((report: Labrinth.TechReview.Internal.FileReport) =>
138+
report.issues.some(
139+
(issue: Labrinth.TechReview.Internal.FileIssue) => issue.issue_type === type,
140+
),
143141
)
144142
})
145143
})
146144
147-
function getHighestSeverity(review: Labrinth.TechReview.Internal.ProjectReview): string {
145+
function getHighestSeverity(review: {
146+
reports: Labrinth.TechReview.Internal.FileReport[]
147+
}): string {
148148
const severities = review.reports
149-
.flatMap((r) => r.files)
150-
.flatMap((f) => f.issues)
149+
.flatMap((r) => r.issues)
151150
.flatMap((i) => i.details)
152151
.map((d) => d.severity)
153152
154153
const order = { SEVERE: 3, HIGH: 2, MEDIUM: 1, LOW: 0 } as Record<string, number>
155154
return severities.sort((a, b) => (order[b] ?? 0) - (order[a] ?? 0))[0] || 'LOW'
156155
}
157156
158-
function hasPendingIssues(review: Labrinth.TechReview.Internal.ProjectReview): boolean {
159-
return review.reports.some((report) =>
160-
report.files.some((file) => file.issues.some((issue) => issue.status === 'pending')),
161-
)
157+
function hasPendingIssues(review: { reports: Labrinth.TechReview.Internal.FileReport[] }): boolean {
158+
return review.reports.some((report) => report.issues.some((issue) => issue.status === 'pending'))
162159
}
163160
164-
function getEarliestDate(review: Labrinth.TechReview.Internal.ProjectReview): number {
165-
const dates = review.reports.map((r) => new Date(r.created_at).getTime())
161+
function getEarliestDate(review: { reports: Labrinth.TechReview.Internal.FileReport[] }): number {
162+
const dates = review.reports.map((r) => new Date(r.created).getTime())
166163
return Math.min(...dates)
167164
}
168165
@@ -232,30 +229,78 @@ function toApiSort(label: string): Labrinth.TechReview.Internal.SearchProjectsSo
232229
}
233230
}
234231
235-
// const {
236-
// data: reviewItems,
237-
// isLoading,
238-
// refetch,
239-
// } = useQuery({
240-
// queryKey: ['tech-reviews', currentSortType],
241-
// queryFn: async () => {
242-
// return await client.labrinth.tech_review_internal.searchProjects({
243-
// limit: 350,
244-
// page: 0,
245-
// sort_by: toApiSort(currentSortType.value),
246-
// })
247-
// },
248-
// initialData: [] as Labrinth.TechReview.Internal.ProjectReview[],
249-
// })
232+
const {
233+
data: searchResponse,
234+
isLoading,
235+
refetch,
236+
} = useQuery({
237+
queryKey: ['tech-reviews', currentSortType],
238+
queryFn: async () => {
239+
return await client.labrinth.tech_review_internal.searchProjects({
240+
limit: 350,
241+
page: 0,
242+
sort_by: toApiSort(currentSortType.value),
243+
})
244+
},
245+
initialData: {
246+
reports: [],
247+
projects: {},
248+
threads: {},
249+
ownership: {},
250+
} as Labrinth.TechReview.Internal.SearchResponse,
251+
})
250252
251253
// TEMPORARY: Mock data for development (58 items to match batch scan progress)
252-
const reviewItems = ref<Labrinth.TechReview.Internal.ProjectReview[]>(
253-
generateMockProjectReviews(58),
254-
)
255-
const isLoading = ref(false)
256-
const refetch = () => {
257-
reviewItems.value = generateMockProjectReviews(58)
258-
}
254+
// const searchResponse = ref<Labrinth.TechReview.Internal.SearchResponse>(
255+
// generateMockSearchResponse(58),
256+
// )
257+
// const isLoading = ref(false)
258+
// const refetch = () => {
259+
// searchResponse.value = generateMockSearchResponse(58)
260+
// }
261+
262+
// Adapter: Transform flat SearchResponse into project-grouped structure
263+
// for easier consumption by existing UI components
264+
const reviewItems = computed(() => {
265+
if (!searchResponse.value || searchResponse.value.reports.length === 0) {
266+
return []
267+
}
268+
269+
const response = searchResponse.value
270+
271+
// Group reports by project
272+
const projectMap = new Map<
273+
string,
274+
{
275+
project: Labrinth.Projects.v3.Project
276+
project_owner: Labrinth.TechReview.Internal.Ownership
277+
thread: Labrinth.TechReview.Internal.DBThread
278+
reports: Labrinth.TechReview.Internal.FileReport[]
279+
}
280+
>()
281+
282+
for (const report of response.reports) {
283+
const projectId = report.project_id
284+
285+
if (!projectMap.has(projectId)) {
286+
// Find the thread associated with this project
287+
const thread = Object.values(response.threads).find((t) => t.project_id === projectId)
288+
289+
if (!thread) continue // Skip if no thread found
290+
291+
projectMap.set(projectId, {
292+
project: response.projects[projectId],
293+
project_owner: response.ownership[projectId],
294+
thread,
295+
reports: [],
296+
})
297+
}
298+
299+
projectMap.get(projectId)!.reports.push(report)
300+
}
301+
302+
return Array.from(projectMap.values())
303+
})
259304
260305
watch(currentSortType, () => {
261306
goToPage(1)

0 commit comments

Comments
 (0)