Add report issue workflow to backend and toolbar UI#35717
Conversation
Convert @Input/@output to model()/output(), replace visible setter side-effect with effect(), and promote screenshotFile to a signal for OnPush correctness. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move dot-report-issue out of dot-toolbar/components since it is no longer toolbar-specific. Update relative imports accordingly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
❌ Issue Linking RequiredThis PR could not be linked to an issue. All PRs must be linked to an issue for tracking purposes. How to fix this:Option 1: Add keyword to PR body (Recommended - auto-removes this comment)
Why is this required?Issue linking ensures proper tracking, documentation, and helps maintain project history. It connects your code changes to the problem they solve.--- This comment was automatically generated by the issue linking workflow |
|
Claude finished @fmontes's task in 4m 32s —— View job PR Review — Report Issue workflowDone reviewing. The PR is well-structured, well-tested, and the two Copilot rounds already caught most of the obvious issues. A few things still look worth a second look — flagging the higher-impact items first. 1.
|
There was a problem hiding this comment.
Pull request overview
Adds an end-to-end “Report an Issue” workflow for the dotCMS Admin UI by introducing a new backend proxy endpoint that forwards issue reports (optionally with a screenshot) to an upstream workflow action, and wiring a new toolbar user-menu entry + dialog that submits multipart data and surfaces validation/errors.
Changes:
- Backend: Introduces
POST /api/v1/report-issueJAX-RS resource, request validation, upstream forwarding (JSON or multipart), and response mapping; adds unit tests. - Frontend: Adds “Report an Issue” menu item and a new dialog component to collect description + optional screenshot and submit via a new API service.
- Docs/i18n: Adds OpenAPI path entry and new UI translation keys.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| dotCMS/src/main/java/com/dotcms/rest/api/v1/reportissue/ReportIssueResource.java | New REST endpoint + upstream forwarder and payload/validation logic for report submissions. |
| dotCMS/src/test/java/com/dotcms/rest/api/v1/reportissue/ReportIssueResourceTest.java | Unit tests covering validation, forwarding behavior, and authorization header handling. |
| dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml | Documents the new /v1/report-issue endpoint in OpenAPI. |
| dotCMS/src/main/webapp/WEB-INF/messages/Language.properties | Adds i18n strings for the new “Report an Issue” UI flow. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-toolbar/components/dot-toolbar-user/store/dot-toolbar-user.store.ts | Adds state + menu action to open the report-issue dialog. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-toolbar/components/dot-toolbar-user/store/dot-toolbar-user.store.spec.ts | Tests new menu item and showReportIssue state updates. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-toolbar/components/dot-toolbar-user/dot-toolbar-user.component.ts | Includes the new report-issue dialog component in toolbar user component imports. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-toolbar/components/dot-toolbar-user/dot-toolbar-user.component.html | Renders the report-issue dialog when store state indicates it should be visible. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-toolbar/components/dot-toolbar-user/dot-toolbar-user.component.spec.ts | Updates test setup to provide services required by the newly imported dialog component. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-report-issue/dot-report-issue.component.ts | New dialog component with reactive form validation, screenshot handling, and submission. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-report-issue/dot-report-issue.component.html | Dialog UI markup, inputs, file upload, and submit/cancel actions. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-report-issue/dot-report-issue.component.spec.ts | Component tests for validation, submission flow, and error handling. |
| core-web/apps/dotcms-ui/src/app/providers.ts | Registers the new report-issue API service in app-wide providers. |
| core-web/apps/dotcms-ui/src/app/api/services/dot-report-issue.service.ts | New Angular service to submit multipart form data to /api/v1/report-issue. |
| core-web/apps/dotcms-ui/src/app/api/services/dot-report-issue.service.spec.ts | Service tests ensuring multipart fields are sent (with/without screenshot). |
Comments suppressed due to low confidence (4)
dotCMS/src/main/java/com/dotcms/rest/api/v1/reportissue/ReportIssueResource.java:449
- Client-supplied metadata is merged into the server-built metadata via metadata.putAll(clientMetadata), which allows a malicious client to override trusted server fields (e.g., submittedAt, user, remoteAddress, serverName) and spoof report context. Consider either nesting client metadata under a dedicated key (e.g., metadata.client) or whitelisting/merging in a way that prevents overriding server-owned keys.
if (user != null) {
final Map<String, Object> userMetadata = new LinkedHashMap<>();
userMetadata.put("userId", Try.of(user::getUserId).getOrNull());
userMetadata.put("email", Try.of(user::getEmailAddress).getOrNull());
userMetadata.put("fullName", Try.of(user::getFullName).getOrNull());
metadata.put("user", userMetadata);
}
if (clientMetadata != null && !clientMetadata.isEmpty()) {
metadata.putAll(clientMetadata);
}
dotCMS/src/main/java/com/dotcms/rest/api/v1/reportissue/ReportIssueResource.java:274
- readBytesWithLimit reads the screenshot InputStream but never closes it. Other multipart handlers in this codebase use try-with-resources around part.getEntityAs(InputStream.class) to ensure streams are closed; consider doing the same here to avoid leaking request resources under load.
private static byte[] readBytesWithLimit(final InputStream inputStream, final long maxBytes)
throws IOException {
if (inputStream == null) {
throw new ReportIssueValidationException("Screenshot is invalid.");
}
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
final byte[] buffer = new byte[8192];
long totalBytes = 0L;
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
totalBytes += bytesRead;
if (totalBytes > maxBytes) {
throw new ReportIssueValidationException("Screenshot exceeds the maximum allowed size.");
}
outputStream.write(buffer, 0, bytesRead);
}
return outputStream.toByteArray();
dotCMS/src/main/java/com/dotcms/rest/api/v1/reportissue/ReportIssueResource.java:118
- The OpenAPI annotations for 400/502 use the generic ResponseEntityView schema, and the 200 response has no schema. In this codebase, other endpoints typically reference a specific response view type (e.g., TempFilesView) to keep the contract explicit; consider introducing a dedicated response view for this endpoint (success + error) and referencing it in
@Schemato avoid an untyped/ambiguous API contract.
@Operation(
operationId = "reportIssue",
summary = "Report a dotCMS UI issue",
description = "Creates a Bug contentlet in the configured upstream reporting dotCMS instance.",
tags = {"Workflow"},
responses = {
@ApiResponse(responseCode = "200", description = "Issue reported",
content = @Content(mediaType = "application/json")),
@ApiResponse(responseCode = "400", description = "Invalid report payload",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = ResponseEntityView.class))),
@ApiResponse(responseCode = "502", description = "Reporting service unavailable or unauthorized",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = ResponseEntityView.class)))
dotCMS/src/main/java/com/dotcms/rest/api/v1/reportissue/ReportIssueResource.java:485
- mapUpstreamResponse can return upstream 4xx responses directly (e.g., 415) when the upstream includes a body, but the endpoint’s OpenAPI annotations (and openapi.yaml entry) only document 200/400/502. Consider either normalizing these upstream errors into the documented 502/400 responses, or expanding the OpenAPI responses to include the possible passthrough status codes so clients have an accurate contract.
private Response mapUpstreamResponse(final ReportIssueForwardResponse upstreamResponse) {
final int statusCode = upstreamResponse.statusCode();
if (statusCode == Response.Status.UNAUTHORIZED.getStatusCode()
|| statusCode == Response.Status.FORBIDDEN.getStatusCode()) {
return error(Response.Status.BAD_GATEWAY, ERROR_PROXY_NOT_AUTHORIZED,
"Report issue service is not authorized. Check the User Proxy plugin configuration.");
}
if (statusCode >= 200 && statusCode < 300) {
return Response.status(statusCode)
.entity(upstreamResponse.body())
.type(upstreamResponse.contentType().orElse(MediaType.APPLICATION_JSON))
.build();
}
if (statusCode >= 400 && statusCode < 500 && UtilMethods.isSet(upstreamResponse.body())) {
return Response.status(statusCode)
.entity(upstreamResponse.body())
.type(upstreamResponse.contentType().orElse(MediaType.APPLICATION_JSON))
.build();
}
- Backend: nest client-supplied metadata under "client" key so server fields cannot be spoofed; update title/browser derivation and tests. - Backend: document why the upstream multipart request uses PUT while the JSON branch uses POST. - Frontend: convert getScreenshotErrorMessage to a computed signal driven by form.statusChanges via toSignal. - Frontend: drop isClosing/queueMicrotask workaround now that handleClose flips the visible model directly. - Frontend: remove redundant take(1) operators on one-shot HttpClient observables. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address review feedback on external PII forwarding: - Frontend: add "Send anonymously" checkbox in the dialog; when checked, the request is submitted with anonymous=true. - Backend: when anonymous=true, strip the user.* identity block from the forwarded metadata. The metadata.anonymous flag is always set so the upstream can audit whether identity was sent. - Backend: add REPORT_ISSUE_INCLUDE_USER_PII config flag (default true). When operators set it to false, user identity is always stripped regardless of the client checkbox. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rename INCLUDE_USER_PII_PROPERTY to REPORT_ISSUE_INCLUDE_USER_PII_PROPERTY (and the matching default) so the Java constant name reflects the report-issue scope. The Config property key is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the operator sets REPORT_ISSUE_INCLUDE_USER_PII=false: - Expose the property via the existing /api/v1/configuration/config endpoint by adding it to the WHITE_LIST. - Frontend reads the value via DotPropertiesService.getKey with the boolean: prefix. - The "Send anonymously" checkbox is forced checked and disabled, and an inline hint explains that anonymous submission is enforced by the administrator. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The submit button is rendered inside PrimeNG's footer ng-template, outside the <form>, so type="submit" never triggered (ngSubmit). Switch to type="button" so the markup matches what the click handler actually does. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
report-issue-2.mp4
Proposed Changes
/api/v1/report-issuebackend endpoint that validates report submissions, forwards them to the workflow action, and handles proxy and upstream failures explicitlyChecklist
Additional Info
This diff includes the follow-up typing cleanup for the UI report-issue service response and the signal-based/report-component refactors that are already on this branch.
It also adds
REPORT_ISSUE_INCLUDE_USER_PII; set it tofalseto force anonymous reports and show the UI hint, ortrueto allow user details in report metadata.We use a plugin for the cross-env auth: https://github.com/dotcms-community/com.dotcms.userproxy/
Screenshots