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 5s —— View job Review — PR #35717High-priority
Medium
Low / nits
Not a problem (sanity-checked)
SummaryFunctionally the change looks coherent and tested. The two things I would not ship without addressing are (1) external PII forwarding by default + no rate/size limit (#1, #2) and (3) Login-As attribution. The frontend close-guard race (#4) and missing dialog flags (#5) are easy follow-ups. |
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();
}
| static final String DEFAULT_WORKFLOW_URL = | ||
| "https://corpsites-headless.dotcms.cloud/api/v1/workflow/actions/default/fire/NEW"; |
| <button | ||
| pButton | ||
| type="submit" | ||
| [label]="'submit' | dm" | ||
| [loading]="isSubmitting()" | ||
| [disabled]="isSubmitting()" | ||
| data-testid="dot-report-issue-submit-button" | ||
| (click)="save()"></button> | ||
| </ng-template> |
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.
Screenshots