Skip to content

Plugin ips rest#1

Merged
qu0d merged 19 commits into
mainfrom
plugin-ips-rest
May 22, 2026
Merged

Plugin ips rest#1
qu0d merged 19 commits into
mainfrom
plugin-ips-rest

Conversation

@qu0d

@qu0d qu0d commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • REST API endpoint to retrieve IPS data with filters (release, date, system, mandatory)
    • Admin UI to manage plugin settings
    • Client library for calling the Jira IPS API and aggregated IPS generation (dev/QA component aggregation)
  • Documentation

    • Plugin usage docs with API request examples
  • Tests

    • Comprehensive unit tests covering API client, auth handling, error decoding, and IPS generation
  • Chores

    • Maven multi-module build and CI/release workflows added; build outputs ignored in VCS

Review Change Stack

@qu0d qu0d self-assigned this May 21, 2026
@qu0d qu0d added the enhancement New feature or request label May 21, 2026
@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR introduces a complete Atlassian JIRA plugin that exposes IPS (Infrastructure Platform Services) Release requirement data via REST API. The implementation spans Maven multi-module project structure, Feign-based HTTP client library, core IPS data aggregation service, admin configuration UI, GitHub CI/CD pipelines, and comprehensive test coverage.

Changes

IPS JIRA API Plugin

