qa: tech review 3 (#5250)
* fix: only collapse if pending -> pass/fail, not pass <-> fail * feat: wrap in full details block * feat: left badge * feat: in mod queue -> in project queue * fix: hash on malic modal * feat: remove return to queue on indiv page * fix: truncate in middle * feat: bulk actions * fix: reactivity problem * feat: project page dropdown option * feat: show metadata if exists * fix: lint * fix: qa problems * feat: debug logging for malicious summary modal * fix: lint * qa: go back on bulk * fix: reactive sets/maps -> refs * fix: lint
This commit is contained in:
@@ -686,6 +686,17 @@
|
||||
tags.staffRoles.includes(auth.user.role) &&
|
||||
!showModerationChecklist,
|
||||
},
|
||||
{
|
||||
id: 'tech-review',
|
||||
link: `/moderation/technical-review/${project.id}`,
|
||||
color: 'orange',
|
||||
hoverOnly: true,
|
||||
shown: auth.user && tags.staffRoles.includes(auth.user.role),
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
shown: auth.user && tags.staffRoles.includes(auth.user.role),
|
||||
},
|
||||
{
|
||||
id: 'report',
|
||||
action: () =>
|
||||
@@ -708,6 +719,7 @@
|
||||
<template #moderation-checklist>
|
||||
<ScaleIcon aria-hidden="true" /> {{ formatMessage(messages.reviewProject) }}
|
||||
</template>
|
||||
<template #tech-review> <ScanEyeIcon aria-hidden="true" /> Tech review </template>
|
||||
<template #report>
|
||||
<ReportIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.reportButton) }}
|
||||
@@ -942,6 +954,7 @@ import {
|
||||
PlusIcon,
|
||||
ReportIcon,
|
||||
ScaleIcon,
|
||||
ScanEyeIcon,
|
||||
SearchIcon,
|
||||
ServerPlusIcon,
|
||||
SettingsIcon,
|
||||
|
||||
@@ -10,7 +10,6 @@ import MaliciousSummaryModal, {
|
||||
import ModerationTechRevCard from '~/components/ui/moderation/ModerationTechRevCard.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const client = injectModrinthClient()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
@@ -82,27 +81,27 @@ if (import.meta.client) {
|
||||
clearExpiredCache()
|
||||
}
|
||||
|
||||
const loadingIssues = ref<Set<string>>(new Set())
|
||||
const decompiledSources = ref<Map<string, string>>(new Map())
|
||||
const loadingIssues = reactive<Set<string>>(new Set())
|
||||
const decompiledSources = reactive<Map<string, string>>(new Map())
|
||||
|
||||
async function loadIssueSource(issueId: string): Promise<void> {
|
||||
if (loadingIssues.value.has(issueId)) return
|
||||
if (loadingIssues.has(issueId)) return
|
||||
|
||||
loadingIssues.value.add(issueId)
|
||||
loadingIssues.add(issueId)
|
||||
|
||||
try {
|
||||
const issueData = await client.labrinth.tech_review_internal.getIssue(issueId)
|
||||
|
||||
for (const detail of issueData.details) {
|
||||
if (detail.decompiled_source) {
|
||||
decompiledSources.value.set(detail.id, detail.decompiled_source)
|
||||
decompiledSources.set(detail.id, detail.decompiled_source)
|
||||
setCachedSource(detail.id, detail.decompiled_source)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load issue source:', error)
|
||||
} finally {
|
||||
loadingIssues.value.delete(issueId)
|
||||
loadingIssues.delete(issueId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,10 +112,10 @@ function tryLoadCachedSourcesForFile(reportId: string): void {
|
||||
if (report) {
|
||||
for (const issue of report.issues) {
|
||||
for (const detail of issue.details) {
|
||||
if (!decompiledSources.value.has(detail.id)) {
|
||||
if (!decompiledSources.has(detail.id)) {
|
||||
const cached = getCachedSource(detail.id)
|
||||
if (cached) {
|
||||
decompiledSources.value.set(detail.id, cached)
|
||||
decompiledSources.set(detail.id, cached)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +131,7 @@ function handleLoadFileSources(reportId: string): void {
|
||||
const report = reviewItem.value.reports.find((r) => r.id === reportId)
|
||||
if (report) {
|
||||
for (const issue of report.issues) {
|
||||
const hasUncached = issue.details.some((d) => !decompiledSources.value.has(d.id))
|
||||
const hasUncached = issue.details.some((d) => !decompiledSources.has(d.id))
|
||||
if (hasUncached) {
|
||||
loadIssueSource(issue.id)
|
||||
}
|
||||
@@ -244,7 +243,6 @@ const reviewItem = computed(() => {
|
||||
|
||||
function handleMarkComplete(_projectId: string) {
|
||||
queryClient.invalidateQueries({ queryKey: ['tech-reviews'] })
|
||||
router.push('/moderation/technical-review')
|
||||
}
|
||||
|
||||
const maliciousSummaryModalRef = ref<InstanceType<typeof MaliciousSummaryModal>>()
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
} from '@modrinth/ui'
|
||||
import { useInfiniteQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import Fuse from 'fuse.js'
|
||||
import { nextTick } from 'vue'
|
||||
import { nextTick, reactive } from 'vue'
|
||||
|
||||
import MaliciousSummaryModal, {
|
||||
type UnsafeFile,
|
||||
@@ -103,27 +103,27 @@ function clearExpiredCache(): void {
|
||||
|
||||
clearExpiredCache()
|
||||
|
||||
const loadingIssues = ref<Set<string>>(new Set())
|
||||
const decompiledSources = ref<Map<string, string>>(new Map())
|
||||
const loadingIssues = reactive<Set<string>>(new Set())
|
||||
const decompiledSources = reactive<Map<string, string>>(new Map())
|
||||
|
||||
async function loadIssueSource(issueId: string): Promise<void> {
|
||||
if (loadingIssues.value.has(issueId)) return
|
||||
if (loadingIssues.has(issueId)) return
|
||||
|
||||
loadingIssues.value.add(issueId)
|
||||
loadingIssues.add(issueId)
|
||||
|
||||
try {
|
||||
const issueData = await client.labrinth.tech_review_internal.getIssue(issueId)
|
||||
|
||||
for (const detail of issueData.details) {
|
||||
if (detail.decompiled_source) {
|
||||
decompiledSources.value.set(detail.id, detail.decompiled_source)
|
||||
decompiledSources.set(detail.id, detail.decompiled_source)
|
||||
setCachedSource(detail.id, detail.decompiled_source)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load issue source:', error)
|
||||
} finally {
|
||||
loadingIssues.value.delete(issueId)
|
||||
loadingIssues.delete(issueId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,10 +133,10 @@ function tryLoadCachedSourcesForFile(reportId: string): void {
|
||||
if (report) {
|
||||
for (const issue of report.issues) {
|
||||
for (const detail of issue.details) {
|
||||
if (!decompiledSources.value.has(detail.id)) {
|
||||
if (!decompiledSources.has(detail.id)) {
|
||||
const cached = getCachedSource(detail.id)
|
||||
if (cached) {
|
||||
decompiledSources.value.set(detail.id, cached)
|
||||
decompiledSources.set(detail.id, cached)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,7 @@ function handleLoadFileSources(reportId: string): void {
|
||||
const report = review.reports.find((r) => r.id === reportId)
|
||||
if (report) {
|
||||
for (const issue of report.issues) {
|
||||
const hasUncached = issue.details.some((d) => !decompiledSources.value.has(d.id))
|
||||
const hasUncached = issue.details.some((d) => !decompiledSources.has(d.id))
|
||||
if (hasUncached) {
|
||||
loadIssueSource(issue.id)
|
||||
}
|
||||
@@ -470,19 +470,23 @@ function handleMarkComplete(projectId: string) {
|
||||
|
||||
// Scroll to the next card after Vue updates the DOM
|
||||
nextTick(() => {
|
||||
const targetIndex = currentIndex
|
||||
if (targetIndex >= 0 && cardRefs.value[targetIndex]) {
|
||||
cardRefs.value[targetIndex].scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
})
|
||||
// Get the project ID at the same position (next project after removal)
|
||||
const nextItem = paginatedItems.value[currentIndex]
|
||||
if (nextItem) {
|
||||
const nextCard = cardRefs.get(nextItem.project.id)
|
||||
if (nextCard) {
|
||||
nextCard.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const maliciousSummaryModalRef = ref<InstanceType<typeof MaliciousSummaryModal>>()
|
||||
const currentUnsafeFiles = ref<UnsafeFile[]>([])
|
||||
const cardRefs = ref<HTMLElement[]>([])
|
||||
const cardRefs = reactive<Map<string, HTMLElement>>(new Map())
|
||||
|
||||
function handleShowMaliciousSummary(unsafeFiles: UnsafeFile[]) {
|
||||
currentUnsafeFiles.value = unsafeFiles
|
||||
@@ -576,7 +580,7 @@ watch([currentSortType, currentResponseFilter, inOtherQueueFilter, currentFilter
|
||||
<template #panel>
|
||||
<div class="flex min-w-64 flex-col gap-3">
|
||||
<label class="flex cursor-pointer items-center justify-between gap-2 text-sm">
|
||||
<span class="whitespace-nowrap font-semibold">In mod queue</span>
|
||||
<span class="whitespace-nowrap font-semibold">In project queue</span>
|
||||
<Toggle v-model="inOtherQueueFilter" />
|
||||
</label>
|
||||
<div class="flex flex-col gap-2">
|
||||
@@ -619,11 +623,15 @@ watch([currentSortType, currentResponseFilter, inOtherQueueFilter, currentFilter
|
||||
No projects in queue.
|
||||
</div>
|
||||
<div
|
||||
v-for="(item, idx) in paginatedItems"
|
||||
:key="item.project.id ?? idx"
|
||||
v-for="item in paginatedItems"
|
||||
:key="item.project.id"
|
||||
:ref="
|
||||
(el) => {
|
||||
if (el) cardRefs[idx] = el as HTMLElement
|
||||
if (el) {
|
||||
cardRefs.set(item.project.id, el as HTMLElement)
|
||||
} else {
|
||||
cardRefs.delete(item.project.id)
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user