Development: Add action buttons in popover for recommendation system - #2520
Development: Add action buttons in popover for recommendation system#2520ge94zec wants to merge 48 commits into
Development: Add action buttons in popover for recommendation system#2520Conversation
…rget text - added endpoint POST that maps source issues to a target language - added prompt that returns one mapped text snippet per line in source complianceIssues order, other fields carry over - wired compliance mapping through client → server API - separated compliance analysis from score calculation - extracted save flow in JobService into Consumer - improved performance by eliminating redundant analysis steps in job creation
- updated openapi - added comment to AiService - simplified AiService mapping - updated prompt
- show suggestions in the compliance popover - add suggestion actions for replace, add, remove, and dismiss - extend compliance prompt and model with suggestion field - migration suggestions field on liquibase
…or-compliance' into chore/2346-enhance-performance-for-compliance
…or-compliance' into chore/2346-enhance-performance-for-compliance
…rformance-for-compliance # Conflicts: # src/main/java/de/tum/cit/aet/job/repository/JobRepository.java # src/main/java/de/tum/cit/aet/job/service/JobService.java # src/main/webapp/app/job/job-creation-form/job-creation-form.component.ts
…buttons-in-popover-for-recommendation-system # Conflicts: # src/main/resources/config/liquibase/master.xml
Removing supervisingProfessor and image from the @EntityGraph caused those associations to become lazy (the FETCH graph overrides entity-level EAGER). getJobById accesses both via the entity returned from assertCanManageJob, so they must be in the graph to avoid LazyInitializationException. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Refined the compliance suggestion prompt for more accurate/correct output - Added client logic to insert, replace, or remove snippets at the correct index in the editor and persist the change via the form
…buttons-in-popover-for-recommendation-system # Conflicts: # src/main/resources/config/liquibase/master.xml
Development: Add action buttons in popover for recommendation system
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| ErrorProne | 2 medium 1 high |
| Security | 18 high |
| CodeStyle | 1 minor |
| Complexity | 2 medium |
🟢 Metrics 78 complexity
Metric Results Complexity 78
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
…lyses - map newly detected issues without retranslating synchronized descriptions - apply accepted suggestions to both language versions - queue actions until target-language mapping completes - insert ADD suggestions after sentence punctuation within list items - prevent duplicate in-flight compliance analyses - preserve mapped highlights and translated suggestions - keep compliance popovers above the sticky footer
|
📊 Client Test Coverage Too Low 🔍 View coverage locally: pnpm run test:ci
open build/test-results/vitest/coverage/index.html🌐 View coverage from GitHub: |
|
🤖 No OpenAPI or client changes needed. |
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| ErrorProne | 2 medium 1 high |
| Security | 17 high |
| CodeStyle | 1 minor |
| Complexity | 1 medium |
🟢 Metrics 34 complexity
Metric Results Complexity 34
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
|
🤖 No OpenAPI or client changes needed. |
|
There hasn't been any activity on this pull request recently. Therefore, this pull request has been automatically marked as stale and will be closed if no further activity occurs within seven days. Thank you for your contributions. |
…recommendation-system
|
🤖 No OpenAPI or client changes needed. |
…buttons-in-popover-for-recommendation-system # Conflicts: # openapi/openapi.yaml # src/main/java/de/tum/cit/aet/ai/dto/MapComplianceIssuesRequestDTO.java # src/main/java/de/tum/cit/aet/ai/service/AiService.java # src/main/resources/prompts/SnippetMapping.st # src/main/webapp/app/generated/model/map-compliance-issues-request-dto.ts # src/main/webapp/app/job/job-creation-form/job-creation-form.component.ts # src/test/java/de/tum/cit/aet/ai/web/rest/AiResourceTest.java # src/test/webapp/app/job/job-creation-form/job-creation-form.component.spec.ts
|
🤖 No OpenAPI or client changes needed. |
- translate existing compliance suggestions during snippet mapping - preserve suggestion meaning across languages - require exact target-text matches for mapped snippets
…buttons-in-popover-for-recommendation-system # Conflicts: # src/main/java/de/tum/cit/aet/ai/service/AiService.java # src/main/java/de/tum/cit/aet/ai/service/ComplianceScoreService.java # src/main/resources/config/liquibase/master.xml # src/main/webapp/app/shared/components/atoms/editor/editor.component.ts # src/main/webapp/app/shared/components/molecules/ai-compliance-popover/ai-compliance-popover.component.ts # src/test/java/de/tum/cit/aet/ai/service/ComplianceScoreServiceTest.java # src/test/webapp/app/job/job-creation-form/job-creation-form.component.spec.ts
|
📊 Server Test Coverage Too Low 🔍 View coverage locally: ./gradlew test jacocoTestReport
open build/reports/jacoco/test/html/index.html🌐 View coverage from GitHub: |
|
🤖 No OpenAPI or client changes needed. |
1 similar comment
|
🤖 No OpenAPI or client changes needed. |
az108
left a comment
There was a problem hiding this comment.
All ten earlier threads are resolved, and with #2465 and #2470 now merged the diff is finally a reviewable 28 files. Four things, one of them a real behaviour bug — details inline.
Two 0-byte files are committed
src/main/java/de/tum/cit/aet/ai/service/ComplianceScoreService.java 0 bytes
src/test/java/de/tum/cit/aet/ai/service/ComplianceScoreServiceTest.java 0 bytes
Both were moved to ai/util/ComplianceScoreCalculator by #2465 and deleted on main. Merge commit 936070dc4 hit a modify/delete conflict and resolved it by keeping the files and emptying them. ComplianceScoreCalculator.java and its test exist intact alongside, so no coverage is lost — this is pure dead weight.
It doesn't break anything (an empty .java is a valid compilation unit, and compileJava/compileTestJava pass), and grep finds zero references to ComplianceScoreService anywhere in the repo. They can just be deleted. I couldn't leave an inline comment since there are no lines to anchor to.
Unrelated, but worth knowing
PR #2638 (feat/2637-port-new-gender-contracts) also claims 00000000000053, with a file that is a byte-identical duplicate of the already-merged 052 and isn't registered in master.xml at all — so it would never run. No git conflict with this PR (different filenames, and #2638 doesn't touch master.xml), and the defect is on that side. Flagging it here only because it surfaced while checking the numbering.
| const dismissedIssues = this.dismissedComplianceHighlights(); | ||
| const highlights = (compliance ?? []).flatMap(issue => | ||
| hasText(issue.text) && issue.category !== undefined && (!hasText(issue.language) || issue.language === lang) | ||
| !dismissedIssues.includes(issue) && |
There was a problem hiding this comment.
dismissedComplianceHighlights stores object references and this filters with .includes(issue) — SameValueZero, so reference equality on a generated plain interface with no identity field. That only holds while the array keeps the same references, and the early-return branch added in this PR at line 1847 breaks exactly that: translateAndStoreOtherLanguage now calls mapIssuesToTargetLanguage even when the text is unchanged, and line 1998 replaces the target-language issues with fresh objects from the mapComplianceIssues response.
Reachable flow, with no re-analysis anywhere in it:
- Draft analyzed in EN, translated to DE, DE issues mapped
- On the DE tab, dismiss a highlight — disappears correctly
- Back on EN, edit any other field (title, location, workload) so autosave fires
performAnalysis('en')cache-returns: no HTTP, noset- The new early-return branch runs
mapComplianceIssues, and line 1998 swaps the DE issues for fresh objects — invisible, since you are on EN - Switch to DE:
languageChangeEffect→applyHighlights→ the dismissed highlight is back
The dismissed object also stays in the array forever as a dead reference.
The four complianceIssues.set(...) call sites separate cleanly: 1467 and 1575 are load paths, 2050 is a real analysis (resetting dismissals there is defensible), but 1998 is pure re-mapping of unchanged text and should not clear anything.
Suggestion: key on something stable rather than identity, e.g. `${issue.language ?? ''}|${issue.id ?? issue.text ?? ''}`. AiService.java:561 carries sourceIssue.getId() through the mapping round-trip, so IDs survive mapping but not a fresh analysis — which is exactly the granularity you want. The text fallback matters because id is optional and the local gender-bias path does not always set it.
If reset-on-new-analysis is intended, worth doing explicitly at 2050 rather than as a side effect of reference equality — that also stops the array growing unbounded.
Dismiss is not covered by the new specs, so this would not have been caught.
| const updatedTargetHtml = applyComplianceSuggestionToHtml(currentTargetHtml, acceptedIssue); | ||
| if (updatedTargetHtml === undefined) continue; | ||
|
|
||
| this.pendingMappedActions.delete(acceptedIssue.id!); |
There was a problem hiding this comment.
this.pendingMappedActions.delete(acceptedIssue.id!);client-development.mdx:230: "Never use the TypeScript non-null assertion operator (!) to bypass type checks. It hides potential runtime errors and defeats the purpose of strict typing."
Worth noting this isn't an established pattern to follow — there is exactly one other real ! assertion in the entire client (document-cache.service.ts:33), so this would be the second.
hasText is a type guard (text.util.ts:8), but the && in the filter on line 1974 produces a plain boolean rather than a predicate on issue, so filter still returns ComplianceIssue[] and id is back to string | undefined in the loop. A local const plus a guard next to the continue you already have:
for (const acceptedIssue of acceptedMappedIssues) {
const acceptedIssueId = acceptedIssue.id;
if (!hasText(acceptedIssueId)) continue;
...
this.pendingMappedActions.delete(acceptedIssueId);(ESLint won't flag it — no-non-null-assertion is off in eslint.config.mjs:174 — so this is convention rather than a lint break.)
| xmlns="http://www.liquibase.org/xml/ns/dbchangelog" | ||
| xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd"> | ||
|
|
||
| <changeSet id="052_add_suggestion_field_to_complianceIssue" author="melissa"> |
There was a problem hiding this comment.
The changeset id is prefixed 052_ but the file is numbered 053. Per liquibase-guidelines.mdx:41, the id should start with the same changelog number as the file:
<changeSet id="053_add_suggestion_field_to_compliance_issue" author="melissa">No functional collision — Liquibase keys on id+author+filename, and there are zero duplicate ids repo-wide — but the convention is actively followed: 29 of the 31 files in the 023–053 range conform.
Two smaller things in the same file while you're in there:
- The filename description is camelCase (
complianceIssue). It's the only camelCase description among all 54 changelogs;liquibase-guidelines.mdx:20asks for snake_case. - Missing
<preConditions onFail="MARK_RAN">— I checked and am not raising this as a real issue: only 7 of 54 changelogs use them, and a plainaddColumnon a column that never existed runs exactly once via DATABASECHANGELOG anyway. Mentioning only so it doesn't get raised later.
Confirmed the migration itself applies: JobResourceTest runs the full changelog against Testcontainers MySQL, 37 tests pass.
| @@ -560,6 +565,7 @@ public List<ComplianceIssue> mapComplianceIssues(MapComplianceIssuesRequestDTO r | |||
| sourceIssue.getArticle(), | |||
| sourceIssue.getExplanation(), | |||
| sourceIssue.getAction(), | |||
| mappedTexts.get(i * 2 + 1).trim(), | |||
| request.toLang() | |||
There was a problem hiding this comment.
can you add a little bit of java doc explaining the i and the calculations here
| * @param issue The compliance issue containing the action, snippet text, and suggestion. | ||
| * @returns The updated editor HTML for issued text snippet | ||
| */ | ||
| public applyComplianceSuggestion(issue: ComplianceIssue): string | undefined { |
There was a problem hiding this comment.
Design point: this puts page-specific domain logic into a shared atom, and the coupling here is a different kind from what the editor carried before.
EditorComponent lives in shared/components/atoms/ and is consumed by eight features — research-group template edit, research-group info, research-group detail view, research-group creation form, review dialog, interviewee assessment, application creation page 3, and job creation. Only the last one has anything to do with compliance.
On main the editor imported exactly two compliance symbols:
ComplianceIssueDTOCategoryEnum // pick the highlight underline colour
ComplianceIssueDTOCategoryEnumValuesThat is narrow and defensible — the atom needs a category the same way it needs a font size. This PR adds ComplianceIssue (the whole DTO) and ComplianceIssueActionEnum, and applyComplianceSuggestion switches on issue.action to decide what REPLACE / REMOVE / ADD mean. The atom went from "knows compliance categories for styling" to "knows the compliance issue model and executes its business rules".
The PR already contains the better design. compliance-suggestion.util.ts implements applyComplianceSuggestionToHtml(html, issue) — the same rule (locate snippet, switch on action, Replace/Remove/Add with sentence-end insertion) as a pure function. This method even imports findSentenceEnd from it, so the extraction was started and then stopped halfway. The result is one rule with two implementations and two different effectors: the Quill API here, DOM Range there.
And they already disagree, which is the part that is a bug today rather than a preference. When the snippet is not found:
| ADD action, snippet not found | |
|---|---|
editor.component.ts:435 |
insertAt = editor.getLength() — appends the sentence at the end of the document |
compliance-suggestion.util.ts:28 |
if (start === -1) return undefined — no change at all |
onComplianceSuggestionAccepted runs both paths for a single accept: this method for the active language, the util for the paired-language HTML. So accepting one ADD suggestion whose snippet is missing in the other language bolts a sentence onto the end of the active editor and silently does nothing to the other. That falls straight out of having the rule in two places.
Suggested shape — keep the atom's real job ("apply a text edit to the live Quill document") and drop the domain:
// atom, domain-free
public applyTextEdit(edit: { index: number; deleteLength: number; insert: string }): string | undefinedThe page then computes the edit from the issue via the util, which already knows how. That removes ComplianceIssue and ComplianceIssueActionEnum from the atom entirely, collapses the two implementations into one, and makes the EN/DE paths symmetric — which fixes the divergence above as a side effect rather than leaving it to surface later.
For context, the gender-decoder surface already on this component (showGenderDecoderButton, biasedAnalysis, genderDecoderClick, showAnalysisModal) is the same pattern and predates this PR, so this is a direction worth stopping rather than something introduced here from scratch.
Checklist
General
Server
Client
Motivation and Context
Description
Steps for Testing
Prerequisites:
Review Progress
Code Review
Manual Tests
Screenshots
Test Coverage
Client
Server
Last updated: 2026-08-24 17:20:18 UTC