mirror of
https://github.com/modrinth/code.git
synced 2026-08-31 12:05:53 +00:00
feat: polish profanity validation
This commit is contained in:
@@ -56,7 +56,7 @@
|
||||
<div
|
||||
v-for="nag in visibleNags"
|
||||
:key="nag.id"
|
||||
class="flex w-72 shrink-0 flex-col gap-3 rounded-2xl border border-solid border-surface-5 bg-surface-2 p-4"
|
||||
class="flex w-[268px] shrink-0 flex-col gap-3 rounded-2xl border border-solid border-surface-5 bg-surface-2 p-4"
|
||||
>
|
||||
<span class="flex items-center gap-2 font-medium text-contrast">
|
||||
<component
|
||||
|
||||
@@ -171,6 +171,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import {
|
||||
CopyIcon,
|
||||
EditIcon,
|
||||
ListFilterIcon,
|
||||
ScaleIcon,
|
||||
@@ -207,14 +208,15 @@ import { useModerationQueue } from '~/services/moderation/queue.ts'
|
||||
import { findNextEligibleQueueProject } from '~/services/moderation/queue-eligibility.ts'
|
||||
import {
|
||||
fetchAllModerationQueueProjects,
|
||||
scanProjectsWithValidationErrors,
|
||||
scanProjectsWithValidationIssues,
|
||||
type ValidationFilterRequest,
|
||||
} from '~/services/moderation/validation-filter.ts'
|
||||
|
||||
useHead({ title: 'Projects queue - Modrinth' })
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const notificationManager = injectNotificationManager()
|
||||
const { addNotification } = notificationManager
|
||||
const moderationQueue = useModerationQueue()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -306,6 +308,7 @@ const filterTypes: ComboboxOption<string>[] = [
|
||||
{ value: 'Servers', label: 'Servers' },
|
||||
{ value: 'Project IDs', label: 'Project IDs' },
|
||||
{ value: 'Validation errors', label: 'Validation errors' },
|
||||
{ value: 'Validation errors + warnings', label: 'Validation errors + warnings' },
|
||||
{ value: 'Fucked up', label: 'Fucked up' },
|
||||
]
|
||||
const filterTypeValues = filterTypes.map((option) => option.value)
|
||||
@@ -314,6 +317,7 @@ const DEFAULT_FILTER_TYPE = filterTypeValues[0]
|
||||
const MODPACK_FILTER_TYPE = 'Modpacks'
|
||||
const PROJECT_IDS_FILTER_TYPE = 'Project IDs'
|
||||
const VALIDATION_ERROR_FILTER_TYPE = 'Validation errors'
|
||||
const VALIDATION_ERROR_AND_WARNING_FILTER_TYPE = 'Validation errors + warnings'
|
||||
const VALIDATION_FILTER_STALE_TIME_MS = 1000 * 60 * 5
|
||||
|
||||
const baseSortTypes: ComboboxOption<string>[] = [
|
||||
@@ -516,6 +520,17 @@ const moderationProjectsQueryKey = computed(
|
||||
() => ['moderation-projects', moderationProjectsRequest.value] as const,
|
||||
)
|
||||
|
||||
const isValidationErrorFilter = computed(
|
||||
() => currentFilterType.value === VALIDATION_ERROR_FILTER_TYPE,
|
||||
)
|
||||
const isValidationErrorAndWarningFilter = computed(
|
||||
() => currentFilterType.value === VALIDATION_ERROR_AND_WARNING_FILTER_TYPE,
|
||||
)
|
||||
const isValidationFilter = computed(
|
||||
() => isValidationErrorFilter.value || isValidationErrorAndWarningFilter.value,
|
||||
)
|
||||
const isProjectIdsFilter = computed(() => currentFilterType.value === PROJECT_IDS_FILTER_TYPE)
|
||||
|
||||
const {
|
||||
data: standardProjectsResponse,
|
||||
isPending: standardProjectsPending,
|
||||
@@ -525,11 +540,7 @@ const {
|
||||
queryKey: moderationProjectsQueryKey,
|
||||
queryFn: ({ queryKey }) => client.labrinth.moderation_internal.getProjects(queryKey[1]),
|
||||
placeholderData: (previousData) => previousData,
|
||||
enabled: computed(
|
||||
() =>
|
||||
currentFilterType.value !== VALIDATION_ERROR_FILTER_TYPE &&
|
||||
currentFilterType.value !== PROJECT_IDS_FILTER_TYPE,
|
||||
),
|
||||
enabled: computed(() => !isValidationFilter.value && !isProjectIdsFilter.value),
|
||||
})
|
||||
|
||||
const validationFilterRequest = computed<ValidationFilterRequest>(() => ({
|
||||
@@ -539,45 +550,80 @@ const validationFilterRequest = computed<ValidationFilterRequest>(() => ({
|
||||
}))
|
||||
|
||||
const validationProjectsQueryKey = computed(
|
||||
() => ['moderation-projects', 'validation-errors', validationFilterRequest.value] as const,
|
||||
() =>
|
||||
[
|
||||
'moderation-projects',
|
||||
'validation',
|
||||
isValidationErrorAndWarningFilter.value,
|
||||
validationFilterRequest.value,
|
||||
] as const,
|
||||
)
|
||||
|
||||
let validationScanNotificationId: string | number | undefined
|
||||
|
||||
function showValidationScanCompleteNotification(
|
||||
response: Labrinth.Moderation.Internal.ProjectsResponse,
|
||||
includeWarnings: boolean,
|
||||
) {
|
||||
if (validationScanNotificationId !== undefined) {
|
||||
notificationManager.removeNotification(validationScanNotificationId)
|
||||
}
|
||||
|
||||
const projectIds = response.projects.map((project) => project.id)
|
||||
const notification = addNotification({
|
||||
title: 'Validation scan complete',
|
||||
text: `Found ${response.total} projects with validation ${includeWarnings ? 'errors or warnings' : 'errors'}.`,
|
||||
type: 'success',
|
||||
autoCloseMs: null,
|
||||
copyable: false,
|
||||
buttons: [
|
||||
{
|
||||
label: 'Copy all IDs',
|
||||
icon: CopyIcon,
|
||||
keepOpen: true,
|
||||
action: () => navigator.clipboard.writeText(projectIds.join('\n')),
|
||||
},
|
||||
],
|
||||
})
|
||||
validationScanNotificationId = notification.id
|
||||
}
|
||||
|
||||
const {
|
||||
data: validationProjectsResponse,
|
||||
isPending: validationProjectsPending,
|
||||
error: validationProjectsError,
|
||||
} = useQuery({
|
||||
queryKey: validationProjectsQueryKey,
|
||||
queryFn: ({ queryKey, signal }) =>
|
||||
scanProjectsWithValidationErrors({
|
||||
queryFn: async ({ queryKey, signal }) => {
|
||||
const response = await scanProjectsWithValidationIssues({
|
||||
client,
|
||||
request: queryKey[2],
|
||||
request: queryKey[3],
|
||||
titleMetadata: {
|
||||
gameVersions: generatedState.value.gameVersions.map(({ version }) => version),
|
||||
loaders: generatedState.value.loaders.map(({ name }) => name),
|
||||
},
|
||||
includeWarnings: queryKey[2],
|
||||
signal,
|
||||
log: debugValidationFilter,
|
||||
}),
|
||||
enabled: computed(
|
||||
() => import.meta.client && currentFilterType.value === VALIDATION_ERROR_FILTER_TYPE,
|
||||
),
|
||||
})
|
||||
showValidationScanCompleteNotification(response, queryKey[2])
|
||||
return response
|
||||
},
|
||||
enabled: computed(() => import.meta.client && isValidationFilter.value),
|
||||
staleTime: VALIDATION_FILTER_STALE_TIME_MS,
|
||||
retry: false,
|
||||
})
|
||||
|
||||
watch(
|
||||
() => currentFilterType.value === VALIDATION_ERROR_FILTER_TYPE,
|
||||
(isValidationFilter) => {
|
||||
if (!isValidationFilter) return
|
||||
const cached = queryClient.getQueryData<Labrinth.Moderation.Internal.ProjectsResponse>(
|
||||
validationProjectsQueryKey.value,
|
||||
)
|
||||
if (cached) {
|
||||
debugValidationFilter(`Using cached scan result with ${cached.total} matching projects`)
|
||||
}
|
||||
},
|
||||
)
|
||||
watch([isValidationFilter, validationProjectsQueryKey], ([isActive]) => {
|
||||
if (!isActive) return
|
||||
const cached = queryClient.getQueryData<Labrinth.Moderation.Internal.ProjectsResponse>(
|
||||
validationProjectsQueryKey.value,
|
||||
)
|
||||
if (cached) {
|
||||
debugValidationFilter(`Using cached scan result with ${cached.total} matching projects`)
|
||||
showValidationScanCompleteNotification(cached, isValidationErrorAndWarningFilter.value)
|
||||
}
|
||||
})
|
||||
|
||||
const projectIdsFilterRequest = computed<ValidationFilterRequest>(() => ({
|
||||
exclude_technical_review: excludeTechnicalReview.value,
|
||||
@@ -640,29 +686,23 @@ const {
|
||||
retry: false,
|
||||
})
|
||||
|
||||
const isValidationErrorFilter = computed(
|
||||
() => currentFilterType.value === VALIDATION_ERROR_FILTER_TYPE,
|
||||
)
|
||||
const isProjectIdsFilter = computed(() => currentFilterType.value === PROJECT_IDS_FILTER_TYPE)
|
||||
const usesLocalPagination = computed(
|
||||
() => isValidationErrorFilter.value || isProjectIdsFilter.value,
|
||||
)
|
||||
const usesLocalPagination = computed(() => isValidationFilter.value || isProjectIdsFilter.value)
|
||||
const moderationProjectsResponse = computed(() =>
|
||||
isValidationErrorFilter.value
|
||||
isValidationFilter.value
|
||||
? validationProjectsResponse.value
|
||||
: isProjectIdsFilter.value
|
||||
? projectIdsProjectsResponse.value
|
||||
: standardProjectsResponse.value,
|
||||
)
|
||||
const pending = computed(() =>
|
||||
isValidationErrorFilter.value
|
||||
isValidationFilter.value
|
||||
? validationProjectsPending.value
|
||||
: isProjectIdsFilter.value
|
||||
? projectIds.value.length > 0 && projectIdsProjectsPending.value
|
||||
: standardProjectsPending.value || standardProjectsPlaceholder.value,
|
||||
)
|
||||
const loadError = computed(() =>
|
||||
isValidationErrorFilter.value
|
||||
isValidationFilter.value
|
||||
? validationProjectsError.value
|
||||
: isProjectIdsFilter.value
|
||||
? projectIdsProjectsError.value
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AbstractModrinthClient, Labrinth } from '@modrinth/api-client'
|
||||
import { hasProjectFieldValidationFailures, type ProjectTitleMetadata } from '@modrinth/moderation'
|
||||
import { type ProjectTitleMetadata, validateProjectFields } from '@modrinth/moderation'
|
||||
|
||||
export type ValidationFilterRequest = Omit<
|
||||
Labrinth.Moderation.Internal.ProjectsRequest,
|
||||
@@ -17,6 +17,7 @@ interface ValidationFilterScanOptions {
|
||||
client: AbstractModrinthClient
|
||||
request: ValidationFilterRequest
|
||||
titleMetadata: ProjectTitleMetadata
|
||||
includeWarnings: boolean
|
||||
signal: AbortSignal
|
||||
log: (message: string) => void
|
||||
}
|
||||
@@ -126,10 +127,11 @@ export async function fetchAllModerationQueueProjects(
|
||||
}
|
||||
}
|
||||
|
||||
export async function scanProjectsWithValidationErrors({
|
||||
export async function scanProjectsWithValidationIssues({
|
||||
client,
|
||||
request,
|
||||
titleMetadata,
|
||||
includeWarnings,
|
||||
signal,
|
||||
log,
|
||||
}: ValidationFilterScanOptions): Promise<Labrinth.Moderation.Internal.ProjectsResponse> {
|
||||
@@ -168,7 +170,8 @@ export async function scanProjectsWithValidationErrors({
|
||||
if (!project) {
|
||||
throw new Error(`V3 projects response omitted queued project ${projectId}`)
|
||||
}
|
||||
if (hasProjectFieldValidationFailures(project, titleMetadata)) {
|
||||
const validation = validateProjectFields(project, titleMetadata)
|
||||
if (includeWarnings ? validation.failures.length > 0 : !validation.valid) {
|
||||
matchingProjectIds.add(projectId)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user