Layer / File(s) Summary
Maven build configuration and CI/CD workflows
pom.xml, client/pom.xml, common/pom.xml, plugin/pom.xml, .github/workflows/*, .github/release.yml, .gitignore, plugin/doc/*
Root POM defines multi-module structure (common, client, plugin) with centralized dependency and plugin management for Kotlin, Feign, Jackson, JUnit, MockK. Build workflows compile/test on push/PR, Maven Release workflow on dispatch, and Check-and-Register workflow verifies artifacts. Release changelog auto-categorizes PRs.
Data models and exception hierarchy
common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/*, common/src/main/kotlin/org/octopusden/octopus/jira/api/exception/*
DTOs model Jira issues, IPS requirements, dev/QA requests, and responses: IssueBean, DevComponent, IPSReqDev, IPSReqQA, IPSRequirement, IPSResponse, IPSRequest. Exception hierarchy with base JiraApiException, code-to-type mapping, and concrete BadRequestException, FailedGenerateIPSException.
Feign HTTP client and authentication
client/pom.xml, client/src/main/kotlin/org/octopusden/octopus/jira/api/client/*, client/src/test/kotlin/org/octopusden/octopus/jira/api/client/*
JiraApiClient Feign interface with getIps GET endpoint; ClassicJiraApiClient implementation supporting bearer token or base64-encoded basic auth, Jackson serialization, custom error decoding with JSON parsing and code-to-exception mapping. Error decoder tests validate exception type conversion and fallback handling; auth tests verify token precedence and validation.
Settings configuration and admin UI
plugin/src/main/kotlin/org/octopusden/octopus/jira/api/config/ApiSettingsProvider.kt, plugin/src/main/java/org/octopusden/octopus/jira/api/ui/admin/*, plugin/src/main/resources/octopus-jira-api-plugin.properties, plugin/src/main/resources/templates/ui/admin/settings-view.vm, plugin/src/main/resources/atlassian-plugin.xml
ApiSettingsProvider loads bundled defaults from properties and persists overrides to plugin global settings via Jira plugin factory; ApiSetting enum defines TEXT/PASSWORD/SELECT setting types. WebWork action classes and Velocity template provide admin UI for updating IPS project, service user, and password settings. Plugin descriptor wires components and REST module.
Core IPS service, REST endpoint, and exception mapping
plugin/src/main/kotlin/org/octopusden/octopus/jira/api/service/IPSService.kt, plugin/src/main/kotlin/org/octopusden/octopus/jira/api/rest/ApiRestController.kt, plugin/src/main/kotlin/org/octopusden/octopus/jira/api/exception/JiraApiExceptionMapper.kt
IPSService.generate() searches release issues via JQL, extracts custom fields, traverses inward Implements links to requirements, processes dev/QA subtasks filtered by system, aggregates components by name with deduplication. ApiRestController GET /api/ips/{ips} parses release or date-range filters, invokes service, handles exceptions and serializes response. JiraApiExceptionMapper converts exceptions to HTTP responses.
Comprehensive IPSService test suite
plugin/src/test/kotlin/org/octopusden/octopus/jira/api/service/IPSServiceTest.kt
JUnit tests with MockK cover error handling (missing user/fields/releases), JQL branching logic (release vs startDate vs neither), response structure validation, requirement extraction, dev/QA subtask population, mandatory-update filtering, component aggregation and deduplication, system filtering edge cases (empty lists), IssueBean field mapping with null priority/resolution defaults, complex multi-requirement scenarios, and null/empty data edge cases.

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "Plugin ips rest" is vague and generic. While it mentions "plugin" and "ips rest", it lacks specific detail about what was actually implemented. The PR adds a complete REST API plugin for IPS functionality, but the title does not clearly convey the main change. Provide a more descriptive title that clearly summarizes the main change, such as 'Add REST API plugin for IPS data retrieval' or 'Implement Jira REST API plugin for IPS functionality'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch plugin-ips-rest

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qu0d qu0d requested a review from rodemark May 21, 2026 00:06

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/build.yml (1)

1-10: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin the reusable workflow to a commit SHA instead of a mutable version tag, and add explicit token permissions.

The workflow currently references the reusable workflow via version tag @v2.3.4, which is mutable and can be moved or deleted. GitHub security best practices recommend pinning to a full commit SHA for immutability. Additionally, explicit permissions should be set to follow the principle of least privilege.

🔧 Proposed fix
 name: Maven Compile & UT
 
 on: [push, pull_request, workflow_dispatch]
 
+permissions:
+  contents: read
+
 jobs:
   run-build-and-deploy:
-    uses: octopusden/octopus-base/.github/workflows/common-java-maven-build.yml@v2.3.4
+    uses: octopusden/octopus-base/.github/workflows/common-java-maven-build.yml@<full-commit-sha>
     with:
       java-version: '8'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build.yml around lines 1 - 10, Update the reusable
workflow invocation by replacing the mutable tag on the `uses:
octopusden/octopus-base/.github/workflows/common-java-maven-build.yml@v2.3.4`
with the full commit SHA for immutability, and add an explicit top-level
`permissions` block to the workflow (least-privilege, e.g., `contents: read` and
any other minimal permissions required by the reused workflow) so the
`run-build-and-deploy` job runs with explicit token scopes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/check-and-register.yml:
- Around line 4-7: The workflow currently uses workflow_run with secrets:
inherit without sufficient validation; add trust-boundary guards on the
consuming job (e.g., the job named build) by adding an if condition that
validates the upstream workflow name, repository and branch before inheriting
secrets — for example use github.event.workflow_run.name (or workflow) == "Maven
Release" AND github.event.workflow_run.head_repository.full_name ==
github.repository AND github.event.workflow_run.head_branch == 'main' AND
github.event.workflow_run.conclusion == 'success' (or
github.event.workflow_run.status/type checks) so the job (build) only runs and
inherits secrets when all these checks pass, and remove/avoid secrets: inherit
unless those guards are present.

In @.github/workflows/release.yml:
- Line 16: Replace the blanket "secrets: inherit" in the release job call to the
external workflow (the "uses:
octopusden/octopus-base/.github/workflows/common-java-maven-release.yml@v2.3.4"
invocation) by listing only the specific secrets that workflow needs (e.g.,
OCTOPUS_API_KEY, MAVEN_CENTRAL_USERNAME, MAVEN_CENTRAL_PASSWORD or whichever the
external workflow documents). Edit the job's "secrets:" mapping to explicitly
map each secret name to a repository secret (e.g., secrets: { OCTOPUS_API_KEY:
${{ secrets.OCTOPUS_API_KEY }}, ... }), removing "inherit" entirely.
- Around line 1-12: Add an explicit top-level permissions block and SHA-pin the
reusable workflow reference: replace the mutable tag in the uses value
"octopusden/octopus-base/.github/workflows/common-java-maven-release.yml@v2.3.4"
with the full commit SHA (you may keep a trailing comment with "v2.3.4" for
clarity) and add a top-level permissions: map granting only required scopes for
this job (e.g., contents: read, id-token: write, packages: read — adjust to the
actual needs) so GITHUB_TOKEN follows least-privilege rules.

In `@doc/octopus-jira-api-plugin.adoc`:
- Line 10: Replace the broken sentence "Octopus Jira API Plugin is specific
Octopus API." with a clear user-facing description such as "The Octopus Jira API
Plugin integrates Jira with the Octopus API." and replace the non-actionable
placeholders since={{}} and sinceDate={{}} with concrete, actionable examples or
templated tokens (e.g., since=VERSION or sinceDate=YYYY-MM-DD) wherever they
appear (also update the other occurrences noted around the same section).
- Line 16: The documented GET path shown in doc/octopus-jira-api-plugin.adoc
omits the required /type/{{type}} segment; update the example request path (the
string starting with "GET
{{jira-url}}/rest/octopus-jira-api/1/api/ips/{{ips}}?") to include
"/type/{{type}}" (i.e., "GET
{{jira-url}}/rest/octopus-jira-api/1/api/ips/{{ips}}/type/{{type}}?") so the
sample matches the API contract and routes correctly.

In `@pom.xml`:
- Around line 282-287: Update the Maven repository checksumPolicy settings to
fail rather than warn: locate the <checksumPolicy> elements inside both
<releases> and <snapshots> blocks for the repository and pluginRepository
entries and change their values from "warn" to "fail" so checksum mismatches
abort the build; ensure you update all occurrences of <checksumPolicy> within
those <releases> and <snapshots> sections in the POM.

In
`@src/main/java/org/octopusden/octopus/jira/api/ui/admin/AbstractViewApiSettings.java`:
- Around line 41-44: The current code in AbstractViewApiSettings reads
request.getParameter(setting.getKey()) and persists any non-null value, allowing
empty strings to overwrite sensitive settings like SERVICE_USER_PASSWORD; change
the logic so you only call settingsProvider.set(setting, value) when value is
non-null and not blank (e.g., value.trim().isEmpty() == false), and for the
specific sensitive key (SERVICE_USER_PASSWORD) treat blank values as "do not
change" so submissions that omit or send an empty password will not overwrite
the stored credential; update the condition around request.getParameter(...) and
settingsProvider.set(...) to enforce this check for both the general case and
the SERVICE_USER_PASSWORD identifier.
- Around line 38-46: The doDefault() method currently mutates settings on GET;
move the persistence logic into a new doExecute() method so doDefault() only
prepares the view, and perform explicit CSRF validation in doExecute() by
reading the atl_token request parameter (via ExecutingHttpRequest.get()) and
validating it before calling settingsProvider.set(...) for each ApiSetting from
getAvailableSettings(); keep getAvailableSettings(), settingsProvider and
ApiSetting.key usage but ensure doExecute() returns SUCCESS only after CSRF
passes and settings are persisted, otherwise return an error/forbidden result.

In
`@src/main/kotlin/org/octopusden/octopus/jira/api/config/ApiSettingsProvider.kt`:
- Around line 32-34: The getString function currently treats empty strings as
valid; update the ApiSettingsProvider.getString implementation so that after
retrieving the value via get(key) it not only checks for null but also rejects
blank/empty strings (use String.isBlank()) and throws the same
IllegalStateException with context (including key.key) when the value is blank;
ensure this change is applied only to getString so other getters' behavior is
unchanged.
- Around line 14-17: The resource InputStream returned by
ApiSettingsProvider::class.java.getResourceAsStream(PROPERTIES_FILE_NAME) must
be null-checked before calling .use() to avoid a NullPointerException during
class init; change the block so you retrieve the stream into a variable, if the
stream is null throw a clear IllegalStateException (or similar) mentioning
PROPERTIES_FILE_NAME and that the config is missing, otherwise call stream.use {
load(stream) } so load(stream) only runs with a non-null stream.
- Around line 23-25: The setter currently writes all values (including
passwords) with pluginSettings.put in operator fun set(setting: ApiSetting,
value: String?), so replace plain-text storage for
ApiSetting.SERVICE_USER_PASSWORD with the Atlassian Plugin Secrets Service:
inject PluginSecretsService into this class, and in operator fun set use
PluginSecretsService (e.g., its secret store API) to persist
SERVICE_USER_PASSWORD while keeping pluginSettings.put for non-secret settings;
also update corresponding getter logic that reads SERVICE_USER_PASSWORD to
retrieve from PluginSecretsService instead of pluginSettings.

In `@src/main/kotlin/org/octopusden/octopus/jira/api/rest/ApiRestService.kt`:
- Around line 90-93: The catch block in ApiRestService (the exception handler
around the IPS generation call) currently returns the raw exception message to
the client; update the Response.serverError() entity to return a generic payload
(e.g., {"error":"Internal server error"}) and remove e.message from the HTTP
response while keeping the detailed error in the server log via
logger.error("Failed to generate IPS data for
${request.ips}:${request.release}", e); ensure the server response does not
expose internal details from the caught Exception.
- Around line 37-38: The route template includes an unused {type} and the code
double-decodes the ips path param; update the `@Path` on getIPS (method getIPS in
ApiRestService) to remove "/type/{type}" so it reads "ips/{ips}", and stop
calling URLDecoder.decode(ips, "UTF-8") when constructing IPSRequest (both
places where IPSRequest(ips = URLDecoder.decode(ips, ...)) is used); pass the
JAX-RS-provided ips value directly to IPSRequest and remove the URLDecoder
import/usages related to this decoding.
- Around line 23-24: The ApiRestService class is currently annotated with
`@AnonymousAllowed` which permits unauthenticated access to sensitive JIRA issue
metadata; remove the `@AnonymousAllowed` annotation from the class or replace it
with an authentication requirement and/or add explicit per-request authorization
checks inside the ApiRestService endpoint methods (e.g., methods handling issue
retrieval) to validate the caller’s identity and permissions before returning
requirements, subtasks, components, versions, or custom fields; ensure you use
your platform’s authentication/authorization utilities (or security annotations)
consistently across each public method in ApiRestService to enforce access
control.

In `@src/main/kotlin/org/octopusden/octopus/jira/api/service/IPSService.kt`:
- Around line 61-65: The response currently sets release = request.release ?: ""
which yields an empty string for date-based requests; change it to prefer
request.release but fall back to the resolved IPS issue's release (e.g. release
= request.release ?: ipsReleaseIssue.release ?: "") so the IPSResponse includes
the concrete release value discovered on ipsReleaseIssue; update the IPSResponse
construction in the code that builds the response (look for IPSResponse(...)
near ipsReleaseIssue and request.release) to use this fallback.

In `@src/main/resources/octopus-jira-api-plugin.properties`:
- Around line 2-3: Remove the hardcoded default credentials by deleting the
literal values for service-user.username and service-user.password and instead
read them from a secure source (e.g., environment variables or a
secret-management provider) at runtime; update the code/config that consumes
these keys to expect externalized values (service-user.username,
service-user.password) and fail fast with a clear error if they are missing, and
add documentation or a sample .env.example showing placeholder names rather than
real credentials.

In `@src/main/resources/templates/ui/admin/settings-view.vm`:
- Around line 4-6: The template is rendering unescaped setting values into input
value attributes (via $settingsProvider.get($setting) and name="$setting.key"),
creating a stored XSS risk; update the Velocity template to HTML-escape the
setting key and value before output (use the Velocity escape tool or an existing
escape utility, e.g., $esc.html($settingsProvider.get($setting)) and
$esc.html($setting.key) or the project’s equivalent) so both the name and value
attributes are safely encoded when rendered in settings-view.vm.
- Around line 3-4: The password input is pre-populating the persisted secret by
using settingsProvider.get($setting) in the value attribute; remove the value
population for type PASSWORD inputs so the <input name="$setting.key"
type="password"> is rendered without a value (or explicitly render an empty
value) and rely on server-side update handling to only change the stored secret
when a non-empty password is submitted; update the template branch that checks
$setting.type.name() == "PASSWORD" and remove or replace the
value="$settingsProvider.get($setting)" usage to prevent exposing secrets in the
DOM.

In `@src/test/kotlin/org/octopusden/octopus/jira/api/service/IPSServiceTest.kt`:
- Around line 173-216: Add a unit test that asserts service.generate(request)
throws when the "System" custom field is missing: mock
customFieldManager.getCustomFieldObjectsByName("System") to return an empty
list, call createRequest() and invoke service.generate(request) expecting an
IllegalStateException, and assert the exception message contains "System"; name
the test testGenerateThrowsWhenSystemCustomFieldNotFound to match the existing
style and place it alongside the other missing-custom-field tests.
- Around line 82-90: The mocked JQL builder setup uses relaxed mocks which
prevents verifying that the correct query clauses are invoked; change the mocks
for JqlClauseBuilder (mockWhereBuilder) and/or mockQueryBuilder to non-relaxed
where needed and add verify blocks in the relevant tests (e.g.,
testGenerateWithReleaseParamBuildsJqlWithIpsReleaseField and the startDate path
test) to assert calls on mockWhereBuilder such as project(IPS_PROJECT),
customField(10002L).eq(RELEASE) for the release path and the appropriate
customField(...).ge(...) / date filter calls for the startDate path, and also
verify JqlQueryBuilder.newBuilder().where() was used to ensure the builder flow
is executed.

---

Outside diff comments:
In @.github/workflows/build.yml:
- Around line 1-10: Update the reusable workflow invocation by replacing the
mutable tag on the `uses:
octopusden/octopus-base/.github/workflows/common-java-maven-build.yml@v2.3.4`
with the full commit SHA for immutability, and add an explicit top-level
`permissions` block to the workflow (least-privilege, e.g., `contents: read` and
any other minimal permissions required by the reused workflow) so the
`run-build-and-deploy` job runs with explicit token scopes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 38ccc03a-3135-4d0f-9bfe-57377970865f

📥 Commits

Reviewing files that changed from the base of the PR and between 47a8f35 and b7f15d2.

⛔ Files ignored due to path filters (2)
  • src/main/resources/images/pluginIcon.png is excluded by !**/*.png
  • src/main/resources/images/pluginLogo.png is excluded by !**/*.png
📒 Files selected for processing (22)
  • .github/release.yml
  • .github/workflows/build.yml
  • .github/workflows/check-and-register.yml
  • .github/workflows/release.yml
  • .gitignore
  • doc/octopus-jira-api-plugin.adoc
  • pom.xml
  • src/main/java/org/octopusden/octopus/jira/api/ui/admin/AbstractViewApiSettings.java
  • src/main/java/org/octopusden/octopus/jira/api/ui/admin/ViewApiSettings.java
  • src/main/kotlin/org/octopusden/octopus/jira/api/config/ApiSettingsProvider.kt
  • src/main/kotlin/org/octopusden/octopus/jira/api/dto/DevComponent.kt
  • src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSReqDev.kt
  • src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSReqQA.kt
  • src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSRequirement.kt
  • src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSResponse.kt
  • src/main/kotlin/org/octopusden/octopus/jira/api/dto/IssueBean.kt
  • src/main/kotlin/org/octopusden/octopus/jira/api/rest/ApiRestService.kt
  • src/main/kotlin/org/octopusden/octopus/jira/api/service/IPSService.kt
  • src/main/resources/atlassian-plugin.xml
  • src/main/resources/octopus-jira-api-plugin.properties
  • src/main/resources/templates/ui/admin/settings-view.vm
  • src/test/kotlin/org/octopusden/octopus/jira/api/service/IPSServiceTest.kt

Comment thread .github/workflows/check-and-register.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread doc/octopus-jira-api-plugin.adoc Outdated
Comment thread plugin/doc/octopus-jira-api-plugin.adoc
Comment thread plugin/src/main/resources/octopus-jira-api-plugin.properties Outdated
Comment thread plugin/src/main/resources/templates/ui/admin/settings-view.vm
Comment thread plugin/src/main/resources/templates/ui/admin/settings-view.vm

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
doc/octopus-jira-api-plugin.adoc (1)

16-16: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Document endpoint path must include the required type segment.

Line 16 still omits /type/{{type}}, so the sample request does not match the REST route contract and can lead clients to a non-existent path.

🔧 Proposed fix
-GET {{jira-url}}/rest/octopus-jira-api/1/api/ips/{{ips}}?
+GET {{jira-url}}/rest/octopus-jira-api/1/api/ips/{{ips}}/type/{{type}}?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@doc/octopus-jira-api-plugin.adoc` at line 16, The example endpoint path is
missing the required path segment /type/{{type}}; update the sample GET URL (the
string starting "GET {{jira-url}}/rest/octopus-jira-api/1/api/ips/{{ips}}?") to
include /type/{{type}} immediately after {{ips}} (i.e.
".../ips/{{ips}}/type/{{type}}?...") and ensure any trailing query parameters
remain intact so the documented request matches the REST route contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@doc/octopus-jira-api-plugin.adoc`:
- Line 16: The example endpoint path is missing the required path segment
/type/{{type}}; update the sample GET URL (the string starting "GET
{{jira-url}}/rest/octopus-jira-api/1/api/ips/{{ips}}?") to include
/type/{{type}} immediately after {{ips}} (i.e.
".../ips/{{ips}}/type/{{type}}?...") and ensure any trailing query parameters
remain intact so the documented request matches the REST route contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9a547658-0250-4f44-a4b0-772d9cf27963

📥 Commits

Reviewing files that changed from the base of the PR and between b7f15d2 and 03402de.

📒 Files selected for processing (3)
  • doc/octopus-jira-api-plugin.adoc
  • pom.xml
  • src/main/kotlin/org/octopusden/octopus/jira/api/rest/ApiRestService.kt

Comment thread plugin/src/main/kotlin/org/octopusden/octopus/jira/api/rest/ApiRestService.kt Outdated
Comment thread src/main/kotlin/org/octopusden/octopus/jira/api/service/IPSService.kt Outdated
Comment thread common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSResponse.kt Outdated
Comment thread plugin/src/main/kotlin/org/octopusden/octopus/jira/api/service/IPSService.kt Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugin/src/main/kotlin/org/octopusden/octopus/jira/api/rest/ApiRestService.kt (1)

59-61: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix since (year-only) parsing in ApiRestService.getIPS()

sinceYear uses LocalDate.parse(sinceYear.toString(), DATE_FORMAT_YEAR) with DATE_FORMAT_YEAR = yyyy, which fails for inputs like 2026 (year-only strings aren’t enough to construct a LocalDate). Build the date explicitly instead.
Also, diagnostics are weakened: logger.error(it) logs only a string, and FailedGenerateIPSException(message: String) doesn’t preserve the original throwable/cause.

Proposed fix
-                    sinceYear != null -> Date.from(
-                        LocalDate.parse(sinceYear.toString(), DATE_FORMAT_YEAR)
-                            .atStartOfDay().toInstant(ZoneOffset.UTC)
-                    )
+                    sinceYear != null -> Date.from(
+                        LocalDate.of(sinceYear, 1, 1)
+                            .atStartOfDay().toInstant(ZoneOffset.UTC)
+                    )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugin/src/main/kotlin/org/octopusden/octopus/jira/api/rest/ApiRestService.kt`
around lines 59 - 61, In ApiRestService.getIPS() fix year-only parsing by
treating sinceYear as an integer/Year and constructing a LocalDate via
LocalDate.of(year, 1, 1) (or Year.of(year).atDay(1)) then convert to Instant at
UTC instead of LocalDate.parse(..., DATE_FORMAT_YEAR); also replace
logger.error(it) usage to log the throwable and message (e.g.,
logger.error("...", it)) and throw FailedGenerateIPSException with the original
throwable as the cause (use the constructor or add a cause parameter) so the
original exception is preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@client/src/main/kotlin/org/octopusden/octopus/jira/api/client/JiraApiClientErrorDecoder.kt`:
- Around line 12-24: The decode implementation in JiraApiClientErrorDecoder
currently consumes the response InputStream and does a case-sensitive headers
lookup, which prevents ErrorDecoder.Default from seeing the original body and
misses differently-cased Content-Type headers; fix it by reading and buffering
the response body into a String (e.g., responseBody), perform a case-insensitive
check for Content-Type by iterating response.headers() keys or normalizing
header names, attempt JSON parsing from the buffered responseBody as before, and
if parsing fails or Content-Type doesn't indicate JSON, recreate or wrap the
original Response with the buffered body intact and pass that Response into
super.decode(methodKey, preservedResponse) so fallback decoding can see the
unconsumed payload (refer to the decode method and variable names responseBody,
objectMapper, ApplicationErrorResponse, JiraApiException).

In `@plugin/pom.xml`:
- Around line 209-215: The maven-enforcer-plugin configuration currently
disables all enforcement via the <fail>false</fail> flag; revert this by
removing or setting <fail>true</fail> and instead selectively disable only the
problematic rules inside the maven-enforcer-plugin configuration (e.g.,
configure specific rule entries like dependencyConvergence, requireMavenVersion,
bannedDependencies with <enabled>false</enabled> or rule-specific <excludes>),
so enforcement remains active for other checks while skipping only the
identified failing rule(s) referenced in the plugin configuration.
- Around line 160-164: The Kotlin test compilation's <sourceDirs> currently
includes a resource folder which causes the Kotlin compiler to parse non-Kotlin
files; remove the <sourceDir>${project.basedir}/src/test/resources</sourceDir>
entry from the <sourceDirs> block so only Kotlin/Java test source directories
remain (leave ${project.basedir}/src/test/kotlin and
${project.basedir}/src/test/java), ensuring test resources are handled by the
resources phase rather than the Kotlin compiler.
- Around line 145-149: Remove the resources entry from the Kotlin compilation
sourceDirs so the Kotlin compiler doesn't try to parse non‑Kotlin files: edit
the <sourceDirs> block and delete the
<sourceDir>${project.basedir}/src/main/resources</sourceDir> line (leave the
Kotlin and Java sourceDir entries intact) so only actual source folders are
provided to the Kotlin compiler (refer to the <sourceDirs> / <sourceDir>
elements in the pom.xml).
- Around line 191-208: The build-helper-maven-plugin declaration incorrectly
uses the Maven runtime property ${maven.version} as its plugin version; update
the <plugin> block for build-helper-maven-plugin so it either specifies a valid
build-helper-maven-plugin version (e.g., a known release) instead of
${maven.version}, or remove the <version> element so the version is inherited
from pluginManagement; adjust the <plugin> element containing
<groupId>org.codehaus.mojo</groupId> and
<artifactId>build-helper-maven-plugin</artifactId> accordingly.

In
`@plugin/src/main/kotlin/org/octopusden/octopus/jira/api/rest/ApiRestService.kt`:
- Around line 91-93: The current code in ApiRestService builds a message string
including ${e} but calls logger.error(it) so the exception stack trace is lost;
update the logging call to pass the throwable (use logger.error(message, e)) and
when throwing FailedGenerateIPSException preserve the original cause (throw a
FailedGenerateIPSException(message, e) or otherwise attach e as the cause)
referencing logger.error, FailedGenerateIPSException, ApiRestService, and the
request.ips/request.release context so the stack trace and root cause are
preserved.

In `@pom.xml`:
- Line 61: The pom pins an insecure Jackson version via the property
jackson.version (currently 2.9.2) and an outdated SLF4J via slf4j.version;
update jackson.version to a supported, non-vulnerable release (e.g., bump to a
current 2.x LTS such as 2.15.x or at minimum 2.12+ depending on compatibility)
and update slf4j.version to a modern release (e.g., 1.7.36 or 2.x) to remediate
CVEs; locate the jackson.version and slf4j.version properties in the POM and
change their values, then run mvn dependency:tree and your test suite to verify
there are no breaking changes and adjust jackson-module-kotlin or other
Jackson-bound artifacts if API/compatibility changes require minor code updates.

---

Outside diff comments:
In
`@plugin/src/main/kotlin/org/octopusden/octopus/jira/api/rest/ApiRestService.kt`:
- Around line 59-61: In ApiRestService.getIPS() fix year-only parsing by
treating sinceYear as an integer/Year and constructing a LocalDate via
LocalDate.of(year, 1, 1) (or Year.of(year).atDay(1)) then convert to Instant at
UTC instead of LocalDate.parse(..., DATE_FORMAT_YEAR); also replace
logger.error(it) usage to log the throwable and message (e.g.,
logger.error("...", it)) and throw FailedGenerateIPSException with the original
throwable as the cause (use the constructor or add a cause parameter) so the
original exception is preserved.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ea5a0c13-0cff-4dad-ad81-460f880d0bac

📥 Commits

Reviewing files that changed from the base of the PR and between 03402de and 5725b8a.

⛔ Files ignored due to path filters (2)
  • plugin/src/main/resources/images/pluginIcon.png is excluded by !**/*.png
  • plugin/src/main/resources/images/pluginLogo.png is excluded by !**/*.png
📒 Files selected for processing (29)
  • client/pom.xml
  • client/src/main/kotlin/org/octopusden/octopus/jira/api/client/JiraApiClient.kt
  • client/src/main/kotlin/org/octopusden/octopus/jira/api/client/JiraApiClientErrorDecoder.kt
  • client/src/main/kotlin/org/octopusden/octopus/jira/api/client/impl/ClassicJiraApiClient.kt
  • client/src/main/kotlin/org/octopusden/octopus/jira/api/client/impl/JiraApiClientParametersProvider.kt
  • client/src/test/kotlin/org/octopusden/octopus/jira/api/client/JiraApiClientErrorDecoderTest.kt
  • client/src/test/kotlin/org/octopusden/octopus/jira/api/client/impl/ClassicJiraApiClientTest.kt
  • common/pom.xml
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/DevComponent.kt
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSReqDev.kt
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSReqQA.kt
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSRequest.kt
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSRequirement.kt
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSResponse.kt
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/IssueBean.kt
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/exception/JiraApiException.kt
  • plugin/doc/octopus-jira-api-plugin.adoc
  • plugin/pom.xml
  • plugin/src/main/java/org/octopusden/octopus/jira/api/ui/admin/AbstractViewApiSettings.java
  • plugin/src/main/java/org/octopusden/octopus/jira/api/ui/admin/ViewApiSettings.java
  • plugin/src/main/kotlin/org/octopusden/octopus/jira/api/config/ApiSettingsProvider.kt
  • plugin/src/main/kotlin/org/octopusden/octopus/jira/api/exception/JiraApiExceptionMapper.kt
  • plugin/src/main/kotlin/org/octopusden/octopus/jira/api/rest/ApiRestService.kt
  • plugin/src/main/kotlin/org/octopusden/octopus/jira/api/service/IPSService.kt
  • plugin/src/main/resources/atlassian-plugin.xml
  • plugin/src/main/resources/octopus-jira-api-plugin.properties
  • plugin/src/main/resources/templates/ui/admin/settings-view.vm
  • plugin/src/test/kotlin/org/octopusden/octopus/jira/api/service/IPSServiceTest.kt
  • pom.xml
💤 Files with no reviewable changes (12)
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/DevComponent.kt
  • plugin/src/main/resources/octopus-jira-api-plugin.properties
  • plugin/doc/octopus-jira-api-plugin.adoc
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSReqQA.kt
  • plugin/src/main/resources/templates/ui/admin/settings-view.vm
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSResponse.kt
  • plugin/src/main/java/org/octopusden/octopus/jira/api/ui/admin/ViewApiSettings.java
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSReqDev.kt
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSRequirement.kt
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/IssueBean.kt
  • plugin/src/main/java/org/octopusden/octopus/jira/api/ui/admin/AbstractViewApiSettings.java
  • plugin/src/main/kotlin/org/octopusden/octopus/jira/api/config/ApiSettingsProvider.kt

Comment thread plugin/pom.xml
Comment thread plugin/pom.xml
Comment thread plugin/pom.xml
Comment thread plugin/pom.xml
Comment thread pom.xml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
plugin/src/main/kotlin/org/octopusden/octopus/jira/api/service/IPSService.kt (1)

92-99: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Populate IPSResponse.release from the resolved issue when request release is absent.

Line 95 still returns an empty release for date-based requests, even when a concrete IPS Release issue was found. This drops response data.

Proposed fix
         return IPSResponse(
             key = ipsReleaseIssue.key,
             ips = request.ips,
-            release = request.release ?: "",
+            release = request.release ?: getCustomFieldStringValue(fieldIpsRelease, ipsReleaseIssue),
             status = ipsReleaseIssue.status.name,
             labels = ipsReleaseIssue.labels.map { it.label },
             requirements = requirements
         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/src/main/kotlin/org/octopusden/octopus/jira/api/service/IPSService.kt`
around lines 92 - 99, The IPSResponse currently sets release to request.release
?: "" which leaves it empty for date-based requests even when a concrete IPS
release issue was resolved; change the assignment to use the resolved IPS
release issue's release value when request.release is null (e.g., release =
request.release ?: ipsReleaseIssue.release or the appropriate property on
ipsReleaseIssue), leaving other fields (key, status, labels, requirements)
unchanged; update the return in the code that builds IPSResponse (the
IPSResponse constructor usage where ipsReleaseIssue and request are available)
to populate release from ipsReleaseIssue when request.release is absent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@plugin/src/main/kotlin/org/octopusden/octopus/jira/api/service/IPSService.kt`:
- Around line 118-125: The mapping over versionToIssues produces duplicate
DevComponent.issues when the same issue appears with multiple fix versions;
after grouping into DevComponent (in the lambda that builds DevComponent for
each compName), change the issues population to remove duplicates (e.g., collect
pairs.map { it.second } then call distinct()/toSet().toList() or distinctBy(...)
as appropriate) so that DevComponent.fixVersions remains the aggregated list
while DevComponent.issues contains only unique issues.

---

Duplicate comments:
In
`@plugin/src/main/kotlin/org/octopusden/octopus/jira/api/service/IPSService.kt`:
- Around line 92-99: The IPSResponse currently sets release to request.release
?: "" which leaves it empty for date-based requests even when a concrete IPS
release issue was resolved; change the assignment to use the resolved IPS
release issue's release value when request.release is null (e.g., release =
request.release ?: ipsReleaseIssue.release or the appropriate property on
ipsReleaseIssue), leaving other fields (key, status, labels, requirements)
unchanged; update the return in the code that builds IPSResponse (the
IPSResponse constructor usage where ipsReleaseIssue and request are available)
to populate release from ipsReleaseIssue when request.release is absent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7fcd8711-2aee-464d-a841-19a6e969bd34

📥 Commits

Reviewing files that changed from the base of the PR and between a45e3a8 and 2abb3e0.

📒 Files selected for processing (7)
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/DevComponent.kt
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSReqDev.kt
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSReqQA.kt
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSRequirement.kt
  • common/src/main/kotlin/org/octopusden/octopus/jira/api/dto/IPSResponse.kt
  • plugin/src/main/kotlin/org/octopusden/octopus/jira/api/rest/ApiRestController.kt
  • plugin/src/main/kotlin/org/octopusden/octopus/jira/api/service/IPSService.kt

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

@pgorbachev pgorbachev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick turnaround on the previous round — most things look great. A couple of small follow-ups below.

Comment thread plugin/src/main/kotlin/org/octopusden/octopus/jira/api/rest/ApiRestController.kt Outdated
}
} catch (e: Exception) {
"Failed to generate IPS data for ${request.ips}:${request.release} ${e}".let {
logger.error(it)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small follow-up — I don't think this one was addressed yet (no reply on the original thread, so flagging again in case it slipped):

logger.error(it) takes only a string, so the Throwable is dropped on the floor and the stack trace never reaches the logs. The two-arg form keeps the cause intact:

catch (e: Exception) {
    logger.error("Failed to generate IPS data for ${request.ips}:${request.release}", e)
    throw FailedGenerateIPSException("Failed to generate IPS data: ${e.message}")
}

Not a merge blocker for me, but it'll save us from having to reproduce issues in dev just to see the stack.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obsolete
now

        } catch (e: Exception) {
            "Failed to generate IPS data for ${request.ips}:${request.release} ${e}".let {
                logger.error(it)
                throw FailedGenerateIPSException(it)
            }

rodemark
rodemark previously approved these changes May 22, 2026
pgorbachev
pgorbachev previously approved these changes May 22, 2026

@pgorbachev pgorbachev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — the bulk of the review feedback is addressed and CI is green. Nice work on the JacksonMapper extraction and the test refactor for the cached custom fields.

Two small items from my last comment can land as a follow-up PR (no need to hold this one):

  • ApiRestController.kt:74 — the second URLDecoder.decode(...) call in the else branch (one-line fix, matches the change already done on line 51)
  • ApiRestController.kt:93logger.error(it) losing the Throwable (two-arg form to preserve the stack trace)

Both are isolated and safe to do separately whenever you have a few minutes.

@pgorbachev pgorbachev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two small things from the latest commit:

private final List<ApiSetting> availableSettings = Arrays.asList(
ApiSetting.IPS_REPORTS_PROJECT,
ApiSetting.SERVICE_USER,
ApiSetting.SERVICE_USER_PASSWORD

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI is red on this — removing SERVICE_USER_PASSWORD from the ApiSetting enum left this Java reference dangling:

[ERROR] ViewApiSettings.java:[14,23] cannot find symbol
[ERROR]   symbol:   variable SERVICE_USER_PASSWORD
[ERROR]   location: class org.octopusden.octopus.jira.api.config.ApiSetting

Just drop this line and the trailing comma on line 13, should be a one-character compile fix.

}
} catch (e: Exception) {
"Failed to generate IPS data for ${request.ips}:${request.release} ${e}".let {
logger.error(it)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be missing something, but I think the "obsolete now" reply may have been a confusion — the catch (e: Exception) block on lines 90–95 wasn't actually touched in 9d2e4d8f, so the snippet you quoted as obsolete is exactly what's still in HEAD:

} catch (e: Exception) {
    "Failed to generate IPS data for ${request.ips}:${request.release} ${e}".let {
        logger.error(it)
        throw FailedGenerateIPSException(it)
    }
}

If you had a different change in mind that I overlooked — happy to take another look. Otherwise the two-arg logger.error(msg, e) form (and dropping ${e} from the message that goes to the client) still applies.

@pgorbachev pgorbachev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving after the latest fixes — 1abe50e9 cleared the compile error and CI is green again. Apologies for my "CI is red" note 8 minutes after you'd already fixed it.

logger.error on lines 90–95 is the only loose end from my side, and as I mentioned it's not a blocker — happy for it to land as a follow-up whenever convenient.

@qu0d qu0d merged commit 6c3225b into main May 22, 2026
4 checks passed
@qu0d qu0d deleted the plugin-ips-rest branch May 22, 2026 13:44
@coderabbitai coderabbitai Bot mentioned this pull request Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants