diff --git a/apps/frontend/src/pages/moderation/technical-review/rules.vue b/apps/frontend/src/pages/moderation/technical-review/rules.vue index 870fa02e3f..5c73571168 100644 --- a/apps/frontend/src/pages/moderation/technical-review/rules.vue +++ b/apps/frontend/src/pages/moderation/technical-review/rules.vue @@ -105,38 +105,73 @@
-

Test traces

+

Test trace

- These results are evaluated from the current expression. + Change the trace details to evaluate the current expression.

-

Original

-
- - {{ example.original.severity }} - - {{ example.original.issue_type }} +

+ Trace details +

+
+ + + +
-
-
Key
-
{{ example.original.key }}
-
File
-
- {{ example.original.file_path }} -
-
+ + +

+ {{ traceDataError }} +

- New state + Rule result

+ + Evaluating… +
+

+ Preview unavailable. +

+
Hidden from reports
+
+ No match +

{{ testTracePreview.summary }}

+
@@ -451,79 +500,67 @@ type RuleTestError = { details: string[] } -const TEST_INPUTS: Labrinth.TechReview.Internal.RuleInput[] = [ - { - schema_version: 1, - trace: { - key: 'known-safe:obfuscated-bootstrap', - issue_type: 'OBFUSCATED_NAMES', - severity: 'high', - jar: 'META-INF/jars/embedded.jar', - file_path: 'com/example/Bootstrap.class', - data: { +type TestTraceForm = { + key: string + issueType: string + severity: Labrinth.TechReview.Internal.DelphiSeverity + jar: string + filePath: string + data: string +} + +const TRACE_SEVERITIES: Labrinth.TechReview.Internal.DelphiSeverity[] = [ + 'low', + 'medium', + 'high', + 'severe', + 'malware', + 'hidden', +] + +const TEST_INPUT_METADATA: Omit = { + schema_version: 1, + sibling_traces: [], + scan: { + delphi_version: 17, + }, + artifact: { + size: 412_892, + hashes: { + sha1: '0123456789abcdef', + sha512: 'fedcba9876543210', + }, + }, + project: { + id: 'example-project', + types: ['mod'], + }, + version: { + id: 'example-version', + loaders: ['fabric'], + }, + file: { + id: 'example-file', + }, +} + +function createTestTraceForm(): TestTraceForm { + return { + key: 'known-safe:obfuscated-bootstrap', + issueType: 'OBFUSCATED_NAMES', + severity: 'high', + jar: 'META-INF/jars/embedded.jar', + filePath: 'com/example/Bootstrap.class', + data: JSON.stringify( + { confidence: 0.97, symbol_count: 42, }, - }, - sibling_traces: [], - scan: { - delphi_version: 17, - }, - artifact: { - size: 412_892, - hashes: { - sha1: '0123456789abcdef', - sha512: 'fedcba9876543210', - }, - }, - project: { - id: 'example-project', - types: ['mod'], - }, - version: { - id: 'example-version', - loaders: ['fabric'], - }, - file: { - id: 'example-file', - }, - }, - { - schema_version: 1, - trace: { - key: 'network/known-telemetry-host', - issue_type: 'SUSPICIOUS_NETWORK_ACCESS', - severity: 'medium', - jar: null, - file_path: 'com/example/Telemetry.class', - data: { - host: 'telemetry.example.com', - }, - }, - sibling_traces: [], - scan: { - delphi_version: 18, - }, - artifact: { - size: 98_304, - hashes: { - sha1: 'abcdef0123456789', - sha512: '0123456789abcdef', - }, - }, - project: { - id: 'telemetry-project', - types: ['mod'], - }, - version: { - id: 'telemetry-version', - loaders: ['neoforge'], - }, - file: { - id: 'telemetry-file', - }, - }, -] + null, + 2, + ), + } +} useHead({ title: 'Delphi rules - Modrinth' }) @@ -549,6 +586,7 @@ const editingRuleId = ref(null) const ruleToDelete = ref(null) const ruleTestEffects = ref>([]) const ruleTestError = ref(null) +const traceDataError = ref(null) const scanProgress = ref(null) const expandedAffectedDetails = reactive( new Map(), @@ -559,6 +597,7 @@ const form = reactive({ priority: 0 as number | undefined, rule: DEFAULT_RULE, }) +const testTraceForm = reactive(createTestTraceForm()) let ruleTestRequestId = 0 let scanAbortController: AbortController | null = null @@ -577,32 +616,25 @@ const ruleInputSchemaText = computed(() => const ruleOutputSchemaText = computed(() => ruleSchema.value ? formatRuleSchema(ruleSchema.value.output, ruleSchema.value.components) : '', ) -const previewExamples = computed(() => - TEST_INPUTS.map(({ trace: original }, index) => { - const effect = ruleTestEffects.value[index] ?? null - const effectiveSeverity = effect?.severity ?? original.severity - let summary: string +const testTracePreview = computed(() => { + const effect = ruleTestEffects.value[0] ?? null + const effectiveSeverity = effect?.severity ?? testTraceForm.severity + let summary: string - if (isTestingRule.value) { - summary = 'Evaluating the current expression...' - } else if (ruleTestError.value) { - summary = 'Preview unavailable.' - } else if (!effect) { - summary = 'Rule does not match; no change.' - } else if (effect.severity && effect.severity !== original.severity) { - summary = `Severity changed from ${original.severity} to ${effect.severity}.` - } else { - summary = 'Rule matched; no visible change.' - } + if (!effect) { + summary = 'This rule would not change the trace.' + } else if (effect.severity !== testTraceForm.severity) { + summary = `Severity changed from ${testTraceForm.severity} to ${effect.severity}.` + } else { + summary = 'The rule matched without changing the severity.' + } - return { - original, - effect, - effectiveSeverity, - summary, - } - }), -) + return { + effect, + effectiveSeverity, + summary, + } +}) function isRuleLive(rule: Labrinth.TechReview.Internal.DelphiRule): boolean { return rule.current_revision === undefined || rule.revision === rule.current_revision @@ -721,6 +753,34 @@ function handleRuleInput(rule: string) { queueRuleTest() } +function getTestRuleInput(): Labrinth.TechReview.Internal.RuleInput | null { + let data: unknown + try { + data = JSON.parse(testTraceForm.data) + } catch { + traceDataError.value = 'Enter valid JSON data for the trace.' + return null + } + + if (typeof data !== 'object' || data === null || Array.isArray(data)) { + traceDataError.value = 'Trace data must be a JSON object.' + return null + } + + traceDataError.value = null + return { + ...TEST_INPUT_METADATA, + trace: { + key: testTraceForm.key, + issue_type: testTraceForm.issueType, + severity: testTraceForm.severity, + jar: testTraceForm.jar.trim() || null, + file_path: testTraceForm.filePath, + data: data as Record, + }, + } +} + async function testRule() { if (!isRuleModalOpen.value) return @@ -728,6 +788,7 @@ async function testRule() { const rule = form.rule.trim() ruleTestEffects.value = [] ruleTestError.value = null + traceDataError.value = null if (!rule) { isTestingRule.value = false @@ -738,11 +799,17 @@ async function testRule() { return } + const input = getTestRuleInput() + if (!input) { + isTestingRule.value = false + return + } + isTestingRule.value = true try { const response = await client.labrinth.tech_review_internal.testRule({ rule, - inputs: TEST_INPUTS, + inputs: [input], }) if (requestId !== ruleTestRequestId) return @@ -777,6 +844,8 @@ function queueRuleTest() { void testRuleDebounced() } +watch(testTraceForm, queueRuleTest, { flush: 'sync' }) + async function loadRules() { isLoading.value = true loadFailed.value = false @@ -855,6 +924,7 @@ function openCreateModal() { form.name = '' form.priority = 0 form.rule = DEFAULT_RULE + Object.assign(testTraceForm, createTestTraceForm()) isRuleModalOpen.value = true ruleModal.value?.show() nextTick(() => ruleEditorInstance.value?.resize(true)) @@ -868,6 +938,7 @@ function openEditModal(rule: Labrinth.TechReview.Internal.DelphiRule) { form.name = rule.name form.priority = rule.priority form.rule = rule.rule + Object.assign(testTraceForm, createTestTraceForm()) isRuleModalOpen.value = true ruleModal.value?.show() nextTick(() => ruleEditorInstance.value?.resize(true))