diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae7a7d2..4b165ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,7 +149,7 @@ jobs: # secret to also exercise license-dependent behavior. services: sonarqube-enterprise: - image: docker.io/library/sonarqube:2026.4.1-enterprise@sha256:d8475bd4bf7d49068acaae50e45f0c8f1a833a9e92e1ab26cf980caaa9f4a5e2 + image: docker.io/library/sonarqube:2026.5.0-enterprise@sha256:eb87329f5afbb5737e6bcb5d6195ad9649cf08875f767e2035780f4c2caf4602 ports: - 9001:9000 env: diff --git a/Makefile b/Makefile index d4373b8..44677a7 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ password := admin # renovate: datasource=docker depName=docker.io/library/sonarqube versioning=docker sonarqube_version := 26.8.0.126808-community # renovate: datasource=docker depName=docker.io/library/sonarqube versioning=docker -sonarqube_enterprise_version := 2026.4.1-enterprise +sonarqube_enterprise_version := 2026.5.0-enterprise # Versions of the Go tools installed on demand by the targets below. # Kept as annotated variables so Renovate bumps them (see renovate.json customManagers). @@ -17,7 +17,7 @@ gotestsum_version := v1.13.0 # renovate: datasource=go depName=github.com/onsi/ginkgo/v2 ginkgo_version := v2.33.0 # renovate: datasource=go depName=github.com/golangci/golangci-lint/v2 -golangci_lint_version := v2.13.2 +golangci_lint_version := v2.14.0 # renovate: datasource=go depName=golang.org/x/vuln govulncheck_version := v1.8.0 diff --git a/assets/api.enterprise.json b/assets/api.enterprise.json index c73be66..1e64621 100644 --- a/assets/api.enterprise.json +++ b/assets/api.enterprise.json @@ -707,6 +707,10 @@ "post": true, "hasResponseExample": false, "changelog": [ + { + "description": "Parameter \u0027url\u0027 is now rejected with a 400 response when it is not a valid absolute HTTP(S) URL", + "version": "2026.5" + }, { "description": "Parameter \u0027URL\u0027 was added", "version": "8.6" @@ -729,7 +733,7 @@ }, { "key": "url", - "description": "Azure API URL", + "description": "Azure API URL. Must be a valid absolute HTTP(S) URL, e.g. https://dev.azure.com/myorg", "required": true, "internal": false, "maximumLength": 2000 @@ -880,6 +884,14 @@ "hasResponseExample": true, "changelog": [], "params": [ + { + "key": "allowedOrganizations", + "description": "Comma-separated list of GitHub organizations whose members are allowed to sign in. Only used when \u0027auth\u0027 is true, and ignored otherwise. When omitted, the value of \u0027organization\u0027 is used as the allow list. An empty allow list lets any GitHub account sign in.", + "required": false, + "internal": false, + "exampleValue": "my-org,my-other-org", + "maximumLength": 4000 + }, { "key": "auth", "description": "Whether to also set up GitHub authentication (sign-in) using this App.", @@ -1008,6 +1020,14 @@ "post": false, "hasResponseExample": true, "changelog": [ + { + "description": "GitHub and GitLab bindings now contain a slug field with the repository\u0027s full name or path", + "version": "2026.5" + }, + { + "description": "Bitbucket Cloud and Bitbucket Server bindings now contain a repositoryUrl field with the URL to the repository", + "version": "2026.5" + }, { "description": "GitHub, GitLab and Azure bindings now contain a repositoryUrl field with the URL to the repository", "version": "2025.6" @@ -1094,10 +1114,15 @@ "key": "set_azure_binding", "description": "Bind a Azure DevOps instance to a project.\u003cbr/\u003eIf the project was already bound to a previous Azure DevOps instance, the binding will be updated to the new one.Requires the \u0027Administer\u0027 permission on the project", "since": "8.1", + "deprecatedSince": "2026.5", "internal": false, "post": true, "hasResponseExample": false, "changelog": [ + { + "description": "This endpoint is deprecated, please use POST api/v2/dop-translation/bound-projects instead. Parameters \u0027projectName\u0027 and \u0027repositoryName\u0027 are now rejected with a 400 response when they contain any of the Azure DevOps reserved characters: \\ / : \u003c \u003e | ? *", + "version": "2026.5" + }, { "description": "Add inline annotations parameter", "version": "2025.1" @@ -1133,14 +1158,14 @@ }, { "key": "projectName", - "description": "Azure project name", + "description": "Azure project name. Must not contain any of the Azure DevOps reserved characters: \\ / : \u003c \u003e | ? *", "since": "8.6", "required": true, "internal": false }, { "key": "repositoryName", - "description": "Azure repository name", + "description": "Azure repository name. Must not contain any of the Azure DevOps reserved characters: \\ / : \u003c \u003e | ? *", "since": "8.6", "required": true, "internal": false @@ -1151,10 +1176,16 @@ "key": "set_bitbucket_binding", "description": "Bind a Bitbucket instance to a project.\u003cbr/\u003eIf the project was already bound to a previous Bitbucket instance, the binding will be updated to the new one.Requires the \u0027Administer\u0027 permission on the project", "since": "8.1", + "deprecatedSince": "2026.5", "internal": false, "post": true, "hasResponseExample": false, - "changelog": [], + "changelog": [ + { + "description": "This endpoint is deprecated, please use POST api/v2/dop-translation/bound-projects instead", + "version": "2026.5" + } + ], "params": [ { "key": "almSetting", @@ -1193,10 +1224,16 @@ "key": "set_bitbucketcloud_binding", "description": "Bind a Bitbucket Cloud setting to a project.\u003cbr/\u003eIf the project was already bound to a different Bitbucket Cloud setting, the binding will be updated to the new one.Requires the \u0027Administer\u0027 permission on the project", "since": "8.7", + "deprecatedSince": "2026.5", "internal": false, "post": true, "hasResponseExample": false, - "changelog": [], + "changelog": [ + { + "description": "This endpoint is deprecated, please use POST api/v2/dop-translation/bound-projects instead", + "version": "2026.5" + } + ], "params": [ { "key": "almSetting", @@ -1229,10 +1266,15 @@ "key": "set_github_binding", "description": "Bind a GitHub instance to a project.\u003cbr/\u003eIf the project was already bound to a previous GitHub instance, the binding will be updated to the new one.Requires the \u0027Administer\u0027 permission on the project", "since": "8.1", + "deprecatedSince": "2026.5", "internal": false, "post": true, "hasResponseExample": false, "changelog": [ + { + "description": "This endpoint is deprecated, please use POST api/v2/dop-translation/bound-projects instead", + "version": "2026.5" + }, { "description": "Add \u0027summaryCommentEnabled\u0027 param to enable/disable of putting analysis summary in a conversation tab of GitHub", "version": "8.3" @@ -1278,10 +1320,15 @@ "key": "set_gitlab_binding", "description": "Bind a GitLab instance to a project.\u003cbr/\u003eIf the project was already bound to a previous Gitlab instance, the binding will be updated to the new one.Requires the \u0027Administer\u0027 permission on the project", "since": "8.1", + "deprecatedSince": "2026.5", "internal": false, "post": true, "hasResponseExample": false, "changelog": [ + { + "description": "This endpoint is deprecated, please use POST api/v2/dop-translation/bound-projects instead", + "version": "2026.5" + }, { "description": "Parameter \u0027repository\u0027 was added", "version": "8.2" @@ -1323,6 +1370,10 @@ "post": true, "hasResponseExample": false, "changelog": [ + { + "description": "Parameter \u0027url\u0027 is now rejected with a 400 response when its value changes and is not a valid absolute HTTP(S) URL", + "version": "2026.5" + }, { "description": "Parameter \u0027personalAccessToken\u0027 is no longer required", "version": "8.7" @@ -1356,7 +1407,7 @@ }, { "key": "url", - "description": "Azure API URL", + "description": "Azure API URL. Must be a valid absolute HTTP(S) URL, e.g. https://dev.azure.com/myorg. The format is only enforced when the value differs from the currently stored URL, so existing pre-validation settings can still be updated without fixing the URL first.", "required": true, "internal": false, "maximumLength": 2000 @@ -2337,7 +2388,9 @@ "REPORT", "ISSUE_SYNC", "AUDIT_PURGE", + "HISTORY_PURGE", "PROJECT_EXPORT", + "A3S_FRONTEND_CONTEXT_ZIP_PROCESSOR", "APP_REFRESH", "SCA_RESCAN_BRANCH", "PROJECT_IMPORT", @@ -2346,7 +2399,8 @@ "GITHUB_AUTH_PROVISIONING", "GITHUB_PROJECT_PERMISSIONS_PROVISIONING", "GITLAB_AUTH_PROVISIONING", - "GITLAB_PROJECT_PERMISSIONS_PROVISIONING" + "GITLAB_PROJECT_PERMISSIONS_PROVISIONING", + "SANDBOX_DEFERRAL_REOPEN" ] } ] @@ -2883,6 +2937,7 @@ "new_duplicated_lines_density", "new_lines", "new_maintainability_rating", + "new_ncloc", "new_reliability_rating", "new_sca_rating_any_issue", "new_security_hotspots_reviewed", @@ -2906,7 +2961,7 @@ }, { "key": "filter", - "description": "Filter of projects on name, key, measure value, quality gate, language, tag or whether a project is a favorite or not.\u003cbr\u003eThe filter must be encoded to form a valid URL (for example \u0027\u003d\u0027 must be replaced by \u0027%3D\u0027).\u003cbr\u003eExamples of use:\u003cul\u003e \u003cli\u003eto filter my favorite projects with a failed quality gate and a coverage greater than or equals to 60% and a coverage strictly lower than 80%:\u003cbr\u003e \u003ccode\u003efilter\u003d\"alert_status \u003d ERROR and isFavorite and coverage \u003e\u003d 60 and coverage \u003c 80\"\u003c/code\u003e\u003c/li\u003e \u003cli\u003eto filter projects with a reliability, security and maintainability rating equals or worse than B:\u003cbr\u003e \u003ccode\u003efilter\u003d\"reliability_rating\u003e\u003d2 and security_rating\u003e\u003d2 and sqale_rating\u003e\u003d2\"\u003c/code\u003e\u003c/li\u003e \u003cli\u003eto filter projects without duplication data:\u003cbr\u003e \u003ccode\u003efilter\u003d\"duplicated_lines_density \u003d NO_DATA\"\u003c/code\u003e\u003c/li\u003e\u003c/ul\u003eTo filter on project name or key, use the \u0027query\u0027 keyword, for instance : \u003ccode\u003efilter\u003d\u0027query \u003d \"Sonar\"\u0027\u003c/code\u003e.\u003cbr\u003e\u003cbr\u003eTo filter on a numeric metric, provide the metric key.\u003cbr\u003eThese are the supported metric keys:\u003cbr\u003e\u003cul\u003e\u003cli\u003ealert_status\u003c/li\u003e\u003cli\u003ecoverage\u003c/li\u003e\u003cli\u003eduplicated_lines_density\u003c/li\u003e\u003cli\u003elines\u003c/li\u003e\u003cli\u003encloc\u003c/li\u003e\u003cli\u003encloc_language_distribution\u003c/li\u003e\u003cli\u003enew_coverage\u003c/li\u003e\u003cli\u003enew_duplicated_lines_density\u003c/li\u003e\u003cli\u003enew_lines\u003c/li\u003e\u003cli\u003enew_maintainability_rating\u003c/li\u003e\u003cli\u003enew_reliability_rating\u003c/li\u003e\u003cli\u003enew_sca_rating_any_issue\u003c/li\u003e\u003cli\u003enew_security_hotspots_reviewed\u003c/li\u003e\u003cli\u003enew_security_rating\u003c/li\u003e\u003cli\u003enew_security_review_rating\u003c/li\u003e\u003cli\u003enew_software_quality_maintainability_rating\u003c/li\u003e\u003cli\u003enew_software_quality_reliability_rating\u003c/li\u003e\u003cli\u003enew_software_quality_security_rating\u003c/li\u003e\u003cli\u003ereliability_rating\u003c/li\u003e\u003cli\u003esca_rating_any_issue\u003c/li\u003e\u003cli\u003esecurity_hotspots_reviewed\u003c/li\u003e\u003cli\u003esecurity_rating\u003c/li\u003e\u003cli\u003esecurity_review_rating\u003c/li\u003e\u003cli\u003esoftware_quality_maintainability_rating\u003c/li\u003e\u003cli\u003esoftware_quality_reliability_rating\u003c/li\u003e\u003cli\u003esoftware_quality_security_rating\u003c/li\u003e\u003cli\u003esqale_rating\u003c/li\u003e\u003c/ul\u003e\u003cbr\u003eTo filter on a rating, provide the corresponding metric key (ex: reliability_rating for reliability rating).\u003cbr\u003eThe possible values are:\u003cul\u003e \u003cli\u003e\u00271\u0027 for rating A\u003c/li\u003e \u003cli\u003e\u00272\u0027 for rating B\u003c/li\u003e \u003cli\u003e\u00273\u0027 for rating C\u003c/li\u003e \u003cli\u003e\u00274\u0027 for rating D\u003c/li\u003e \u003cli\u003e\u00275\u0027 for rating E\u003c/li\u003e\u003c/ul\u003eTo filter on a Quality Gate status use the metric key \u0027alert_status\u0027. Only the \u0027\u003d\u0027 operator can be used.\u003cbr\u003eThe possible values are:\u003cul\u003e \u003cli\u003e\u0027OK\u0027 for Passed\u003c/li\u003e \u003cli\u003e\u0027WARN\u0027 for Warning\u003c/li\u003e \u003cli\u003e\u0027ERROR\u0027 for Failed\u003c/li\u003e\u003c/ul\u003eTo filter on languages use the \u0027languages\u0027 keyword: \u003cul\u003e \u003cli\u003eto filter on a single language you can use \u0027languages \u003d java\u0027\u003c/li\u003e \u003cli\u003eto filter on several languages you must use \u0027languages IN (java, js)\u0027\u003c/li\u003e\u003c/ul\u003eUse the WS api/languages/list to find the key of a language.\u003cbr\u003e To filter on tags use the \u0027tags\u0027 keyword:\u003cul\u003e \u003cli\u003eto filter on one tag you can use \u003ccode\u003etags \u003d finance\u003c/code\u003e\u003c/li\u003e \u003cli\u003eto filter on several tags you must use \u003ccode\u003etags in (offshore, java)\u003c/code\u003e\u003c/li\u003e\u003c/ul\u003eTo filter on a qualifier use key \u0027qualifier\u0027. Only the \u0027\u003d\u0027 operator can be used.\u003cbr\u003eThe possible values are:\u003cul\u003e \u003cli\u003eTRK - for projects\u003c/li\u003e \u003cli\u003eAPP - for applications\u003c/li\u003e\u003c/ul\u003e", + "description": "Filter of projects on name, key, measure value, quality gate, language, tag or whether a project is a favorite or not.\u003cbr\u003eThe filter must be encoded to form a valid URL (for example \u0027\u003d\u0027 must be replaced by \u0027%3D\u0027).\u003cbr\u003eExamples of use:\u003cul\u003e \u003cli\u003eto filter my favorite projects with a failed quality gate and a coverage greater than or equals to 60% and a coverage strictly lower than 80%:\u003cbr\u003e \u003ccode\u003efilter\u003d\"alert_status \u003d ERROR and isFavorite and coverage \u003e\u003d 60 and coverage \u003c 80\"\u003c/code\u003e\u003c/li\u003e \u003cli\u003eto filter projects with a reliability, security and maintainability rating equals or worse than B:\u003cbr\u003e \u003ccode\u003efilter\u003d\"reliability_rating\u003e\u003d2 and security_rating\u003e\u003d2 and sqale_rating\u003e\u003d2\"\u003c/code\u003e\u003c/li\u003e \u003cli\u003eto filter projects without duplication data:\u003cbr\u003e \u003ccode\u003efilter\u003d\"duplicated_lines_density \u003d NO_DATA\"\u003c/code\u003e\u003c/li\u003e\u003c/ul\u003eTo filter on project name or key, use the \u0027query\u0027 keyword, for instance : \u003ccode\u003efilter\u003d\u0027query \u003d \"Sonar\"\u0027\u003c/code\u003e.\u003cbr\u003e\u003cbr\u003eTo filter on a numeric metric, provide the metric key.\u003cbr\u003eThese are the supported metric keys:\u003cbr\u003e\u003cul\u003e\u003cli\u003ealert_status\u003c/li\u003e\u003cli\u003ecoverage\u003c/li\u003e\u003cli\u003eduplicated_lines_density\u003c/li\u003e\u003cli\u003elines\u003c/li\u003e\u003cli\u003encloc\u003c/li\u003e\u003cli\u003encloc_language_distribution\u003c/li\u003e\u003cli\u003enew_coverage\u003c/li\u003e\u003cli\u003enew_duplicated_lines_density\u003c/li\u003e\u003cli\u003enew_lines\u003c/li\u003e\u003cli\u003enew_maintainability_rating\u003c/li\u003e\u003cli\u003enew_ncloc\u003c/li\u003e\u003cli\u003enew_reliability_rating\u003c/li\u003e\u003cli\u003enew_sca_rating_any_issue\u003c/li\u003e\u003cli\u003enew_security_hotspots_reviewed\u003c/li\u003e\u003cli\u003enew_security_rating\u003c/li\u003e\u003cli\u003enew_security_review_rating\u003c/li\u003e\u003cli\u003enew_software_quality_maintainability_rating\u003c/li\u003e\u003cli\u003enew_software_quality_reliability_rating\u003c/li\u003e\u003cli\u003enew_software_quality_security_rating\u003c/li\u003e\u003cli\u003ereliability_rating\u003c/li\u003e\u003cli\u003esca_rating_any_issue\u003c/li\u003e\u003cli\u003esecurity_hotspots_reviewed\u003c/li\u003e\u003cli\u003esecurity_rating\u003c/li\u003e\u003cli\u003esecurity_review_rating\u003c/li\u003e\u003cli\u003esoftware_quality_maintainability_rating\u003c/li\u003e\u003cli\u003esoftware_quality_reliability_rating\u003c/li\u003e\u003cli\u003esoftware_quality_security_rating\u003c/li\u003e\u003cli\u003esqale_rating\u003c/li\u003e\u003c/ul\u003e\u003cbr\u003eTo filter on a rating, provide the corresponding metric key (ex: reliability_rating for reliability rating).\u003cbr\u003eThe possible values are:\u003cul\u003e \u003cli\u003e\u00271\u0027 for rating A\u003c/li\u003e \u003cli\u003e\u00272\u0027 for rating B\u003c/li\u003e \u003cli\u003e\u00273\u0027 for rating C\u003c/li\u003e \u003cli\u003e\u00274\u0027 for rating D\u003c/li\u003e \u003cli\u003e\u00275\u0027 for rating E\u003c/li\u003e\u003c/ul\u003eTo filter on a Quality Gate status use the metric key \u0027alert_status\u0027. Only the \u0027\u003d\u0027 operator can be used.\u003cbr\u003eThe possible values are:\u003cul\u003e \u003cli\u003e\u0027OK\u0027 for Passed\u003c/li\u003e \u003cli\u003e\u0027WARN\u0027 for Warning\u003c/li\u003e \u003cli\u003e\u0027ERROR\u0027 for Failed\u003c/li\u003e\u003c/ul\u003eTo filter on languages use the \u0027languages\u0027 keyword: \u003cul\u003e \u003cli\u003eto filter on a single language you can use \u0027languages \u003d java\u0027\u003c/li\u003e \u003cli\u003eto filter on several languages you must use \u0027languages IN (java, js)\u0027\u003c/li\u003e\u003c/ul\u003eUse the WS api/languages/list to find the key of a language.\u003cbr\u003e To filter on tags use the \u0027tags\u0027 keyword:\u003cul\u003e \u003cli\u003eto filter on one tag you can use \u003ccode\u003etags \u003d finance\u003c/code\u003e\u003c/li\u003e \u003cli\u003eto filter on several tags you must use \u003ccode\u003etags in (offshore, java)\u003c/code\u003e\u003c/li\u003e\u003c/ul\u003eTo filter on a qualifier use key \u0027qualifier\u0027. Only the \u0027\u003d\u0027 operator can be used.\u003cbr\u003eThe possible values are:\u003cul\u003e \u003cli\u003eTRK - for projects\u003c/li\u003e \u003cli\u003eAPP - for applications\u003c/li\u003e\u003c/ul\u003e", "required": false, "internal": false, "minimumLength": 2 @@ -2949,6 +3004,7 @@ "new_duplicated_lines_density", "new_lines", "new_maintainability_rating", + "new_ncloc", "new_reliability_rating", "new_sca_rating_any_issue", "new_security_hotspots_reviewed", @@ -3075,6 +3131,10 @@ "post": false, "hasResponseExample": true, "changelog": [ + { + "description": "Response field \u0027refQualifier\u0027 has been added.", + "version": "2026.5" + }, { "description": "The use of module keys in parameter \u0027component\u0027 is removed", "version": "10.1" @@ -4115,6 +4175,53 @@ } ] }, + { + "key": "migrate_to_issues", + "description": "Migrate Security Hotspots to Issues. Requires \u0027Administer System\u0027 permission. Reports \u0027migrated\u0027 per project, plus a scope-wide \u0027skipped\u0027 count of findings that cannot migrate because their rule is still a Security Hotspot.", + "since": "2026.4", + "internal": true, + "post": true, + "hasResponseExample": false, + "changelog": [], + "params": [ + { + "key": "dryRun", + "description": "If true, only count hotspots to migrate without performing any writes.", + "required": false, + "internal": false, + "defaultValue": "false", + "possibleValues": [ + "true", + "false", + "yes", + "no" + ] + }, + { + "key": "project", + "description": "Project key. If not provided, all projects are migrated.", + "required": false, + "internal": false + } + ] + }, + { + "key": "migration_status", + "description": "Report how many Security Hotspots remain to be migrated to Issues (optionally scoped to a project). The migration is complete for the scope once \u0027remainingHotspots\u0027 reaches zero. Findings whose rule is still a Security Hotspot cannot be migrated yet and are reported separately as \u0027notConvertedHotspots\u0027; they move into \u0027remainingHotspots\u0027 once their rule is converted. Requires \u0027Administer System\u0027 permission.", + "since": "2026.4", + "internal": true, + "post": false, + "hasResponseExample": false, + "changelog": [], + "params": [ + { + "key": "project", + "description": "Project key. If not provided, the status is reported for the whole instance.", + "required": false, + "internal": false + } + ] + }, { "key": "pull", "description": "This endpoint fetches and returns all (unless filtered by optional params) the hotspots for a given branch. The hotspots returned are not paginated, so the response size can be big. Requires project \u0027Browse\u0027 permission.", @@ -6261,15 +6368,15 @@ "required": false, "internal": false, "possibleValues": [ - "CREATION_DATE", - "HOTSPOTS", - "TYPE_SEVERITY", "UPDATE_DATE", - "CLOSE_DATE", - "QUALITY_SEVERITY", - "FILE_LINE", + "TYPE_SEVERITY", + "HOTSPOTS", + "IMPACT_RANK", + "CREATION_DATE", + "SEVERITY", "STATUS", - "SEVERITY" + "FILE_LINE", + "CLOSE_DATE" ] }, { @@ -7103,7 +7210,7 @@ }, { "key": "metricKeys", - "description": "Comma-separated list of metric keys. Types DISTRIB are not allowed. For type DATA only maintainability_issues, reliability_issues, new_maintainability_issues, new_security_issues, new_reliability_issues, security_issues metrics are supported", + "description": "Comma-separated list of metric keys. Types DISTRIB are not allowed. For type DATA only reliability_issues, maintainability_issues, security_issues, new_reliability_issues, new_security_issues, new_maintainability_issues metrics are supported", "required": true, "internal": false, "exampleValue": "ncloc,complexity,violations", @@ -7208,8 +7315,8 @@ "defaultValue": "all", "possibleValues": [ "all", - "children", - "leaves" + "leaves", + "children" ] } ] @@ -7608,11 +7715,11 @@ { "path": "api/new_code_periods", "since": "8.0", - "description": "Manage \u003ca href\u003d\"https://docs.sonarsource.com/sonarqube-server/2026.4/project-administration/configuring-new-code-calculation#setting-specific-new-code-definition-for-project\" target\u003d\"_blank\" rel\u003d\"noopener noreferrer\"\u003enew code definition\u003c/a\u003e.", + "description": "Manage \u003ca href\u003d\"https://docs.sonarsource.com/sonarqube-server/2026.5/project-administration/configuring-new-code-calculation#setting-specific-new-code-definition-for-project\" target\u003d\"_blank\" rel\u003d\"noopener noreferrer\"\u003enew code definition\u003c/a\u003e.", "actions": [ { "key": "list", - "description": "Lists the \u003ca href\u003d\"https://docs.sonarsource.com/sonarqube-server/2026.4/project-administration/configuring-new-code-calculation#setting-specific-new-code-definition-for-project\" target\u003d\"_blank\" rel\u003d\"noopener noreferrer\"\u003enew code definition\u003c/a\u003e for all branches in a project.\u003cbr\u003eRequires the permission to browse the project", + "description": "Lists the \u003ca href\u003d\"https://docs.sonarsource.com/sonarqube-server/2026.5/project-administration/configuring-new-code-calculation#setting-specific-new-code-definition-for-project\" target\u003d\"_blank\" rel\u003d\"noopener noreferrer\"\u003enew code definition\u003c/a\u003e for all branches in a project.\u003cbr\u003eRequires the permission to browse the project", "since": "8.0", "internal": false, "post": false, @@ -7629,7 +7736,7 @@ }, { "key": "set", - "description": "Updates the \u003ca href\u003d\"https://docs.sonarsource.com/sonarqube-server/2026.4/project-administration/configuring-new-code-calculation#setting-specific-new-code-definition-for-project\" target\u003d\"_blank\" rel\u003d\"noopener noreferrer\"\u003enew code definition\u003c/a\u003e on different levels:\u003cbr\u003e\u003cul\u003e\u003cli\u003eNot providing a project key and a branch key will update the default value at global level. Existing projects or branches having a specific new code definition will not be impacted\u003c/li\u003e\u003cli\u003eProject key must be provided to update the value for a project\u003c/li\u003e\u003cli\u003eBoth project and branch keys must be provided to update the value for a branch\u003c/li\u003e\u003c/ul\u003eRequires one of the following permissions: \u003cul\u003e\u003cli\u003e\u0027Administer System\u0027 to change the global setting\u003c/li\u003e\u003cli\u003e\u0027Administer\u0027 rights on the specified project to change the project setting\u003c/li\u003e\u003c/ul\u003e", + "description": "Updates the \u003ca href\u003d\"https://docs.sonarsource.com/sonarqube-server/2026.5/project-administration/configuring-new-code-calculation#setting-specific-new-code-definition-for-project\" target\u003d\"_blank\" rel\u003d\"noopener noreferrer\"\u003enew code definition\u003c/a\u003e on different levels:\u003cbr\u003e\u003cul\u003e\u003cli\u003eNot providing a project key and a branch key will update the default value at global level. Existing projects or branches having a specific new code definition will not be impacted\u003c/li\u003e\u003cli\u003eProject key must be provided to update the value for a project\u003c/li\u003e\u003cli\u003eBoth project and branch keys must be provided to update the value for a branch\u003c/li\u003e\u003c/ul\u003eRequires one of the following permissions: \u003cul\u003e\u003cli\u003e\u0027Administer System\u0027 to change the global setting\u003c/li\u003e\u003cli\u003e\u0027Administer\u0027 rights on the specified project to change the project setting\u003c/li\u003e\u003c/ul\u003e", "since": "8.0", "internal": false, "post": true, @@ -7664,7 +7771,7 @@ }, { "key": "show", - "description": "Shows the \u003ca href\u003d\"https://docs.sonarsource.com/sonarqube-server/2026.4/project-administration/configuring-new-code-calculation#setting-specific-new-code-definition-for-project\" target\u003d\"_blank\" rel\u003d\"noopener noreferrer\"\u003enew code definition\u003c/a\u003e.\u003cbr\u003e If the component requested doesn\u0027t exist or if no new code definition is set for it, a value is inherited from the project or from the global setting.Requires one of the following permissions if a component is specified: \u003cul\u003e\u003cli\u003e\u0027Administer\u0027 rights on the specified component\u003c/li\u003e\u003cli\u003e\u0027Execute analysis\u0027 rights on the specified component\u003c/li\u003e\u003c/ul\u003e", + "description": "Shows the \u003ca href\u003d\"https://docs.sonarsource.com/sonarqube-server/2026.5/project-administration/configuring-new-code-calculation#setting-specific-new-code-definition-for-project\" target\u003d\"_blank\" rel\u003d\"noopener noreferrer\"\u003enew code definition\u003c/a\u003e.\u003cbr\u003e If the component requested doesn\u0027t exist or if no new code definition is set for it, a value is inherited from the project or from the global setting.Requires one of the following permissions if a component is specified: \u003cul\u003e\u003cli\u003e\u0027Administer\u0027 rights on the specified component\u003c/li\u003e\u003cli\u003e\u0027Execute analysis\u0027 rights on the specified component\u003c/li\u003e\u003c/ul\u003e", "since": "8.0", "internal": false, "post": false, @@ -7687,7 +7794,7 @@ }, { "key": "unset", - "description": "Unsets the \u003ca href\u003d\"https://docs.sonarsource.com/sonarqube-server/2026.4/project-administration/configuring-new-code-calculation#setting-specific-new-code-definition-for-project\" target\u003d\"_blank\" rel\u003d\"noopener noreferrer\"\u003enew code definition\u003c/a\u003e for a branch, project or global. It requires the inherited New Code Definition to be compatible with the Clean as You Code methodology, and one of the following permissions: \u003cul\u003e\u003cli\u003e\u0027Administer System\u0027 to change the global setting\u003c/li\u003e\u003cli\u003e\u0027Administer\u0027 rights for a specified component\u003c/li\u003e\u003c/ul\u003e", + "description": "Unsets the \u003ca href\u003d\"https://docs.sonarsource.com/sonarqube-server/2026.5/project-administration/configuring-new-code-calculation#setting-specific-new-code-definition-for-project\" target\u003d\"_blank\" rel\u003d\"noopener noreferrer\"\u003enew code definition\u003c/a\u003e for a branch, project or global. It requires the inherited New Code Definition to be compatible with the Clean as You Code methodology, and one of the following permissions: \u003cul\u003e\u003cli\u003e\u0027Administer System\u0027 to change the global setting\u003c/li\u003e\u003cli\u003e\u0027Administer\u0027 rights for a specified component\u003c/li\u003e\u003c/ul\u003e", "since": "8.0", "internal": false, "post": true, @@ -7757,6 +7864,33 @@ } ] }, + { + "key": "add_group", + "description": "Subscribe a group to a notification type. Requires system administration permission.", + "since": "2026.5", + "internal": true, + "post": true, + "hasResponseExample": false, + "changelog": [], + "params": [ + { + "key": "groupUuid", + "description": "Group UUID", + "required": true, + "internal": false, + "exampleValue": "AU-TpxcA-iU5OvuD2FL7" + }, + { + "key": "type", + "description": "Notification type", + "required": true, + "internal": false, + "possibleValues": [ + "security-alert-raised" + ] + } + ] + }, { "key": "list", "description": "List notifications of the authenticated user.\u003cbr\u003eRequires one of the following permissions:\u003cul\u003e \u003cli\u003eAuthentication if no login is provided\u003c/li\u003e \u003cli\u003eSystem administration if a login is provided\u003c/li\u003e\u003c/ul\u003e", @@ -7766,6 +7900,19 @@ "hasResponseExample": true, "changelog": [], "params": [ + { + "key": "filter", + "description": "Category of notification types to return.", + "since": "2026.5", + "required": false, + "internal": false, + "defaultValue": "all", + "possibleValues": [ + "user", + "groupSubscription", + "all" + ] + }, { "key": "login", "description": "User login", @@ -7775,6 +7922,24 @@ } ] }, + { + "key": "list_group_subscriptions", + "description": "List notification group subscriptions for the current user\u0027s groups. Requires authentication.", + "since": "2026.5", + "internal": true, + "post": false, + "hasResponseExample": true, + "changelog": [] + }, + { + "key": "list_groups", + "description": "List group notification subscriptions. Requires system administration permission.", + "since": "2026.5", + "internal": true, + "post": false, + "hasResponseExample": true, + "changelog": [] + }, { "key": "remove", "description": "Remove a notification for the authenticated user.\u003cbr\u003eRequires one of the following permissions:\u003cul\u003e \u003cli\u003eAuthentication if no login is provided\u003c/li\u003e \u003cli\u003eSystem administration if a login is provided\u003c/li\u003e\u003c/ul\u003e", @@ -7816,6 +7981,33 @@ "exampleValue": "SQ-MyNewIssues" } ] + }, + { + "key": "remove_group", + "description": "Remove a group notification subscription. No-op if not found. Requires system administration permission.", + "since": "2026.5", + "internal": true, + "post": true, + "hasResponseExample": false, + "changelog": [], + "params": [ + { + "key": "groupUuid", + "description": "Group UUID", + "required": true, + "internal": false, + "exampleValue": "AU-TpxcA-iU5OvuD2FL7" + }, + { + "key": "type", + "description": "Notification type", + "required": true, + "internal": false, + "possibleValues": [ + "security-alert-raised" + ] + } + ] } ] }, @@ -7851,7 +8043,7 @@ }, { "key": "permission", - "description": "The permission you would like to grant to the group.\u003cul\u003e\u003cli\u003ePossible values for global permissions: admin, gateadmin, profileadmin, provisioning, scan, applicationcreator, portfoliocreator\u003c/li\u003e\u003cli\u003ePossible values for project permissions admin, codeviewer, issueadmin, securityhotspotadmin, architectureadmin, scan, user\u003c/li\u003e\u003c/ul\u003e", + "description": "The permission you would like to grant to the group.\u003cul\u003e\u003cli\u003ePossible values for global permissions: admin, gateadmin, profileadmin, architectureadmin, provisioning, scan, applicationcreator, portfoliocreator\u003c/li\u003e\u003cli\u003ePossible values for project permissions admin, codeviewer, issueadmin, securityhotspotadmin, architectureadmin, scan, user\u003c/li\u003e\u003c/ul\u003e", "required": true, "internal": false }, @@ -7985,7 +8177,7 @@ }, { "key": "permission", - "description": "The permission you would like to grant to the user\u003cul\u003e\u003cli\u003ePossible values for global permissions: admin, gateadmin, profileadmin, provisioning, scan, applicationcreator, portfoliocreator\u003c/li\u003e\u003cli\u003ePossible values for project permissions admin, codeviewer, issueadmin, securityhotspotadmin, architectureadmin, scan, user\u003c/li\u003e\u003c/ul\u003e", + "description": "The permission you would like to grant to the user\u003cul\u003e\u003cli\u003ePossible values for global permissions: admin, gateadmin, profileadmin, architectureadmin, provisioning, scan, applicationcreator, portfoliocreator\u003c/li\u003e\u003cli\u003ePossible values for project permissions admin, codeviewer, issueadmin, securityhotspotadmin, architectureadmin, scan, user\u003c/li\u003e\u003c/ul\u003e", "required": true, "internal": false }, @@ -8271,7 +8463,7 @@ }, { "key": "permission", - "description": "Permission.\u003cul\u003e\u003cli\u003ePossible values for global permissions: admin, gateadmin, profileadmin, provisioning, scan, applicationcreator, portfoliocreator\u003c/li\u003e\u003cli\u003ePossible values for project permissions admin, codeviewer, issueadmin, securityhotspotadmin, architectureadmin, scan, user\u003c/li\u003e\u003c/ul\u003e", + "description": "Permission.\u003cul\u003e\u003cli\u003ePossible values for global permissions: admin, gateadmin, profileadmin, architectureadmin, provisioning, scan, applicationcreator, portfoliocreator\u003c/li\u003e\u003cli\u003ePossible values for project permissions admin, codeviewer, issueadmin, securityhotspotadmin, architectureadmin, scan, user\u003c/li\u003e\u003c/ul\u003e", "required": false, "internal": false }, @@ -8335,7 +8527,7 @@ }, { "key": "permission", - "description": "The permission you would like to revoke from the group.\u003cul\u003e\u003cli\u003ePossible values for global permissions: admin, gateadmin, profileadmin, provisioning, scan, applicationcreator, portfoliocreator\u003c/li\u003e\u003cli\u003ePossible values for project permissions admin, codeviewer, issueadmin, securityhotspotadmin, architectureadmin, scan, user\u003c/li\u003e\u003c/ul\u003e", + "description": "The permission you would like to revoke from the group.\u003cul\u003e\u003cli\u003ePossible values for global permissions: admin, gateadmin, profileadmin, architectureadmin, provisioning, scan, applicationcreator, portfoliocreator\u003c/li\u003e\u003cli\u003ePossible values for project permissions admin, codeviewer, issueadmin, securityhotspotadmin, architectureadmin, scan, user\u003c/li\u003e\u003c/ul\u003e", "required": true, "internal": false }, @@ -8469,7 +8661,7 @@ }, { "key": "permission", - "description": "The permission you would like to revoke from the user.\u003cul\u003e\u003cli\u003ePossible values for global permissions: admin, gateadmin, profileadmin, provisioning, scan, applicationcreator, portfoliocreator\u003c/li\u003e\u003cli\u003ePossible values for project permissions admin, codeviewer, issueadmin, securityhotspotadmin, architectureadmin, scan, user\u003c/li\u003e\u003c/ul\u003e", + "description": "The permission you would like to revoke from the user.\u003cul\u003e\u003cli\u003ePossible values for global permissions: admin, gateadmin, profileadmin, architectureadmin, provisioning, scan, applicationcreator, portfoliocreator\u003c/li\u003e\u003cli\u003ePossible values for project permissions admin, codeviewer, issueadmin, securityhotspotadmin, architectureadmin, scan, user\u003c/li\u003e\u003c/ul\u003e", "required": true, "internal": false }, @@ -8807,7 +8999,7 @@ }, { "key": "permission", - "description": "Permission.\u003cul\u003e\u003cli\u003ePossible values for global permissions: admin, gateadmin, profileadmin, provisioning, scan, applicationcreator, portfoliocreator\u003c/li\u003e\u003cli\u003ePossible values for project permissions admin, codeviewer, issueadmin, securityhotspotadmin, architectureadmin, scan, user\u003c/li\u003e\u003c/ul\u003e", + "description": "Permission.\u003cul\u003e\u003cli\u003ePossible values for global permissions: admin, gateadmin, profileadmin, architectureadmin, provisioning, scan, applicationcreator, portfoliocreator\u003c/li\u003e\u003cli\u003ePossible values for project permissions admin, codeviewer, issueadmin, securityhotspotadmin, architectureadmin, scan, user\u003c/li\u003e\u003c/ul\u003e", "required": false, "internal": false }, @@ -9127,6 +9319,10 @@ "post": false, "hasResponseExample": true, "changelog": [ + { + "description": "Add response field \u0027relativePathFromScmRoot\u0027", + "version": "2026.5" + }, { "description": "Add response field \u0027qualityProfile\u0027 for events related to quality profile changes", "version": "10.3" @@ -10713,7 +10909,7 @@ }, { "key": "metric", - "description": "Condition metric.\u003cbr/\u003e Only metric of the following types are allowed:\u003cul\u003e\u003cli\u003eINT\u003c/li\u003e\u003cli\u003eMILLISEC\u003c/li\u003e\u003cli\u003eRATING\u003c/li\u003e\u003cli\u003eWORK_DUR\u003c/li\u003e\u003cli\u003eFLOAT\u003c/li\u003e\u003cli\u003ePERCENT\u003c/li\u003e\u003cli\u003eLEVEL\u003c/li\u003e\u003c/ul\u003eFollowing metrics are forbidden:\u003cul\u003e\u003cli\u003ealert_status\u003c/li\u003e\u003cli\u003esecurity_hotspots\u003c/li\u003e\u003cli\u003enew_security_hotspots\u003c/li\u003e\u003c/ul\u003e", + "description": "Condition metric.\u003cbr/\u003e Only metric of the following types are allowed:\u003cul\u003e\u003cli\u003eINT\u003c/li\u003e\u003cli\u003eMILLISEC\u003c/li\u003e\u003cli\u003eRATING\u003c/li\u003e\u003cli\u003eWORK_DUR\u003c/li\u003e\u003cli\u003eFLOAT\u003c/li\u003e\u003cli\u003ePERCENT\u003c/li\u003e\u003cli\u003eLEVEL\u003c/li\u003e\u003c/ul\u003eFollowing metrics are forbidden:\u003cul\u003e\u003cli\u003ealert_status\u003c/li\u003e\u003cli\u003enew_security_hotspots\u003c/li\u003e\u003cli\u003esecurity_hotspots\u003c/li\u003e\u003c/ul\u003e", "required": true, "internal": false, "exampleValue": "blocker_violations, vulnerabilities, new_code_smells" @@ -11514,7 +11710,7 @@ }, { "key": "metric", - "description": "Condition metric.\u003cbr/\u003e Only metric of the following types are allowed:\u003cul\u003e\u003cli\u003eINT\u003c/li\u003e\u003cli\u003eMILLISEC\u003c/li\u003e\u003cli\u003eRATING\u003c/li\u003e\u003cli\u003eWORK_DUR\u003c/li\u003e\u003cli\u003eFLOAT\u003c/li\u003e\u003cli\u003ePERCENT\u003c/li\u003e\u003cli\u003eLEVEL\u003c/li\u003e\u003c/ul\u003eFollowing metrics are forbidden:\u003cul\u003e\u003cli\u003ealert_status\u003c/li\u003e\u003cli\u003esecurity_hotspots\u003c/li\u003e\u003cli\u003enew_security_hotspots\u003c/li\u003e\u003c/ul\u003e", + "description": "Condition metric.\u003cbr/\u003e Only metric of the following types are allowed:\u003cul\u003e\u003cli\u003eINT\u003c/li\u003e\u003cli\u003eMILLISEC\u003c/li\u003e\u003cli\u003eRATING\u003c/li\u003e\u003cli\u003eWORK_DUR\u003c/li\u003e\u003cli\u003eFLOAT\u003c/li\u003e\u003cli\u003ePERCENT\u003c/li\u003e\u003cli\u003eLEVEL\u003c/li\u003e\u003c/ul\u003eFollowing metrics are forbidden:\u003cul\u003e\u003cli\u003ealert_status\u003c/li\u003e\u003cli\u003enew_security_hotspots\u003c/li\u003e\u003cli\u003esecurity_hotspots\u003c/li\u003e\u003c/ul\u003e", "required": true, "internal": false, "exampleValue": "blocker_violations, vulnerabilities, new_code_smells" @@ -11912,12 +12108,12 @@ "description": "Sort field", "required": false, "internal": false, - "exampleValue": "name", + "exampleValue": "key", "possibleValues": [ - "name", - "createdAt", + "key", "updatedAt", - "key" + "createdAt", + "name" ] }, { @@ -12070,8 +12266,8 @@ "kubernetes", "css", "py", - "plsql", "apex", + "plsql", "java", "xml", "powershell", @@ -12110,8 +12306,10 @@ "tsql", "ruby", "cs", + "r", "cobol", "php", + "dataweave", "gosu", "objc", "ts", @@ -12145,8 +12343,8 @@ "kubernetes", "css", "py", - "plsql", "apex", + "plsql", "java", "xml", "powershell", @@ -12185,8 +12383,10 @@ "tsql", "ruby", "cs", + "r", "cobol", "php", + "dataweave", "gosu", "objc", "ts", @@ -12227,8 +12427,8 @@ "kubernetes", "css", "py", - "plsql", "apex", + "plsql", "java", "xml", "powershell", @@ -12267,8 +12467,10 @@ "tsql", "ruby", "cs", + "r", "cobol", "php", + "dataweave", "gosu", "objc", "ts", @@ -12318,8 +12520,8 @@ "kubernetes", "css", "py", - "plsql", "apex", + "plsql", "java", "xml", "powershell", @@ -12358,8 +12560,10 @@ "tsql", "ruby", "cs", + "r", "cobol", "php", + "dataweave", "gosu", "objc", "ts", @@ -12393,8 +12597,8 @@ "kubernetes", "css", "py", - "plsql", "apex", + "plsql", "java", "xml", "powershell", @@ -12433,8 +12637,10 @@ "tsql", "ruby", "cs", + "r", "cobol", "php", + "dataweave", "gosu", "objc", "ts", @@ -12520,8 +12726,8 @@ "kubernetes", "css", "py", - "plsql", "apex", + "plsql", "java", "xml", "powershell", @@ -12560,8 +12766,10 @@ "tsql", "ruby", "cs", + "r", "cobol", "php", + "dataweave", "gosu", "objc", "ts", @@ -12703,6 +12911,7 @@ "cs", "css", "dart", + "dataweave", "docker", "flex", "githubactions", @@ -12724,6 +12933,7 @@ "postgres", "powershell", "py", + "r", "rpg", "ruby", "rust", @@ -13048,12 +13258,12 @@ "description": "Sort field", "required": false, "internal": false, - "exampleValue": "name", + "exampleValue": "key", "possibleValues": [ - "name", - "createdAt", + "key", "updatedAt", - "key" + "createdAt", + "name" ] }, { @@ -13186,8 +13396,8 @@ "kubernetes", "css", "py", - "plsql", "apex", + "plsql", "java", "xml", "powershell", @@ -13226,8 +13436,10 @@ "tsql", "ruby", "cs", + "r", "cobol", "php", + "dataweave", "gosu", "objc", "ts", @@ -13277,6 +13489,7 @@ "cs", "css", "dart", + "dataweave", "docker", "flex", "githubactions", @@ -13298,6 +13511,7 @@ "postgres", "powershell", "py", + "r", "rpg", "ruby", "rust", @@ -13368,8 +13582,8 @@ "kubernetes", "css", "py", - "plsql", "apex", + "plsql", "java", "xml", "powershell", @@ -13408,8 +13622,10 @@ "tsql", "ruby", "cs", + "r", "cobol", "php", + "dataweave", "gosu", "objc", "ts", @@ -13533,8 +13749,8 @@ "kubernetes", "css", "py", - "plsql", "apex", + "plsql", "java", "xml", "powershell", @@ -13573,8 +13789,10 @@ "tsql", "ruby", "cs", + "r", "cobol", "php", + "dataweave", "gosu", "objc", "ts", @@ -13608,8 +13826,8 @@ "kubernetes", "css", "py", - "plsql", "apex", + "plsql", "java", "xml", "powershell", @@ -13648,8 +13866,10 @@ "tsql", "ruby", "cs", + "r", "cobol", "php", + "dataweave", "gosu", "objc", "ts", @@ -13690,8 +13910,8 @@ "kubernetes", "css", "py", - "plsql", "apex", + "plsql", "java", "xml", "powershell", @@ -13730,8 +13950,10 @@ "tsql", "ruby", "cs", + "r", "cobol", "php", + "dataweave", "gosu", "objc", "ts", @@ -13863,6 +14085,7 @@ "cs", "css", "dart", + "dataweave", "docker", "flex", "githubactions", @@ -13884,6 +14107,7 @@ "postgres", "powershell", "py", + "r", "rpg", "ruby", "rust", @@ -13936,8 +14160,8 @@ "kubernetes", "css", "py", - "plsql", "apex", + "plsql", "java", "xml", "powershell", @@ -13976,8 +14200,10 @@ "tsql", "ruby", "cs", + "r", "cobol", "php", + "dataweave", "gosu", "objc", "ts", @@ -14046,8 +14272,8 @@ "kubernetes", "css", "py", - "plsql", "apex", + "plsql", "java", "xml", "powershell", @@ -14086,8 +14312,10 @@ "tsql", "ruby", "cs", + "r", "cobol", "php", + "dataweave", "gosu", "objc", "ts", @@ -14156,8 +14384,8 @@ "kubernetes", "css", "py", - "plsql", "apex", + "plsql", "java", "xml", "powershell", @@ -14196,8 +14424,10 @@ "tsql", "ruby", "cs", + "r", "cobol", "php", + "dataweave", "gosu", "objc", "ts", @@ -14912,7 +15142,7 @@ "description": "Comma-separated list of additional fields to be returned in the response. All the fields are returned by default, except actives.", "required": false, "internal": false, - "exampleValue": "sysTags,gapDescription", + "exampleValue": "noteLogin,isExternal", "possibleValues": [ "actives", "cleanCodeAttribute", @@ -15170,12 +15400,12 @@ "description": "Sort field", "required": false, "internal": false, - "exampleValue": "name", + "exampleValue": "key", "possibleValues": [ - "name", - "createdAt", + "key", "updatedAt", - "key" + "createdAt", + "name" ] }, { @@ -15291,6 +15521,10 @@ "post": false, "hasResponseExample": true, "changelog": [ + { + "description": "The \u0027contextKey\u0027 parameter has been added.", + "version": "2026.5" + }, { "description": "Description-related fields in the response are obfuscated for anonymous users.", "version": "2026.2" @@ -15406,6 +15640,15 @@ "no" ] }, + { + "key": "contextKey", + "description": "Return only the description sections matching the given rule-description context key, along with sections that have no context. When not set, all description sections are returned.", + "since": "2026.5", + "required": false, + "internal": false, + "exampleValue": "spring", + "maximumLength": 50 + }, { "key": "key", "description": "Rule key", @@ -16457,6 +16700,10 @@ "post": false, "hasResponseExample": true, "changelog": [ + { + "description": "The field \u0027lta\u0027 is added to each upgrade entry to indicate whether it belongs to a Long-Term Active line", + "version": "2026.5" + }, { "description": "The field \u0027documentationUrl\u0027 is added to each upgrade entry to point to the target version\u0027s documentation", "version": "2026.2" @@ -17199,6 +17446,10 @@ "post": true, "hasResponseExample": false, "changelog": [ + { + "description": "Support for new notice \u0027showProjectCoverageTour\u0027 was added.", + "version": "2026.5" + }, { "description": "Support for new notice \u0027showSandboxedIssuesIntro\u0027 was added.", "version": "2026.1" @@ -17235,6 +17486,7 @@ "educationPrinciples", "sonarlintAd", "showDesignAndArchitectureBanner", + "showProjectCoverageTour", "showNewModesBanner", "showSandboxedIssuesIntro", "issueCleanCodeGuide", @@ -17325,13 +17577,17 @@ }, { "key": "search", - "description": "Get a list of users. By default, only active users are returned.\u003cbr/\u003eThe following fields are only returned when user has Administer System permission or for logged-in in user :\u003cul\u003e \u003cli\u003e\u0027email\u0027\u003c/li\u003e \u003cli\u003e\u0027externalIdentity\u0027\u003c/li\u003e \u003cli\u003e\u0027externalProvider\u0027\u003c/li\u003e \u003cli\u003e\u0027groups\u0027\u003c/li\u003e \u003cli\u003e\u0027lastConnectionDate\u0027\u003c/li\u003e \u003cli\u003e\u0027sonarLintLastConnectionDate\u0027\u003c/li\u003e \u003cli\u003e\u0027tokensCount\u0027\u003c/li\u003e\u003c/ul\u003eField \u0027lastConnectionDate\u0027 is only updated every hour, so it may not be accurate, for instance when a user authenticates many times in less than one hour.", + "description": "Get a list of users. By default, only active users are returned.\u003cbr/\u003eThe following fields are only returned when user has Administer System permission or for logged-in in user :\u003cul\u003e \u003cli\u003e\u0027email\u0027\u003c/li\u003e \u003cli\u003e\u0027externalIdentity\u0027\u003c/li\u003e \u003cli\u003e\u0027externalProvider\u0027\u003c/li\u003e \u003cli\u003e\u0027groups\u0027\u003c/li\u003e \u003cli\u003e\u0027lastConnectionDate\u0027\u003c/li\u003e \u003cli\u003e\u0027scmAccounts\u0027\u003c/li\u003e \u003cli\u003e\u0027sonarLintLastConnectionDate\u0027\u003c/li\u003e \u003cli\u003e\u0027tokensCount\u0027\u003c/li\u003e\u003c/ul\u003eField \u0027lastConnectionDate\u0027 is only updated every hour, so it may not be accurate, for instance when a user authenticates many times in less than one hour.", "since": "3.6", "deprecatedSince": "10.4", "internal": false, "post": false, "hasResponseExample": true, "changelog": [ + { + "description": "Field \u0027scmAccounts\u0027 is only returned when user has Administer System permission or for the logged-in user", + "version": "26.9" + }, { "description": "Deprecated. Use GET api/v2/users-management/users instead", "version": "10.4" @@ -18746,7 +19002,7 @@ }, { "key": "delivery", - "description": "Get a webhook delivery by its id.\u003cbr/\u003eRequire \u0027Administer System\u0027 permission.\u003cbr/\u003eNote that additional information are returned by api/webhooks/delivery.", + "description": "Get a webhook delivery by its id.\u003cbr/\u003eRequire \u0027Administer\u0027 permission on the related project.", "since": "6.2", "internal": false, "post": false, diff --git a/assets/api.enterprise.v2.json b/assets/api.enterprise.v2.json index 0329004..eb97567 100644 --- a/assets/api.enterprise.v2.json +++ b/assets/api.enterprise.v2.json @@ -3,7 +3,7 @@ "info": { "title": "SonarQube Web API v2", "description": "The SonarQube API v2 is a REST API which enables you to interact with SonarQube programmatically.\nWhile not all endpoints of the former Web API are available yet, the ones available are stable and can be used in production environments.\n", - "version": "2026.4.1.126914" + "version": "2026.5.0.132233" }, "servers": [ { @@ -17,44 +17,180 @@ "description": "the intended API" }, { - "name": "scanner-data-upload", - "description": "the scanner-data-upload API" + "name": "project-relationships", + "description": "the project-relationships API" }, { - "name": "models", - "description": "the models API" + "name": "OrgEntitlement", + "description": "the OrgEntitlement API" }, { - "name": "scanner-data", - "description": "the scanner-data API" + "name": "organization-components", + "description": "the organization-components API" }, { - "name": "private-project-configurations", - "description": "the private-project-configurations API" + "name": "built-in-dashboards", + "description": "the built-in-dashboards API" }, { - "name": "structure", - "description": "the structure API" + "name": "configuration", + "description": "the configuration API" }, { - "name": "directives", - "description": "the directives API" + "name": "Analysis Stats", + "description": "the Analysis Stats API" + }, + { + "name": "VortexImpact", + "description": "the VortexImpact API" + }, + { + "name": "Public Contexts", + "description": "the Public Contexts API" + }, + { + "name": "CagEntitlement", + "description": "the CagEntitlement API" + }, + { + "name": "project-sca-resolution", + "description": "the project-sca-resolution API" + }, + { + "name": "external-interfaces", + "description": "the external-interfaces API" + }, + { + "name": "measures-history", + "description": "the measures-history API" + }, + { + "name": "scanner-data", + "description": "the scanner-data API" + }, + { + "name": "CagImpactEvents", + "description": "the CagImpactEvents API" }, { "name": "graphs", "description": "the graphs API" }, { - "name": "project-configurations", - "description": "the project-configurations API" + "name": "Collection Config", + "description": "the Collection Config API" }, { "name": "snapshots", "description": "the snapshots API" }, + { + "name": "project-issue-counts", + "description": "the project-issue-counts API" + }, + { + "name": "organization-placeholders", + "description": "the organization-placeholders API" + }, + { + "name": "CagUsage", + "description": "the CagUsage API" + }, + { + "name": "Analysis", + "description": "the Analysis API" + }, + { + "name": "boundary-descriptors", + "description": "the boundary-descriptors API" + }, + { + "name": "pull-request-jobs", + "description": "the pull-request-jobs API" + }, + { + "name": "issue-density-history", + "description": "the issue-density-history API" + }, + { + "name": "sca-resolution-history", + "description": "the sca-resolution-history API" + }, + { + "name": "organization-architectures", + "description": "the organization-architectures API" + }, + { + "name": "custom-dashboards", + "description": "the custom-dashboards API" + }, + { + "name": "project-configurations", + "description": "the project-configurations API" + }, + { + "name": "issue-resolution-history", + "description": "the issue-resolution-history API" + }, + { + "name": "Private Contexts", + "description": "the Private Contexts API" + }, + { + "name": "project-issue-resolution", + "description": "the project-issue-resolution API" + }, { "name": "analysis", "description": "the analysis API" + }, + { + "name": "project-measures", + "description": "the project-measures API" + }, + { + "name": "scanner-data-upload", + "description": "the scanner-data-upload API" + }, + { + "name": "credit-cost", + "description": "the credit-cost API" + }, + { + "name": "models", + "description": "the models API" + }, + { + "name": "structure", + "description": "the structure API" + }, + { + "name": "directives", + "description": "the directives API" + }, + { + "name": "issue-count-history", + "description": "the issue-count-history API" + }, + { + "name": "sdks", + "description": "the sdks API" + }, + { + "name": "backlog-jobs", + "description": "the backlog-jobs API" + }, + { + "name": "patterns", + "description": "the patterns API" + }, + { + "name": "CagHub", + "description": "the CagHub API" + }, + { + "name": "jobs", + "description": "the jobs API" } ], "paths": { @@ -206,13 +342,102 @@ "x-sonar-internal": true } }, + "/remediation-agent/scheduled-agent-configs/{id}": { + "get": { + "tags": [ + "scheduled-agent-configs" + ], + "operationId": "getScheduledAgentConfig", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScheduledAgentConfigResource" + } + } + } + } + } + }, + "put": { + "tags": [ + "scheduled-agent-configs" + ], + "operationId": "updateScheduledAgentConfig", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScheduledAgentConfigUpdateRestRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScheduledAgentConfigResource" + } + } + } + } + } + }, + "delete": { + "tags": [ + "scheduled-agent-configs" + ], + "operationId": "deleteScheduledAgentConfig", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/dop-translation/bound-projects": { "put": { "tags": [ "Dop Translation" ], "summary": "Create or update a bound SonarQube project", - "description": "Create a SonarQube project bound to a DevOps platform repository, or update the binding if the project already exists.\nThis is an idempotent operation. If the project already exists with the same key, its binding will be updated.\nAutoconfigure Pull-Request decoration mechanism.\nRequires the 'Create Projects' permission and setting a Personal Access Token with api/alm_integrations/set_pat for a user who will be using this endpoint\n", + "description": "Create a SonarQube project bound to a DevOps platform repository, or update the binding if the project already exists.\nThis is an idempotent operation. If the project already exists with the same key, its binding will be updated.\nAutoconfigure Pull-Request decoration mechanism.\nCreating a new project requires the 'Create Projects' permission and setting a Personal Access Token with\napi/alm_integrations/set_pat for a user who will be using this endpoint.\nUpdating an existing project additionally requires the 'Administer' permission on that project.\n", "operationId": "createOrUpdateBoundProject", "requestBody": { "content": { @@ -244,6 +469,9 @@ "403": { "description": "Insufficient permissions - requires 'Create Projects' permission" }, + "404": { + "description": "Project already exists and the caller lacks 'Administer' permission on it" + }, "500": { "description": "Internal server error - failed to fetch repository from DevOps platform (e.g., repository not found, invalid PAT, or insufficient permissions)" } @@ -292,6 +520,40 @@ } } }, + "/a3s/context-uploads/{contextId}": { + "put": { + "tags": [ + "q" + ], + "operationId": "젔", + "parameters": [ + { + "name": "contextId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "Content-Length", + "in": "header", + "required": false, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-sonar-internal": "true" + } + }, "/users-management/users": { "get": { "tags": [ @@ -909,92 +1171,69 @@ "x-sonar-internal": "true" } }, - "/marketplace/azure/billing": { - "post": { + "/remediation-agent/scheduled-agent-configs": { + "get": { "tags": [ - "Marketplace Azure" + "scheduled-agent-configs" ], - "summary": "Bills user's Azure account with the cost of SonarQube Server license", - "description": "Used by admin to bill user's Azure account with the cost of SonarQube Server license.", - "operationId": "billAzureAccount", + "operationId": "searchScheduledAgentConfigs", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AzureBillingRestResponse" + "$ref": "#/components/schemas/ScheduledAgentConfigSearchRestResponse" } } } } } - } - }, - "/jira/work-items": { - "get": { + }, + "post": { "tags": [ - "Jira" + "scheduled-agent-configs" ], - "summary": "Fetch Jira work items", - "description": "Fetch the Jira work items for a specific Sonar project and resource.\nAccepts only authenticated requests.\n", - "operationId": "饘", - "parameters": [ - { - "name": "sonarProjectId", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "resourceId", - "in": "query", - "required": true, - "schema": { - "type": "string" + "operationId": "createScheduledAgentConfig", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScheduledAgentConfigCreateRestRequest" + } } }, - { - "name": "resourceType", - "in": "query", - "required": true, - "schema": { - "type": "string", - "enum": [ - "SONAR_ISSUE", - "DEPENDENCY_RISK" - ] - } - } - ], + "required": true + }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/JiraWorkItemResource" + "$ref": "#/components/schemas/ScheduledAgentConfigResource" } } } } - }, - "x-sonar-internal": "true" - }, + } + } + }, + "/remediation-agent/pull-request-jobs": { "post": { "tags": [ - "Jira" + "pull-request-jobs", + "jobs", + "backlog-jobs" ], - "summary": "Create Jira work item", - "description": "Create a Jira work item for a specific resource.\nAccepts only authenticated requests with issue administration permission.\n", - "operationId": "饘_1", + "summary": "Create a remediation pull-request job", + "description": "Triggers a new pull-request-scoped remediation job for a project", + "operationId": "createPullRequestJob", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PostJiraWorkItemRequestResource" + "$ref": "#/components/schemas/CreatePullRequestJobRequest" } } }, @@ -1002,88 +1241,159 @@ }, "responses": { "201": { - "description": "Created", + "description": "Job created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/JiraWorkItemResource" + "$ref": "#/components/schemas/Job" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "403": { + "description": "No project access, or an issue key does not belong to projectKey", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" } } } } }, - "x-sonar-internal": "true" - }, - "delete": { + "security": [ + { + "TokenAuth": [] + } + ] + } + }, + "/remediation-agent/backlog-jobs": { + "post": { "tags": [ - "Jira" + "pull-request-jobs", + "jobs", + "backlog-jobs" ], - "summary": "Delete Jira work items", - "description": "Delete the Jira work items associated with a specific resource.\n", - "operationId": "狗", - "parameters": [ - { - "name": "sonarProjectId", - "in": "query", - "required": true, - "schema": { - "type": "string" + "summary": "Create a remediation backlog job", + "description": "Triggers a new full-repository remediation job for a project", + "operationId": "createBacklogJob", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateBacklogJobRequest" + } } }, - { - "name": "resourceId", - "in": "query", - "required": true, - "schema": { - "type": "string" + "required": true + }, + "responses": { + "201": { + "description": "Job created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } } }, - { - "name": "resourceType", - "in": "query", - "required": true, - "schema": { - "type": "string", - "enum": [ - "SONAR_ISSUE", - "DEPENDENCY_RISK" - ] + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "403": { + "description": "No project access, or an issue key does not belong to projectKey", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } } - } - ], - "responses": { - "204": { - "description": "No Content" } }, - "x-sonar-internal": "true" + "security": [ + { + "TokenAuth": [] + } + ] } }, - "/jira/project-bindings": { - "get": { + "/marketplace/azure/billing": { + "post": { "tags": [ - "Jira" + "Marketplace Azure" ], - "summary": "Fetch Jira project bindings", - "description": "Fetch the Jira project binding for a specific Sonar project.\nAccepts only authenticated requests.\n", - "operationId": "饘_2", - "parameters": [ - { - "name": "sonarProjectId", - "in": "query", - "required": true, - "schema": { - "type": "string" + "summary": "Bills user's Azure account with the cost of SonarQube Server license", + "description": "Used by admin to bill user's Azure account with the cost of SonarQube Server license.", + "operationId": "billAzureAccount", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AzureBillingRestResponse" + } + } } } + } + } + }, + "/llm-connectivity/llm-providers": { + "get": { + "tags": [ + "LLM Providers" ], + "summary": "List configured LLM providers", + "description": "Requires system administration.", + "operationId": "getProviders", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/JiraProjectBindingResource" + "$ref": "#/components/schemas/LlmProvidersResponse" } } } @@ -1093,16 +1403,16 @@ }, "post": { "tags": [ - "Jira" + "LLM Providers" ], - "summary": "Create or update Jira project binding", - "description": "Create or update a Jira project binding for a specific Sonar project.\nAccepts only authenticated requests.\n", - "operationId": "饘_3", + "summary": "Create an LLM provider", + "description": "The connection is tested before the provider is persisted. Requires system administration.", + "operationId": "createProvider", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PostJiraProjectBindingRequestResource" + "$ref": "#/components/schemas/LlmProviderCreateRequest" } } }, @@ -1114,50 +1424,100 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/JiraProjectBindingResource" + "$ref": "#/components/schemas/LlmProviderResponse" } } } } }, "x-sonar-internal": "true" - }, - "delete": { + } + }, + "/llm-connectivity/llm-provider-validations": { + "post": { "tags": [ - "Jira" + "LLM Providers" ], - "summary": "Delete Jira project binding", - "description": "Delete the Jira project binding for a specific Sonar project.\nAccepts only authenticated requests.\n", - "operationId": "狗_1", + "summary": "Run a connection test for an existing provider", + "description": "Runs a fresh live connection test. Nothing is persisted. Requires system administration.", + "operationId": "validateProvider", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderValidationCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderValidationResponse" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/llm-connectivity/llm-provider-mappings": { + "get": { + "tags": [ + "LLM Providers" + ], + "summary": "List per-capability provider mappings", + "description": "Optionally filtered to a single AI capability. Requires system administration.", + "operationId": "getMappings", "parameters": [ { - "name": "sonarProjectId", + "name": "aiCapability", "in": "query", - "required": true, + "required": false, "schema": { - "type": "string" + "type": [ + "string", + "null" + ], + "enum": [ + "AI_CODEFIX", + "HUNTER_AGENT", + "REMEDIATION_AGENT" + ] } } ], "responses": { - "204": { - "description": "No Content" + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderMappingsResponse" + } + } + } } }, "x-sonar-internal": "true" }, - "patch": { + "post": { "tags": [ - "Jira" + "LLM Providers" ], - "summary": "Update Jira project binding", - "description": "Update an existing Jira project binding for a specific Sonar project.\nAccepts only authenticated requests.\n", - "operationId": "饘_4", + "summary": "Create or replace the provider mapping for an AI capability", + "description": "Creates or replaces the provider mapping for the given AI capability. Rejected when the provider or configured models cannot be validated, or when Hunter does not have both models. Requires system administration.", + "operationId": "upsertMapping", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PatchJiraProjectBindingRequestResource" + "$ref": "#/components/schemas/ProviderMappingUpsertRequest" } } }, @@ -1169,7 +1529,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/JiraProjectBindingResource" + "$ref": "#/components/schemas/ProviderMappingResponse" } } } @@ -1178,22 +1538,41 @@ "x-sonar-internal": "true" } }, - "/jira/organization-bindings": { + "/jira/work-items": { "get": { "tags": [ "Jira" ], - "summary": "Fetch the Jira instance binding", - "description": "Fetch the Jira instance binding.\nRequires global administrator permission.\n", - "operationId": "狗_2", + "summary": "Fetch Jira work items", + "description": "Fetch the Jira work items for a specific Sonar project and resource.\nAccepts only authenticated requests.\n", + "operationId": "젔_1", "parameters": [ { - "name": "sonarOrganizationUuid", + "name": "sonarProjectId", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "resourceId", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "resourceType", "in": "query", "required": true, "schema": { "type": "string", - "format": "uuid" + "enum": [ + "SONAR_ISSUE", + "DEPENDENCY_RISK" + ] } } ], @@ -1203,7 +1582,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/JiraOrganizationBindingResource" + "$ref": "#/components/schemas/JiraWorkItemResource" } } } @@ -1215,14 +1594,14 @@ "tags": [ "Jira" ], - "summary": "Create a new Jira instance binding", - "description": "Receives the 3LO state and authorization code parameters and attempts to create an instance binding.\nIf successful, returns the binding. Otherwise, returns a list of available resources.\nRequires global administrator permission.\n", - "operationId": "饘_5", + "summary": "Create Jira work item", + "description": "Create a Jira work item for a specific resource.\nAccepts only authenticated requests with issue administration permission.\n", + "operationId": "젔_2", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PostJiraOrganizationBindingRequestResource" + "$ref": "#/components/schemas/PostJiraWorkItemRequestResource" } } }, @@ -1234,7 +1613,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PostJiraOrganizationBindingResponseResource" + "$ref": "#/components/schemas/JiraWorkItemResource" } } } @@ -1246,9 +1625,237 @@ "tags": [ "Jira" ], - "summary": "Delete the Jira instance binding", - "description": "Deletes the Jira instance binding from the database.\nRequires global administrator permission.\n", - "operationId": "饘_6", + "summary": "Delete Jira work items", + "description": "Delete the Jira work items associated with a specific resource.\n", + "operationId": "藕", + "parameters": [ + { + "name": "sonarProjectId", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "resourceId", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "resourceType", + "in": "query", + "required": true, + "schema": { + "type": "string", + "enum": [ + "SONAR_ISSUE", + "DEPENDENCY_RISK" + ] + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "x-sonar-internal": "true" + } + }, + "/jira/project-bindings": { + "get": { + "tags": [ + "Jira" + ], + "summary": "Fetch Jira project bindings", + "description": "Fetch the Jira project binding for a specific Sonar project.\nAccepts only authenticated requests.\n", + "operationId": "젔_3", + "parameters": [ + { + "name": "sonarProjectId", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JiraProjectBindingResource" + } + } + } + } + }, + "x-sonar-internal": "true" + }, + "post": { + "tags": [ + "Jira" + ], + "summary": "Create or update Jira project binding", + "description": "Create or update a Jira project binding for a specific Sonar project.\nAccepts only authenticated requests.\n", + "operationId": "젔_4", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PostJiraProjectBindingRequestResource" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JiraProjectBindingResource" + } + } + } + } + }, + "x-sonar-internal": "true" + }, + "delete": { + "tags": [ + "Jira" + ], + "summary": "Delete Jira project binding", + "description": "Delete the Jira project binding for a specific Sonar project.\nAccepts only authenticated requests.\n", + "operationId": "藕_1", + "parameters": [ + { + "name": "sonarProjectId", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "x-sonar-internal": "true" + }, + "patch": { + "tags": [ + "Jira" + ], + "summary": "Update Jira project binding", + "description": "Update an existing Jira project binding for a specific Sonar project.\nAccepts only authenticated requests.\n", + "operationId": "젔_5", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchJiraProjectBindingRequestResource" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JiraProjectBindingResource" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/jira/organization-bindings": { + "get": { + "tags": [ + "Jira" + ], + "summary": "Fetch the Jira instance binding", + "description": "Fetch the Jira instance binding.\nRequires global administrator permission.\n", + "operationId": "藕_2", + "parameters": [ + { + "name": "sonarOrganizationUuid", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JiraOrganizationBindingResource" + } + } + } + } + }, + "x-sonar-internal": "true" + }, + "post": { + "tags": [ + "Jira" + ], + "summary": "Create a new Jira instance binding", + "description": "Receives the 3LO state and authorization code parameters and attempts to create an instance binding.\nIf successful, returns the binding. Otherwise, returns a list of available resources.\nRequires global administrator permission.\n", + "operationId": "젔_6", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PostJiraOrganizationBindingRequestResource" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PostJiraOrganizationBindingResponseResource" + } + } + } + } + }, + "x-sonar-internal": "true" + }, + "delete": { + "tags": [ + "Jira" + ], + "summary": "Delete the Jira instance binding", + "description": "Deletes the Jira instance binding from the database.\nRequires global administrator permission.\n", + "operationId": "젔_7", "parameters": [ { "name": "sonarOrganizationUuid", @@ -1273,7 +1880,7 @@ ], "summary": "Binds the specified pending instance binding with the specified jira cloud ID", "description": "Binds the specified pending instance binding with the specified jira cloud ID\n", - "operationId": "饘_7", + "operationId": "젔_8", "requestBody": { "content": { "application/json": { @@ -1673,6 +2280,40 @@ "x-sonar-internal": "true" } }, + "/dop-translation/scm-access-tokens": { + "post": { + "tags": [ + "Dop Translation" + ], + "summary": "Provide a scoped SCM access token", + "description": "Provide a scoped git credential for the given project's bound DevOps Platform (GitHub or GitLab).\nGitHub credentials are short-lived and minted per call; GitLab credentials are cached in memory per\nSonarQube Server node and refreshed before expiry. Internal endpoint used by the agentic-workflows\nremediation orchestrator (SONAR-31165).\nRequires the 'Administer System' permission or trusted privileged-service authentication.\n", + "operationId": "generateScmAccessToken", + "parameters": [ + { + "name": "project", + "in": "query", + "description": "Project key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScmAccessTokenRestResponse" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, "/dop-translation/gitlab-synchronization-runs": { "post": { "tags": [ @@ -1858,73 +2499,74 @@ "x-sonar-internal": "true" } }, - "/dop-translation/github-configurations": { - "get": { + "/dop-translation/github-installation-tokens": { + "post": { "tags": [ "Dop Translation" ], - "summary": "Search GitHub configs", - "description": " Get the list of GitHub configurations.\n Note that a single configuration is supported at this time.\n Requires 'Administer System' permission.\n", - "operationId": "searchGithubConfiguration", + "summary": "Mint a GitHub App installation token", + "description": "Mint a short-lived GitHub App installation token, scoped to the bound repository of the given\nproject. Internal endpoint used by the agentic-workflows remediation orchestrator (SONAR-30903).\nRequires the 'Administer System' permission.\n", + "operationId": "generateInstallationToken", + "parameters": [ + { + "name": "project", + "in": "query", + "description": "Project key", + "required": true, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GithubConfigurationSearchRestResponse" + "$ref": "#/components/schemas/GithubInstallationTokenRestResponse" } } } } }, "x-sonar-internal": "true" - }, - "post": { + } + }, + "/dop-translation/github-configurations": { + "get": { "tags": [ "Dop Translation" ], - "summary": "Create GitHub configuration", - "description": " Create a new GitHub configuration.\n Note that only a single configuration can exist at a time.\n Requires 'Administer System' permission.\n", - "operationId": "createGithubConfiguration", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GithubConfigurationCreateRestRequest" - } - } - }, - "required": true - }, + "summary": "Search GitHub configs", + "description": " Get the list of GitHub configurations.\n Note that a single configuration is supported at this time.\n Requires 'Administer System' permission.\n", + "operationId": "searchGithubConfiguration", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GithubConfigurationResource" + "$ref": "#/components/schemas/GithubConfigurationSearchRestResponse" } } } } }, "x-sonar-internal": "true" - } - }, - "/clean-code-policy/rules": { + }, "post": { "tags": [ - "Clean Code Policy" + "Dop Translation" ], - "summary": "Custom rule creation", - "description": " Create a custom rule.\n Requires the 'Administer Quality Profiles' permission.\n", - "operationId": "create_6", + "summary": "Create GitHub configuration", + "description": " Create a new GitHub configuration.\n Note that only a single configuration can exist at a time.\n Requires 'Administer System' permission.\n", + "operationId": "createGithubConfiguration", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RuleCreateRestRequest" + "$ref": "#/components/schemas/GithubConfigurationCreateRestRequest" } } }, @@ -1936,119 +2578,155 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RuleRestResponse" + "$ref": "#/components/schemas/GithubConfigurationResource" } } } } }, - "x-internal": "true" + "x-sonar-internal": "true" } }, - "/authorizations/groups": { + "/detection-agent/jobs": { "get": { "tags": [ - "Authorizations" + "jobs" ], - "summary": "Group search", - "description": " Get the list of groups.\n The results are sorted alphabetically by group name.\n", - "operationId": "search_1", + "summary": "List detection jobs", + "description": "Returns a paginated list of detection jobs for a project. ", + "operationId": "listJobs", "parameters": [ { - "name": "managed", + "name": "projectId", "in": "query", - "description": "Return managed or non-managed groups. Only available for managed instances, throws for non-managed instances", - "required": false, + "description": "Restrict to jobs of this project.", + "required": true, "schema": { - "type": [ - "boolean", - "null" - ], - "description": "Return managed or non-managed groups. Only available for managed instances, throws for non-managed instances" + "type": "string" } }, { - "name": "q", + "name": "branchId", "in": "query", - "description": "Filter on name.\nThis parameter performs a partial match (contains), it is case insensitive.", + "description": "Restrict to jobs of this branch.", "required": false, "schema": { "type": [ "string", "null" - ], - "description": "Filter on name.\nThis parameter performs a partial match (contains), it is case insensitive." + ] } }, { - "name": "userId", + "name": "status", "in": "query", - "description": "Filter groups containing the user. Only available for system administrators. Using != operator will search for groups without the user.", + "description": "Job statuses to include. Repeat the parameter to provide multiple statuses.", "required": false, "schema": { "type": [ - "string", - "null" + "null", + "array" ], - "description": "Filter groups containing the user. Only available for system administrators. Using != operator will search for groups without the user.", - "internal": "true" - }, - "x-internal": "true" + "items": { + "type": "string", + "enum": [ + "QUEUED", + "IN_PROGRESS", + "COMPLETED", + "FAILED", + "SKIPPED" + ] + }, + "maxItems": 2147483647, + "minItems": 1 + } }, { - "name": "pageSize", + "name": "pageIndex", "in": "query", - "description": "Number of results per page. A value of 0 will only return the pagination information.", + "description": "1-based page index.", "required": false, "schema": { "type": "integer", "format": "int32", - "default": 50, - "description": "Number of results per page. A value of 0 will only return the pagination information.", - "maximum": 500, - "minimum": 0 + "default": 1, + "minimum": 1 } }, { - "name": "pageIndex", + "name": "pageSize", "in": "query", - "description": "1-based page index", + "description": "Number of items per page.", "required": false, "schema": { "type": "integer", "format": "int32", - "default": 1, - "description": "1-based page index", - "exclusiveMinimum": 0, + "default": 50, + "maximum": 500, "minimum": 1 } } ], "responses": { "200": { - "description": "OK", + "description": "A page of jobs.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GroupsSearchRestResponse" + "$ref": "#/components/schemas/JobsResponse" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobsResponse" + } + } + } + }, + "404": { + "description": "The caller lacks access to the target project, or the project does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobsResponse" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobsResponse" } } } } - } + }, + "security": [ + { + "TokenAuth": [] + } + ] }, "post": { "tags": [ - "Authorizations" + "jobs" ], - "summary": "Create a new group", - "description": "Create a new group.", - "operationId": "create_7", + "summary": "Create a detection job", + "description": "Triggers a new agentic detection job for a project", + "operationId": "createJob", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GroupCreateRestRequest" + "$ref": "#/components/schemas/CreateJobRequest" } } }, @@ -2056,194 +2734,278 @@ }, "responses": { "201": { - "description": "Created", + "description": "Job created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GroupRestResponse" + "$ref": "#/components/schemas/Job" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "403": { + "description": "The organization is not entitled", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "404": { + "description": "Branch, owning project, or NCLOC measure not found; or the caller lacks project administration rights on the project. ", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "422": { + "description": "A job is already pending or running for the project, or the organization has insufficient Hunter Agent credits", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "503": { + "description": "The entitlement provider is unavailable", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" } } } } + }, + "security": [ + { + "TokenAuth": [] + } + ] + } + }, + "/detection-agent/jobs/results": { + "post": { + "tags": [ + "n" + ], + "operationId": "젔_9", + "parameters": [ + { + "name": "jobId", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgenticJobResult" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } } } }, - "/authorizations/group-memberships": { + "/dashboards": { "get": { "tags": [ - "Authorizations" + "custom-dashboards" ], - "summary": "Search across group memberships", - "description": " Get the list of groups and members matching the query.\n", - "operationId": "search_2", + "summary": "List dashboards for a resource", + "operationId": "listDashboards", "parameters": [ { - "name": "userId", + "name": "resourceId", "in": "query", - "description": "ID of the user for which to search groups. If not set, all groups are returned.", - "required": false, + "required": true, "schema": { - "type": [ - "string", - "null" - ], - "description": "ID of the user for which to search groups. If not set, all groups are returned." + "type": "string" } }, { - "name": "groupId", + "name": "resourceType", + "in": "query", + "required": true, + "schema": { + "type": "string", + "enum": [ + "project", + "portfolio" + ] + } + }, + { + "name": "q", "in": "query", - "description": "ID of the group for which to search members. If not set, all groups are returned.", "required": false, "schema": { "type": [ "string", "null" - ], - "description": "ID of the group for which to search members. If not set, all groups are returned." + ] } }, { - "name": "pageSize", + "name": "pageIndex", "in": "query", - "description": "Number of results per page. A value of 0 will only return the pagination information.", "required": false, "schema": { "type": "integer", "format": "int32", - "default": 50, - "description": "Number of results per page. A value of 0 will only return the pagination information.", - "maximum": 500, - "minimum": 0 + "default": 1, + "minimum": 1 } }, { - "name": "pageIndex", + "name": "pageSize", "in": "query", - "description": "1-based page index", "required": false, "schema": { "type": "integer", "format": "int32", - "default": 1, - "description": "1-based page index", - "exclusiveMinimum": 0, - "minimum": 1 + "default": 50, + "maximum": 5000, + "minimum": 0 } } ], "responses": { "200": { - "description": "OK", + "description": "Successful operation", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GroupsMembershipSearchRestResponse" + "$ref": "#/components/schemas/DashboardsResponse" } } } - } - } - }, - "post": { - "tags": [ - "Authorizations" - ], - "summary": "Add a group membership", - "description": "Add a user to a group.", - "operationId": "create_8", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GroupMembershipCreateRestRequest" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } } } }, - "required": true - }, - "responses": { - "201": { - "description": "Created", + "401": { + "description": "Unauthorized", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GroupMembershipRestResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } - } - } - } - }, - "/atlassian/application-configuration": { - "get": { - "tags": [ - "Atlassian" - ], - "summary": "Fetch the Atlassian Authentication details if they exist", - "description": "Fetch the Atlassian Authentication details if they exist.\nSpecifically, only the client ID is returned for security purposes.\n", - "operationId": "饘_8", - "responses": { - "200": { - "description": "OK", + }, + "403": { + "description": "Forbidden", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AtlassianAuthenticationDetailsResultResource" + "$ref": "#/components/schemas/ErrorResponse" } } } - } - }, - "x-sonar-internal": "true" - }, - "post": { - "tags": [ - "Atlassian" - ], - "summary": "Create/Update the Atlassian Authentication details", - "description": "Create/Update the Atlassian Authentication details.\n", - "operationId": "饘_9", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AtlassianAuthenticationDetailsResource" + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } } } }, - "required": true - }, - "responses": { - "200": { - "description": "OK", + "429": { + "description": "Too Many Requests", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AtlassianAuthenticationDetailsResultResource" + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "501": { + "description": "Not Implemented", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } } }, - "x-sonar-internal": "true" - } - }, - "/architecture/scanner-data": { + "security": [ + { + "TokenAuth": [] + } + ] + }, "post": { "tags": [ - "scanner-data-upload", - "scanner-data" + "custom-dashboards" ], - "summary": "Initiate scanner data upload", - "description": "Initiate scanner data upload. External endpoint used by scanner clients.", - "operationId": "scannerDataPost", + "summary": "Create a custom dashboard", + "operationId": "createDashboard", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Bq" + "$ref": "#/components/schemas/CreateDashboardRequest" } } }, @@ -2251,11 +3013,11 @@ }, "responses": { "201": { - "description": "Created", + "description": "Successful operation", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/L" + "$ref": "#/components/schemas/DashboardResponse" } } } @@ -2265,7 +3027,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -2275,7 +3037,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -2285,7 +3047,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -2295,7 +3067,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -2305,7 +3077,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "501": { + "description": "Not Implemented", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -2313,60 +3095,72 @@ }, "security": [ { - "lambdaAuthorizer": [] + "TokenAuth": [] } ] } }, - "/architecture/scanner-data-upload": { + "/clean-code-policy/rules": { "post": { "tags": [ - "scanner-data-upload", - "scanner-data" + "Clean Code Policy" ], - "summary": "Upload scanner data report directly", - "description": "Upload a scanner data report directly as multipart/form-data. The target project and branch are resolved server-side from `ceTaskId`, and the report bytes are carried in the `payload` part. Used by SonarQube Server scanner clients. ", - "operationId": "scannerDataUploadPost", - "parameters": [ - { - "name": "ceTaskId", - "in": "query", - "description": "Compute Engine task ID. Used to resolve the target project and branch.", - "required": true, - "schema": { - "type": "string" + "summary": "Custom rule creation", + "description": " Create a custom rule.\n Requires the 'Administer Quality Profiles' permission.\n", + "operationId": "create_6", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleCreateRestRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleRestResponse" + } + } } } + }, + "x-internal": "true" + } + }, + "/cag/cag-usage-events": { + "post": { + "tags": [ + "CagUsage" ], + "summary": "Record a CAG tool usage event", + "description": "Records a single Context Augmentation tool-call event. The event is acknowledged with 202 once accepted; a storage failure answers 500. Request shape (organizationId / projectId / invocationId) matches the existing SonarQube Cloud contract so clients are unaffected by which host answers the request. ", + "operationId": "recordCagUsageEvent", "requestBody": { "content": { - "multipart/form-data": { + "application/json": { "schema": { - "type": "object", - "properties": { - "payload": { - "type": "string", - "format": "binary", - "description": "Scanner data report bytes." - } - }, - "required": [ - "payload" - ] + "$ref": "#/components/schemas/CagUsageEventRequest" } } - } + }, + "required": true }, "responses": { - "201": { - "description": "Created" + "202": { + "description": "Usage event accepted for recording" }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -2376,7 +3170,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -2386,17 +3180,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" - } - } - } - }, - "429": { - "description": "Too Many Requests", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -2406,7 +3190,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -2414,50 +3198,40 @@ }, "security": [ { - "lambdaAuthorizer": [] + "TokenAuth": [] } - ] + ], + "x-sonar-internal": "true" } }, - "/architecture/models": { - "get": { + "/cag/cag-impact-events": { + "post": { "tags": [ - "models" - ], - "summary": "List models for a project", - "description": "Return all models for a project. May be empty.", - "operationId": "listModels", - "parameters": [ - { - "name": "projectId", - "in": "query", - "description": "Project Id", - "required": true, - "schema": { - "type": "string" - } - } + "CagImpactEvents" ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Model" - } - } + "summary": "Record a CAG Vortex impact event", + "description": "Records one versioned Vortex impact event. Null or missing eventType and eventVersion return 400. The payload must conform to the named payload schema for the (eventType, eventVersion) pair: NavigationPayloadV1, OutputCompressionPayloadV1, OutputRestorePayloadV1, GuidelinesPayloadV1, DependencyCheckPayloadV1, or IntendedArchitecturePayloadV1 (all in components/schemas). ", + "operationId": "recordCagImpactEvent", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CagImpactEventRequest" } } }, + "required": true + }, + "responses": { + "202": { + "description": "Impact event accepted for recording" + }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -2467,7 +3241,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -2477,17 +3251,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" - } - } - } - }, - "429": { - "description": "Too Many Requests", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -2497,7 +3261,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -2505,221 +3269,196 @@ }, "security": [ { - "lambdaAuthorizer": [] + "TokenAuth": [] } ], "x-sonar-internal": "true" - }, + } + }, + "/billing/overage": { "post": { "tags": [ - "models" + "Billing" ], - "summary": "Create a model", - "description": "Create a model and return it including the generated id", - "operationId": "createModel", + "summary": "Enables, updates, or disables the admin's overage arrangement for a feature", + "description": " Enables, updates, or disables the overage arrangement for a feature.\n A single POST covers all three actions. Requires system administrator privileges.\n", + "operationId": "setOverage", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PostModel" + "$ref": "#/components/schemas/OverageRestRequest" } } }, "required": true }, "responses": { - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Model" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/X" - } - } - } - }, - "401": { - "description": "Unauthorized", + "200": { + "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/LicenseFeatureRestResponse" } } } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/X" - } - } + } + }, + "x-sonar-internal": "true" + } + }, + "/billing/consumption-records": { + "post": { + "tags": [ + "Billing" + ], + "summary": "Records a consumption event for a CONSUMABLE feature", + "description": " Records usage after a billable action. Does not enforce caps — use entitlement-checks first.\n Returns 202 because drain and LicenseSpring reporting remain asynchronous.\n Requires authentication.\n", + "operationId": "createConsumptionRecord", + "parameters": [ + { + "name": "Idempotency-Key", + "in": "header", + "description": "Accepted for SQC compatibility; not yet deduplicated on Server (at-least-once semantics)", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] } - }, - "429": { - "description": "Too Many Requests", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/X" - } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateConsumptionRecordRestRequest" } } }, - "500": { - "description": "Internal Server Error", + "required": true + }, + "responses": { + "202": { + "description": "Accepted", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ConsumptionRecordRestResponse" } } } } }, - "security": [ - { - "lambdaAuthorizer": [] - } - ], "x-sonar-internal": "true" } }, - "/architecture/directives": { + "/authorizations/groups": { "get": { "tags": [ - "directives" + "Authorizations" ], - "summary": "List directives for a project", - "description": "Return all directives for a project sorted by dateCreated ascending (oldest first). May be empty.", - "operationId": "directivesGet", + "summary": "Group search", + "description": " Get the list of groups.\n The results are sorted alphabetically by group name.\n", + "operationId": "search_1", "parameters": [ { - "name": "projectId", + "name": "managed", "in": "query", - "description": "Project Id", - "required": true, + "description": "Return managed or non-managed groups. Only available for managed instances, throws for non-managed instances", + "required": false, "schema": { - "type": "string" + "type": [ + "boolean", + "null" + ], + "description": "Return managed or non-managed groups. Only available for managed instances, throws for non-managed instances" } }, { - "name": "pageIndex", + "name": "q", "in": "query", - "description": "Page number for pagination (1-based). Specifies which page of results to return.", + "description": "Filter on name.\nThis parameter performs a partial match (contains), it is case insensitive.", "required": false, "schema": { - "type": "integer", - "format": "int32", - "default": 1, - "minimum": 1 + "type": [ + "string", + "null" + ], + "description": "Filter on name.\nThis parameter performs a partial match (contains), it is case insensitive." } }, + { + "name": "userId", + "in": "query", + "description": "Filter groups containing the user. Only available for system administrators. Using != operator will search for groups without the user.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "description": "Filter groups containing the user. Only available for system administrators. Using != operator will search for groups without the user.", + "internal": "true" + }, + "x-internal": "true" + }, { "name": "pageSize", "in": "query", - "description": "Number of items per page. Set to 0 to return only the total count without any items (useful for counting).", + "description": "Number of results per page. A value of 0 will only return the pagination information.", "required": false, "schema": { "type": "integer", "format": "int32", "default": 50, + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "maximum": 500, "minimum": 0 } + }, + { + "name": "pageIndex", + "in": "query", + "description": "1-based page index", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "description": "1-based page index", + "exclusiveMinimum": 0, + "minimum": 1 + } } ], "responses": { "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Zq" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/X" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/X" - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/X" - } - } - } - }, - "429": { - "description": "Too Many Requests", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/X" - } - } - } - }, - "500": { - "description": "Internal Server Error", + "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/GroupsSearchRestResponse" } } } } - }, - "security": [ - { - "lambdaAuthorizer": [] - } - ] + } }, "post": { "tags": [ - "directives" + "Authorizations" ], - "summary": "Store a directive", - "description": "Store a directive", - "operationId": "directivesPost", + "summary": "Create a new group", + "description": "Create a new group.", + "operationId": "create_7", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Dq" + "$ref": "#/components/schemas/GroupCreateRestRequest" } } }, @@ -2731,27 +3470,222 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/y" + "$ref": "#/components/schemas/GroupRestResponse" } } } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/X" - } - } + } + } + } + }, + "/authorizations/group-memberships": { + "get": { + "tags": [ + "Authorizations" + ], + "summary": "Search across group memberships", + "description": " Get the list of groups and members matching the query.\n", + "operationId": "search_2", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "ID of the user for which to search groups. If not set, all groups are returned.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "description": "ID of the user for which to search groups. If not set, all groups are returned." } }, - "401": { + { + "name": "groupId", + "in": "query", + "description": "ID of the group for which to search members. If not set, all groups are returned.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "description": "ID of the group for which to search members. If not set, all groups are returned." + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "maximum": 500, + "minimum": 0 + } + }, + { + "name": "pageIndex", + "in": "query", + "description": "1-based page index", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "description": "1-based page index", + "exclusiveMinimum": 0, + "minimum": 1 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupsMembershipSearchRestResponse" + } + } + } + } + } + }, + "post": { + "tags": [ + "Authorizations" + ], + "summary": "Add a group membership", + "description": "Add a user to a group.", + "operationId": "create_8", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupMembershipCreateRestRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupMembershipRestResponse" + } + } + } + } + } + } + }, + "/atlassian/application-configuration": { + "get": { + "tags": [ + "Atlassian" + ], + "summary": "Fetch the Atlassian Authentication details if they exist", + "description": "Fetch the Atlassian Authentication details if they exist.\nSpecifically, only the client ID is returned for security purposes.\n", + "operationId": "젔_10", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AtlassianAuthenticationDetailsResultResource" + } + } + } + } + }, + "x-sonar-internal": "true" + }, + "post": { + "tags": [ + "Atlassian" + ], + "summary": "Create/Update the Atlassian Authentication details", + "description": "Create/Update the Atlassian Authentication details.\n", + "operationId": "젔_11", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AtlassianAuthenticationDetailsResource" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AtlassianAuthenticationDetailsResultResource" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/architecture/sdks": { + "get": { + "tags": [ + "sdks" + ], + "summary": "List SDKs", + "description": "Return the SDKs defined for the given organization.", + "operationId": "sdksGet", + "parameters": [ + { + "name": "organizationId", + "in": "query", + "description": "UUID v4 of the organization.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SdkListResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -2761,7 +3695,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -2771,7 +3705,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -2781,7 +3715,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -2792,21 +3726,19 @@ "lambdaAuthorizer": [] } ] - } - }, - "/architecture/analysis": { + }, "post": { "tags": [ - "analysis" + "sdks" ], - "summary": "Store analysis data", - "description": "Store analysis data. External endpoint used by scanner clients.", - "operationId": "analysisPost", + "summary": "Create an SDK", + "description": "Create a new SDK for the given organization.", + "operationId": "sdksPost", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/cq" + "$ref": "#/components/schemas/PostSdkBody" } } }, @@ -2818,7 +3750,7 @@ "content": { "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/Sdk" } } } @@ -2828,7 +3760,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -2838,7 +3770,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -2848,7 +3780,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -2858,7 +3790,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -2868,7 +3800,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -2881,182 +3813,108 @@ ] } }, - "/users-management/users/{id}": { - "get": { + "/architecture/scanner-data": { + "post": { "tags": [ - "Users Management" + "scanner-data-upload", + "scanner-data" ], - "summary": "Fetch a single user", - "description": "Fetch a single user.\nThe following fields are only returned when user has Administer System permission or for logged-in in user :\n 'email'\n 'externalIdentity'\n 'externalProvider'\n 'groups'\n 'lastConnectionDate'\n 'sonarLintLastConnectionDate'\n 'tokensCount'\n Field 'sonarqubeLastConnectionDate' is only updated every hour, so it may not be accurate, for instance when a user authenticates many times in less than one hour.\n", - "operationId": "fetchUser", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "The id of the user to fetch.", - "required": true, - "schema": { - "type": "string" + "summary": "Initiate scanner data upload", + "description": "Initiate scanner data upload. External endpoint used by scanner clients.", + "operationId": "scannerDataPost", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PostScannerDataBody" + } } - } - ], + }, + "required": true + }, "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UserRestResponseForAdmins" + "$ref": "#/components/schemas/ScannerDataResponse" } } } - } - } - }, - "delete": { - "tags": [ - "Users Management" - ], - "summary": "Deactivate a user", - "description": "Deactivates a user. Requires Administer System permission.", - "operationId": "deactivate", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "The ID of the user to delete.", - "required": true, - "schema": { - "type": "string" - } }, - { - "name": "anonymize", - "in": "query", - "description": "Anonymize user in addition to deactivating it.", - "required": false, - "schema": { - "type": "boolean", - "default": false + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } - } - ], - "responses": { - "204": { - "description": "No Content" - } - } - }, - "patch": { - "tags": [ - "Users Management" - ], - "summary": "Update a user", - "description": "Update users attributes.\n", - "operationId": "updateUser", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/UserUpdateRestRequest" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } } } }, - "required": true - }, - "responses": { - "200": { - "description": "OK", + "403": { + "description": "Forbidden", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UserRestResponseForAdmins" + "$ref": "#/components/schemas/Error" } } } - } - } - } - }, - "/system/email-configurations/{id}": { - "get": { - "tags": [ - "System" - ], - "summary": "Fetch an email configuration", - "description": "Fetch a Email configuration. Requires 'Administer System' permission.\n", - "operationId": "getEmailConfiguration", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "The id of the configuration to fetch.", - "required": true, - "schema": { - "type": "string" + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } - } - ], - "responses": { - "200": { - "description": "OK", + }, + "500": { + "description": "Internal Server Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmailConfigurationResource" + "$ref": "#/components/schemas/Error" } } } } }, - "x-sonar-internal": "true" - }, - "delete": { - "tags": [ - "System" - ], - "summary": "Delete an email configuration", - "description": "Delete an email configuration.\nRequires 'Administer System' permission.\n", - "operationId": "deleteEmailConfiguration", - "parameters": [ + "security": [ { - "name": "id", - "in": "path", - "description": "The id of the configuration to delete.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "No Content" + "lambdaAuthorizer": [] } - }, - "x-sonar-internal": "true" - }, - "patch": { + ] + } + }, + "/architecture/scanner-data-upload": { + "post": { "tags": [ - "System" + "scanner-data-upload", + "scanner-data" ], - "summary": "Update an email configuration", - "description": "Update an email configuration. Requires 'Administer System' permission.\n", - "operationId": "updateEmailConfiguration", + "summary": "Upload scanner data report directly", + "description": "Upload a scanner data report directly as multipart/form-data. The target project and branch are resolved server-side from `ceTaskId`, and the report bytes are carried in the `payload` part. Used by SonarQube Server scanner clients. ", + "operationId": "scannerDataUploadPost", "parameters": [ { - "name": "id", - "in": "path", + "name": "ceTaskId", + "in": "query", + "description": "Compute Engine task ID. Used to resolve the target project and branch.", "required": true, "schema": { "type": "string" @@ -3065,55 +3923,73 @@ ], "requestBody": { "content": { - "application/merge-patch+json": { + "multipart/form-data": { "schema": { - "$ref": "#/components/schemas/EmailConfigurationUpdateRestRequest" + "type": "object", + "properties": { + "payload": { + "type": "string", + "format": "binary", + "description": "Scanner data report bytes." + } + }, + "required": [ + "payload" + ] } } - }, - "required": true + } }, "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmailConfigurationResource" + "$ref": "#/components/schemas/Error" } } } - } - }, - "x-sonar-internal": "true" - } - }, - "/sca/license-profiles/{license-profile-key}": { - "get": { - "tags": [ - "License Profiles" - ], - "summary": "Get the license policy for a given profile", - "description": "Get the license policy for a given profile.\n", - "operationId": "get_1", - "parameters": [ - { - "name": "license-profile-key", - "in": "path", - "description": "The license profile key.", - "required": true, - "schema": { - "type": "string" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } - } - ], - "responses": { - "200": { - "description": "OK", + }, + "403": { + "description": "Forbidden", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/LicenseProfileDetailsResource" + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" } } } @@ -3121,134 +3997,111 @@ }, "security": [ { - "bearerAuth": [] + "lambdaAuthorizer": [] } - ], - "x-sonar-internal": true - }, - "delete": { + ] + } + }, + "/architecture/project-relationships": { + "get": { "tags": [ - "License Profiles" + "project-relationships" ], - "summary": "Delete the license profile", - "description": "Delete the license profile.", - "operationId": "delete", + "summary": "List project relationships for a project", + "description": "Return all project relationships for a project. May be empty.", + "operationId": "projectRelationshipsGet", "parameters": [ { - "name": "license-profile-key", - "in": "path", - "description": "The id of the license profile", + "name": "projectId", + "in": "query", + "description": "Project Id", "required": true, "schema": { "type": "string" } + }, + { + "name": "pageIndex", + "in": "query", + "description": "Page number for pagination (1-based). Specifies which page of results to return.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "minimum": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Number of items per page. Set to 0 to return only the total count without any items (useful for counting).", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "minimum": 0 + } } ], "responses": { - "204": { - "description": "No Content" - } - }, - "security": [ - { - "bearerAuth": [] - } - ], - "x-sonar-internal": true - }, - "patch": { - "tags": [ - "License Profiles" - ], - "summary": "Update the license profile", - "description": " Update the license profile for projects to use when evaluating license issues.\n", - "operationId": "update", - "parameters": [ - { - "name": "license-profile-key", - "in": "path", - "description": "The id of the license profile", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LicenseProfilesUpdateRestRequest" + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectRelationshipPagedResponse" + } } } }, - "required": true - }, - "responses": { - "200": { - "description": "OK", + "400": { + "description": "Bad Request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/LicenseProfileResource" + "$ref": "#/components/schemas/Error" } } } - } - }, - "security": [ - { - "bearerAuth": [] - } - ], - "x-sonar-internal": true - } - }, - "/sca/license-profiles/{license-profile-key}/licenses/{license-policy-id}": { - "patch": { - "tags": [ - "License Profiles" - ], - "summary": "Update the policy for a single license", - "description": " Update the policy for a single license in the license profile.\n", - "operationId": "patchLicense", - "parameters": [ - { - "name": "license-profile-key", - "in": "path", - "description": "The license profile key.", - "required": true, - "schema": { - "type": "string" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } }, - { - "name": "license-policy-id", - "in": "path", - "description": "The license ID.", - "required": true, - "schema": { - "type": "string" + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LicensePolicyLicenseUpdateRestRequest" + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } } } }, - "required": true - }, - "responses": { - "200": { - "description": "OK", + "500": { + "description": "Internal Server Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/LicensePolicyLicenseResource" + "$ref": "#/components/schemas/Error" } } } @@ -3256,104 +4109,84 @@ }, "security": [ { - "bearerAuth": [] + "lambdaAuthorizer": [] } - ], - "x-sonar-internal": true - } - }, - "/sca/license-profiles/{license-profile-key}/categories/{category-key}": { - "patch": { + ] + }, + "post": { "tags": [ - "License Profiles" - ], - "summary": "Update the policy for a license category", - "description": " Update the policy for an entire category in the license profile.\n", - "operationId": "patchCategory", - "parameters": [ - { - "name": "license-profile-key", - "in": "path", - "description": "The license profile key.", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "category-key", - "in": "path", - "description": "The category key.", - "required": true, - "schema": { - "type": "string", - "enum": [ - "UNKNOWN", - "COPYLEFT_WEAK", - "COPYLEFT_STRONG", - "COPYLEFT_NETWORK", - "COPYLEFT_MAXIMAL", - "PERMISSIVE_STANDARD", - "PERMISSIVE_AMATEUR" - ] - } - } + "project-relationships" ], + "summary": "Store a project relationship", + "description": "Store a project relationship", + "operationId": "projectRelationshipsPost", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/LicenseProfileCategoryUpdateRestRequest" + "$ref": "#/components/schemas/PostProjectRelationshipBody" } } }, "required": true }, "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/LicenseProfileCategoryResource" + "$ref": "#/components/schemas/ProjectRelationship" } } } - } - }, - "security": [ - { - "bearerAuth": [] - } - ], - "x-sonar-internal": true - } - }, - "/sca/license-profiles/assigned-projects": { - "patch": { - "tags": [ - "License Profiles" - ], - "summary": "Assign project to license profile", - "description": " Configure which license profile should be used when analyzing a project for license issues.\n", - "operationId": "update_1", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AssignedProjectsUpdateRestRequest" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } } } }, - "required": true - }, - "responses": { - "200": { - "description": "The provided project has been assigned to the license profile.", + "401": { + "description": "Unauthorized", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/LicenseProfileResource" + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" } } } @@ -3361,157 +4194,263 @@ }, "security": [ { - "bearerAuth": [] + "lambdaAuthorizer": [] } - ], - "x-sonar-internal": "true" + ] } }, - "/sca/issues-releases/{key}/changelog": { - "delete": { + "/architecture/patterns": { + "get": { "tags": [ - "Issues-Releases" + "patterns" ], - "summary": "Delete a comment from an issue-release pair", - "description": "Delete a comment from a (issue,release) pair (dependency risk).\n\nThis API is subject to change without notice.\n", - "operationId": "deleteComment", + "summary": "List patterns for an organization", + "description": "Return all patterns for an organization. May be empty.", + "operationId": "patternsGet", "parameters": [ { - "name": "key", - "in": "path", - "description": "The key of the (issue,release) pair.", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "issueReleaseChangeKey", + "name": "organizationId", "in": "query", - "description": "Issue release change key", + "description": "UUID v4 of the organization.", "required": true, "schema": { - "type": "string", - "description": "Issue release change key" + "type": "string" } } ], "responses": { - "204": { - "description": "No Content" + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pattern" + } + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } }, "security": [ { - "bearerAuth": [] + "lambdaAuthorizer": [] } - ], - "x-sonar-internal": true + ] }, - "patch": { + "post": { "tags": [ - "Issues-Releases" - ], - "summary": "Update a comment on an issue-release pair", - "description": "Update a comment on a (issue,release) pair (dependency risk).\n\nThis API is subject to change without notice.\n", - "operationId": "updateComment", - "parameters": [ - { - "name": "key", - "in": "path", - "description": "The key of the (issue,release) pair.", - "required": true, - "schema": { - "type": "string" - } - } + "patterns" ], + "summary": "Create a pattern", + "description": "Create a pattern and return it including the generated id", + "operationId": "patternsPost", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IssueReleaseUpdateCommentRestRequest" + "$ref": "#/components/schemas/PostPattern" } } }, "required": true }, "responses": { - "200": { - "description": "OK" + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pattern" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } }, "security": [ { - "bearerAuth": [] + "lambdaAuthorizer": [] } - ], - "x-sonar-internal": true + ] } }, - "/sca/issues-releases/{id}": { - "patch": { + "/architecture/organization-placeholders": { + "get": { "tags": [ - "Issues-Releases" + "organization-placeholders" ], - "summary": "Update the issue-release pair's information, specifically around severity", - "description": "Update the issue-release pair's information, specifically around severity. This is used to dismiss an increased\nseverity warning, or to manually change the severity.\n\nThis API is subject to change without notice.\n", - "operationId": "updateIssueRelease", + "summary": "List organization placeholders", + "description": "Return the placeholders for the given organization.", + "operationId": "organizationPlaceholdersGet", "parameters": [ { - "name": "id", - "in": "path", - "description": "The key of the (issue,release) pair.", + "name": "organizationId", + "in": "query", + "description": "UUID v4 of the organization.", "required": true, "schema": { "type": "string" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/IssueReleaseSetSeverityRestRequest" + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationPlaceholderListResponse" + } } } }, - "required": true - }, - "responses": { - "200": { - "description": "OK", + "400": { + "description": "Bad Request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IssueReleaseDetailsResource" + "$ref": "#/components/schemas/Error" } } } - } - }, - "security": [ - { - "bearerAuth": [] - } - ], - "x-sonar-internal": true - } - }, - "/sca/feature-enablements": { - "get": { - "tags": [ - "Feature Enablement" - ], - "summary": "Get the status of SCA feature opt-in (should only be used for the configuration UX in frontend)", - "operationId": "getFeatureEnablement", - "responses": { - "200": { - "description": "OK", + }, + "401": { + "description": "Unauthorized", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FeatureEnablementResource" + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" } } } @@ -3519,231 +4458,109 @@ }, "security": [ { - "bearerAuth": [] + "lambdaAuthorizer": [] } - ], - "x-sonar-internal": "true" + ] }, - "patch": { + "post": { "tags": [ - "Feature Enablement" + "organization-placeholders" ], - "summary": "Update SCA feature opt-in (should only be used for the configuration UX in frontend)", - "operationId": "updateFeatureEnablement", + "summary": "Create a placeholder", + "description": "Create a new placeholder for the given organization.", + "operationId": "organizationPlaceholdersPost", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FeatureEnablementRequest" + "$ref": "#/components/schemas/PostOrganizationPlaceholderBody" } } }, "required": true }, "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FeatureEnablementResource" + "$ref": "#/components/schemas/OrganizationPlaceholder" } } } - } - }, - "security": [ - { - "bearerAuth": [] - } - ], - "x-sonar-internal": "true" - } - }, - "/jira/work-types": { - "get": { - "tags": [ - "Jira" - ], - "summary": "Returns a list of all the available Jira work types for a specific Jira project", - "description": "Returns a list of all the available Jira work types for a specific Jira project.\nAlso checks which work types are selected for a given sonar project. Conditionally, also includes field metadata.\n", - "operationId": "饘_10", - "parameters": [ - { - "name": "jiraProjectKey", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "sonarOrganizationUuid", - "in": "query", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "sonarProjectId", - "in": "query", - "required": false, - "schema": { - "type": "string" - } }, - { - "name": "includeFields", - "in": "query", - "required": false, - "schema": { - "type": "boolean" - } - } - ], - "responses": { - "200": { - "description": "OK", + "400": { + "description": "Bad Request", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/JiraWorkTypeResultResource" - } + "$ref": "#/components/schemas/Error" } } } - } - }, - "x-sonar-internal": "true" - }, - "patch": { - "tags": [ - "Jira" - ], - "summary": "Saves the selected Jira work types for a project", - "description": "Receives a Jira project key and a list of work type and stores the work types.\n", - "operationId": "饘_11", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/JiraWorkTypesSelectionResource" - } - } }, - "required": true - }, - "responses": { - "200": { - "description": "OK" - } - }, - "x-sonar-internal": "true" - } - }, - "/jira/organization-binding-edit": { - "patch": { - "tags": [ - "Jira" - ], - "summary": "Update Jira organization binding properties", - "description": "Update properties of an existing Jira organization binding, such as token sharing configuration.\nRequires global administrator permission.\n", - "operationId": "饘_12", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PatchJiraOrganizationBindingEditResource" + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } } } }, - "required": true - }, - "responses": { - "201": { - "description": "Created", + "403": { + "description": "Forbidden", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/JiraOrganizationBindingResource" + "$ref": "#/components/schemas/Error" } } } - } - }, - "x-sonar-internal": "true" - } - }, - "/issues/sandbox-settings": { - "get": { - "tags": [ - "Issues" - ], - "summary": "Fetch instance sandbox settings", - "description": "Fetch the current instance-level sandbox settings.\nReturns configuration including enabled status, default value, allowOverride setting, and software qualities or rule types based on current mode.\nRequires 'Administer System' permission.\n", - "operationId": "getSandboxSettings", - "responses": { - "200": { - "description": "OK", + }, + "429": { + "description": "Too Many Requests", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboxSettingsResource" + "$ref": "#/components/schemas/Error" } } } - } - }, - "x-sonar-internal": "true" - }, - "patch": { - "tags": [ - "Issues" - ], - "summary": "Update instance sandbox settings", - "description": "Update the instance-level sandbox settings.\nCannot enable sandbox without providing software qualities or rule types unless they already exist.\nWhen disabling allowOverride, all project-level software quality overrides are deleted.\nRequires 'Administer System' permission.\n", - "operationId": "patchSandboxSettings", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboxSettingsResource" - } - } }, - "required": true - }, - "responses": { - "200": { - "description": "OK", + "500": { + "description": "Internal Server Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboxSettingsResource" + "$ref": "#/components/schemas/Error" } } } } }, - "x-sonar-internal": "true" + "security": [ + { + "lambdaAuthorizer": [] + } + ] } }, - "/issues/sandbox-settings/{projectKey}": { + "/architecture/models": { "get": { "tags": [ - "Issues" + "models" ], - "summary": "Fetch project sandbox settings", - "description": "Fetch the sandbox settings for a specific project.\nReturns effective configuration including enabled status, software qualities overrides, and whether settings are inherited from instance or overridden at project level.\nRequires 'Administer Project' permission.\n", - "operationId": "getProjectSandboxSettings", + "summary": "List models for a project", + "description": "Return all models for a project. May be empty.", + "operationId": "listModels", "parameters": [ { - "name": "projectKey", - "in": "path", + "name": "projectId", + "in": "query", + "description": "Project Id", "required": true, "schema": { "type": "string" @@ -3752,742 +4569,495 @@ ], "responses": { "200": { - "description": "OK", + "description": "Success", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboxSettingsProjectResource" + "type": "array", + "items": { + "$ref": "#/components/schemas/Model" + } + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" } } } } }, + "security": [ + { + "lambdaAuthorizer": [] + } + ], "x-sonar-internal": "true" }, - "patch": { + "post": { "tags": [ - "Issues" - ], - "summary": "Update project sandbox settings", - "description": "Update the sandbox settings for a specific project.\nThe enabled setting can be changed independently of software quality overrides.\nWhen allowOverride is disabled at instance level, software quality changes are not allowed but enabled changes are still permitted.\nSetting overridden=false removes project-level software quality overrides to inherit from instance settings.\nRequires 'Administer Project' permission and instance sandbox must be enabled.\n", - "operationId": "patchProjectSandboxSettings", - "parameters": [ - { - "name": "projectKey", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } + "models" ], + "summary": "Create a model", + "description": "Create a model and return it including the generated id", + "operationId": "createModel", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboxSettingsProjectResource" + "$ref": "#/components/schemas/PostModel" } } }, "required": true }, "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboxSettingsProjectResource" + "$ref": "#/components/schemas/Model" } } } - } - }, - "x-sonar-internal": "true" - } - }, - "/integrations/integration-configurations/{id}": { - "delete": { - "tags": [ - "integration-configurations-controller" - ], - "summary": "Delete an integration configuration and all its related data.", - "description": "Requires global administrator permission.\nThis will permanently delete the integration configuration, all associated workspaces, user bindings, and subscriptions.\n", - "operationId": "delete_1", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "ID of the integration configuration", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "204": { - "description": "No Content" - } - }, - "x-sonar-internal": "true" - }, - "patch": { - "tags": [ - "integration-configurations-controller" - ], - "summary": "Update an integration configuration.", - "description": "Requires global administrator permission.", - "operationId": "update_2", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "ID of the integration configuration", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/IntegrationConfigurationPatchRequest" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } } } }, - "required": true - }, - "responses": { - "200": { - "description": "OK", + "401": { + "description": "Unauthorized", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IntegrationConfigurationResponse" + "$ref": "#/components/schemas/Error" } } } - } - }, - "x-sonar-internal": "true" - } - }, - "/fix-suggestions/feature-enablements": { - "get": { - "tags": [ - "Fix Suggestions" - ], - "summary": "Fetch the Ai CodeFix feature enablement configurations", - "operationId": "getFeatureEnablement_1", - "responses": { - "200": { - "description": "OK", + }, + "403": { + "description": "Forbidden", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FeatureEnablementResponse" + "$ref": "#/components/schemas/Error" } } } - } - }, - "x-sonar-internal": "true" - }, - "patch": { - "tags": [ - "Fix Suggestions" - ], - "summary": "Enable/Disable the AI CodeFix feature at the instance or project level", - "operationId": "updateFeatureEnablement_1", - "requestBody": { - "content": { - "application/merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/FixSuggestionsFeatureEnablementRequest" + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } } } }, - "required": true - }, - "responses": { - "204": { - "description": "No Content" - } - }, - "x-sonar-internal": "true" - } - }, - "/entitlements/license": { - "get": { - "tags": [ - "Entitlements" - ], - "summary": "Returns information about the license", - "description": " Returns information about the current license.\n Requires 'Administer System' permission.\n", - "operationId": "getLicense", - "responses": { - "200": { - "description": "OK", + "500": { + "description": "Internal Server Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/LicenseRestResponse" + "$ref": "#/components/schemas/Error" } } } } }, - "x-sonar-internal": "true" - }, - "delete": { - "tags": [ - "Entitlements" - ], - "summary": "Deletes the existing license", - "description": " Deletes the license. Both license keys received from Sonar and from 3rd party system can be removed.\n Requires 'Administer System' permission.\n", - "operationId": "deleteLicense", - "responses": { - "200": { - "description": "OK" + "security": [ + { + "lambdaAuthorizer": [] } - }, - "x-sonar-internal": "true" - }, - "patch": { - "tags": [ - "Entitlements" ], - "summary": "Refreshes the existing license", - "description": " Fetches the latest information about the license and updates it on the SonarQube Server instance.\n Requires 'Administer System' permission.\n", - "operationId": "refreshLicense", - "responses": { - "200": { - "description": "OK" - } - }, "x-sonar-internal": "true" } }, - "/dop-translation/gitlab-permission-mappings/{role}": { - "delete": { + "/architecture/directives": { + "get": { "tags": [ - "Dop Translation" + "directives" ], - "summary": "Delete a single permission mappings", - "description": "Requires 'Administer System' permission.", - "operationId": "deleteMapping", + "summary": "List directives for a project", + "description": "Return all directives for a project sorted by dateCreated ascending (oldest first). May be empty.", + "operationId": "directivesGet", "parameters": [ { - "name": "role", - "in": "path", - "description": "The name of the role to delete", + "name": "projectId", + "in": "query", + "description": "Project Id", "required": true, "schema": { "type": "string" } - } - ], - "responses": { - "204": { - "description": "No Content" - } - }, - "x-sonar-internal": "true" - }, - "patch": { - "tags": [ - "Dop Translation" - ], - "summary": "Update a single permission mapping", - "description": "Requires 'Administer System' permission.", - "operationId": "updateMapping", - "parameters": [ + }, { - "name": "role", - "in": "path", - "description": "The name of the role to update", - "required": true, + "name": "pageIndex", + "in": "query", + "description": "Page number for pagination (1-based). Specifies which page of results to return.", + "required": false, "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/DevOpsPermissionMappingUpdateRequest" - } + "type": "integer", + "format": "int32", + "default": 1, + "minimum": 1 } }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PermissionMappingsResource" - } - } - } - } - }, - "x-sonar-internal": "true" - } - }, - "/dop-translation/gitlab-configurations/{id}": { - "get": { - "tags": [ - "Dop Translation" - ], - "summary": "Fetch a GitLab configuration", - "description": "Fetch a GitLab configuration. Requires authentication. System administrators receive the full\nconfiguration; other logged-in users receive a reduced response with only the provisioning status.\n", - "operationId": "getGitlabConfiguration", - "parameters": [ { - "name": "id", - "in": "path", - "description": "The id of the configuration to fetch.", - "required": true, + "name": "pageSize", + "in": "query", + "description": "Number of items per page. Set to 0 to return only the total count without any items (useful for counting).", + "required": false, "schema": { - "type": "string" + "type": "integer", + "format": "int32", + "default": 50, + "minimum": 0 } } ], "responses": { "200": { - "description": "OK", + "description": "Success", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GitlabConfigurationRestResponseForAdmins" + "$ref": "#/components/schemas/DirectivePagedResponse" } } } - } - }, - "x-sonar-internal": "true" - }, - "delete": { - "tags": [ - "Dop Translation" - ], - "summary": "Delete a GitLab configuration", - "description": "Delete a GitLab configuration.\nRequires 'Administer System' permission.\n", - "operationId": "deleteGitlabConfiguration", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "The id of the configuration to delete.", - "required": true, - "schema": { - "type": "string" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } - } - ], - "responses": { - "204": { - "description": "No Content" - } - }, - "x-sonar-internal": "true" - }, - "patch": { - "tags": [ - "Dop Translation" - ], - "summary": "Update a Gitlab configuration", - "description": "Update a Gitlab configuration. Requires 'Administer System' permission.\n", - "operationId": "updateGitlabConfiguration", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } - } - ], - "requestBody": { - "content": { - "application/merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/GitlabConfigurationUpdateRestRequest" + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } } } }, - "required": true - }, - "responses": { - "200": { - "description": "OK", + "429": { + "description": "Too Many Requests", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GitlabConfigurationRestResponseForAdmins" + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" } } } } }, - "x-sonar-internal": "true" - } - }, - "/dop-translation/github-permission-mappings/{role}": { - "delete": { - "tags": [ - "Dop Translation" - ], - "summary": "Delete a single permission mappings", - "description": "Requires 'Administer System' permission.", - "operationId": "deleteMapping_1", - "parameters": [ + "security": [ { - "name": "role", - "in": "path", - "description": "The name of the role to delete", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "No Content" + "lambdaAuthorizer": [] } - }, - "x-sonar-internal": "true" + ] }, - "patch": { + "post": { "tags": [ - "Dop Translation" - ], - "summary": "Update a single permission mapping", - "description": "Requires 'Administer System' permission.", - "operationId": "updateMapping_1", - "parameters": [ - { - "name": "role", - "in": "path", - "description": "The name of the role to update", - "required": true, - "schema": { - "type": "string" - } - } + "directives" ], + "summary": "Store a directive", + "description": "Store a directive", + "operationId": "directivesPost", "requestBody": { "content": { - "application/merge-patch+json": { + "application/json": { "schema": { - "$ref": "#/components/schemas/DevOpsPermissionMappingUpdateRequest" + "$ref": "#/components/schemas/PostDirectiveBody" } } }, "required": true }, "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PermissionMappingsResource" + "$ref": "#/components/schemas/Directive" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" } } } } }, - "x-sonar-internal": "true" + "security": [ + { + "lambdaAuthorizer": [] + } + ] } }, - "/dop-translation/github-configurations/{id}": { + "/architecture/boundary-descriptors": { "get": { "tags": [ - "Dop Translation" + "boundary-descriptors" ], - "summary": "Fetch a GitHub configuration", - "description": "Fetch a GitHub configuration. Requires 'Administer System' permission.\n", - "operationId": "getGithubConfiguration", + "summary": "List boundary descriptors for a project", + "description": "Return all boundary descriptors for a project sorted by their uuid. May be empty.", + "operationId": "boundaryDescriptorsGet", "parameters": [ { - "name": "id", - "in": "path", - "description": "The id of the configuration to fetch.", + "name": "projectId", + "in": "query", + "description": "Project Id", "required": true, "schema": { "type": "string" } + }, + { + "name": "pageIndex", + "in": "query", + "description": "Page number for pagination (1-based). Specifies which page of results to return.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "minimum": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Number of items per page. Set to 0 to return only the total count without any items (useful for counting).", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "minimum": 0 + } } ], "responses": { "200": { - "description": "OK", + "description": "Success", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GithubConfigurationResource" + "$ref": "#/components/schemas/BoundaryDescriptorPagedResponse" } } } - } - }, - "x-sonar-internal": "true" - }, - "delete": { - "tags": [ - "Dop Translation" - ], - "summary": "Delete a GitHub configuration", - "description": "Delete a GitHub configuration.\nRequires 'Administer System' permission.\n", - "operationId": "deleteGithubConfiguration", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "The id of the configuration to delete.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "No Content" - } - }, - "x-sonar-internal": "true" - }, - "patch": { - "tags": [ - "Dop Translation" - ], - "summary": "Update a GitHub configuration", - "description": "Update a GitHub configuration. Requires 'Administer System' permission.\n", - "operationId": "updateGithubConfiguration", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/GithubConfigurationUpdateRestRequest" - } - } }, - "required": true - }, - "responses": { - "200": { - "description": "OK", + "400": { + "description": "Bad Request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GithubConfigurationResource" + "$ref": "#/components/schemas/Error" } } } - } - }, - "x-sonar-internal": "true" - } - }, - "/clean-code-policy/mode": { - "get": { - "tags": [ - "Clean Code Policy" - ], - "summary": "Retrieve current instance Mode", - "description": "Fetch the current instance mode. Can be Multi-Quality Rules (MQR) Mode or Standard Experience.\n", - "operationId": "getMode", - "responses": { - "200": { - "description": "OK", + }, + "401": { + "description": "Unauthorized", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ModeResource" + "$ref": "#/components/schemas/Error" } } } - } - }, - "x-sonar-internal": "true" - }, - "patch": { - "tags": [ - "Clean Code Policy" - ], - "summary": "Update current instance Mode", - "description": "Update the current instance mode. Can be Multi-Quality Rules (MQR) Mode or Standard Experience.\nRequires 'Administer System' permission.\n", - "operationId": "patchMode", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ModeResource" + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } } } }, - "required": true - }, - "responses": { - "200": { - "description": "OK", + "429": { + "description": "Too Many Requests", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ModeResource" + "$ref": "#/components/schemas/Error" } } } - } - }, - "x-sonar-internal": "true" - } - }, - "/authorizations/groups/{id}": { - "get": { - "tags": [ - "Authorizations" - ], - "summary": "Fetch a single group", - "description": "Fetch a single group.", - "operationId": "fetchGroup", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "The id of the group to fetch.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", + }, + "500": { + "description": "Internal Server Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GroupRestResponse" + "$ref": "#/components/schemas/Error" } } } } - } - }, - "delete": { - "tags": [ - "Authorizations" - ], - "summary": "Deletes a group", - "description": "Deletes a group.", - "operationId": "deleteGroup", - "parameters": [ + }, + "security": [ { - "name": "id", - "in": "path", - "description": "The ID of the group to delete.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "No Content" + "lambdaAuthorizer": [] } - } + ] }, - "patch": { + "post": { "tags": [ - "Authorizations" - ], - "summary": "Update a group", - "description": "Update a group name or description.\n", - "operationId": "updateGroup", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } + "boundary-descriptors" ], + "summary": "Store a boundary descriptor", + "description": "Store a boundary descriptor", + "operationId": "boundaryDescriptorsPost", "requestBody": { "content": { - "application/merge-patch+json": { + "application/json": { "schema": { - "$ref": "#/components/schemas/GroupUpdateRestRequest" + "$ref": "#/components/schemas/PostBoundaryDescriptorBody" } } }, "required": true }, "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GroupRestResponse" - } - } - } - } - } - } - }, - "/architecture/models/{id}": { - "get": { - "tags": [ - "models" - ], - "summary": "Get a model by id", - "description": "Return a single model by id", - "operationId": "getModel", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "200": { - "description": "Success", + "201": { + "description": "Created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Model" + "$ref": "#/components/schemas/BoundaryDescriptor" } } } @@ -4497,7 +5067,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -4507,7 +5077,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -4517,17 +5087,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -4537,7 +5097,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -4547,7 +5107,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -4557,67 +5117,64 @@ { "lambdaAuthorizer": [] } - ], - "x-sonar-internal": "true" - }, - "delete": { + ] + } + }, + "/architecture/analysis": { + "post": { "tags": [ - "models" + "analysis" ], - "summary": "Delete a model", - "description": "Delete a model by id", - "operationId": "deleteModel", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" + "summary": "Store analysis data", + "description": "Store analysis data. External endpoint used by scanner clients.", + "operationId": "analysisPost", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PostAnalysisBody" + } } - } - ], - "responses": { - "204": { - "description": "Deleted successfully" }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { + "required": true + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "type": "string" } } } }, - "401": { - "description": "Unauthorized", + "400": { + "description": "Bad Request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } }, - "403": { - "description": "Forbidden", + "401": { + "description": "Unauthorized", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } }, - "404": { - "description": "Not Found", + "403": { + "description": "Forbidden", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -4627,7 +5184,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -4637,7 +5194,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -4647,44 +5204,33 @@ { "lambdaAuthorizer": [] } - ], - "x-sonar-internal": "true" - }, - "patch": { + ] + } + }, + "/a3s/private/analyses": { + "post": { "tags": [ - "models" - ], - "summary": "Update a model", - "description": "Update model data. Only the 'data' field can be updated.", - "operationId": "updateModel", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } + "Analysis" ], + "summary": "Create a new analysis", + "operationId": "createPrivateAnalysis", "requestBody": { "content": { - "application/merge-patch+json": { + "application/json": { "schema": { - "$ref": "#/components/schemas/PatchModel" + "$ref": "#/components/schemas/PrivateAnalysisCreationRequest" } } }, "required": true }, "responses": { - "200": { - "description": "Success", + "201": { + "description": "Analysis created successfully", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Model" + "$ref": "#/components/schemas/Analysis" } } } @@ -4694,7 +5240,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -4704,7 +5250,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -4714,7 +5260,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -4724,312 +5270,268 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } }, - "429": { - "description": "Too Many Requests", + "413": { + "description": "Payload Too Large", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/PayloadTooLargeError" } } } }, - "500": { - "description": "Internal Server Error", + "429": { + "description": "Too Many Requests", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } - } - }, - "security": [ - { - "lambdaAuthorizer": [] - } - ], - "x-sonar-internal": "true" - } - }, - "/system/migrations-status": { - "get": { - "tags": [ - "System" - ], - "summary": "Gets the status of ongoing database migrations, if any", - "description": "Return the detailed status of ongoing database migrations including starting date. If no migration is ongoing or needed it is still possible to call this endpoint and receive appropriate information.", - "operationId": "getStatus", - "responses": { - "200": { - "description": "OK", + }, + "500": { + "description": "Internal Server Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DatabaseMigrationsResponse" + "$ref": "#/components/schemas/Error" } } } - } - } - } - }, - "/system/liveness": { - "get": { - "tags": [ - "System" - ], - "summary": "Provide liveness of SonarQube, meant to be used as a liveness probe on Kubernetes", - "description": " Require 'Administer System' permission or authentication with passcode.\n\n When SonarQube is fully started, liveness check for database connectivity, Compute Engine status, and, except for DataCenter Edition, if ElasticSearch is Green or Yellow.\n\n When SonarQube is on Safe Mode (for example when a database migration is running), liveness check only for database connectivity\n", - "operationId": "livenessCheck", - "parameters": [ - { - "name": "X-Sonar-Passcode", - "in": "header", - "description": "Passcode can be provided, see SonarQube documentation", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "This SonarQube node is alive" }, - "default": { - "description": "This SonarQube node is not alive and should be rescheduled" - } - } - } - }, - "/system/health": { - "get": { - "tags": [ - "System" - ], - "operationId": "getHealth", - "parameters": [ - { - "name": "X-Sonar-Passcode", - "in": "header", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", + "503": { + "description": "Service Unavailable", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Health" + "$ref": "#/components/schemas/Error" } } } } - } + }, + "security": [ + { + "SigV4Auth": [] + } + ] } }, - "/software-quality-reports/accessibility-reports": { + "/a3s/private/a3s-frontend-context/contexts": { "get": { "tags": [ - "software-quality-reports-controller" + "Private Contexts" ], - "summary": "Get accessibility report", - "description": "Get the accessibility report for a project branch.", - "operationId": "饘_13", + "summary": "Search for contexts", + "operationId": "searchContexts", "parameters": [ { - "name": "branchKey", + "name": "branchId", "in": "query", - "description": "Branch key. If not provided, the main branch is used.", + "description": "The identifier of a branch on SonarQube (a UUID on SonarQube Cloud, possibly a legacy identifier on SonarQube Server). Must specify either branchId or branchName, but not both.", "required": false, "schema": { "type": [ "string", "null" ] - }, - "example": "my-branch" - }, - { - "name": "projectKey", - "in": "query", - "description": "Project key.", - "required": true, - "schema": { - "type": "string" - }, - "example": "my-project" + } }, { - "name": "standard", + "name": "branchName", "in": "query", - "description": "Accessibility standard.", - "required": true, + "description": "The name of a branch. Must specify either branchId or branchName, but not both. When branchName is specified, projectId is required.", + "required": false, "schema": { - "type": "string", - "enum": [ - "wcag" - ] - }, - "example": "wcag" + "type": [ + "string", + "null" + ], + "maxLength": 2147483647, + "minLength": 1 + } }, { - "name": "version", + "name": "projectId", "in": "query", - "description": "Accessibility standard version.", - "required": true, + "description": "The identifier of a project (a UUID on SonarQube Cloud, possibly a legacy identifier on SonarQube Server). Required when branchName is specified to disambiguate branches with the same name across different projects.", + "required": false, "schema": { - "type": "string", - "enum": [ - "2.0", - "2.1", - "2.2" + "type": [ + "string", + "null" ] - }, - "example": 2.1 + } } ], "responses": { "200": { - "description": "OK", + "description": "Successful operation", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetAccessibilityReportResponse" + "$ref": "#/components/schemas/Contexts" } } } - } - }, - "x-sonar-internal": "true" - } - }, - "/sca/self-test": { - "get": { - "tags": [ - "Self Test" - ], - "summary": "Run a self test", - "description": "Run a self test that the dependency analysis (SCA) feature is enabled and\nable to reach the cloud services it relies on.\n\nThis is an internal API and is subject to change without notice.\n", - "operationId": "performSelfTest", - "responses": { - "200": { - "description": "OK", + }, + "400": { + "description": "Bad Request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SelfTestResponse" + "$ref": "#/components/schemas/Error" } } } - } - }, - "security": [ - { - "bearerAuth": [] - } - ], - "x-sonar-internal": "true" - } - }, - "/sca/sbom-reports": { - "get": { - "tags": [ - "SBOM Reports" - ], - "summary": "Get a software bill of materials (SBOM) report", - "description": "Return a report based on the dependencies in this component's branch, using\nthe type parameter and Accept header to select the report to generate.\n\nRight now, the available reports have specialized MIME types that\ngo along with those formats:\n\n* CycloneDX: https://cyclonedx.org/specification/overview/\n * JSON & XML\n* SPDX 2.3: https://spdx.github.io/spdx-spec/v2.3/\n * JSON & XML\n* SPDX 3.0: https://spdx.github.io/spdx-spec/v3.0.1/\n * JSON\n", - "operationId": "generateReport", - "parameters": [ - { - "name": "component", - "in": "query", - "description": "Key of the component (project, application, portfolio) to build report for", - "required": true, - "schema": { - "type": "string", - "description": "Key of the component (project, application, portfolio) to build report for" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } }, - { - "name": "branch", - "in": "query", - "description": "Key of the branch to build report for. If not provided, the default branch will be used unless a pull request key is provided.", - "required": false, - "schema": { - "type": "string", - "description": "Key of the branch to build report for. If not provided, the default branch will be used unless a pull request key is provided." + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } }, - { - "name": "pullRequestKey", - "in": "query", - "description": "Key of the pull request to build report for.", - "required": false, - "schema": { - "type": "string", - "description": "Key of the pull request to build report for." + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } }, - { - "name": "type", - "in": "query", - "description": "Type of report to generate.\nThe `Accept` header sent by the client determines the format of the report.\nCurrently supported: cyclonedx (application/vnd.cyclonedx+json), cyclonedx (application/vnd.cyclonedx+xml),\nspdx_23 (application/spdx+json), spdx_23 (application/spdx+xml),\nspdx_30 (application/spdx+json)\n", - "required": true, - "schema": { - "type": "string", - "description": "Type of report to generate.\nThe `Accept` header sent by the client determines the format of the report.\nCurrently supported: cyclonedx (application/vnd.cyclonedx+json), cyclonedx (application/vnd.cyclonedx+xml),\nspdx_23 (application/spdx+json), spdx_23 (application/spdx+xml),\nspdx_30 (application/spdx+json)\n", - "enum": [ - "cyclonedx", - "spdx_23", - "spdx_30" - ] + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } }, - { - "name": "onlyProductionScope", - "in": "query", - "description": "If false, include all dependencies. Default (true/absent) filters to production dependencies only.", - "required": false, - "schema": { - "type": "boolean", - "description": "If false, include all dependencies. Default (true/absent) filters to production dependencies only." + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } } + }, + "security": [ + { + "SigV4Auth": [] + } + ], + "x-sonar-internal": "true" + }, + "post": { + "tags": [ + "Private Contexts" ], + "summary": "Create a new context", + "operationId": "createPrivateContext", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContextCreationRequest" + } + } + }, + "required": true + }, "responses": { - "200": { - "description": "The desired report in the desired output format", + "201": { + "description": "Context created successfully", "content": { - "application/vnd.cyclonedx+json": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/ContextWithUploadUrl" } - }, - "application/vnd.cyclonedx+xml": { + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/Error" } - }, - "application/spdx+json": { + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/Error" } - }, - "application/spdx+xml": { + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" } } } @@ -5037,85 +5539,96 @@ }, "security": [ { - "bearerAuth": [] + "SigV4Auth": [] } ], - "x-sonar-public": "true" + "x-sonar-internal": "true" } }, - "/sca/risk-reports": { - "get": { + "/a3s/contexts": { + "post": { "tags": [ - "Risk Reports" + "Public Contexts" ], - "summary": "Get a report for all the current SCA dependency risks for a given component and branch", - "description": "Returns list of all risks for a given component and branch. CSV format is available by setting `Accept: text/csv` in your request headers. VEX (CycloneDX 1.6) format is available by setting `Accept: application/vnd.cyclonedx+json`.\n", - "operationId": "getReport", - "parameters": [ - { - "name": "component", - "in": "query", - "description": "Key of the component (project, application, portfolio) to build report for", - "required": true, - "schema": { - "type": "string", - "description": "Key of the component (project, application, portfolio) to build report for" + "summary": "Create a new context", + "operationId": "createPublicContext", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContextCreationRequest" + } } }, - { - "name": "branch", - "in": "query", - "description": "Key of the branch to build report for. If not provided, the default branch will be used unless a pull request key is provided.", - "required": false, - "schema": { - "type": "string", - "description": "Key of the branch to build report for. If not provided, the default branch will be used unless a pull request key is provided." + "required": true + }, + "responses": { + "201": { + "description": "Context created successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContextWithUploadUrl" + } + } } }, - { - "name": "pullRequestKey", - "in": "query", - "description": "Key of the pull request to build report for.", - "required": false, - "schema": { - "type": "string", - "description": "Key of the pull request to build report for." + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } }, - { - "name": "riskType", - "in": "query", - "required": false, - "schema": { - "type": "array", - "description": "Types of risk to filter the report by. If not provided, all risk types are included.", - "items": { - "type": "string" - }, - "uniqueItems": true + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } - } - ], - "responses": { - "200": { - "description": "The report in the desired output format", + }, + "403": { + "description": "Forbidden", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/RiskReportItem" - } + "$ref": "#/components/schemas/Error" } - }, - "text/csv": { + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/Error" } - }, - "application/vnd.cyclonedx+json": { + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" } } } @@ -5123,140 +5636,133 @@ }, "security": [ { - "bearerAuth": [] + "TokenAuth": [] } ], - "x-sonar-public": "true" + "x-sonar-internal": "true" } }, - "/sca/releases": { - "get": { + "/a3s/analyses": { + "post": { "tags": [ - "Releases" + "Analysis" ], - "summary": "Search for releases", - "description": "Search for package releases that appear in the analyzed project,\napplication, or portfolio, as determined by software composition analysis.\n\nThis endpoint returns one result for each release,\nrather than one result for each time a release is pulled in.\nEach result may appear in multiple files or scopes that\nall use the same version of the affected package.\n\nThe search results do not include full details on the dependencies\nthat pull in each release, only a summary. To get the full dependency\ndetails, use the /releases/{key} endpoint for a single release.\n\nIn the terminology of this endpoint, a \"release\" is a version of\na package like \"lodash 1.2.3\", and a \"dependency\" is a specific file\nand scope that pulls in the release such as \"subproject/pom.xml test\".\nEach returned release may have multiple dependencies.\n\nThis API is subject to change without notice.\n", - "operationId": "search_3", + "summary": "Create a new analysis", + "operationId": "createPublicAnalysis", "parameters": [ { - "name": "projectKey", - "in": "query", - "description": "Key of the component (project, application, or portfolio) to fetch all dependencies from", - "required": true, - "schema": { - "type": "string", - "description": "Key of the component (project, application, or portfolio) to fetch all dependencies from" - } - }, - { - "name": "branchKey", - "in": "query", - "description": "Key of the branch to fetch all dependencies from. If not provided, the default branch will be used unless a pull request key is provided.", + "name": "X-Sonar-Invocation-Id", + "in": "header", + "description": "Identifier of the sonarqube-cli invocation that triggered this analysis request, generated client-side. Reported back on every telemetry event produced for this analysis. ", "required": false, "schema": { - "type": "string", - "description": "Key of the branch to fetch all dependencies from. If not provided, the default branch will be used unless a pull request key is provided." + "type": [ + "string", + "null" + ], + "maxLength": 40, + "minLength": 0, + "pattern": "^\\S+$" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicAnalysisCreationRequest" + } } }, - { - "name": "pullRequestKey", - "in": "query", - "description": "Key of the pull request to fetch all dependencies from.", - "required": false, - "schema": { - "type": "string", - "description": "Key of the pull request to fetch all dependencies from." + "required": true + }, + "responses": { + "201": { + "description": "Analysis created successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Analysis" + } + } } }, - { - "name": "direct", - "in": "query", - "description": "Filter on the direct attribute. TRUE stands for direct dependencies, FALSE for transitive dependencies.", - "required": false, - "schema": { - "type": "boolean", - "description": "Filter on the direct attribute. TRUE stands for direct dependencies, FALSE for transitive dependencies." + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } }, - { - "name": "newlyIntroduced", - "in": "query", - "description": "Filter on the newlyIntroduced attribute. TRUE stands for only releases introduced vs. the target branch, FALSE for releases shared with target.", - "required": false, - "schema": { - "type": "boolean", - "description": "Filter on the newlyIntroduced attribute. TRUE stands for only releases introduced vs. the target branch, FALSE for releases shared with target." + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } }, - { - "name": "productionScope", - "in": "query", - "description": "Filter on the productionScope attribute. TRUE stands for only production dependencies, FALSE for only not in production. If a release is both, it matches both.", - "required": false, - "schema": { - "type": "boolean", - "description": "Filter on the productionScope attribute. TRUE stands for only production dependencies, FALSE for only not in production. If a release is both, it matches both." + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } }, - { - "name": "packageManagers", - "in": "query", - "required": false, - "schema": { - "type": "array", - "description": "Filter on the package manager", - "items": { - "type": "string" - }, - "uniqueItems": true + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } }, - { - "name": "q", - "in": "query", - "description": "Filter on the package name. This parameter performs a partial match (contains and case insensitive) on the package name.", - "required": false, - "schema": { - "type": "string", - "description": "Filter on the package name. This parameter performs a partial match (contains and case insensitive) on the package name." + "413": { + "description": "Payload Too Large", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeError" + } + } } }, - { - "name": "pageSize", - "in": "query", - "description": "Number of results per page. A value of 0 will only return the pagination information.", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 50, - "description": "Number of results per page. A value of 0 will only return the pagination information.", - "maximum": 500, - "minimum": 0 + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } }, - { - "name": "pageIndex", - "in": "query", - "description": "1-based page index", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 1, - "description": "1-based page index", - "exclusiveMinimum": 0, - "minimum": 1 + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } - } - ], - "responses": { - "200": { - "description": "OK", + }, + "503": { + "description": "Service Unavailable", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ReleasesSearchRestResponse" + "$ref": "#/components/schemas/Error" } } } @@ -5264,25 +5770,25 @@ }, "security": [ { - "bearerAuth": [] + "TokenAuth": [] } ], - "x-sonar-internal": true + "x-sonar-internal": "true" } }, - "/sca/releases/{key}": { + "/users-management/users/{id}": { "get": { "tags": [ - "Releases" + "Users Management" ], - "summary": "Get a single release", - "description": "Fetch a single release by its key.\n\nThis single-release endpoint lists full details for all dependencies\nthat mention the release.\n\nIn the terminology of this endpoint, a \"release\" is a version of\na package like \"lodash 1.2.3\", and a \"dependency\" is a specific file\nand scope that pulls in the release such as \"subproject/pom.xml test\".\nEach returned release may have multiple dependencies.\n\nThis API is subject to change without notice.\n", - "operationId": "fetchRelease", + "summary": "Fetch a single user", + "description": "Fetch a single user.\nThe following fields are only returned when user has Administer System permission or for logged-in in user :\n 'email'\n 'externalIdentity'\n 'externalProvider'\n 'groups'\n 'lastConnectionDate'\n 'sonarLintLastConnectionDate'\n 'tokensCount'\n Field 'sonarqubeLastConnectionDate' is only updated every hour, so it may not be accurate, for instance when a user authenticates many times in less than one hour.\n", + "operationId": "fetchUser", "parameters": [ { - "name": "key", + "name": "id", "in": "path", - "description": "The key of the release to fetch.", + "description": "The id of the user to fetch.", "required": true, "schema": { "type": "string" @@ -5295,222 +5801,204 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ReleaseDetailResource" + "$ref": "#/components/schemas/UserRestResponseForAdmins" } } } } - }, - "security": [ - { - "bearerAuth": [] - } - ], - "x-sonar-internal": true - } - }, - "/sca/releases/search": { - "get": { + } + }, + "delete": { "tags": [ - "Releases" + "Users Management" ], - "summary": "Search for package releases across an organization", - "description": "Search for all analyzed branches across all projects in an organization that contain a specific\npackage, identified by its Package URL (PURL).\n\nThe PURL may include a version for an exact match (e.g. pkg:npm/lodash@4.17.21), or omit the\nversion to find all versions of the package in use (e.g. pkg:npm/lodash).\n\nReturns one result per branch that contains a matching release, including feature branches and\npull requests. Each result includes the exact packageUrl found on that branch.\n\nThis is an internal API and is subject to change without notice.\n", - "operationId": "search_4", + "summary": "Deactivate a user", + "description": "Deactivates a user. Requires Administer System permission.", + "operationId": "deactivate", "parameters": [ { - "name": "purl", - "in": "query", - "description": "Package URL (PURL) to search for. May include a version for exact matching (e.g. pkg:npm/lodash@4.17.21) or omit the version to find all versions in use (e.g. pkg:npm/lodash).", + "name": "id", + "in": "path", + "description": "The ID of the user to delete.", "required": true, "schema": { - "type": "string", - "description": "Package URL (PURL) to search for. May include a version for exact matching (e.g. pkg:npm/lodash@4.17.21) or omit the version to find all versions in use (e.g. pkg:npm/lodash).", - "minLength": 1 - } - }, - { - "name": "pageSize", - "in": "query", - "description": "Number of results per page. A value of 0 will only return the pagination information.", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 50, - "description": "Number of results per page. A value of 0 will only return the pagination information.", - "maximum": 500, - "minimum": 0 + "type": "string" } }, { - "name": "pageIndex", + "name": "anonymize", "in": "query", - "description": "1-based page index", + "description": "Anonymize user in addition to deactivating it.", "required": false, "schema": { - "type": "integer", - "format": "int32", - "default": 1, - "description": "1-based page index", - "exclusiveMinimum": 0, - "minimum": 1 + "type": "boolean", + "default": false } } ], "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { + "204": { + "description": "No Content" + } + } + }, + "patch": { + "tags": [ + "Users Management" + ], + "summary": "Update a user", + "description": "Update users attributes.\n", + "operationId": "updateUser", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/UserUpdateRestRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { "schema": { - "$ref": "#/components/schemas/ReleaseSearchRestResponse" + "$ref": "#/components/schemas/UserRestResponseForAdmins" } } } } - }, - "security": [ - { - "bearerAuth": [] - } - ], - "x-sonar-internal": "true" + } } }, - "/sca/reachability/list-definitions": { + "/system/email-configurations/{id}": { "get": { "tags": [ - "Reachability Definitions" + "System" ], - "summary": "Get reachability definitions", - "description": "Returns the encrypted reachability definitions for the specified language.\n", - "operationId": "listDefinitions", + "summary": "Fetch an email configuration", + "description": "Fetch a Email configuration. Requires 'Administer System' permission.\n", + "operationId": "getEmailConfiguration", "parameters": [ { - "name": "languageKey", - "in": "query", + "name": "id", + "in": "path", + "description": "The id of the configuration to fetch.", "required": true, "schema": { "type": "string" } - }, + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailConfigurationResource" + } + } + } + } + }, + "x-sonar-internal": "true" + }, + "delete": { + "tags": [ + "System" + ], + "summary": "Delete an email configuration", + "description": "Delete an email configuration.\nRequires 'Administer System' permission.\n", + "operationId": "deleteEmailConfiguration", + "parameters": [ { - "name": "projectKey", - "in": "query", - "required": false, + "name": "id", + "in": "path", + "description": "The id of the configuration to delete.", + "required": true, "schema": { "type": "string" } - }, + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "x-sonar-internal": "true" + }, + "patch": { + "tags": [ + "System" + ], + "summary": "Update an email configuration", + "description": "Update an email configuration. Requires 'Administer System' permission.\n", + "operationId": "updateEmailConfiguration", + "parameters": [ { - "name": "organization", - "in": "query", - "description": "Organization key. Use 'projectKey' instead.", - "required": false, - "deprecated": true, + "name": "id", + "in": "path", + "required": true, "schema": { - "type": "string", - "description": "Organization key. Use 'projectKey' instead." + "type": "string" } } ], + "requestBody": { + "content": { + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/EmailConfigurationUpdateRestRequest" + } + } + }, + "required": true + }, "responses": { "200": { "description": "OK", "content": { - "application/octet-stream": { + "application/json": { "schema": { - "type": "string", - "format": "byte" + "$ref": "#/components/schemas/EmailConfigurationResource" } } } } }, - "security": [ - { - "bearerAuth": [] - } - ], "x-sonar-internal": "true" } }, - "/sca/license-profiles/assignable-projects": { + "/sca/license-profiles/{license-profile-key}": { "get": { "tags": [ "License Profiles" ], - "summary": "License profile assignable projects", - "description": " List the projects that can be assigned to the license profile. Assigning the\n project to a license profile will cause that license profile to be used when\n analyzing the project for license issues.\n", - "operationId": "index_1", + "summary": "Get the license policy for a given profile", + "description": "Get the license policy for a given profile.\n", + "operationId": "get_1", "parameters": [ { - "name": "licenseProfileUuid", - "in": "query", - "description": "(Deprecated) The key of the license profile whose assignable projects should be retrieved.\n", - "required": false, - "schema": { - "type": "string", - "description": "(Deprecated) The key of the license profile whose assignable projects should be retrieved.\n" - } - }, - { - "name": "licenseProfileId", - "in": "query", - "description": "The key of the license profile whose assignable projects should be retrieved.\n", - "required": false, - "schema": { - "type": "string", - "description": "The key of the license profile whose assignable projects should be retrieved.\n" - } - }, - { - "name": "assignedToLicenseProfile", - "in": "query", - "description": " Providing this parameter filters by whether projects are assigned to the license profile or not.\n Omitting this parameter includes all projects which can be assigned to the license profile,\n regardless of whether they are already assigned to the license profile.\n", - "required": false, - "schema": { - "type": "boolean", - "description": " Providing this parameter filters by whether projects are assigned to the license profile or not.\n Omitting this parameter includes all projects which can be assigned to the license profile,\n regardless of whether they are already assigned to the license profile.\n" - } - }, - { - "name": "q", - "in": "query", - "description": " Providing this parameter performs a partial match (contains and case insensitive) on the project name.\n", - "required": false, - "schema": { - "type": "string", - "description": " Providing this parameter performs a partial match (contains and case insensitive) on the project name.\n" - } - }, - { - "name": "pageSize", - "in": "query", - "description": "Number of results per page. A value of 0 will only return the pagination information.", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 50, - "description": "Number of results per page. A value of 0 will only return the pagination information.", - "maximum": 500, - "minimum": 0 - } - }, - { - "name": "pageIndex", - "in": "query", - "description": "1-based page index", - "required": false, + "name": "license-profile-key", + "in": "path", + "description": "The license profile key.", + "required": true, "schema": { - "type": "integer", - "format": "int32", - "default": 1, - "description": "1-based page index", - "exclusiveMinimum": 0, - "minimum": 1 + "type": "string" } } ], @@ -5520,7 +6008,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AssignableProjectsIndexRestResponse" + "$ref": "#/components/schemas/LicenseProfileDetailsResource" } } } @@ -5531,317 +6019,281 @@ "bearerAuth": [] } ], - "x-sonar-internal": "true" - } - }, - "/sca/issues-releases": { - "get": { + "x-sonar-internal": true + }, + "delete": { "tags": [ - "Issues-Releases" + "License Profiles" ], - "summary": "Search for issue-release pairs", - "description": "Search for software composition analysis issues (dependency risks)\nof a project, paired with releases that appear in the analyzed\nproject, application, or portfolio.\n\nThis endpoint returns one result for each (issue,release) pair,\nrather than one result for each distinct issue. So for example\nif a library uses two different versions of a package and\nboth versions are affected by the same vulnerability, you will\nget two results not one.\n\nHowever each result may appear in multiple files or scopes that\nall use the same version of the affected package.\n\nIn the terminology of this endpoint, a \"release\" is a version of\na package like \"lodash 1.2.3\", an \"issue\" is a problem such as\n\"CVE-1234\", and a \"dependency\" is a specific file and scope that\npulls in the release such as \"subproject/pom.xml test\".\n\nThis API is subject to change without notice.\n", - "operationId": "search_5", + "summary": "Delete the license profile", + "description": "Delete the license profile.", + "operationId": "delete", "parameters": [ { - "name": "projectKey", - "in": "query", - "description": "Key of the component (project, application, portfolio) to fetch all dependency risks from.", + "name": "license-profile-key", + "in": "path", + "description": "The id of the license profile", "required": true, "schema": { - "type": "string", - "description": "Key of the component (project, application, portfolio) to fetch all dependency risks from." + "type": "string" } - }, + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "security": [ { - "name": "branchKey", - "in": "query", - "description": "Key of the branch to fetch all dependency risks from. If not provided, the default branch will be used unless a pull request key is provided.", - "required": false, - "schema": { - "type": "string", - "description": "Key of the branch to fetch all dependency risks from. If not provided, the default branch will be used unless a pull request key is provided." - } - }, + "bearerAuth": [] + } + ], + "x-sonar-internal": true + }, + "patch": { + "tags": [ + "License Profiles" + ], + "summary": "Update the license profile", + "description": " Update the license profile for projects to use when evaluating license issues.\n", + "operationId": "update", + "parameters": [ { - "name": "pullRequestKey", - "in": "query", - "description": "Key of the pull request to fetch all dependency risks from.", - "required": false, + "name": "license-profile-key", + "in": "path", + "description": "The id of the license profile", + "required": true, "schema": { - "type": "string", - "description": "Key of the pull request to fetch all dependency risks from." + "type": "string" } - }, - { - "name": "sort", - "in": "query", - "description": "Sort order", - "required": false, - "schema": { - "type": "string", - "default": "+identity", - "description": "Sort order", - "enum": [ - "+identity", - "-identity", - "+severity", - "-severity", - "+cvssScore", - "-cvssScore" - ] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LicenseProfilesUpdateRestRequest" + } } }, - { - "name": "packageManagers", - "in": "query", - "required": false, - "schema": { - "type": "array", - "description": "Filter on the package manager", - "items": { - "type": "string" - }, - "uniqueItems": true + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LicenseProfileResource" + } + } } - }, + } + }, + "security": [ { - "name": "types", - "in": "query", - "required": false, - "schema": { - "type": "array", - "description": "Filter on the issue type", - "items": { - "type": "string" - }, - "uniqueItems": true - } - }, + "bearerAuth": [] + } + ], + "x-sonar-internal": true + } + }, + "/sca/license-profiles/{license-profile-key}/licenses/{license-policy-id}": { + "patch": { + "tags": [ + "License Profiles" + ], + "summary": "Update the policy for a single license", + "description": " Update the policy for a single license in the license profile.\n", + "operationId": "patchLicense", + "parameters": [ { - "name": "qualities", - "in": "query", - "required": false, + "name": "license-profile-key", + "in": "path", + "description": "The license profile key.", + "required": true, "schema": { - "type": "array", - "description": "Filter on the quality domain", - "items": { - "type": "string" - }, - "uniqueItems": true + "type": "string" } }, { - "name": "severities", - "in": "query", - "required": false, + "name": "license-policy-id", + "in": "path", + "description": "The license ID.", + "required": true, "schema": { - "type": "array", - "description": "Filter on the severity", - "items": { - "type": "string" - }, - "uniqueItems": true + "type": "string" } - }, - { - "name": "statuses", - "in": "query", - "required": false, - "schema": { - "type": "array", - "description": "Filter on the status", - "items": { - "type": "string" - }, - "uniqueItems": true + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LicensePolicyLicenseUpdateRestRequest" + } } }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LicensePolicyLicenseResource" + } + } + } + } + }, + "security": [ { - "name": "packageName", - "in": "query", - "description": "Filter on the package name. This parameter performs a partial match (contains and case insensitive) on the package name.", - "required": false, + "bearerAuth": [] + } + ], + "x-sonar-internal": true + } + }, + "/sca/license-profiles/{license-profile-key}/categories/{category-key}": { + "patch": { + "tags": [ + "License Profiles" + ], + "summary": "Update the policy for a license category", + "description": " Update the policy for an entire category in the license profile.\n", + "operationId": "patchCategory", + "parameters": [ + { + "name": "license-profile-key", + "in": "path", + "description": "The license profile key.", + "required": true, "schema": { - "type": "string", - "description": "Filter on the package name. This parameter performs a partial match (contains and case insensitive) on the package name." + "type": "string" } }, { - "name": "vulnerabilityId", - "in": "query", - "description": "Filter on the vulnerability ID. This parameter performs a partial match (contains and case insensitive) on the vulnerability ID.", - "required": false, + "name": "category-key", + "in": "path", + "description": "The category key.", + "required": true, "schema": { "type": "string", - "description": "Filter on the vulnerability ID. This parameter performs a partial match (contains and case insensitive) on the vulnerability ID." + "enum": [ + "UNKNOWN", + "COPYLEFT_WEAK", + "COPYLEFT_STRONG", + "COPYLEFT_NETWORK", + "COPYLEFT_MAXIMAL", + "PERMISSIVE_STANDARD", + "PERMISSIVE_AMATEUR" + ] } - }, - { - "name": "newlyIntroduced", - "in": "query", - "description": "Filter on the isNew attribute. TRUE stands for only releases introduced vs. the target branch, FALSE for releases shared with target.", - "required": false, - "schema": { - "type": "boolean", - "description": "Filter on the isNew attribute. TRUE stands for only releases introduced vs. the target branch, FALSE for releases shared with target." + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LicenseProfileCategoryUpdateRestRequest" + } } }, - { - "name": "direct", - "in": "query", - "description": "Filter on the direct attribute. TRUE stands for only direct dependencies, FALSE for only transitive. If a release is both, it matches both.", - "required": false, - "schema": { - "type": "boolean", - "description": "Filter on the direct attribute. TRUE stands for only direct dependencies, FALSE for only transitive. If a release is both, it matches both." + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LicenseProfileCategoryResource" + } + } } - }, + } + }, + "security": [ { - "name": "productionScope", - "in": "query", - "description": "Filter on the productionScope attribute. TRUE stands for only production dependencies, FALSE for only not in production. If a release is both, it matches both.", - "required": false, - "schema": { - "type": "boolean", - "description": "Filter on the productionScope attribute. TRUE stands for only production dependencies, FALSE for only not in production. If a release is both, it matches both." - } - }, - { - "name": "assignees", - "in": "query", - "required": false, - "schema": { - "type": "array", - "description": "Filter on assignee logins.", - "items": { - "type": "string" - }, - "uniqueItems": true - } - }, - { - "name": "projects", - "in": "query", - "required": false, - "schema": { - "type": "array", - "description": "Filter on project keys.", - "items": { - "type": "string" - }, - "uniqueItems": true - } - }, - { - "name": "assigned", - "in": "query", - "description": "Filter on whether the issue is assigned or not.", - "required": false, - "schema": { - "type": "boolean", - "description": "Filter on whether the issue is assigned or not." - } - }, - { - "name": "onlyShowConfirmedReachable", - "in": "query", - "description": "Filter to only show issue releases with confirmed reachable CVE locations.", - "required": false, - "schema": { - "type": "boolean", - "description": "Filter to only show issue releases with confirmed reachable CVE locations." - } - }, - { - "name": "fixedInPullRequestKey", - "in": "query", - "description": "Key of a pull request. Filters to issues on the current branch that either do not exist on the specified PR branch or have status FIXED on it.", - "required": false, - "schema": { - "type": "string", - "description": "Key of a pull request. Filters to issues on the current branch that either do not exist on the specified PR branch or have status FIXED on it." + "bearerAuth": [] + } + ], + "x-sonar-internal": true + } + }, + "/sca/license-profiles/assigned-projects": { + "patch": { + "tags": [ + "License Profiles" + ], + "summary": "Assign project to license profile", + "description": " Configure which license profile should be used when analyzing a project for license issues.\n", + "operationId": "update_1", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssignedProjectsUpdateRestRequest" + } } }, - { - "name": "securityStandard", - "in": "query", - "description": "Security standard for CWE filtering (e.g., 'cwe', 'owaspTop10'). When provided with securityStandardVersion, expands to CWE IDs and filters issues containing those CWEs. Can be combined with securityStandardCategory for fine-grained filtering.", - "required": false, - "schema": { - "type": "string", - "description": "Security standard for CWE filtering (e.g., 'cwe', 'owaspTop10'). When provided with securityStandardVersion, expands to CWE IDs and filters issues containing those CWEs. Can be combined with securityStandardCategory for fine-grained filtering." + "required": true + }, + "responses": { + "200": { + "description": "The provided project has been assigned to the license profile.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LicenseProfileResource" + } + } } - }, + } + }, + "security": [ { - "name": "securityStandardVersion", - "in": "query", - "description": "Version of the security standard (e.g., '2024' for CWE Top 25, '2021' for OWASP Top 10). Required when using securityStandard. Ignored if securityStandard is null.", - "required": false, - "schema": { - "type": "string", - "description": "Version of the security standard (e.g., '2024' for CWE Top 25, '2021' for OWASP Top 10). Required when using securityStandard. Ignored if securityStandard is null." - } - }, + "bearerAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/sca/issues-releases/{key}/changelog": { + "delete": { + "tags": [ + "Issues-Releases" + ], + "summary": "Delete a comment from an issue-release pair", + "description": "Delete a comment from a (issue,release) pair (dependency risk).\n\nThis API is subject to change without notice.\n", + "operationId": "deleteComment", + "parameters": [ { - "name": "securityStandardCategory", - "in": "query", - "description": "Category within the security standard (e.g., 'cwe-119' for CWE Top 25, 'a6' for OWASP Top 10). If null, all CWE IDs for the given standard and version are included. Ignored if securityStandard is null.", - "required": false, + "name": "key", + "in": "path", + "description": "The key of the (issue,release) pair.", + "required": true, "schema": { - "type": "string", - "description": "Category within the security standard (e.g., 'cwe-119' for CWE Top 25, 'a6' for OWASP Top 10). If null, all CWE IDs for the given standard and version are included. Ignored if securityStandard is null." + "type": "string" } }, { - "name": "securityStandardLevel", + "name": "issueReleaseChangeKey", "in": "query", - "description": "Level within the security standard (e.g., 'Level 1', 'Level 2', 'Level 3' for OWASP ASVS). Levels are inclusive (e.g., Level 2 includes all Level 1 and Level 2 requirements). Ignored if securityStandard is null or does not support levels.", - "required": false, + "description": "Issue release change key", + "required": true, "schema": { "type": "string", - "description": "Level within the security standard (e.g., 'Level 1', 'Level 2', 'Level 3' for OWASP ASVS). Levels are inclusive (e.g., Level 2 includes all Level 1 and Level 2 requirements). Ignored if securityStandard is null or does not support levels." - } - }, - { - "name": "pageSize", - "in": "query", - "description": "Number of results per page. A value of 0 will only return the pagination information.", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 50, - "description": "Number of results per page. A value of 0 will only return the pagination information.", - "maximum": 500, - "minimum": 0 - } - }, - { - "name": "pageIndex", - "in": "query", - "description": "1-based page index", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 1, - "description": "1-based page index", - "exclusiveMinimum": 0, - "minimum": 1 + "description": "Issue release change key" } } ], "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/IssuesReleasesSearchRestResponse" - } - } - } + "204": { + "description": "No Content" } }, "security": [ @@ -5850,37 +6302,38 @@ } ], "x-sonar-internal": true - } - }, - "/sca/issues-releases/{key}": { - "get": { + }, + "patch": { "tags": [ "Issues-Releases" ], - "summary": "Get a single issue-release pair", - "description": "Fetch a single (issue,release) pair (dependency risk), using the key from the search endpoint.\n\nThis API is subject to change without notice.\n", - "operationId": "fetchIssueRelease", + "summary": "Update a comment on an issue-release pair", + "description": "Update a comment on a (issue,release) pair (dependency risk).\n\nThis API is subject to change without notice.\n", + "operationId": "updateComment", "parameters": [ { "name": "key", "in": "path", - "description": "The key of the issue,release pair to fetch.", + "description": "The key of the (issue,release) pair.", "required": true, "schema": { "type": "string" } } ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/IssueReleaseDetailsResource" - } + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IssueReleaseUpdateCommentRestRequest" } } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" } }, "security": [ @@ -5891,17 +6344,17 @@ "x-sonar-internal": true } }, - "/sca/issues-releases/{key}/changelogs": { - "get": { + "/sca/issues-releases/{id}": { + "patch": { "tags": [ "Issues-Releases" ], - "summary": "Get the changelog for a single issue-release pair", - "description": "Get the changelog for a single (issue,release) pair (dependency risk), using the key from the search endpoint.\n\nThis API is subject to change without notice.\n", - "operationId": "getChangelog", + "summary": "Update the issue-release pair's information, specifically around severity", + "description": "Update the issue-release pair's information, specifically around severity. This is used to dismiss an increased\nseverity warning, or to manually change the severity.\n\nThis API is subject to change without notice.\n", + "operationId": "updateIssueRelease", "parameters": [ { - "name": "key", + "name": "id", "in": "path", "description": "The key of the (issue,release) pair.", "required": true, @@ -5910,13 +6363,23 @@ } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IssueReleaseSetSeverityRestRequest" + } + } + }, + "required": true + }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IssuesReleasesChangesRestResponse" + "$ref": "#/components/schemas/IssueReleaseDetailsResource" } } } @@ -5930,56 +6393,20 @@ "x-sonar-internal": true } }, - "/sca/issues-releases/all-assignees": { + "/sca/feature-enablements": { "get": { "tags": [ - "Issues-Releases" - ], - "summary": "Get the list of users assigned to at least one issue in a given branch", - "description": "Get the list of users assigned to at least one issue in a given branch.\n", - "operationId": "getAllAssignees", - "parameters": [ - { - "name": "projectKey", - "in": "query", - "description": "Key of the project to fetch all assignee users from.", - "required": true, - "schema": { - "type": "string", - "description": "Key of the project to fetch all assignee users from." - } - }, - { - "name": "branchKey", - "in": "query", - "description": "Key of the branch to fetch all assignee users from. If not provided, the default branch will be used unless a pull request key is provided.", - "required": false, - "schema": { - "type": "string", - "description": "Key of the branch to fetch all assignee users from. If not provided, the default branch will be used unless a pull request key is provided." - } - }, - { - "name": "pullRequestKey", - "in": "query", - "description": "Key of the pull request to fetch all assignee users from.", - "required": false, - "schema": { - "type": "string", - "description": "Key of the pull request to fetch all assignee users from." - } - } + "Feature Enablement" ], + "summary": "Get the status of SCA feature opt-in (should only be used for the configuration UX in frontend)", + "operationId": "getFeatureEnablement", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UserResource" - } + "$ref": "#/components/schemas/FeatureEnablementResource" } } } @@ -5990,52 +6417,31 @@ "bearerAuth": [] } ], - "x-sonar-internal": true - } - }, - "/sca/enabled": { - "get": { + "x-sonar-internal": "true" + }, + "patch": { "tags": [ - "SCA Enabled" + "Feature Enablement" ], - "summary": "Get whether SCA is licensed and set up for a resource's organization or enterprise, prefer the /feature-enabled path in new code", - "operationId": "getFeatureEnabled", - "parameters": [], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ScaFeatureEnabledResource" - } + "summary": "Update SCA feature opt-in (should only be used for the configuration UX in frontend)", + "operationId": "updateFeatureEnablement", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FeatureEnablementRequest" } } - } + }, + "required": true }, - "security": [ - { - "bearerAuth": [] - } - ], - "x-sonar-internal": "true" - } - }, - "/sca/feature-enabled": { - "get": { - "tags": [ - "SCA Enabled" - ], - "summary": "Get whether SCA is licensed and set up for a resource's organization or enterprise, prefer the /feature-enabled path in new code", - "operationId": "getFeatureEnabled_1", - "parameters": [], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ScaFeatureEnabledResource" + "$ref": "#/components/schemas/FeatureEnablementResource" } } } @@ -6049,47 +6455,22 @@ "x-sonar-internal": "true" } }, - "/sca/clis": { + "/llm-connectivity/llm-providers/{id}": { "get": { "tags": [ - "SCA CLIs" + "LLM Providers" ], - "summary": "Get available CLI downloads", - "description": "Gets the available SCA CLI downloads. The response includes metadata about each option\nsuch as the filename and operating system.\n\nThis is an internal API and is subject to change without notice.\n", - "operationId": "getScaClisMetadata", + "summary": "Get one LLM provider", + "description": "Requires system administration.", + "operationId": "getProvider", "parameters": [ { - "name": "os", - "in": "query", - "description": "Filter by operating system", - "required": false, - "schema": { - "type": "string", - "description": "Filter by operating system", - "enum": [ - "windows", - "linux", - "macos" - ], - "example": "windows" - }, - "example": "windows" - }, - { - "name": "arch", - "in": "query", - "description": "Filter by CPU architecture", - "required": false, + "name": "id", + "in": "path", + "required": true, "schema": { - "type": "string", - "description": "Filter by CPU architecture", - "enum": [ - "x64", - "aarch64" - ], - "example": "x64" - }, - "example": "x64" + "type": "string" + } } ], "responses": { @@ -6098,105 +6479,120 @@ "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ScaCliInfoRestResponse" - } + "$ref": "#/components/schemas/LlmProviderResponse" } } } } }, - "security": [ + "x-sonar-internal": "true" + }, + "delete": { + "tags": [ + "LLM Providers" + ], + "summary": "Delete an LLM provider", + "description": "Blocked when the provider is selected by an AI capability. Requires system administration.", + "operationId": "deleteProvider", + "parameters": [ { - "bearerAuth": [] + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } } ], + "responses": { + "204": { + "description": "No Content" + } + }, "x-sonar-internal": "true" - } - }, - "/sca/clis/{id}": { - "get": { + }, + "patch": { "tags": [ - "SCA CLIs" + "LLM Providers" ], - "summary": "Get metadata for a specific CLI download", - "description": "Gets the metadata for a specific SCA CLI download. The response includes the\nfilename, SHA-256 checksum, operating system, and CPU architecture.\n\nThis is an internal API and is subject to change without notice.\n", - "operationId": "downloadScaCli", + "summary": "Update an LLM provider", + "description": "The connection is re-tested before changes are persisted. Requires system administration.", + "operationId": "updateProvider", "parameters": [ { "name": "id", "in": "path", - "description": "The ID of the SCA CLI download", "required": true, "schema": { "type": "string" } } ], + "requestBody": { + "content": { + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/LlmProviderUpdateRequest" + } + } + }, + "required": true + }, "responses": { "200": { "description": "OK", "content": { - "application/octet-stream": { - "schema": { - "type": "string", - "format": "binary" - } - }, "application/json": { "schema": { - "$ref": "#/components/schemas/ScaCliInfoRestResponse" + "$ref": "#/components/schemas/LlmProviderResponse" } } } } }, - "security": [ - { - "bearerAuth": [] - } - ], "x-sonar-internal": "true" } }, - "/sca/analyses": { + "/jira/work-types": { "get": { "tags": [ - "Analyses" + "Jira" ], - "summary": "Fetch analysis status for one branch", - "description": "Fetch analysis status information for a branch. If the branch has\nnever been analyzed, will return a status of 404.\n\nThis is an internal API and is subject to change without notice.\n", - "operationId": "fetchAnalysis", + "summary": "Returns a list of all the available Jira work types for a specific Jira project", + "description": "Returns a list of all the available Jira work types for a specific Jira project.\nAlso checks which work types are selected for a given sonar project. Conditionally, also includes field metadata.\n", + "operationId": "젔_12", "parameters": [ { - "name": "projectKey", + "name": "jiraProjectKey", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "sonarOrganizationUuid", "in": "query", - "description": "Key of the project to fetch analysis status from", "required": true, "schema": { "type": "string", - "description": "Key of the project to fetch analysis status from" + "format": "uuid" } }, { - "name": "branchKey", + "name": "sonarProjectId", "in": "query", - "description": "Key of the branch to fetch analysis status from. If not provided, the default branch will be used unless a pull request key is provided.", "required": false, "schema": { - "type": "string", - "description": "Key of the branch to fetch analysis status from. If not provided, the default branch will be used unless a pull request key is provided." + "type": "string" } }, { - "name": "pullRequestKey", + "name": "includeFields", "in": "query", - "description": "Key of the pull request to fetch analysis status from.", "required": false, "schema": { - "type": "string", - "description": "Key of the pull request to fetch analysis status from." + "type": "boolean" } } ], @@ -6206,71 +6602,67 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AnalysisResource" + "type": "array", + "items": { + "$ref": "#/components/schemas/JiraWorkTypeResultResource" + } } } } } }, - "security": [ - { - "bearerAuth": [] - } - ], "x-sonar-internal": "true" - } - }, - "/monitoring/alerts": { - "get": { + }, + "patch": { "tags": [ - "Monitoring" + "Jira" ], - "summary": "List active admin alerts", - "description": "Returns the list of admin alerts whose condition is currently active.\nEach entry contains the alert identifier and a message describing the condition.\nRequires 'Administer System' permission.\n", - "operationId": "getActiveAlerts", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AlertsResponse" - } + "summary": "Saves the selected Jira work types for a project", + "description": "Receives a Jira project key and a list of work type and stores the work types.\n", + "operationId": "젔_13", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JiraWorkTypesSelectionResource" } } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" } }, "x-sonar-internal": "true" } }, - "/jira/user-actions": { - "get": { + "/jira/organization-binding-edit": { + "patch": { "tags": [ "Jira" ], - "summary": "Returns a list of available user actions for a project", - "description": "Returns a list of available user actions for the authenticated user in the context of a specific project.\nIf the user is not authenticated, returns an empty list.\n", - "operationId": "饘_14", - "parameters": [ - { - "name": "sonarProjectId", - "in": "query", - "required": true, - "schema": { - "type": "string" + "summary": "Update Jira organization binding properties", + "description": "Update properties of an existing Jira organization binding, such as token sharing configuration.\nRequires global administrator permission.\n", + "operationId": "젔_14", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchJiraOrganizationBindingEditResource" + } } - } - ], + }, + "required": true + }, "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "type": "string" - } + "$ref": "#/components/schemas/JiraOrganizationBindingResource" } } } @@ -6279,35 +6671,52 @@ "x-sonar-internal": "true" } }, - "/jira/projects": { + "/issues/sandbox-settings": { "get": { "tags": [ - "Jira" + "Issues" ], - "summary": "Returns a list of all the available Jira projects", - "description": "Returns a list of all the available Jira projects", - "operationId": "饘_15", - "parameters": [ - { - "name": "sonarOrganizationUuid", - "in": "query", - "required": true, - "schema": { - "type": "string", - "format": "uuid" + "summary": "Fetch instance sandbox settings", + "description": "Fetch the current instance-level sandbox settings.\nReturns configuration including enabled status, default value, allowOverride setting, and software qualities or rule types based on current mode.\nRequires 'Administer System' permission.\n", + "operationId": "getSandboxSettings", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSettingsResource" + } + } } } + }, + "x-sonar-internal": "true" + }, + "patch": { + "tags": [ + "Issues" ], + "summary": "Update instance sandbox settings", + "description": "Update the instance-level sandbox settings.\nCannot enable sandbox without providing software qualities or rule types unless they already exist.\nWhen disabling allowOverride, all project-level software quality overrides are deleted.\nRequires 'Administer System' permission.\n", + "operationId": "patchSandboxSettings", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSettingsResource" + } + } + }, + "required": true + }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/JiraProjectResultResource" - } + "$ref": "#/components/schemas/SandboxSettingsResource" } } } @@ -6316,17 +6725,17 @@ "x-sonar-internal": "true" } }, - "/jira/linked-issues-count/{sonarProjectId}": { + "/issues/sandbox-settings/{projectKey}": { "get": { "tags": [ - "Jira" + "Issues" ], - "summary": "Count linked Jira issues", - "description": "Count the number of Jira issues linked to a specific Sonar project.\nAccepts only authenticated requests.\n", - "operationId": "饘_16", + "summary": "Fetch project sandbox settings", + "description": "Fetch the sandbox settings for a specific project.\nReturns effective configuration including enabled status, software qualities overrides, and whether settings are inherited from instance or overridden at project level.\nRequires 'Administer Project' permission.\n", + "operationId": "getProjectSandboxSettings", "parameters": [ { - "name": "sonarProjectId", + "name": "projectKey", "in": "path", "required": true, "schema": { @@ -6340,66 +6749,119 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/LinkedIssuesCountResource" + "$ref": "#/components/schemas/SandboxSettingsProjectResource" } } } } }, "x-sonar-internal": "true" - } - }, - "/integrations/user-bindings/{id}": { - "get": { + }, + "patch": { "tags": [ - "Slack User Bindings" + "Issues" ], - "summary": "Get Slack user binding", - "description": "Retrieves a Slack user binding by its ID. Users can only access their own bindings\nunless they have administrative privileges.\n", - "operationId": "getUserBinding", - "parameters": [], + "summary": "Update project sandbox settings", + "description": "Update the sandbox settings for a specific project.\nThe enabled setting can be changed independently of software quality overrides.\nWhen allowOverride is disabled at instance level, software quality changes are not allowed but enabled changes are still permitted.\nSetting overridden=false removes project-level software quality overrides to inherit from instance settings.\nRequires 'Administer Project' permission and instance sandbox must be enabled.\n", + "operationId": "patchProjectSandboxSettings", + "parameters": [ + { + "name": "projectKey", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSettingsProjectResource" + } + } + }, + "required": true + }, "responses": { "200": { - "description": "User binding retrieved successfully", + "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UserBindingResponse" + "$ref": "#/components/schemas/SandboxSettingsProjectResource" } } } - }, - "401": { - "description": "Authentication required" - }, - "403": { - "description": "Insufficient permissions" - }, - "404": { - "description": "User binding not found" - }, - "500": { - "description": "Internal server error" } }, "x-sonar-internal": "true" } }, - "/fix-suggestions/supported-rules": { - "get": { + "/integrations/integration-configurations/{id}": { + "delete": { "tags": [ - "Fix Suggestions" + "integration-configurations-controller" ], - "summary": "Get the supported rules", - "description": "Returns the list of rules for which a fix suggestion can be generated", - "operationId": "supportedRules", + "summary": "Delete an integration configuration and all its related data.", + "description": "Requires global administrator permission.\nThis will permanently delete the integration configuration, all associated workspaces, user bindings, and subscriptions.\n", + "operationId": "delete_1", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of the integration configuration", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "x-sonar-internal": "true" + }, + "patch": { + "tags": [ + "integration-configurations-controller" + ], + "summary": "Update an integration configuration.", + "description": "Requires global administrator permission.", + "operationId": "update_2", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of the integration configuration", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IntegrationConfigurationPatchRequest" + } + } + }, + "required": true + }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SupportedRulesDto" + "$ref": "#/components/schemas/IntegrationConfigurationResponse" } } } @@ -6408,195 +6870,163 @@ "x-sonar-internal": "true" } }, - "/fix-suggestions/supported-llm-providers": { + "/fix-suggestions/feature-enablements": { "get": { "tags": [ "Fix Suggestions" ], - "summary": "Get the supported LLM providers", - "description": "Returns the LLM providers that could be used for requesting an AI fix suggestion", - "operationId": "getLlmProviders", + "summary": "Fetch the Ai CodeFix feature enablement configurations", + "operationId": "getFeatureEnablement_1", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/LlmProviderResponseDto" - } + "$ref": "#/components/schemas/FeatureEnablementResponse" } } } } }, "x-sonar-internal": "true" + }, + "patch": { + "tags": [ + "Fix Suggestions" + ], + "summary": "Enable/Disable the AI CodeFix feature at the instance or project level", + "operationId": "updateFeatureEnablement_1", + "requestBody": { + "content": { + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/FixSuggestionsFeatureEnablementRequest" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "No Content" + } + }, + "x-sonar-internal": "true" } }, - "/fix-suggestions/service-info": { + "/entitlements/license": { "get": { "tags": [ - "Fix Suggestions" + "Entitlements" ], - "summary": "Request status of AI CodeFix service", - "operationId": "get_2", + "summary": "Returns information about the license", + "description": " Returns information about the current license.\n Requires 'Administer System' permission.\n", + "operationId": "getLicense", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ServiceInfo" + "$ref": "#/components/schemas/LicenseRestResponse" } } } } }, "x-sonar-internal": "true" - } - }, - "/fix-suggestions/issues/{issueId}": { - "get": { - "tags": [ - "Fix Suggestions" - ], - "summary": "Fetch AI suggestion availability for the given issueId", - "description": "Requires Code Viewer permission.", - "operationId": "get_3", - "parameters": [ - { - "name": "issueId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } + }, + "delete": { + "tags": [ + "Entitlements" ], + "summary": "Deletes the existing license", + "description": " Deletes the license. Both license keys received from Sonar and from 3rd party system can be removed.\n Requires 'Administer System' permission.\n", + "operationId": "deleteLicense", "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FixSuggestionIssueResponse" - } - } - } + "description": "OK" } - } - } - }, - "/entitlements/purchasable-features": { - "get": { + }, + "x-sonar-internal": "true" + }, + "patch": { "tags": [ "Entitlements" ], - "summary": "Returns a list of all available purchasable features for this edition", - "description": " Returns a list of all available purchasable features for this edition including the ones who are already purchased\n", - "operationId": "getPurchasableFeatures", + "summary": "Refreshes the existing license", + "description": " Fetches the latest information about the license and updates it on the SonarQube Server instance.\n Requires 'Administer System' permission.\n", + "operationId": "refreshLicense", "responses": { "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PurchasableFeatureRestResponse" - } - } - } - } + "description": "OK" } }, "x-sonar-internal": "true" } }, - "/dop-translation/project-bindings": { - "get": { + "/dop-translation/gitlab-permission-mappings/{role}": { + "delete": { "tags": [ "Dop Translation" ], - "summary": "Search across project bindings", - "operationId": "getProjectBindingByProjectId", + "summary": "Delete a single permission mappings", + "description": "Requires 'Administer System' permission.", + "operationId": "deleteMapping", "parameters": [ { - "name": "repository", - "in": "query", - "description": "Filter on the repository name.\nThis parameter performs an exact, case insensitive, match.\n", - "required": false, - "schema": { - "type": [ - "string", - "null" - ], - "description": "Filter on the repository name.\nThis parameter performs an exact, case insensitive, match.\n" - } - }, - { - "name": "dopSettingId", - "in": "query", - "description": "Filter on the DevOps Platform setting id.", - "required": false, - "schema": { - "type": [ - "string", - "null" - ], - "description": "Filter on the DevOps Platform setting id." - } - }, - { - "name": "repositoryUrl", - "in": "query", - "description": "Filter on the repository URL.\nThis parameter can be in different formats, the traditional URL or the git remote URL (https or ssh).\n", - "required": false, - "schema": { - "type": [ - "string", - "null" - ], - "description": "Filter on the repository URL.\nThis parameter can be in different formats, the traditional URL or the git remote URL (https or ssh).\n" - } - }, - { - "name": "pageSize", - "in": "query", - "description": "Number of results per page. A value of 0 will only return the pagination information.", - "required": false, + "name": "role", + "in": "path", + "description": "The name of the role to delete", + "required": true, "schema": { - "type": "integer", - "format": "int32", - "default": 50, - "description": "Number of results per page. A value of 0 will only return the pagination information.", - "maximum": 500, - "minimum": 0 + "type": "string" } - }, + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "x-sonar-internal": "true" + }, + "patch": { + "tags": [ + "Dop Translation" + ], + "summary": "Update a single permission mapping", + "description": "Requires 'Administer System' permission.", + "operationId": "updateMapping", + "parameters": [ { - "name": "pageIndex", - "in": "query", - "description": "1-based page index", - "required": false, + "name": "role", + "in": "path", + "description": "The name of the role to update", + "required": true, "schema": { - "type": "integer", - "format": "int32", - "default": 1, - "description": "1-based page index", - "exclusiveMinimum": 0, - "minimum": 1 + "type": "string" } } ], + "requestBody": { + "content": { + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/DevOpsPermissionMappingUpdateRequest" + } + } + }, + "required": true + }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ProjectBindingsSearchRestResponse" + "$ref": "#/components/schemas/PermissionMappingsResource" } } } @@ -6605,18 +7035,19 @@ "x-sonar-internal": "true" } }, - "/dop-translation/project-bindings/{id}": { + "/dop-translation/gitlab-configurations/{id}": { "get": { "tags": [ "Dop Translation" ], - "summary": "Fetch a single Project Binding", - "operationId": "getProjectBinding", + "summary": "Fetch a GitLab configuration", + "description": "Fetch a GitLab configuration. Requires authentication. System administrators receive the full\nconfiguration; other logged-in users receive a reduced response with only the provisioning status.\n", + "operationId": "getGitlabConfiguration", "parameters": [ { "name": "id", "in": "path", - "description": "The id of the project-bindings to fetch.", + "description": "The id of the configuration to fetch.", "required": true, "schema": { "type": "string" @@ -6629,28 +7060,26 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ProjectBinding" + "$ref": "#/components/schemas/GitlabConfigurationRestResponseForAdmins" } } } } }, "x-sonar-internal": "true" - } - }, - "/dop-translation/jfrog-evidence/{taskId}": { - "get": { + }, + "delete": { "tags": [ - "DevOps Platform Integration" + "Dop Translation" ], - "summary": "Get JFrog evidence for a Compute Engine task", - "description": "Returns a JFrog evidence statement for the specified Compute Engine task.\nThe evidence contains quality gate status and conditions in the in-toto Statement format.\nRequires 'Browse' permission on the project associated with the task.", - "operationId": "饘_17", + "summary": "Delete a GitLab configuration", + "description": "Delete a GitLab configuration.\nRequires 'Administer System' permission.\n", + "operationId": "deleteGitlabConfiguration", "parameters": [ { - "name": "taskId", + "name": "id", "in": "path", - "description": "The UUID of the Compute Engine task", + "description": "The id of the configuration to delete.", "required": true, "schema": { "type": "string" @@ -6658,75 +7087,115 @@ } ], "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/K" - } - } - } + "204": { + "description": "No Content" } - } - } - }, - "/dop-translation/dop-settings": { - "get": { + }, + "x-sonar-internal": "true" + }, + "patch": { "tags": [ "Dop Translation" ], - "summary": "List all DevOps Platform Integration settings", - "description": "Requires the 'Create Projects' permission ", - "operationId": "fetchAllDopSettings", + "summary": "Update a Gitlab configuration", + "description": "Update a Gitlab configuration. Requires 'Administer System' permission.\n", + "operationId": "updateGitlabConfiguration", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/GitlabConfigurationUpdateRestRequest" + } + } + }, + "required": true + }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DopSettingsRestResponse" + "$ref": "#/components/schemas/GitlabConfigurationRestResponseForAdmins" } } } } - } + }, + "x-sonar-internal": "true" } }, - "/atlassian/auth-url": { - "get": { + "/dop-translation/github-permission-mappings/{role}": { + "delete": { "tags": [ - "Atlassian" + "Dop Translation" ], - "summary": "Generate Jira authentication URL", - "description": "Generate the authentication URL for Jira OAuth flow.\nRequires authenticated user.\n", - "operationId": "饘_18", + "summary": "Delete a single permission mappings", + "description": "Requires 'Administer System' permission.", + "operationId": "deleteMapping_1", "parameters": [ { - "name": "sonarOrganizationKey", - "in": "query", + "name": "role", + "in": "path", + "description": "The name of the role to delete", "required": true, "schema": { "type": "string" } - }, + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "x-sonar-internal": "true" + }, + "patch": { + "tags": [ + "Dop Translation" + ], + "summary": "Update a single permission mapping", + "description": "Requires 'Administer System' permission.", + "operationId": "updateMapping_1", + "parameters": [ { - "name": "sonarOrganizationUuid", - "in": "query", + "name": "role", + "in": "path", + "description": "The name of the role to update", "required": true, "schema": { - "type": "string", - "format": "uuid" + "type": "string" } } ], + "requestBody": { + "content": { + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/DevOpsPermissionMappingUpdateRequest" + } + } + }, + "required": true + }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/PermissionMappingsResource" } } } @@ -6735,110 +7204,151 @@ "x-sonar-internal": "true" } }, - "/architecture/structure": { + "/dop-translation/github-configurations/{id}": { "get": { "tags": [ - "structure" + "Dop Translation" ], - "summary": "Get structure graph for a branch and language", - "description": "Return the structure graph for a given organization, branch and ecosystem. All parameters are mandatory.", - "operationId": "structureGet", + "summary": "Fetch a GitHub configuration", + "description": "Fetch a GitHub configuration. Requires 'Administer System' permission.\n", + "operationId": "getGithubConfiguration", "parameters": [ { - "name": "organizationId", - "in": "query", - "description": "UUID v4 of the organization.", + "name": "id", + "in": "path", + "description": "The id of the configuration to fetch.", "required": true, "schema": { "type": "string" } - }, + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GithubConfigurationResource" + } + } + } + } + }, + "x-sonar-internal": "true" + }, + "delete": { + "tags": [ + "Dop Translation" + ], + "summary": "Delete a GitHub configuration", + "description": "Delete a GitHub configuration.\nRequires 'Administer System' permission.\n", + "operationId": "deleteGithubConfiguration", + "parameters": [ { - "name": "branchId", - "in": "query", - "description": "branch Id", + "name": "id", + "in": "path", + "description": "The id of the configuration to delete.", "required": true, "schema": { "type": "string" } - }, + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "x-sonar-internal": "true" + }, + "patch": { + "tags": [ + "Dop Translation" + ], + "summary": "Update a GitHub configuration", + "description": "Update a GitHub configuration. Requires 'Administer System' permission.\n", + "operationId": "updateGithubConfiguration", + "parameters": [ { - "name": "ecosystem", - "in": "query", - "description": "Ecosystem/language of the graph (e.g. java, js, ts, py, cs).", + "name": "id", + "in": "path", "required": true, "schema": { - "type": "string", - "enum": [ - "java", - "js", - "ts", - "py", - "cs" - ] + "type": "string" } } ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/d" - } - } + "requestBody": { + "content": { + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/GithubConfigurationUpdateRestRequest" } } }, - "400": { - "description": "Bad Request", + "required": true + }, + "responses": { + "200": { + "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/GithubConfigurationResource" } } } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/X" - } - } + } + }, + "x-sonar-internal": "true" + } + }, + "/detection-agent/project-configs/{projectId}": { + "get": { + "tags": [ + "configuration" + ], + "summary": "Get project configuration", + "description": "Retrieve the detection-agent configuration for a project. Scheduling reflects the instance-wide detection-agent setting, so it reads as disabled while the detection agent is disabled instance-wide. Entitlement and credit availability are not reflected here: a scheduled run also requires the instance to be entitled and to hold enough credits at fire time, so scheduling can read as enabled on an instance where a run would be refused for one of those reasons. ", + "operationId": "getProjectConfiguration", + "parameters": [ + { + "name": "projectId", + "in": "path", + "required": true, + "schema": { + "type": "string" } - }, - "403": { - "description": "Forbidden", + } + ], + "responses": { + "200": { + "description": "Success", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ProjectConfiguration" } } } }, - "429": { - "description": "Too Many Requests", + "404": { + "description": "The caller lacks access to the target project, or the project does not exist", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ProjectConfiguration" } } } }, "500": { - "description": "Internal Server Error", + "description": "Internal server error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ProjectConfiguration" } } } @@ -6846,120 +7356,84 @@ }, "security": [ { - "lambdaAuthorizer": [] + "TokenAuth": [] } ] - } - }, - "/architecture/snapshots": { - "get": { + }, + "patch": { "tags": [ - "snapshots" + "configuration" ], - "summary": "List snapshots for a project", - "description": "Return all snapshots for a project, sorted by analysis date descending. May be empty.", - "operationId": "snapshotsGet", + "summary": "Upsert project configuration", + "description": "Create or update the detection-agent configuration (including scheduling) for a project. Enabling scheduling with no schedule available — neither supplied in the request nor already stored — provisions a default one rather than failing: a daily run at 02:00 Etc/UTC on the project's main branch, persisted and returned in the response. Each missing piece is defaulted independently; a partial structured config is still refused with 400. While the detection agent is disabled instance-wide, a request that sets scheduling.enabled to true is refused with 403: nothing can dispatch, so scheduling cannot be switched on. Disabling is always accepted, and a request that carries no scheduling intent writes nothing and behaves as a read. The instance-wide setting never touches a project's stored schedule, which takes effect again once that setting is turned back on. ", + "operationId": "upsertProjectConfiguration", "parameters": [ { - "name": "organizationId", - "in": "query", - "description": "UUID v4 of the organization.", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "branchId", - "in": "query", - "description": "branch Id", + "name": "projectId", + "in": "path", "required": true, "schema": { "type": "string" } - }, - { - "name": "pageIndex", - "in": "query", - "description": "Page number for pagination (1-based). Specifies which page of results to return.", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 1, - "minimum": 1 - } - }, - { - "name": "pageSize", - "in": "query", - "description": "Number of items per page. Set to 0 to return only the total count without any items (useful for counting).", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 50, - "minimum": 0 - } } ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/$" - } + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectConfigurationRequest" } } }, - "400": { - "description": "Bad Request", + "required": true + }, + "responses": { + "200": { + "description": "Project configuration upserted successfully", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ProjectConfiguration" } } } }, - "401": { - "description": "Unauthorized", + "400": { + "description": "Bad request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ProjectConfiguration" } } } }, "403": { - "description": "Forbidden", + "description": "The request enables scheduling while the detection agent is disabled instance-wide, so no scheduled run could dispatch; or the supplied scheduling.branchId references a pull request rather than a branch, and only branches can be scheduled. ", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ProjectConfiguration" } } } }, - "429": { - "description": "Too Many Requests", + "404": { + "description": "Project or branch not found, or the caller lacks project administration rights", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ProjectConfiguration" } } } }, "500": { - "description": "Internal Server Error", + "description": "Internal server error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ProjectConfiguration" } } } @@ -6967,56 +7441,35 @@ }, "security": [ { - "lambdaAuthorizer": [] + "TokenAuth": [] } ] } }, - "/architecture/project-configurations": { + "/dashboards/{id}": { "get": { "tags": [ - "project-configurations", - "private-project-configurations" + "custom-dashboards" ], - "summary": "Get project configurations", - "description": "Return project configurations for a given project. Used by the scanner to retrieve intended architecture and directives. External endpoint used by scanner clients. Exactly one of projectId or projectKey must be provided.", - "operationId": "projectConfigurationsGet", + "summary": "Get a custom dashboard", + "operationId": "getDashboard", "parameters": [ { - "name": "projectId", - "in": "query", - "description": "Project Id (mutually exclusive with projectKey)", - "required": false, - "schema": { - "type": [ - "string", - "null" - ] - } - }, - { - "name": "projectKey", - "in": "query", - "description": "Project Key (mutually exclusive with projectId)", - "required": false, + "name": "id", + "in": "path", + "required": true, "schema": { - "type": [ - "string", - "null" - ] + "type": "string" } } ], "responses": { "200": { - "description": "Success", + "description": "Successful operation", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/o" - } + "$ref": "#/components/schemas/DashboardResponse" } } } @@ -7026,7 +7479,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7036,7 +7489,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7046,7 +7499,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7056,7 +7509,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7066,7 +7519,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7076,7 +7529,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "501": { + "description": "Not Implemented", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7084,66 +7547,36 @@ }, "security": [ { - "lambdaAuthorizer": [] + "TokenAuth": [] } ] - } - }, - "/architecture/private/architecture/project-configurations": { - "get": { + }, + "delete": { "tags": [ - "project-configurations", - "private-project-configurations" + "custom-dashboards" ], - "summary": "Get project configurations", - "description": "Return project configurations for a given project. Used by the scanner to retrieve intended architecture and directives. Internal endpoint for service-to-service communication using SigV4 authentication. Exactly one of projectId or projectKey must be provided.", - "operationId": "privateArchitectureProjectConfigurationsGet", + "summary": "Delete a custom dashboard", + "operationId": "deleteDashboard", "parameters": [ { - "name": "projectId", - "in": "query", - "description": "Project Id (mutually exclusive with projectKey)", - "required": false, - "schema": { - "type": [ - "string", - "null" - ] - } - }, - { - "name": "projectKey", - "in": "query", - "description": "Project Key (mutually exclusive with projectId)", - "required": false, + "name": "id", + "in": "path", + "required": true, "schema": { - "type": [ - "string", - "null" - ] + "type": "string" } } ], "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/o" - } - } - } - } + "204": { + "description": "Dashboard successfully deleted" }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7153,7 +7586,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7163,7 +7596,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7173,7 +7606,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7183,7 +7616,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7193,7 +7626,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "501": { + "description": "Not Implemented", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7201,40 +7644,43 @@ }, "security": [ { - "sigv4": [] + "TokenAuth": [] } ] - } - }, - "/architecture/intended": { - "get": { + }, + "patch": { "tags": [ - "intended" + "custom-dashboards" ], - "summary": "Export intended architecture for a project", - "description": "search intended architecture by project. Requires authentication and browse source permission to the project.", - "operationId": "intendedGet", + "summary": "Update a custom dashboard", + "operationId": "updateDashboard", "parameters": [ { - "name": "projectId", - "in": "query", - "description": "Project Id", + "name": "id", + "in": "path", "required": true, "schema": { "type": "string" } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateDashboardRequest" + } + } + }, + "required": true + }, "responses": { "200": { - "description": "Success", + "description": "Successful operation", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/E" - } + "$ref": "#/components/schemas/DashboardResponse" } } } @@ -7244,7 +7690,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7254,7 +7700,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7264,7 +7710,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7274,7 +7730,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7284,7 +7740,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "501": { + "description": "Not Implemented", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -7292,140 +7758,178 @@ }, "security": [ { - "lambdaAuthorizer": [] + "TokenAuth": [] } ] } }, - "/architecture/graphs": { + "/clean-code-policy/mode": { "get": { "tags": [ - "graphs" - ], - "summary": "Return graphs metadata for an analysis", - "description": "Return graphs metadata for an analysis", - "operationId": "getGraphsMetadata", - "parameters": [ - { - "name": "organizationId", - "in": "query", - "description": "UUID v4 of the organization.", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "analysisId", - "in": "query", - "description": "Analysis Id", - "required": false, - "schema": { - "type": [ - "string", - "null" - ] - } - }, - { - "name": "branchId", - "in": "query", - "description": "branch Id", - "required": false, - "schema": { - "type": [ - "string", - "null" - ] - } - } + "Clean Code Policy" ], + "summary": "Retrieve current instance Mode", + "description": "Fetch the current instance mode. Can be Multi-Quality Rules (MQR) Mode or Standard Experience.\n", + "operationId": "getMode", "responses": { "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/k" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/X" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/X" - } - } - } - }, - "403": { - "description": "Forbidden", + "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ModeResource" } } } - }, - "429": { - "description": "Too Many Requests", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/X" - } + } + }, + "x-sonar-internal": "true" + }, + "patch": { + "tags": [ + "Clean Code Policy" + ], + "summary": "Update current instance Mode", + "description": "Update the current instance mode. Can be Multi-Quality Rules (MQR) Mode or Standard Experience.\nRequires 'Administer System' permission.\n", + "operationId": "patchMode", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModeResource" } } }, - "500": { - "description": "Internal Server Error", + "required": true + }, + "responses": { + "200": { + "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/ModeResource" } } } } }, - "security": [ - { - "lambdaAuthorizer": [] - } - ], "x-sonar-internal": "true" } }, - "/architecture/graphs/{id}": { + "/authorizations/groups/{id}": { "get": { "tags": [ - "graphs" + "Authorizations" ], - "summary": "Return the selected graph", - "description": "Return the selected graph", - "operationId": "getGraph", + "summary": "Fetch a single group", + "description": "Fetch a single group.", + "operationId": "fetchGroup", "parameters": [ { "name": "id", "in": "path", + "description": "The id of the group to fetch.", "required": true, "schema": { - "type": "string", - "format": "uuid" + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupRestResponse" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Authorizations" + ], + "summary": "Deletes a group", + "description": "Deletes a group.", + "operationId": "deleteGroup", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The ID of the group to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + }, + "patch": { + "tags": [ + "Authorizations" + ], + "summary": "Update a group", + "description": "Update a group name or description.\n", + "operationId": "updateGroup", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/GroupUpdateRestRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupRestResponse" + } + } + } + } + } + } + }, + "/architecture/sdks/{id}": { + "get": { + "tags": [ + "sdks" + ], + "summary": "Get an SDK by id", + "description": "Return the SDK with the given id.", + "operationId": "sdksIdGet", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" } } ], @@ -7435,7 +7939,7 @@ "content": { "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/Sdk" } } } @@ -7445,7 +7949,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7455,7 +7959,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7465,7 +7969,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7475,7 +7979,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7485,7 +7989,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7495,7 +7999,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7505,18 +8009,15 @@ { "lambdaAuthorizer": [] } - ], - "x-sonar-internal": "true" - } - }, - "/architecture/directives/{id}": { - "get": { + ] + }, + "delete": { "tags": [ - "directives" + "sdks" ], - "summary": "Get a directive by id", - "description": "Return a single directive by id", - "operationId": "directivesIdGet", + "summary": "Delete an SDK", + "description": "Delete the SDK with the given id, together with its boundary descriptors.", + "operationId": "sdksIdDelete", "parameters": [ { "name": "id", @@ -7529,22 +8030,15 @@ } ], "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/y" - } - } - } + "204": { + "description": "Deleted successfully" }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7554,7 +8048,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7564,7 +8058,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7574,7 +8068,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7584,7 +8078,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7594,7 +8088,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7606,13 +8100,13 @@ } ] }, - "delete": { + "patch": { "tags": [ - "directives" + "sdks" ], - "summary": "Delete a directive", - "description": "Delete a directive by id", - "operationId": "directivesIdDelete", + "summary": "Update an SDK", + "description": "Update the mutable fields of an SDK.", + "operationId": "sdksIdPatch", "parameters": [ { "name": "id", @@ -7624,16 +8118,33 @@ } } ], + "requestBody": { + "content": { + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/PatchSdkBody" + } + } + }, + "required": true + }, "responses": { - "204": { - "description": "Deleted successfully" + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Sdk" + } + } + } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7643,7 +8154,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7653,7 +8164,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7663,7 +8174,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7673,7 +8184,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7683,7 +8194,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/X" + "$ref": "#/components/schemas/Error" } } } @@ -7696,955 +8207,15254 @@ ] } }, - "/analysis/version": { - "get": { - "tags": [ - "Analysis" - ], - "summary": "Server version", - "description": "Get the version of the Scanner Engine", - "operationId": "getVersion", - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - "/analysis/jres": { + "/architecture/project-relationships/{id}": { "get": { "tags": [ - "Analysis" + "project-relationships" ], - "summary": "All JREs metadata", - "description": "Get metadata of all available JREs", - "operationId": "getJresMetadata", + "summary": "Get a project relationship by id", + "description": "Return a single project relationship by id", + "operationId": "projectRelationshipsIdGet", "parameters": [ { - "name": "os", - "in": "query", - "description": "Filter the JRE by operating system. Accepted values are 'windows', 'linux', 'macos', 'alpine' (case-insensitive), with some aliases", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "arch", - "in": "query", - "description": "Filter the JRE by CPU architecture. Accepted values are 'x64' and 'aarch64' (case-insensitive), with some aliases.", - "required": false, + "name": "id", + "in": "path", + "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } } ], "responses": { "200": { - "description": "OK", + "description": "Success", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/JreInfoRestResponse" - } + "$ref": "#/components/schemas/ProjectRelationship" } } } - } - } - } - }, - "/analysis/jres/{id}": { - "get": { - "tags": [ - "Analysis" - ], - "summary": "JRE download/metadata", - "description": "This endpoint return the JRE metadata by default. To download the JRE binary asset, set the Accept header of the request to 'application/octet-stream'.", - "operationId": "downloadJre", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "The ID of the JRE", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", + }, + "400": { + "description": "Bad Request", "content": { - "application/octet-stream": { - "schema": { - "type": "string", - "format": "binary" - } - }, "application/json": { "schema": { - "$ref": "#/components/schemas/JreInfoRestResponse" + "$ref": "#/components/schemas/Error" } } } - } - } - } - }, - "/analysis/engine": { - "get": { - "tags": [ - "Analysis" - ], - "summary": "Scanner engine download/metadata", - "description": "This endpoint return the Scanner Engine metadata by default. To download the Scanner Engine, set the Accept header of the request to 'application/octet-stream'.", - "operationId": "downloadScannerEngine", - "responses": { - "200": { - "description": "OK", + }, + "401": { + "description": "Unauthorized", "content": { - "application/octet-stream": { - "schema": { - "type": "string", - "format": "binary" - } - }, "application/json": { "schema": { - "$ref": "#/components/schemas/EngineInfoRestResponse" + "$ref": "#/components/schemas/Error" } } } - } - } - } - }, - "/analysis/active_rules": { - "get": { + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + }, + "delete": { "tags": [ - "Analysis" + "project-relationships" ], - "summary": "Get all active rules for a specific project", - "description": "Used by the scanner-engine to get all active rules for a given project.", - "operationId": "getActiveRules", + "summary": "Delete a project relationship", + "description": "Delete a project relationship by id", + "operationId": "projectRelationshipsIdDelete", "parameters": [ { - "name": "projectKey", - "in": "query", - "description": "Project Key", + "name": "id", + "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } } ], "responses": { - "200": { - "description": "OK", + "204": { + "description": "Deleted successfully" + }, + "400": { + "description": "Bad Request", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ActiveRule" - } + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" } } } } - } - } - }, - "/sca/license-profiles/assigned-projects/{project-key}": { - "delete": { + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + }, + "patch": { "tags": [ - "License Profiles" + "project-relationships" ], - "summary": "Remove the project's assignment to the license profile.", - "description": " Remove the project's assignment to the license profile. If there is a default license profile,\n it will be used to analyze license issues for this project.\n", - "operationId": "delete_2", + "summary": "Update a project relationship", + "description": "Update project relationship fields.", + "operationId": "projectRelationshipsIdPatch", "parameters": [ { - "name": "project-key", + "name": "id", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } } ], + "requestBody": { + "content": { + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/PatchProjectRelationshipBody" + } + } + }, + "required": true + }, "responses": { - "204": { - "description": "No Content" + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectRelationship" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } }, "security": [ { - "bearerAuth": [] + "lambdaAuthorizer": [] } - ], - "x-sonar-internal": "true" + ] } }, - "/authorizations/group-memberships/{id}": { - "delete": { + "/architecture/patterns/{id}": { + "get": { "tags": [ - "Authorizations" + "patterns" ], - "summary": "Remove a group membership", - "description": "Remove a user from a group", - "operationId": "delete_3", + "summary": "Get a pattern by id", + "description": "Return a single pattern by id", + "operationId": "patternsIdGet", "parameters": [ { "name": "id", "in": "path", - "description": "The ID of the group membership to delete.", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } } ], "responses": { - "204": { - "description": "No Content" - } - } - } - } - }, - "components": { - "schemas": { - "UpdateAssigneeRestRequest": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pattern" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + }, + "delete": { + "tags": [ + "patterns" + ], + "summary": "Delete a pattern", + "description": "Delete a pattern by id", + "operationId": "patternsIdDelete", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Deleted successfully" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + }, + "patch": { + "tags": [ + "patterns" + ], + "summary": "Update a pattern", + "description": "Update pattern data and/or name.", + "operationId": "patternsIdPatch", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/PatchPattern" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pattern" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + } + }, + "/architecture/organization-placeholders/{id}": { + "get": { + "tags": [ + "organization-placeholders" + ], + "summary": "Get a placeholder by id", + "description": "Return the placeholder with the given id.", + "operationId": "organizationPlaceholdersIdGet", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationPlaceholder" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + }, + "delete": { + "tags": [ + "organization-placeholders" + ], + "summary": "Delete a placeholder", + "description": "Delete the placeholder with the given id. A placeholder that is referenced by a project relationship cannot be deleted.", + "operationId": "organizationPlaceholdersIdDelete", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Deleted successfully" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + }, + "patch": { + "tags": [ + "organization-placeholders" + ], + "summary": "Update a placeholder", + "description": "Update the mutable fields of a placeholder.", + "operationId": "organizationPlaceholdersIdPatch", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/PatchOrganizationPlaceholderBody" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationPlaceholder" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + } + }, + "/architecture/models/{id}": { + "get": { + "tags": [ + "models" + ], + "summary": "Get a model by id", + "description": "Return a single model by id", + "operationId": "getModel", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Model" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ], + "x-sonar-internal": "true" + }, + "delete": { + "tags": [ + "models" + ], + "summary": "Delete a model", + "description": "Delete a model by id", + "operationId": "deleteModel", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Deleted successfully" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ], + "x-sonar-internal": "true" + }, + "patch": { + "tags": [ + "models" + ], + "summary": "Update a model", + "description": "Update model data. Only the 'data' field can be updated.", + "operationId": "updateModel", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/PatchModel" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Model" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/architecture/boundary-descriptors/{id}": { + "get": { + "tags": [ + "boundary-descriptors" + ], + "summary": "Get a boundary descriptor by id", + "description": "Return a single boundary descriptor by id", + "operationId": "boundaryDescriptorsIdGet", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BoundaryDescriptor" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + }, + "delete": { + "tags": [ + "boundary-descriptors" + ], + "summary": "Delete a boundary descriptor", + "description": "Delete a boundary descriptor by id", + "operationId": "boundaryDescriptorsIdDelete", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Deleted successfully" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + }, + "patch": { + "tags": [ + "boundary-descriptors" + ], + "summary": "Update a boundary descriptor", + "description": "Update a boundary descriptor. All mutable fields must be present in the body.", + "operationId": "boundaryDescriptorsIdPatch", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/PatchBoundaryDescriptor" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BoundaryDescriptor" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + } + }, + "/system/migrations-status": { + "get": { + "tags": [ + "System" + ], + "summary": "Gets the status of ongoing database migrations, if any", + "description": "Return the detailed status of ongoing database migrations including starting date. If no migration is ongoing or needed it is still possible to call this endpoint and receive appropriate information.", + "operationId": "getStatus", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatabaseMigrationsResponse" + } + } + } + } + } + } + }, + "/system/liveness": { + "get": { + "tags": [ + "System" + ], + "summary": "Provide liveness of SonarQube, meant to be used as a liveness probe on Kubernetes", + "description": " Require 'Administer System' permission or authentication with passcode.\n\n When SonarQube is fully started, liveness check for database connectivity, Compute Engine status, and, except for DataCenter Edition, if ElasticSearch is Green or Yellow.\n\n When SonarQube is on Safe Mode (for example when a database migration is running), liveness check only for database connectivity\n", + "operationId": "livenessCheck", + "parameters": [ + { + "name": "X-Sonar-Passcode", + "in": "header", + "description": "Passcode can be provided, see SonarQube documentation", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "This SonarQube node is alive" + }, + "default": { + "description": "This SonarQube node is not alive and should be rescheduled" + } + } + } + }, + "/system/health": { + "get": { + "tags": [ + "System" + ], + "operationId": "getHealth", + "parameters": [ + { + "name": "X-Sonar-Passcode", + "in": "header", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Health" + } + } + } + } + } + } + }, + "/software-quality-reports/compliance-reports": { + "get": { + "tags": [ + "compliance-reports-controller" + ], + "summary": "Get compliance report", + "description": "Get the compliance report for a project, application, or portfolio.", + "operationId": "젔_15", + "parameters": [ + { + "name": "branchKey", + "in": "query", + "description": "Branch key. If not provided, the main branch is used for projects and applications.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + }, + "example": "my-branch" + }, + { + "name": "componentKey", + "in": "query", + "description": "Project, application, or portfolio key.", + "required": true, + "schema": { + "type": "string" + }, + "example": "my-project" + }, + { + "name": "standard", + "in": "query", + "description": "Compliance standard.", + "required": true, + "schema": { + "type": "string", + "enum": [ + "misra", + "wcag" + ] + }, + "example": "misra" + }, + { + "name": "version", + "in": "query", + "description": "Compliance standard version.", + "required": true, + "schema": { + "type": "string" + }, + "example": 2023 + }, + { + "name": "level", + "in": "query", + "description": "Optional WCAG conformance level. Valid values are A, AA, and AAA and they are inclusive. If omitted, all levels are included. Not supported for MISRA.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "A", + "AA", + "AAA" + ] + }, + "example": "AA" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetComplianceReportResponse" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/software-quality-reports/accessibility-reports": { + "get": { + "tags": [ + "software-quality-reports-controller" + ], + "summary": "Get accessibility report", + "description": "Get the accessibility report for a project branch.", + "operationId": "젔_16", + "parameters": [ + { + "name": "branchKey", + "in": "query", + "description": "Branch key. If not provided, the main branch is used.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + }, + "example": "my-branch" + }, + { + "name": "projectKey", + "in": "query", + "description": "Project key.", + "required": true, + "schema": { + "type": "string" + }, + "example": "my-project" + }, + { + "name": "standard", + "in": "query", + "description": "Accessibility standard.", + "required": true, + "schema": { + "type": "string", + "enum": [ + "wcag" + ] + }, + "example": "wcag" + }, + { + "name": "version", + "in": "query", + "description": "Accessibility standard version.", + "required": true, + "schema": { + "type": "string", + "enum": [ + "2.0", + "2.1", + "2.2" + ] + }, + "example": 2.1 + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetAccessibilityReportResponse" + } + } + } + } + }, + "deprecated": true, + "x-sonar-internal": "true" + } + }, + "/security-alerts/alerts": { + "get": { + "tags": [ + "Security Alerts" + ], + "summary": "Search security alerts", + "description": "List security alerts, one row per alert, aggregating its affected branches. Requires 'Browse' permission on the affected projects; results are limited to alerts on projects visible to the current user.", + "operationId": "searchSecurityAlerts", + "parameters": [ + { + "name": "alertTypes", + "in": "query", + "description": "Filter by alert type, repeatable", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "DEPENDENCY_RISK" + ] + } + } + }, + { + "name": "statuses", + "in": "query", + "description": "Filter by aggregate alert status, repeatable", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "OPEN", + "RESOLVED" + ] + } + } + }, + { + "name": "sort", + "in": "query", + "description": "Field to sort by", + "required": false, + "schema": { + "type": "string", + "default": "LAST_DETECTED_AT", + "enum": [ + "FIRST_DETECTED_AT", + "LAST_DETECTED_AT" + ] + } + }, + { + "name": "direction", + "in": "query", + "description": "Sort direction", + "required": false, + "schema": { + "type": "string", + "default": "DESC", + "enum": [ + "ASC", + "DESC" + ] + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "maximum": 500, + "minimum": 0 + } + }, + { + "name": "pageIndex", + "in": "query", + "description": "1-based page index", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "description": "1-based page index", + "exclusiveMinimum": 0, + "minimum": 1 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SecurityAlertSearchRestResponse" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/security-alerts/alerts/{id}": { + "get": { + "tags": [ + "Security Alerts" + ], + "summary": "Get a security alert by ID", + "description": "Returns a single security alert by its ID. Returns 404 if the alert does not exist or the current user does not have 'Browse' permission on any affected project.", + "operationId": "getSecurityAlertById", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SecurityAlertRestResponse" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/sca/self-test": { + "get": { + "tags": [ + "Self Test" + ], + "summary": "Run a self test", + "description": "Run a self test that the dependency analysis (SCA) feature is enabled and\nable to reach the cloud services it relies on.\n\nThis is an internal API and is subject to change without notice.\n", + "operationId": "performSelfTest", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SelfTestResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/sca/sbom-reports": { + "get": { + "tags": [ + "SBOM Reports" + ], + "summary": "Get a software bill of materials (SBOM) report", + "description": "Return a report based on the dependencies in this component's branch, using\nthe type parameter and Accept header to select the report to generate.\n\nRight now, the available reports have specialized MIME types that\ngo along with those formats:\n\n* CycloneDX: https://cyclonedx.org/specification/overview/\n * JSON & XML\n* SPDX 2.3: https://spdx.github.io/spdx-spec/v2.3/\n * JSON & XML\n* SPDX 3.0: https://spdx.github.io/spdx-spec/v3.0.1/\n * JSON\n", + "operationId": "generateReport", + "parameters": [ + { + "name": "component", + "in": "query", + "description": "Key of the component (project, application, portfolio) to build report for", + "required": true, + "schema": { + "type": "string", + "description": "Key of the component (project, application, portfolio) to build report for" + } + }, + { + "name": "branch", + "in": "query", + "description": "Key of the branch to build report for. If not provided, the default branch will be used unless a pull request key is provided.", + "required": false, + "schema": { + "type": "string", + "description": "Key of the branch to build report for. If not provided, the default branch will be used unless a pull request key is provided." + } + }, + { + "name": "pullRequestKey", + "in": "query", + "description": "Key of the pull request to build report for.", + "required": false, + "schema": { + "type": "string", + "description": "Key of the pull request to build report for." + } + }, + { + "name": "type", + "in": "query", + "description": "Type of report to generate.\nThe `Accept` header sent by the client determines the format of the report.\nCurrently supported: cyclonedx (application/vnd.cyclonedx+json), cyclonedx (application/vnd.cyclonedx+xml),\nspdx_23 (application/spdx+json), spdx_23 (application/spdx+xml),\nspdx_30 (application/spdx+json)\n", + "required": true, + "schema": { + "type": "string", + "description": "Type of report to generate.\nThe `Accept` header sent by the client determines the format of the report.\nCurrently supported: cyclonedx (application/vnd.cyclonedx+json), cyclonedx (application/vnd.cyclonedx+xml),\nspdx_23 (application/spdx+json), spdx_23 (application/spdx+xml),\nspdx_30 (application/spdx+json)\n", + "enum": [ + "cyclonedx", + "spdx_23", + "spdx_30" + ] + } + }, + { + "name": "onlyProductionScope", + "in": "query", + "description": "If false, include all dependencies. Default (true/absent) filters to production dependencies only.", + "required": false, + "schema": { + "type": "boolean", + "description": "If false, include all dependencies. Default (true/absent) filters to production dependencies only." + } + } + ], + "responses": { + "200": { + "description": "The desired report in the desired output format", + "content": { + "application/vnd.cyclonedx+json": { + "schema": { + "type": "string" + } + }, + "application/vnd.cyclonedx+xml": { + "schema": { + "type": "string" + } + }, + "application/spdx+json": { + "schema": { + "type": "string" + } + }, + "application/spdx+xml": { + "schema": { + "type": "string" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-public": "true" + } + }, + "/sca/risk-reports": { + "get": { + "tags": [ + "Risk Reports" + ], + "summary": "Get a report for all the current SCA dependency risks for a given component and branch", + "description": "Returns list of all risks for a given component and branch. CSV format is available by setting `Accept: text/csv` in your request headers. VEX (CycloneDX 1.6) format is available by setting `Accept: application/vnd.cyclonedx+json`.\n", + "operationId": "getReport", + "parameters": [ + { + "name": "component", + "in": "query", + "description": "Key of the component (project, application, portfolio) to build report for", + "required": true, + "schema": { + "type": "string", + "description": "Key of the component (project, application, portfolio) to build report for" + } + }, + { + "name": "branch", + "in": "query", + "description": "Key of the branch to build report for. If not provided, the default branch will be used unless a pull request key is provided.", + "required": false, + "schema": { + "type": "string", + "description": "Key of the branch to build report for. If not provided, the default branch will be used unless a pull request key is provided." + } + }, + { + "name": "pullRequestKey", + "in": "query", + "description": "Key of the pull request to build report for.", + "required": false, + "schema": { + "type": "string", + "description": "Key of the pull request to build report for." + } + }, + { + "name": "riskType", + "in": "query", + "required": false, + "schema": { + "type": "array", + "description": "Types of risk to filter the report by. If not provided, all risk types are included.", + "items": { + "type": "string" + }, + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "description": "The report in the desired output format", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RiskReportItem" + } + } + }, + "text/csv": { + "schema": { + "type": "string" + } + }, + "application/vnd.cyclonedx+json": { + "schema": { + "type": "string" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-public": "true" + } + }, + "/sca/releases": { + "get": { + "tags": [ + "Releases" + ], + "summary": "Search for releases", + "description": "Search for package releases that appear in the analyzed project,\napplication, or portfolio, as determined by software composition analysis.\n\nThis endpoint returns one result for each release,\nrather than one result for each time a release is pulled in.\nEach result may appear in multiple files or scopes that\nall use the same version of the affected package.\n\nThe search results do not include full details on the dependencies\nthat pull in each release, only a summary. To get the full dependency\ndetails, use the /releases/{key} endpoint for a single release.\n\nIn the terminology of this endpoint, a \"release\" is a version of\na package like \"lodash 1.2.3\", and a \"dependency\" is a specific file\nand scope that pulls in the release such as \"subproject/pom.xml test\".\nEach returned release may have multiple dependencies.\n\nThis API is subject to change without notice.\n", + "operationId": "search_3", + "parameters": [ + { + "name": "projectKey", + "in": "query", + "description": "Key of the component (project, application, or portfolio) to fetch all dependencies from", + "required": true, + "schema": { + "type": "string", + "description": "Key of the component (project, application, or portfolio) to fetch all dependencies from" + } + }, + { + "name": "branchKey", + "in": "query", + "description": "Key of the branch to fetch all dependencies from. If not provided, the default branch will be used unless a pull request key is provided.", + "required": false, + "schema": { + "type": "string", + "description": "Key of the branch to fetch all dependencies from. If not provided, the default branch will be used unless a pull request key is provided." + } + }, + { + "name": "pullRequestKey", + "in": "query", + "description": "Key of the pull request to fetch all dependencies from.", + "required": false, + "schema": { + "type": "string", + "description": "Key of the pull request to fetch all dependencies from." + } + }, + { + "name": "direct", + "in": "query", + "description": "Filter on the direct attribute. TRUE stands for direct dependencies, FALSE for transitive dependencies.", + "required": false, + "schema": { + "type": "boolean", + "description": "Filter on the direct attribute. TRUE stands for direct dependencies, FALSE for transitive dependencies." + } + }, + { + "name": "newlyIntroduced", + "in": "query", + "description": "Filter on the newlyIntroduced attribute. TRUE stands for only releases introduced vs. the target branch, FALSE for releases shared with target.", + "required": false, + "schema": { + "type": "boolean", + "description": "Filter on the newlyIntroduced attribute. TRUE stands for only releases introduced vs. the target branch, FALSE for releases shared with target." + } + }, + { + "name": "productionScope", + "in": "query", + "description": "Filter on the productionScope attribute. TRUE stands for only production dependencies, FALSE for only not in production. If a release is both, it matches both.", + "required": false, + "schema": { + "type": "boolean", + "description": "Filter on the productionScope attribute. TRUE stands for only production dependencies, FALSE for only not in production. If a release is both, it matches both." + } + }, + { + "name": "packageManagers", + "in": "query", + "required": false, + "schema": { + "type": "array", + "description": "Filter on the package manager", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + { + "name": "q", + "in": "query", + "description": "Filter on the package name. This parameter performs a partial match (contains and case insensitive) on the package name.", + "required": false, + "schema": { + "type": "string", + "description": "Filter on the package name. This parameter performs a partial match (contains and case insensitive) on the package name." + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "maximum": 500, + "minimum": 0 + } + }, + { + "name": "pageIndex", + "in": "query", + "description": "1-based page index", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "description": "1-based page index", + "exclusiveMinimum": 0, + "minimum": 1 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReleasesSearchRestResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-internal": true + } + }, + "/sca/releases/{key}": { + "get": { + "tags": [ + "Releases" + ], + "summary": "Get a single release", + "description": "Fetch a single release by its key.\n\nThis single-release endpoint lists full details for all dependencies\nthat mention the release.\n\nIn the terminology of this endpoint, a \"release\" is a version of\na package like \"lodash 1.2.3\", and a \"dependency\" is a specific file\nand scope that pulls in the release such as \"subproject/pom.xml test\".\nEach returned release may have multiple dependencies.\n\nThis API is subject to change without notice.\n", + "operationId": "fetchRelease", + "parameters": [ + { + "name": "key", + "in": "path", + "description": "The key of the release to fetch.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReleaseDetailResource" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-internal": true + } + }, + "/sca/releases/search": { + "get": { + "tags": [ + "Releases" + ], + "summary": "Search for package releases across an organization", + "description": "Search for all analyzed branches across all projects in an organization that contain a specific\npackage, identified by its Package URL (PURL).\n\nThe PURL may include a version for an exact match (e.g. pkg:npm/lodash@4.17.21), or omit the\nversion to find all versions of the package in use (e.g. pkg:npm/lodash).\n\nReturns one result per branch that contains a matching release, including feature branches and\npull requests. Each result includes the exact packageUrl found on that branch.\n\nThis is an internal API and is subject to change without notice.\n", + "operationId": "search_4", + "parameters": [ + { + "name": "purl", + "in": "query", + "description": "Package URL (PURL) to search for. May include a version for exact matching (e.g. pkg:npm/lodash@4.17.21) or omit the version to find all versions in use (e.g. pkg:npm/lodash).", + "required": true, + "schema": { + "type": "string", + "description": "Package URL (PURL) to search for. May include a version for exact matching (e.g. pkg:npm/lodash@4.17.21) or omit the version to find all versions in use (e.g. pkg:npm/lodash).", + "minLength": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "maximum": 500, + "minimum": 0 + } + }, + { + "name": "pageIndex", + "in": "query", + "description": "1-based page index", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "description": "1-based page index", + "exclusiveMinimum": 0, + "minimum": 1 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReleaseSearchRestResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/sca/reachability/list-paths": { + "get": { + "tags": [ + "Reachability Definitions" + ], + "summary": "Get reachability paths", + "description": "Returns a page of distinct reachability paths.", + "operationId": "listPaths", + "parameters": [ + { + "name": "languageKey", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "projectKey", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 10000, + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "maximum": 10000, + "minimum": 0 + } + }, + { + "name": "pageIndex", + "in": "query", + "description": "1-based page index", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "description": "1-based page index", + "exclusiveMinimum": 0, + "minimum": 1 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReachabilityPathsRestResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/sca/license-profiles/assignable-projects": { + "get": { + "tags": [ + "License Profiles" + ], + "summary": "License profile assignable projects", + "description": " List the projects that can be assigned to the license profile. Assigning the\n project to a license profile will cause that license profile to be used when\n analyzing the project for license issues.\n", + "operationId": "index_1", + "parameters": [ + { + "name": "licenseProfileUuid", + "in": "query", + "description": "(Deprecated) The key of the license profile whose assignable projects should be retrieved.\n", + "required": false, + "schema": { + "type": "string", + "description": "(Deprecated) The key of the license profile whose assignable projects should be retrieved.\n" + } + }, + { + "name": "licenseProfileId", + "in": "query", + "description": "The key of the license profile whose assignable projects should be retrieved.\n", + "required": false, + "schema": { + "type": "string", + "description": "The key of the license profile whose assignable projects should be retrieved.\n" + } + }, + { + "name": "assignedToLicenseProfile", + "in": "query", + "description": " Providing this parameter filters by whether projects are assigned to the license profile or not.\n Omitting this parameter includes all projects which can be assigned to the license profile,\n regardless of whether they are already assigned to the license profile.\n", + "required": false, + "schema": { + "type": "boolean", + "description": " Providing this parameter filters by whether projects are assigned to the license profile or not.\n Omitting this parameter includes all projects which can be assigned to the license profile,\n regardless of whether they are already assigned to the license profile.\n" + } + }, + { + "name": "q", + "in": "query", + "description": " Providing this parameter performs a partial match (contains and case insensitive) on the project name.\n", + "required": false, + "schema": { + "type": "string", + "description": " Providing this parameter performs a partial match (contains and case insensitive) on the project name.\n" + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "maximum": 500, + "minimum": 0 + } + }, + { + "name": "pageIndex", + "in": "query", + "description": "1-based page index", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "description": "1-based page index", + "exclusiveMinimum": 0, + "minimum": 1 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssignableProjectsIndexRestResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/sca/issues-releases": { + "get": { + "tags": [ + "Issues-Releases" + ], + "summary": "Search for issue-release pairs", + "description": "Search for software composition analysis issues (dependency risks)\nof a project, paired with releases that appear in the analyzed\nproject, application, or portfolio.\n\nThis endpoint returns one result for each (issue,release) pair,\nrather than one result for each distinct issue. So for example\nif a library uses two different versions of a package and\nboth versions are affected by the same vulnerability, you will\nget two results not one.\n\nHowever each result may appear in multiple files or scopes that\nall use the same version of the affected package.\n\nIn the terminology of this endpoint, a \"release\" is a version of\na package like \"lodash 1.2.3\", an \"issue\" is a problem such as\n\"CVE-1234\", and a \"dependency\" is a specific file and scope that\npulls in the release such as \"subproject/pom.xml test\".\n\nThis API is subject to change without notice.\n", + "operationId": "search_5", + "parameters": [ + { + "name": "projectKey", + "in": "query", + "description": "Key of the component (project, application, portfolio) to fetch all dependency risks from.", + "required": true, + "schema": { + "type": "string", + "description": "Key of the component (project, application, portfolio) to fetch all dependency risks from." + } + }, + { + "name": "branchKey", + "in": "query", + "description": "Key of the branch to fetch all dependency risks from. If not provided, the default branch will be used unless a pull request key is provided.", + "required": false, + "schema": { + "type": "string", + "description": "Key of the branch to fetch all dependency risks from. If not provided, the default branch will be used unless a pull request key is provided." + } + }, + { + "name": "pullRequestKey", + "in": "query", + "description": "Key of the pull request to fetch all dependency risks from.", + "required": false, + "schema": { + "type": "string", + "description": "Key of the pull request to fetch all dependency risks from." + } + }, + { + "name": "sort", + "in": "query", + "description": "Sort order", + "required": false, + "schema": { + "type": "string", + "default": "+identity", + "description": "Sort order", + "enum": [ + "+identity", + "-identity", + "+severity", + "-severity", + "+cvssScore", + "-cvssScore", + "+creationDate", + "-creationDate" + ] + } + }, + { + "name": "packageManagers", + "in": "query", + "required": false, + "schema": { + "type": "array", + "description": "Filter on the package manager", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + { + "name": "types", + "in": "query", + "required": false, + "schema": { + "type": "array", + "description": "Filter on the issue type", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + { + "name": "qualities", + "in": "query", + "required": false, + "schema": { + "type": "array", + "description": "Filter on the quality domain", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + { + "name": "severities", + "in": "query", + "required": false, + "schema": { + "type": "array", + "description": "Filter on the severity", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + { + "name": "statuses", + "in": "query", + "required": false, + "schema": { + "type": "array", + "description": "Filter on the status", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + { + "name": "packageName", + "in": "query", + "description": "Filter on the package name. This parameter performs a partial match (contains and case insensitive) on the package name.", + "required": false, + "schema": { + "type": "string", + "description": "Filter on the package name. This parameter performs a partial match (contains and case insensitive) on the package name." + } + }, + { + "name": "vulnerabilityId", + "in": "query", + "description": "Filter on the vulnerability ID. This parameter performs a partial match (contains and case insensitive) on the vulnerability ID.", + "required": false, + "schema": { + "type": "string", + "description": "Filter on the vulnerability ID. This parameter performs a partial match (contains and case insensitive) on the vulnerability ID." + } + }, + { + "name": "newlyIntroduced", + "in": "query", + "description": "Filter on the isNew attribute. TRUE stands for only releases introduced vs. the target branch, FALSE for releases shared with target.", + "required": false, + "schema": { + "type": "boolean", + "description": "Filter on the isNew attribute. TRUE stands for only releases introduced vs. the target branch, FALSE for releases shared with target." + } + }, + { + "name": "direct", + "in": "query", + "description": "Filter on the direct attribute. TRUE stands for only direct dependencies, FALSE for only transitive. If a release is both, it matches both.", + "required": false, + "schema": { + "type": "boolean", + "description": "Filter on the direct attribute. TRUE stands for only direct dependencies, FALSE for only transitive. If a release is both, it matches both." + } + }, + { + "name": "productionScope", + "in": "query", + "description": "Filter on the productionScope attribute. TRUE stands for only production dependencies, FALSE for only not in production. If a release is both, it matches both.", + "required": false, + "schema": { + "type": "boolean", + "description": "Filter on the productionScope attribute. TRUE stands for only production dependencies, FALSE for only not in production. If a release is both, it matches both." + } + }, + { + "name": "assignees", + "in": "query", + "required": false, + "schema": { + "type": "array", + "description": "Filter on assignee logins.", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + { + "name": "projects", + "in": "query", + "required": false, + "schema": { + "type": "array", + "description": "Filter on project keys.", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + { + "name": "assigned", + "in": "query", + "description": "Filter on whether the issue is assigned or not.", + "required": false, + "schema": { + "type": "boolean", + "description": "Filter on whether the issue is assigned or not." + } + }, + { + "name": "onlyShowConfirmedReachable", + "in": "query", + "description": "Filter to only show issue releases with confirmed reachable CVE locations.", + "required": false, + "schema": { + "type": "boolean", + "description": "Filter to only show issue releases with confirmed reachable CVE locations." + } + }, + { + "name": "fixedInPullRequestKey", + "in": "query", + "description": "Key of a pull request. Filters to issues on the current branch that either do not exist on the specified PR branch or have status FIXED on it.", + "required": false, + "schema": { + "type": "string", + "description": "Key of a pull request. Filters to issues on the current branch that either do not exist on the specified PR branch or have status FIXED on it." + } + }, + { + "name": "securityStandard", + "in": "query", + "description": "Security standard for CWE filtering (e.g., 'cwe', 'owaspTop10'). When provided with securityStandardVersion, expands to CWE IDs and filters issues containing those CWEs. Can be combined with securityStandardCategory for fine-grained filtering.", + "required": false, + "schema": { + "type": "string", + "description": "Security standard for CWE filtering (e.g., 'cwe', 'owaspTop10'). When provided with securityStandardVersion, expands to CWE IDs and filters issues containing those CWEs. Can be combined with securityStandardCategory for fine-grained filtering." + } + }, + { + "name": "securityStandardVersion", + "in": "query", + "description": "Version of the security standard (e.g., '2024' for CWE Top 25, '2021' for OWASP Top 10). Required when using securityStandard. Ignored if securityStandard is null.", + "required": false, + "schema": { + "type": "string", + "description": "Version of the security standard (e.g., '2024' for CWE Top 25, '2021' for OWASP Top 10). Required when using securityStandard. Ignored if securityStandard is null." + } + }, + { + "name": "securityStandardCategory", + "in": "query", + "description": "Category within the security standard (e.g., 'cwe-119' for CWE Top 25, 'a6' for OWASP Top 10). If null, all CWE IDs for the given standard and version are included. Ignored if securityStandard is null.", + "required": false, + "schema": { + "type": "string", + "description": "Category within the security standard (e.g., 'cwe-119' for CWE Top 25, 'a6' for OWASP Top 10). If null, all CWE IDs for the given standard and version are included. Ignored if securityStandard is null." + } + }, + { + "name": "securityStandardLevel", + "in": "query", + "description": "Level within the security standard (e.g., 'Level 1', 'Level 2', 'Level 3' for OWASP ASVS). Levels are inclusive (e.g., Level 2 includes all Level 1 and Level 2 requirements). Ignored if securityStandard is null or does not support levels.", + "required": false, + "schema": { + "type": "string", + "description": "Level within the security standard (e.g., 'Level 1', 'Level 2', 'Level 3' for OWASP ASVS). Levels are inclusive (e.g., Level 2 includes all Level 1 and Level 2 requirements). Ignored if securityStandard is null or does not support levels." + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "maximum": 500, + "minimum": 0 + } + }, + { + "name": "pageIndex", + "in": "query", + "description": "1-based page index", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "description": "1-based page index", + "exclusiveMinimum": 0, + "minimum": 1 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IssuesReleasesSearchRestResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-internal": true + } + }, + "/sca/issues-releases/{key}": { + "get": { + "tags": [ + "Issues-Releases" + ], + "summary": "Get a single issue-release pair", + "description": "Fetch a single (issue,release) pair (dependency risk), using the key from the search endpoint.\n\nThis API is subject to change without notice.\n", + "operationId": "fetchIssueRelease", + "parameters": [ + { + "name": "key", + "in": "path", + "description": "The key of the issue,release pair to fetch.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IssueReleaseDetailsResource" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-internal": true + } + }, + "/sca/issues-releases/{key}/changelogs": { + "get": { + "tags": [ + "Issues-Releases" + ], + "summary": "Get the changelog for a single issue-release pair", + "description": "Get the changelog for a single (issue,release) pair (dependency risk), using the key from the search endpoint.\n\nThis API is subject to change without notice.\n", + "operationId": "getChangelog", + "parameters": [ + { + "name": "key", + "in": "path", + "description": "The key of the (issue,release) pair.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IssuesReleasesChangesRestResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-internal": true + } + }, + "/sca/issues-releases/all-assignees": { + "get": { + "tags": [ + "Issues-Releases" + ], + "summary": "Get the list of users assigned to at least one issue in a given branch", + "description": "Get the list of users assigned to at least one issue in a given branch.\n", + "operationId": "getAllAssignees", + "parameters": [ + { + "name": "projectKey", + "in": "query", + "description": "Key of the project to fetch all assignee users from.", + "required": true, + "schema": { + "type": "string", + "description": "Key of the project to fetch all assignee users from." + } + }, + { + "name": "branchKey", + "in": "query", + "description": "Key of the branch to fetch all assignee users from. If not provided, the default branch will be used unless a pull request key is provided.", + "required": false, + "schema": { + "type": "string", + "description": "Key of the branch to fetch all assignee users from. If not provided, the default branch will be used unless a pull request key is provided." + } + }, + { + "name": "pullRequestKey", + "in": "query", + "description": "Key of the pull request to fetch all assignee users from.", + "required": false, + "schema": { + "type": "string", + "description": "Key of the pull request to fetch all assignee users from." + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserResource" + } + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-internal": true + } + }, + "/sca/enabled": { + "get": { + "tags": [ + "SCA Enabled" + ], + "summary": "Get whether SCA is licensed and set up for a resource's organization or enterprise, prefer the /feature-enabled path in new code", + "operationId": "getFeatureEnabled", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScaFeatureEnabledResource" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/sca/feature-enabled": { + "get": { + "tags": [ + "SCA Enabled" + ], + "summary": "Get whether SCA is licensed and set up for a resource's organization or enterprise, prefer the /feature-enabled path in new code", + "operationId": "getFeatureEnabled_1", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScaFeatureEnabledResource" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/sca/clis": { + "get": { + "tags": [ + "SCA CLIs" + ], + "summary": "Get available CLI downloads", + "description": "Gets the available SCA CLI downloads. The response includes metadata about each option\nsuch as the filename and operating system.\n\nThis is an internal API and is subject to change without notice.\n", + "operationId": "getScaClisMetadata", + "parameters": [ + { + "name": "os", + "in": "query", + "description": "Filter by operating system", + "required": false, + "schema": { + "type": "string", + "description": "Filter by operating system", + "enum": [ + "windows", + "linux", + "macos" + ], + "example": "windows" + }, + "example": "windows" + }, + { + "name": "arch", + "in": "query", + "description": "Filter by CPU architecture", + "required": false, + "schema": { + "type": "string", + "description": "Filter by CPU architecture", + "enum": [ + "x64", + "aarch64" + ], + "example": "x64" + }, + "example": "x64" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ScaCliInfoRestResponse" + } + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/sca/clis/{id}": { + "get": { + "tags": [ + "SCA CLIs" + ], + "summary": "Get metadata for a specific CLI download", + "description": "Gets the metadata for a specific SCA CLI download. The response includes the\nfilename, SHA-256 checksum, operating system, and CPU architecture.\n\nThis is an internal API and is subject to change without notice.\n", + "operationId": "downloadScaCli", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The ID of the SCA CLI download", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScaCliInfoRestResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/sca/analyses": { + "get": { + "tags": [ + "Analyses" + ], + "summary": "Fetch analysis status for one branch", + "description": "Fetch analysis status information for a branch. If the branch has\nnever been analyzed, will return a status of 404.\n\nThis is an internal API and is subject to change without notice.\n", + "operationId": "fetchAnalysis", + "parameters": [ + { + "name": "projectKey", + "in": "query", + "description": "Key of the project to fetch analysis status from", + "required": true, + "schema": { + "type": "string", + "description": "Key of the project to fetch analysis status from" + } + }, + { + "name": "branchKey", + "in": "query", + "description": "Key of the branch to fetch analysis status from. If not provided, the default branch will be used unless a pull request key is provided.", + "required": false, + "schema": { + "type": "string", + "description": "Key of the branch to fetch analysis status from. If not provided, the default branch will be used unless a pull request key is provided." + } + }, + { + "name": "pullRequestKey", + "in": "query", + "description": "Key of the pull request to fetch analysis status from.", + "required": false, + "schema": { + "type": "string", + "description": "Key of the pull request to fetch analysis status from." + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AnalysisResource" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/remediation-agent/scheduled-agent-configs/{projectKey}/effective": { + "get": { + "tags": [ + "scheduled-agent-configs" + ], + "operationId": "getEffectiveScheduledAgentConfig", + "parameters": [ + { + "name": "projectKey", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScheduledAgentConfigEffectiveRestResponse" + } + } + } + } + } + } + }, + "/remediation-agent/pull-request-jobs/{jobId}": { + "get": { + "tags": [ + "pull-request-jobs", + "jobs", + "backlog-jobs" + ], + "summary": "Get a remediation pull-request job", + "description": "Retrieve a remediation pull-request job, including its current status", + "operationId": "getPullRequestJob", + "parameters": [ + { + "name": "jobId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "404": { + "description": "Job not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ] + } + }, + "/remediation-agent/jobs": { + "get": { + "tags": [ + "pull-request-jobs", + "jobs", + "backlog-jobs" + ], + "summary": "List remediation jobs", + "description": "Returns a paginated list of remediation jobs for a project. Default sort is `-createdAt` (newest first). ", + "operationId": "listJobs_1", + "parameters": [ + { + "name": "projectKey", + "in": "query", + "description": "Restrict to jobs of this project.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "description": "Filter by status. Omit to return all statuses.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "enum": [ + "PENDING", + "IN_PROGRESS", + "COMPLETED", + "FAILED" + ] + } + }, + { + "name": "sourcePullRequestKey", + "in": "query", + "description": "Filter by the key of the original pull request being remediated.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "pageIndex", + "in": "query", + "description": "1-based page index.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "minimum": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Page size. Set to 0 to retrieve only the total count without any items. ", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "maximum": 500, + "minimum": 0 + } + }, + { + "name": "sort", + "in": "query", + "description": "Sort by a resource field. Prefix `+` for ascending, `-` for descending. Default: `-createdAt` (newest first). Allowed fields: `createdAt`, `startedAt`, `finishedAt`, `status`. ", + "required": false, + "schema": { + "type": "string", + "default": "-createdAt" + } + } + ], + "responses": { + "200": { + "description": "A page of jobs.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobSearchResponse" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobSearchResponse" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobSearchResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ] + } + }, + "/remediation-agent/backlog-jobs/{jobId}": { + "get": { + "tags": [ + "pull-request-jobs", + "jobs", + "backlog-jobs" + ], + "summary": "Get a remediation backlog job", + "description": "Retrieve a remediation backlog job, including its current status", + "operationId": "getBacklogJob", + "parameters": [ + { + "name": "jobId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "404": { + "description": "Job not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ] + } + }, + "/onboarding/statistics": { + "get": { + "tags": [ + "onboarding-controller" + ], + "operationId": "getOnboardingStatistics", + "parameters": [ + { + "name": "organizationKey", + "in": "query", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "startDate", + "in": "query", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + { + "name": "endDate", + "in": "query", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OnboardingStatisticsResponse" + } + } + } + } + } + } + }, + "/onboarding/projects": { + "get": { + "tags": [ + "onboarding-controller" + ], + "operationId": "getOnboardingProjects", + "parameters": [ + { + "name": "organizationKey", + "in": "query", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "q", + "in": "query", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "scanStatus", + "in": "query", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "enum": [ + "SCANNED", + "NOT_SCANNED" + ] + } + }, + { + "name": "analysisMode", + "in": "query", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "enum": [ + "CI", + "AUTOMATIC", + "NONE" + ] + } + }, + { + "name": "pageIndex", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "minimum": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 100, + "maximum": 500, + "minimum": 1 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OnboardingProjectsResponse" + } + } + } + } + } + } + }, + "/onboarding/overview": { + "get": { + "tags": [ + "onboarding-controller" + ], + "operationId": "getOnboardingOverview", + "parameters": [ + { + "name": "organizationKey", + "in": "query", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OnboardingOverviewResponse" + } + } + } + } + } + } + }, + "/monitoring/alerts": { + "get": { + "tags": [ + "Monitoring" + ], + "summary": "List active admin alerts", + "description": "Returns the list of admin alerts whose condition is currently active.\nEach entry contains the alert identifier and a message describing the condition.\nRequires 'Administer System' permission.\n", + "operationId": "getActiveAlerts", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AlertsResponse" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/llm-connectivity/supported-models": { + "get": { + "tags": [ + "LLM Providers" + ], + "summary": "List supported models for an AI capability", + "description": "Proxies the agent-orchestrator's supported-models endpoint for the given AI capability. Requires system administration.", + "operationId": "getSupportedModels", + "parameters": [ + { + "name": "aiCapability", + "in": "query", + "required": true, + "schema": { + "type": "string", + "enum": [ + "AI_CODEFIX", + "HUNTER_AGENT", + "REMEDIATION_AGENT" + ] + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SupportedModelsResponse" + } + } + } + }, + "400": { + "description": "The AI capability does not support listing models" + }, + "502": { + "description": "The agent-orchestrator returned an invalid response" + }, + "503": { + "description": "The agent-orchestrator is unavailable" + } + }, + "x-sonar-internal": "true" + } + }, + "/llm-connectivity/llm-provider-definitions": { + "get": { + "tags": [ + "LLM Providers" + ], + "summary": "List available LLM provider types", + "description": "Returns the provider types and the configuration fields each requires. Requires system administration.", + "operationId": "getProviderDefinitions", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LlmProviderDefinitionsResponse" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/jira/user-actions": { + "get": { + "tags": [ + "Jira" + ], + "summary": "Returns a list of available user actions for a project", + "description": "Returns a list of available user actions for the authenticated user in the context of a specific project.\nIf the user is not authenticated, returns an empty list.\n", + "operationId": "젔_17", + "parameters": [ + { + "name": "sonarProjectId", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/jira/projects": { + "get": { + "tags": [ + "Jira" + ], + "summary": "Returns a list of all the available Jira projects", + "description": "Returns a list of all the available Jira projects", + "operationId": "젔_18", + "parameters": [ + { + "name": "sonarOrganizationUuid", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/JiraProjectResultResource" + } + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/jira/linked-issues-count/{sonarProjectId}": { + "get": { + "tags": [ + "Jira" + ], + "summary": "Count linked Jira issues", + "description": "Count the number of Jira issues linked to a specific Sonar project.\nAccepts only authenticated requests.\n", + "operationId": "젔_19", + "parameters": [ + { + "name": "sonarProjectId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LinkedIssuesCountResource" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/integrations/user-bindings/{id}": { + "get": { + "tags": [ + "Slack User Bindings" + ], + "summary": "Get Slack user binding", + "description": "Retrieves a Slack user binding by its ID. Users can only access their own bindings\nunless they have administrative privileges.\n", + "operationId": "getUserBinding", + "parameters": [], + "responses": { + "200": { + "description": "User binding retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserBindingResponse" + } + } + } + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Insufficient permissions" + }, + "404": { + "description": "User binding not found" + }, + "500": { + "description": "Internal server error" + } + }, + "x-sonar-internal": "true" + } + }, + "/history/sca-resolution-history": { + "get": { + "tags": [ + "sca-resolution-history" + ], + "summary": "Provides SCA resolution history data for an entity.", + "description": "Returns the history of SCA resolution metrics for an entity. All history is recorded in UTC. SCA MTTR values are calculated using a trailing 30-day window ending on each returned day. ", + "operationId": "getScaResolutionHistory", + "parameters": [ + { + "name": "entityId", + "in": "query", + "description": "The ID of the entity to retrieve SCA resolution history for.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "entityType", + "in": "query", + "description": "The entity's type.", + "required": true, + "schema": { + "type": "string", + "enum": [ + "PORTFOLIO", + "PROJECT_BRANCH", + "APPLICATION" + ] + } + }, + { + "name": "statistic", + "in": "query", + "description": "The SCA resolution statistic to return.", + "required": true, + "schema": { + "type": "string", + "enum": [ + "SCA_MTTR" + ] + } + }, + { + "name": "startDate", + "in": "query", + "description": "Inclusive start of the date range. Past values are unbounded.", + "required": true, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "endDate", + "in": "query", + "description": "Inclusive end of the date range. Defaults to the current date and time in UTC. Future values are clamped to the current time. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + { + "name": "severities", + "in": "query", + "description": "Limit results to issues with specific impact severities (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "BLOCKER", + "HIGH", + "INFO", + "LOW", + "MEDIUM" + ] + } + } + }, + { + "name": "sliceBy", + "in": "query", + "description": "Optionally group SCA resolution metrics by severity.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "enum": [ + "SEVERITY" + ] + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScaResolutionHistoryResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ] + } + }, + "/history/project-sca-resolution": { + "get": { + "tags": [ + "project-sca-resolution" + ], + "summary": "Get project SCA resolution.", + "description": "Get SCA resolution values for the flattened list of projects in a portfolio or application. Identify the selection with either the legacy portfolioId parameter, or with both entityType and entityId. The two selector forms are mutually exclusive. ", + "operationId": "getProjectScaResolution", + "parameters": [ + { + "name": "statistic", + "in": "query", + "description": "The SCA resolution statistic to return.", + "required": true, + "schema": { + "type": "string", + "enum": [ + "SCA_MTTR" + ] + } + }, + { + "name": "portfolioId", + "in": "query", + "description": "Legacy portfolio ID. Either portfolioId or both entityType and entityId must be provided, but not both forms. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "entityType", + "in": "query", + "description": "Type of the selection whose projects are returned as a flattened list. Must be provided together with entityId and cannot be combined with portfolioId. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "enum": [ + "PORTFOLIO", + "APPLICATION" + ] + } + }, + { + "name": "entityId", + "in": "query", + "description": "Portfolio ID or application branch ID, according to entityType. Must be provided together with entityType and cannot be combined with portfolioId. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "severities", + "in": "query", + "description": "Limit results to issues with specific impact severities (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "BLOCKER", + "HIGH", + "INFO", + "LOW", + "MEDIUM" + ] + } + } + }, + { + "name": "nameContains", + "in": "query", + "description": "Search query for project names, case insensitive", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "trendSince", + "in": "query", + "description": "The date from which to compare project SCA resolution values. Past values are unbounded. The value must be before the current date in UTC; values on or after the current date are rejected. If omitted, trend is not returned in the response. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + { + "name": "pageIndex", + "in": "query", + "description": "Index of the page to fetch", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "minimum": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Size of the page to fetch.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "maximum": 5000, + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Successfully got project SCA resolution values", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectScaResolutionResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ] + } + }, + "/history/project-measures": { + "get": { + "tags": [ + "project-measures" + ], + "summary": "Get current project measure values.", + "description": "Get current measure values for the flattened list of projects in a portfolio or application. Identify the selection with either the legacy portfolioId parameter, or with both entityType and entityId. The two selector forms are mutually exclusive. ", + "operationId": "getProjectMeasures", + "parameters": [ + { + "name": "metricKey", + "in": "query", + "description": "Metric key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "metricValue", + "in": "query", + "description": "Filter by metric value (exact match). Only supports primitive metric types. It does not support metrics of type DISTR or DATA. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "nameContains", + "in": "query", + "description": "Search query for project names, case insensitive", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "pageIndex", + "in": "query", + "description": "Index of the page to fetch", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "minimum": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Size of the page to fetch.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "maximum": 5000, + "minimum": 0 + } + }, + { + "name": "portfolioId", + "in": "query", + "description": "Legacy portfolio ID. Either portfolioId or both entityType and entityId must be provided, but not both forms. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "entityType", + "in": "query", + "description": "Type of the selection whose projects are returned as a flattened list. Must be provided together with entityId and cannot be combined with portfolioId. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "enum": [ + "PORTFOLIO", + "APPLICATION" + ] + } + }, + { + "name": "entityId", + "in": "query", + "description": "Portfolio ID or application branch ID, according to entityType. Must be provided together with entityType and cannot be combined with portfolioId. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "referenceDate", + "in": "query", + "description": "The date from which to return reference measure values for computing trends. If omitted, reference values will not be returned in the response. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + { + "name": "sort", + "in": "query", + "description": "Defines the sort order of project measures. The value is a comma-separated list of fields to sort by. Each field is prefixed with '+' (URL encoded) for ascending or '-' for descending. Valid sort fields are 'measure.currentValue' and 'projectName'. Sorting is done in lexicographical order, i.e. subsequent sort fields are used as tie breakers. ", + "required": false, + "schema": { + "type": "array", + "default": [ + "-measure.currentValue" + ], + "items": { + "type": "string" + } + } + }, + { + "name": "requireValue", + "in": "query", + "description": "Only return projects with a non-null metric value.", + "required": false, + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Successfully got project measure values", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectMeasuresResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ] + } + }, + "/history/project-issue-resolution": { + "get": { + "tags": [ + "project-issue-resolution" + ], + "summary": "Get project issue resolution", + "description": "Get issue resolution values for the flattened list of projects in a portfolio or application. Identify the selection with either the legacy portfolioId parameter, or with both entityType and entityId. The two selector forms are mutually exclusive. ", + "operationId": "getProjectIssueResolution", + "parameters": [ + { + "name": "statistic", + "in": "query", + "description": "The project issue resolution statistic to return.", + "required": true, + "schema": { + "type": "string", + "enum": [ + "MTTR", + "RECENT_MTTR", + "RESOLVED_ISSUES" + ] + } + }, + { + "name": "portfolioId", + "in": "query", + "description": "Legacy portfolio ID. Either portfolioId or both entityType and entityId must be provided, but not both forms. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "entityType", + "in": "query", + "description": "Type of the selection whose projects are returned as a flattened list. Must be provided together with entityId and cannot be combined with portfolioId. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "enum": [ + "PORTFOLIO", + "APPLICATION" + ] + } + }, + { + "name": "entityId", + "in": "query", + "description": "Portfolio ID or application branch ID, according to entityType. Must be provided together with entityType and cannot be combined with portfolioId. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "severities", + "in": "query", + "description": "Limit results to issues with specific impact severities (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "BLOCKER", + "HIGH", + "INFO", + "LOW", + "MEDIUM" + ] + } + } + }, + { + "name": "typeSeverities", + "in": "query", + "description": "Limit results to issues with specific stored issue severities (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "INFO", + "MINOR", + "MAJOR", + "CRITICAL", + "BLOCKER" + ] + } + } + }, + { + "name": "issueTypes", + "in": "query", + "description": "Limit results to issues with specific types (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "BUG", + "CODE_SMELL", + "VULNERABILITY" + ] + } + } + }, + { + "name": "impacts", + "in": "query", + "description": "Limit results to issues with specific impacts (comma-separated). An impact is a combination of software quality and severity. ", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + { + "name": "nameContains", + "in": "query", + "description": "Search query for project names, case insensitive", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "trendSince", + "in": "query", + "description": "Date used as the trend comparison point.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + { + "name": "pageIndex", + "in": "query", + "description": "Index of the page to fetch", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "minimum": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Number of projects to return. A value of 0 returns pagination only.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "maximum": 5000, + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Successfully got project issue resolution values", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectIssueResolutionResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ] + } + }, + "/history/project-issue-counts": { + "get": { + "tags": [ + "project-issue-counts" + ], + "summary": "Get project issue counts.", + "description": "Get issue counts for the flattened list of projects in a portfolio or application. Identify the selection with either the legacy portfolioId parameter, or with both entityType and entityId. The two selector forms are mutually exclusive. ", + "operationId": "getProjectIssueCounts", + "parameters": [ + { + "name": "portfolioId", + "in": "query", + "description": "Legacy portfolio ID. Either portfolioId or both entityType and entityId must be provided, but not both forms. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "entityType", + "in": "query", + "description": "Type of the selection whose projects are returned as a flattened list. Must be provided together with entityId and cannot be combined with portfolioId. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "enum": [ + "PORTFOLIO", + "APPLICATION" + ] + } + }, + { + "name": "entityId", + "in": "query", + "description": "Portfolio ID or application branch ID, according to entityType. Must be provided together with entityType and cannot be combined with portfolioId. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "ruleKeys", + "in": "query", + "description": "Comma separated list of ruleKeys", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + { + "name": "severities", + "in": "query", + "description": "Limit results to issues with specific impact severities (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "BLOCKER", + "HIGH", + "INFO", + "LOW", + "MEDIUM" + ] + } + } + }, + { + "name": "typeSeverities", + "in": "query", + "description": "Limit results to issues with specific stored issue severities (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "INFO", + "MINOR", + "MAJOR", + "CRITICAL", + "BLOCKER" + ] + } + } + }, + { + "name": "issueTypes", + "in": "query", + "description": "Limit results to issues with specific types (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "BUG", + "CODE_SMELL", + "VULNERABILITY" + ] + } + } + }, + { + "name": "statuses", + "in": "query", + "description": "Limit results to issues with specific statuses (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "ACCEPTED", + "CONFIRMED", + "FALSE_POSITIVE", + "FIXED", + "OPEN" + ] + } + } + }, + { + "name": "impacts", + "in": "query", + "description": "Limit results to issues with specific impacts (comma-separated). An impact is a combination of software quality and severity. ", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + { + "name": "nameContains", + "in": "query", + "description": "Search query for project names, case insensitive", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "referenceDate", + "in": "query", + "description": "The date from which to return reference measure values for computing trends. If omitted, reference values will not be returned in the response. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + { + "name": "pageIndex", + "in": "query", + "description": "Index of the page to fetch", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "minimum": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Size of the page to fetch.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "maximum": 5000, + "minimum": 0 + } + }, + { + "name": "sort", + "in": "query", + "description": "Defines the sort order of project issue counts. The value is a comma-separated list of fields to sort by. Each field is prefixed with '+' (URL encoded) for ascending or '-' for descending. Valid sort fields are 'issueCount' and 'projectName'. Sorting is done in lexicographical order, i.e. subsequent sort fields are used as tie breakers. ", + "required": false, + "schema": { + "type": "array", + "default": [ + "-issueCount" + ], + "items": { + "type": "string" + } + } + }, + { + "name": "requireIssues", + "in": "query", + "description": "Only return projects with an issue count greater than 0.", + "required": false, + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Successfully got project issue counts", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectIssueCountsResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ] + } + }, + "/history/measures-history": { + "get": { + "tags": [ + "measures-history" + ], + "summary": "Get measures history.", + "description": "Returns the history of measures for the given entity.", + "operationId": "getMeasuresHistory", + "parameters": [ + { + "name": "entityType", + "in": "query", + "description": "Type of entity to retrieve measures history for", + "required": true, + "schema": { + "type": "string", + "enum": [ + "PORTFOLIO", + "PROJECT_BRANCH", + "APPLICATION" + ] + } + }, + { + "name": "entityId", + "in": "query", + "description": "UUID of the entity", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "metricKeys", + "in": "query", + "description": "Comma-separated list of at least one supported metric key to filter by.", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "maxItems": 2147483647, + "minItems": 1 + } + }, + { + "name": "startDate", + "in": "query", + "description": "Inclusive start of the date range (ISO-8601)", + "required": true, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "endDate", + "in": "query", + "description": "Inclusive end of the date range (ISO-8601). Defaults to current date.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "Measures history for the given entity", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MeasuresHistoryResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ] + } + }, + "/history/issue-resolution-history": { + "get": { + "tags": [ + "issue-resolution-history" + ], + "summary": "Provides issue resolution history data for an entity", + "description": "Returns issue resolution metrics for an entity. All history is recorded at UTC midnight. MTTR and RECENT_MTTR use a trailing 30-day window ending on each returned day. SQS deliberately permits dates older than one year under the approved history date-range policy. ", + "operationId": "getIssueResolutionHistory", + "parameters": [ + { + "name": "entityId", + "in": "query", + "description": "The ID of the entity to retrieve issue resolution history for.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "entityType", + "in": "query", + "description": "The entity's type.", + "required": true, + "schema": { + "type": "string", + "enum": [ + "PORTFOLIO", + "PROJECT_BRANCH", + "APPLICATION" + ] + } + }, + { + "name": "statistic", + "in": "query", + "description": "The issue resolution statistic to return.", + "required": true, + "schema": { + "type": "string", + "enum": [ + "MTTR", + "RECENT_MTTR", + "RESOLVED_ISSUES" + ] + } + }, + { + "name": "startDate", + "in": "query", + "description": "The inclusive start of the history range in ISO 8601 format.", + "required": true, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "endDate", + "in": "query", + "description": "The inclusive end of the history range. Defaults to the current date.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + { + "name": "impacts", + "in": "query", + "description": "Limit results to issues with specific impacts (comma-separated). An impact is a combination of software quality and severity. ", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + { + "name": "issueTypes", + "in": "query", + "description": "Limit results to issues with specific types (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "BUG", + "CODE_SMELL", + "VULNERABILITY" + ] + } + } + }, + { + "name": "severities", + "in": "query", + "description": "Limit results to issues with specific impact severities (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "BLOCKER", + "HIGH", + "INFO", + "LOW", + "MEDIUM" + ] + } + } + }, + { + "name": "typeSeverities", + "in": "query", + "description": "Limit results to issues with specific stored issue severities (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "INFO", + "MINOR", + "MAJOR", + "CRITICAL", + "BLOCKER" + ] + } + } + }, + { + "name": "sliceBy", + "in": "query", + "description": "Optionally group issue resolution metrics by a dimension.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "enum": [ + "SEVERITY", + "TYPE_SEVERITY", + "SOFTWARE_QUALITY", + "TYPE" + ] + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IssueResolutionHistoryResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ] + } + }, + "/history/issue-density-history": { + "get": { + "tags": [ + "issue-density-history" + ], + "summary": "Provides issue density history data for an entity", + "description": "Returns the history of issue density (issues per 1000 NCLOC) for an entity. The maximum resolution for history data is 1 day. All history is recorded in UTC timezone. Issue density values carry-forward for days with no changes. Density is calculated as (issue count / NCLOC) * 1000. ", + "operationId": "getIssueDensityHistory", + "parameters": [ + { + "name": "entityId", + "in": "query", + "description": "The ID of the entity to retrieve issue density history for.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "entityType", + "in": "query", + "description": "The entity's type.", + "required": true, + "schema": { + "type": "string", + "enum": [ + "PORTFOLIO", + "PROJECT_BRANCH", + "APPLICATION" + ] + } + }, + { + "name": "startDate", + "in": "query", + "description": "The date and time to begin returning issue density history in ISO 8601 format (inclusive). The start date may not be more than 1 year in the past. ", + "required": true, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "endDate", + "in": "query", + "description": "The date and time to stop returning issue density history in ISO 8601 format (inclusive). If not specified, the current date and time at UTC offset 0 will be used. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + { + "name": "impacts", + "in": "query", + "description": "Limit results to issues with specific impacts (comma-separated). An impact is a combination of software quality and severity. ", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + { + "name": "issueTypes", + "in": "query", + "description": "Limit results to issues with specific types (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "BUG", + "CODE_SMELL", + "VULNERABILITY" + ] + } + } + }, + { + "name": "ruleKeys", + "in": "query", + "description": "Comma separated list of ruleKeys", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + { + "name": "severities", + "in": "query", + "description": "Limit results to issues with specific impact severities (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "BLOCKER", + "HIGH", + "INFO", + "LOW", + "MEDIUM" + ] + } + } + }, + { + "name": "typeSeverities", + "in": "query", + "description": "Limit results to issues with specific stored issue severities (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "INFO", + "MINOR", + "MAJOR", + "CRITICAL", + "BLOCKER" + ] + } + } + }, + { + "name": "sliceBy", + "in": "query", + "description": "Optionally group issue counts by a specific dimension. If not provided, issue counts will contain a single distribution with the count of all issues. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "enum": [ + "RULE_KEY", + "SEVERITY", + "TYPE_SEVERITY", + "SOFTWARE_QUALITY", + "STATUS", + "TYPE" + ] + } + }, + { + "name": "statuses", + "in": "query", + "description": "Limit results to issues with specific statuses (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "ACCEPTED", + "CONFIRMED", + "FALSE_POSITIVE", + "FIXED", + "OPEN" + ] + } + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IssueDensityHistoryResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ] + } + }, + "/history/issue-count-history": { + "get": { + "tags": [ + "issue-count-history" + ], + "summary": "Provides issue count history data for an entity", + "description": "Returns the history of issue counts for an entity. The maximum resolution for history data is 1 day. You cannot zoom in on a period less than 1 day (e.g. 1hour). All history is recorded in UTC timezone. Issue counts will carry-forward for days with no changes. ", + "operationId": "getIssueCountHistory", + "parameters": [ + { + "name": "entityId", + "in": "query", + "description": "The ID of the entity to retrieve issue count history for.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "entityType", + "in": "query", + "description": "The entity's type.", + "required": true, + "schema": { + "type": "string", + "enum": [ + "PORTFOLIO", + "PROJECT_BRANCH", + "APPLICATION" + ] + } + }, + { + "name": "startDate", + "in": "query", + "description": "The date and time to begin returning issue count history in ISO 8601 format (inclusive). The start date may not be more than 1 year in the past. ", + "required": true, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "endDate", + "in": "query", + "description": "The date and time to stop returning issue count history in ISO 8601 format (inclusive). If not specified, the current date and time at UTC offset 0 will be used. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + { + "name": "impacts", + "in": "query", + "description": "Limit results to issues with specific impacts (comma-separated). An impact is a combination of software quality and severity. ", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + { + "name": "issueTypes", + "in": "query", + "description": "Limit results to issues with specific types (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "BUG", + "CODE_SMELL", + "VULNERABILITY" + ] + } + } + }, + { + "name": "ruleKeys", + "in": "query", + "description": "Comma separated list of ruleKeys", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + { + "name": "severities", + "in": "query", + "description": "Limit results to issues with specific impact severities (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "BLOCKER", + "HIGH", + "INFO", + "LOW", + "MEDIUM" + ] + } + } + }, + { + "name": "typeSeverities", + "in": "query", + "description": "Limit results to issues with specific stored issue severities (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "INFO", + "MINOR", + "MAJOR", + "CRITICAL", + "BLOCKER" + ] + } + } + }, + { + "name": "sliceBy", + "in": "query", + "description": "Optionally group issue counts by a specific dimension. If not provided, issue counts will contain a single distribution with the count of all issues. ", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "enum": [ + "RULE_KEY", + "SEVERITY", + "TYPE_SEVERITY", + "SOFTWARE_QUALITY", + "STATUS", + "TYPE" + ] + } + }, + { + "name": "statuses", + "in": "query", + "description": "Limit results to issues with specific statuses (comma-separated).", + "required": false, + "schema": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "ACCEPTED", + "CONFIRMED", + "FALSE_POSITIVE", + "FIXED", + "OPEN" + ] + } + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IssueCountHistoryResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ] + } + }, + "/fix-suggestions/supported-rules": { + "get": { + "tags": [ + "Fix Suggestions" + ], + "summary": "Get the supported rules", + "description": "Returns the list of rules for which a fix suggestion can be generated", + "operationId": "supportedRules", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SupportedRulesDto" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/fix-suggestions/supported-llm-providers": { + "get": { + "tags": [ + "Fix Suggestions" + ], + "summary": "Get the supported LLM providers", + "description": "Returns the LLM providers that could be used for requesting an AI fix suggestion", + "operationId": "getLlmProviders", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LlmProviderResponseDto" + } + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/fix-suggestions/service-info": { + "get": { + "tags": [ + "Fix Suggestions" + ], + "summary": "Request status of AI CodeFix service", + "operationId": "get_2", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServiceInfo" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/fix-suggestions/issues/{issueId}": { + "get": { + "tags": [ + "Fix Suggestions" + ], + "summary": "Fetch AI suggestion availability for the given issueId", + "description": "Requires Code Viewer permission.", + "operationId": "get_3", + "parameters": [ + { + "name": "issueId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FixSuggestionIssueResponse" + } + } + } + } + } + } + }, + "/entitlements/purchasable-features": { + "get": { + "tags": [ + "Entitlements" + ], + "summary": "Returns a list of all available purchasable features for this edition", + "description": " Returns a list of all available purchasable features for this edition including the ones who are already purchased\n", + "operationId": "getPurchasableFeatures", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PurchasableFeatureRestResponse" + } + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/dop-translation/project-bindings": { + "get": { + "tags": [ + "Dop Translation" + ], + "summary": "Search across project bindings", + "operationId": "getProjectBindingByProjectId", + "parameters": [ + { + "name": "repository", + "in": "query", + "description": "Filter on the repository name.\nThis parameter performs an exact, case insensitive, match.\n", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "description": "Filter on the repository name.\nThis parameter performs an exact, case insensitive, match.\n" + } + }, + { + "name": "dopSettingId", + "in": "query", + "description": "Filter on the DevOps Platform setting id.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "description": "Filter on the DevOps Platform setting id." + } + }, + { + "name": "repositoryUrl", + "in": "query", + "description": "Filter on the repository URL.\nThis parameter can be in different formats, the traditional URL or the git remote URL (https or ssh).\n", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "description": "Filter on the repository URL.\nThis parameter can be in different formats, the traditional URL or the git remote URL (https or ssh).\n" + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "maximum": 500, + "minimum": 0 + } + }, + { + "name": "pageIndex", + "in": "query", + "description": "1-based page index", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "description": "1-based page index", + "exclusiveMinimum": 0, + "minimum": 1 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectBindingsSearchRestResponse" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/dop-translation/project-bindings/{id}": { + "get": { + "tags": [ + "Dop Translation" + ], + "summary": "Fetch a single Project Binding", + "operationId": "getProjectBinding", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The id of the project-bindings to fetch.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectBinding" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/dop-translation/permission-checks": { + "get": { + "tags": [ + "Dop Translation" + ], + "summary": "Check DevOps Platform permissions for the Remediation Agent", + "description": "Validates whether the configured DevOps Platforms (GitHub, GitLab, Azure DevOps) grant the write permissions the\nSonarQube Remediation Agent needs to clone a repository, push a branch and open a pull/merge request. Results are\ncached for a short time. Internal endpoint used by the Remediation Agent UI.\n\nWithout 'project' it checks configurations instance-wide and requires the 'Administer System' permission or\ntrusted privileged-service authentication; 'configuration' narrows that to a single configuration. With 'project'\nit checks the platform bound to that project and requires 'Browse' permission on the project; a GitHub project is\nchecked against the installation covering its own repository, so two projects sharing one configuration can get\ndifferent results. 'project' and 'configuration' cannot be combined.\n\nFor GitHub, the response also reports permissions the app itself is not configured to request, and — for\nadministrator requests — every installation that has not approved the permissions it does request, whether an\norganization or a personal account owns it. The counts and list are valid only when 'installationCheckStatus' is\nCOMPLETE. If the scan cannot finish, 'installationCheckStatus' is FAILED and 'status' is CHECK_FAILED. The\nresponse does not include the counts or list.\n", + "operationId": "checkDopPermissions", + "parameters": [ + { + "name": "project", + "in": "query", + "description": "Key of the project whose bound DevOps Platform should be checked", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "configuration", + "in": "query", + "description": "Key of a single DevOps Platform configuration to check, instead of all of them. Cannot be combined with 'project'.\n", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "refresh", + "in": "query", + "description": "Bypass the cache and run a live check, then store its result. Requires 'configuration' and the\n'Administer System' permission.\n", + "required": false, + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PermissionChecksRestResponse" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/dop-translation/jfrog-evidence/{taskId}": { + "get": { + "tags": [ + "DevOps Platform Integration" + ], + "summary": "Get JFrog evidence for a Compute Engine task", + "description": "Returns a JFrog evidence statement for the specified Compute Engine task.\nThe evidence contains quality gate status and conditions in the in-toto Statement format.\nRequires 'Browse' permission on the project associated with the task.", + "operationId": "젔_20", + "parameters": [ + { + "name": "taskId", + "in": "path", + "description": "The UUID of the Compute Engine task", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/n" + } + } + } + } + } + } + }, + "/dop-translation/dop-settings": { + "get": { + "tags": [ + "Dop Translation" + ], + "summary": "List all DevOps Platform Integration settings", + "description": "Requires the 'Create Projects' permission ", + "operationId": "fetchAllDopSettings", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DopSettingsRestResponse" + } + } + } + } + } + } + }, + "/detection-agent/jobs/credit-cost": { + "get": { + "tags": [ + "credit-cost" + ], + "summary": "Estimate detection job credit cost", + "description": "Returns the LOC-based credit cost for the latest persisted NCLOC of a branch.", + "operationId": "getCreditCost", + "parameters": [ + { + "name": "branchId", + "in": "query", + "description": "Stable identifier of the branch to estimate.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Credit cost calculated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreditCostResponse" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreditCostResponse" + } + } + } + }, + "403": { + "description": "The supplied branchId references a pull request rather than a branch.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreditCostResponse" + } + } + } + }, + "404": { + "description": "Branch, owning project, or NCLOC measure not found; or the caller lacks project administration rights on the project. ", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreditCostResponse" + } + } + } + }, + "503": { + "description": "The entitlement provider is unavailable", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreditCostResponse" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreditCostResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ] + } + }, + "/detection-agent/instance-config": { + "get": { + "tags": [ + "configuration" + ], + "summary": "Get instance configuration", + "description": "Retrieve the instance-wide detection-agent configuration", + "operationId": "getInstanceConfiguration", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceConfiguration" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceConfiguration" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ] + } + }, + "/dashboards/built-ins": { + "get": { + "tags": [ + "built-in-dashboards" + ], + "summary": "List built-in dashboards", + "operationId": "listBuiltInDashboards", + "parameters": [ + { + "name": "q", + "in": "query", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "resourceType", + "in": "query", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "enum": [ + "project", + "portfolio" + ] + } + }, + { + "name": "pageIndex", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "minimum": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "maximum": 5000, + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BuiltInDashboardsResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "501": { + "description": "Not Implemented", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/dashboards/built-ins/{key}": { + "get": { + "tags": [ + "built-in-dashboards" + ], + "summary": "Get a built-in dashboard", + "operationId": "getBuiltInDashboard", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BuiltInDashboardResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "501": { + "description": "Not Implemented", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/cag/ping": { + "get": { + "tags": [ + "CagHub" + ], + "summary": "Authenticated liveness probe for the CAG Hub service", + "description": "Validates the caller's SonarCloud / SonarQube Server user token and returns a generic success payload. Zero-dependency authenticated liveness probe shared by both hosts. No business logic. ", + "operationId": "ping", + "responses": { + "200": { + "description": "Valid token - service is reachable", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PingResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/cag/impact/verify-metrics": { + "get": { + "tags": [ + "VortexImpact" + ], + "summary": "Get Verify impact metrics for the organization", + "description": "Returns resolved issues, and the Verify contribution to engineering time saved, for the requested calendar date range. Groups public SQAA analysis events into inferred sessions and detects issue-count decreases between successive successful analyses. Date boundaries follow the same UTC-calendar interpretation as /cag/impact/guide-metrics. ", + "operationId": "getVerifyMetrics", + "parameters": [ + { + "name": "organizationId", + "in": "query", + "description": "UUID v4 of the organization. ", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "from", + "in": "query", + "description": "Inclusive start of the calendar date range (YYYY-MM-DD, UTC).", + "required": true, + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "to", + "in": "query", + "description": "Inclusive end of the calendar date range (YYYY-MM-DD, UTC). Must not precede from.", + "required": true, + "schema": { + "type": "string", + "format": "date" + } + } + ], + "responses": { + "200": { + "description": "Verify impact metrics for the period", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VerifyMetrics" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/cag/impact/projects": { + "get": { + "tags": [ + "VortexImpact" + ], + "summary": "Get paginated Vortex impact per project for the organization", + "description": "Returns every project, including projects with no Vortex activity in the period. Each row combines Guide and Verify impact. The page is ordered by total Guide plus Verify calls descending, then latest successful activity descending with no activity last, then project key ascending. Successful compression and restoration establish activity without increasing Guide calls. Sorting and filtering by calculated impact values are outside the V0 contract. Date boundaries follow the same UTC-calendar interpretation as /cag/impact/guide-metrics. ", + "operationId": "getProjectImpact", + "parameters": [ + { + "name": "organizationId", + "in": "query", + "description": "UUID v4 of the organization. ", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "from", + "in": "query", + "description": "Inclusive start of the calendar date range (YYYY-MM-DD, UTC).", + "required": true, + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "to", + "in": "query", + "description": "Inclusive end of the calendar date range (YYYY-MM-DD, UTC). Must not precede from.", + "required": true, + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "pageIndex", + "in": "query", + "description": "Page to retrieve, 1-based. Defaults to 1.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "minimum": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Number of projects per page. Defaults to 50. Use 0 to return only the total count.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "maximum": 500, + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Paginated Vortex impact per project for the period", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VortexProjectPage" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/cag/impact/project-activity": { + "get": { + "tags": [ + "VortexImpact" + ], + "summary": "Get Vortex usage by project analysis cohort for the organization", + "description": "Groups projects into mutually exclusive main-branch, long-lived-branch, pull-request-only, and no-analysis cohorts for the requested period, then reports Vortex usage within each cohort. Analysis precedence follows that order. Successful output-compression and restoration events establish CAG usage; failed events do not. Date boundaries follow the same UTC-calendar interpretation as /cag/impact/guide-metrics. Only periods ending on the current date are supported. To tolerate browser and Server timezone differences, a `to` value within one day of the current UTC date is accepted and normalized to the current UTC date. ", + "operationId": "getProjectActivity", + "parameters": [ + { + "name": "organizationId", + "in": "query", + "description": "UUID v4 of the organization. ", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "from", + "in": "query", + "description": "Inclusive start of the calendar date range (YYYY-MM-DD, UTC).", + "required": true, + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "to", + "in": "query", + "description": "Inclusive end of the calendar date range (YYYY-MM-DD, UTC). Must not precede from.", + "required": true, + "schema": { + "type": "string", + "format": "date" + } + } + ], + "responses": { + "200": { + "description": "Vortex usage grouped by project analysis cohort for the period", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VortexProjectActivity" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/cag/impact/guide-metrics": { + "get": { + "tags": [ + "VortexImpact" + ], + "summary": "Get Guide impact metrics for the organization", + "description": "Returns prevented issues, tokens saved, and the Guide contribution to engineering time saved for the requested calendar date range. Covers navigation tools, output compression, get_guidelines, check_dependency, and get_intended_architecture events. Only successful events contribute to calculated values. Dates are inclusive UTC calendar boundaries: from=2026-08-01 to=2026-08-31 selects [2026-08-01T00:00Z, 2026-09-01T00:00Z). ", + "operationId": "getGuideMetrics", + "parameters": [ + { + "name": "organizationId", + "in": "query", + "description": "UUID v4 of the organization. ", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "from", + "in": "query", + "description": "Inclusive start of the calendar date range (YYYY-MM-DD, UTC).", + "required": true, + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "to", + "in": "query", + "description": "Inclusive end of the calendar date range (YYYY-MM-DD, UTC). Must not precede from.", + "required": true, + "schema": { + "type": "string", + "format": "date" + } + } + ], + "responses": { + "200": { + "description": "Guide impact metrics for the period", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GuideMetrics" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/cag/cag-usage-stats/{id}": { + "get": { + "tags": [ + "CagUsage" + ], + "summary": "Get CAG tool usage statistics for an organization", + "description": "Returns, for every project, the number of Context Augmentation tool calls recorded over the requested period. Response shape (id / projects[].id,name,toolCallCount) matches the existing SonarQube Cloud contract so clients are unaffected by which host answers the request. Projects with no usage in the period are still returned, with a toolCallCount of zero, and the list is ordered by toolCallCount descending. ", + "operationId": "getCagUsageStats", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "UUID v4 of the organization. On SonarQube Server, the `@OrganizationId` annotation resolves this value server-side to the caller's default organization id, so callers on that host may pass any syntactically valid UUID as a placeholder in the path — the value must still parse as a UUID, but its content is otherwise ignored. ", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "period", + "in": "query", + "description": "Period for the usage statistics", + "required": true, + "schema": { + "type": "string", + "enum": [ + "LAST_DAY", + "LAST_7_DAYS", + "LAST_30_DAYS" + ] + } + } + ], + "responses": { + "200": { + "description": "CAG usage statistics for the organization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CagUsageStats" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/cag/cag-entitlement/{id}": { + "get": { + "tags": [ + "CagEntitlement" + ], + "summary": "Get the CAG entitlement for an organization", + "description": "Returns the Context Augmentation entitlement decision for an organization. Response shape (allowed / hasEntitlement / featureKey / consumption) matches the existing SonarQube Cloud contract so clients are unaffected by which host answers the request. An unresolvable check (license unreadable, billing unreachable) is 503, not a not-entitled 200. ", + "operationId": "getCagEntitlement", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "UUID v4 of the organization. On SonarQube Server, the `@OrganizationId` annotation resolves this value server-side to the caller's default organization id, so callers on that host may pass any syntactically valid UUID as a placeholder in the path — the value must still parse as a UUID, but its content is otherwise ignored. ", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Entitlement check result", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CagEntitlement" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "503": { + "description": "Entitlement state could not be resolved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/billing/entitlement-checks": { + "get": { + "tags": [ + "Billing" + ], + "summary": "Returns the entitlement status for a given feature", + "description": " Returns live entitlement status for a single feature. Used to get entitlement status, including\n consumption and remaining quota for CONSUMABLE features. Requires authentication.\n", + "operationId": "getEntitlementCheck", + "parameters": [ + { + "name": "featureKey", + "in": "query", + "description": "Feature key to check entitlement for", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "resourceId", + "in": "query", + "description": "Organization ID, for compatibility with SQC", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "resourceType", + "in": "query", + "description": "Resource type, only 'organization' is supported, for compatibility with SQC", + "required": false, + "schema": { + "type": "string", + "default": "organization", + "enum": [ + "organization" + ] + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntitlementRestResponse" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/atlassian/auth-url": { + "get": { + "tags": [ + "Atlassian" + ], + "summary": "Generate Jira authentication URL", + "description": "Generate the authentication URL for Jira OAuth flow.\nRequires authenticated user.\n", + "operationId": "젔_21", + "parameters": [ + { + "name": "sonarOrganizationKey", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "sonarOrganizationUuid", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + }, + "x-sonar-internal": "true" + } + }, + "/architecture/structure": { + "get": { + "tags": [ + "structure" + ], + "summary": "Get structure graph for a branch and language", + "description": "Return the structure graph for a given organization, branch and ecosystem. All parameters are mandatory.", + "operationId": "structureGet", + "parameters": [ + { + "name": "organizationId", + "in": "query", + "description": "UUID v4 of the organization.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "branchId", + "in": "query", + "description": "branch Id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "ecosystem", + "in": "query", + "description": "Ecosystem/language of the graph (e.g. java, js, ts, py, cs).", + "required": true, + "schema": { + "type": "string", + "enum": [ + "java", + "js", + "ts", + "py", + "cs" + ] + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/StructureGraph" + } + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + } + }, + "/architecture/snapshots": { + "get": { + "tags": [ + "snapshots" + ], + "summary": "List snapshots for a project", + "description": "Return all snapshots for a project, sorted by analysis date descending. May be empty.", + "operationId": "snapshotsGet", + "parameters": [ + { + "name": "organizationId", + "in": "query", + "description": "UUID v4 of the organization.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "branchId", + "in": "query", + "description": "branch Id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "pageIndex", + "in": "query", + "description": "Page number for pagination (1-based). Specifies which page of results to return.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "minimum": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Number of items per page. Set to 0 to return only the total count without any items (useful for counting).", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SnapshotPagedResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + } + }, + "/architecture/project-configurations": { + "get": { + "tags": [ + "project-configurations" + ], + "summary": "Get project configurations", + "description": "Return project configurations for a given project. Used by the scanner to retrieve intended architecture and directives. External endpoint used by scanner clients. Exactly one of projectId or projectKey must be provided.", + "operationId": "projectConfigurationsGet", + "parameters": [ + { + "name": "projectId", + "in": "query", + "description": "Project Id (mutually exclusive with projectKey)", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "projectKey", + "in": "query", + "description": "Project Key (mutually exclusive with projectId)", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProjectConfiguration" + } + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + } + }, + "/architecture/organization-components": { + "get": { + "tags": [ + "organization-components" + ], + "summary": "List organization components", + "description": "Return the organization components (projects and placeholders) for the given organization. Placeholders are included only when the placeholder feature is enabled for the organization.", + "operationId": "organizationComponentsGet", + "parameters": [ + { + "name": "organizationId", + "in": "query", + "description": "UUID v4 of the organization.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationComponentListResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + } + }, + "/architecture/organization-components/{id}": { + "get": { + "tags": [ + "organization-components" + ], + "summary": "Get an organization component by id", + "description": "Return the organization component (project or placeholder) with the given id.", + "operationId": "organizationComponentsIdGet", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationComponent" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + } + }, + "/architecture/organization-architectures": { + "get": { + "tags": [ + "organization-architectures" + ], + "summary": "Get current architecture for an organization", + "description": "fetch the current architecture for the organization", + "operationId": "organizationArchitecturesGet", + "parameters": [ + { + "name": "organizationId", + "in": "query", + "description": "UUID v4 of the organization.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationArchitectureResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + } + }, + "/architecture/intended": { + "get": { + "tags": [ + "intended" + ], + "summary": "Export intended architecture for a project", + "description": "search intended architecture by project. Requires authentication and browse source permission to the project.", + "operationId": "intendedGet", + "parameters": [ + { + "name": "projectId", + "in": "query", + "description": "Project Id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IntendedModelExport" + } + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + } + }, + "/architecture/graphs": { + "get": { + "tags": [ + "graphs" + ], + "summary": "Return graphs metadata for an analysis", + "description": "Return graphs metadata for an analysis", + "operationId": "getGraphsMetadata", + "parameters": [ + { + "name": "organizationId", + "in": "query", + "description": "UUID v4 of the organization.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "analysisId", + "in": "query", + "description": "Analysis Id", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + }, + { + "name": "branchId", + "in": "query", + "description": "branch Id", + "required": false, + "schema": { + "type": [ + "string", + "null" + ] + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetGraphMetadataResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/architecture/graphs/{id}": { + "get": { + "tags": [ + "graphs" + ], + "summary": "Return the selected graph", + "description": "Return the selected graph", + "operationId": "getGraph", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/architecture/external-interfaces": { + "get": { + "tags": [ + "external-interfaces" + ], + "summary": "List external interfaces", + "description": "Allow searching for external interfaces within an organization, filtering by direction or organization component ID if provided.", + "operationId": "externalInterfacesGet", + "parameters": [ + { + "name": "organizationId", + "in": "query", + "description": "UUID v4 of the organization.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "componentId", + "in": "query", + "description": "The component whose external interfaces are returned. When omitted, the external interfaces of every component in the organization are returned.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "format": "uuid" + } + }, + { + "name": "direction", + "in": "query", + "description": "When set, only external interfaces of this direction (entry or exit) are returned.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "enum": [ + "EXIT_POINT", + "ENTRY_POINT" + ] + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExternalInterfaceListResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + } + }, + "/architecture/directives/{id}": { + "get": { + "tags": [ + "directives" + ], + "summary": "Get a directive by id", + "description": "Return a single directive by id", + "operationId": "directivesIdGet", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Directive" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + }, + "delete": { + "tags": [ + "directives" + ], + "summary": "Delete a directive", + "description": "Delete a directive by id", + "operationId": "directivesIdDelete", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Deleted successfully" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "lambdaAuthorizer": [] + } + ] + } + }, + "/analysis/version": { + "get": { + "tags": [ + "Analysis" + ], + "summary": "Server version", + "description": "Get the version of the Scanner Engine", + "operationId": "getVersion", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/analysis/jres": { + "get": { + "tags": [ + "Analysis" + ], + "summary": "All JREs metadata", + "description": "Get metadata of all available JREs", + "operationId": "getJresMetadata", + "parameters": [ + { + "name": "os", + "in": "query", + "description": "Filter the JRE by operating system. Accepted values are 'windows', 'linux', 'macos', 'alpine' (case-insensitive), with some aliases", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "arch", + "in": "query", + "description": "Filter the JRE by CPU architecture. Accepted values are 'x64' and 'aarch64' (case-insensitive), with some aliases.", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/JreInfoRestResponse" + } + } + } + } + } + } + } + }, + "/analysis/jres/{id}": { + "get": { + "tags": [ + "Analysis" + ], + "summary": "JRE download/metadata", + "description": "This endpoint return the JRE metadata by default. To download the JRE binary asset, set the Accept header of the request to 'application/octet-stream'.", + "operationId": "downloadJre", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The ID of the JRE", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/JreInfoRestResponse" + } + } + } + } + } + } + }, + "/analysis/engine": { + "get": { + "tags": [ + "Analysis" + ], + "summary": "Scanner engine download/metadata", + "description": "This endpoint return the Scanner Engine metadata by default. To download the Scanner Engine, set the Accept header of the request to 'application/octet-stream'.", + "operationId": "downloadScannerEngine", + "responses": { + "200": { + "description": "OK", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/EngineInfoRestResponse" + } + } + } + } + } + } + }, + "/analysis/active_rules": { + "get": { + "tags": [ + "Analysis" + ], + "summary": "Get all active rules for a specific project", + "description": "Used by the scanner-engine to get all active rules for a given project.", + "operationId": "getActiveRules", + "parameters": [ + { + "name": "projectKey", + "in": "query", + "description": "Project Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ActiveRule" + } + } + } + } + } + } + } + }, + "/agentic/jobs": { + "get": { + "tags": [ + "Agentic" + ], + "summary": "Search agentic jobs", + "description": "List agentic orchestration jobs (Hunter and Remediation agents), most recent first.\nRequires system administration permission.\n", + "operationId": "search_6", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "Comma-separated list of job ids to filter on.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "description": "Comma-separated list of job ids to filter on." + } + }, + { + "name": "status", + "in": "query", + "description": "Comma-separated list of statuses to filter on.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "description": "Comma-separated list of statuses to filter on.", + "enum": [ + "PENDING", + "IN_PROGRESS", + "COMPLETED", + "FAILED" + ] + } + }, + { + "name": "type", + "in": "query", + "description": "Comma-separated list of agent types to filter on.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "description": "Comma-separated list of agent types to filter on.", + "enum": [ + "HUNTER", + "REMEDIATION" + ] + } + }, + { + "name": "createdAfter", + "in": "query", + "description": "Only return jobs created on or after this date (or date-time), e.g. 2026-01-01 or 2026-01-01T12:00:00+0000.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "description": "Only return jobs created on or after this date (or date-time), e.g. 2026-01-01 or 2026-01-01T12:00:00+0000." + } + }, + { + "name": "createdBefore", + "in": "query", + "description": "Only return jobs created on or before this date (or date-time), e.g. 2026-01-01 or 2026-01-01T12:00:00+0000.", + "required": false, + "schema": { + "type": [ + "string", + "null" + ], + "description": "Only return jobs created on or before this date (or date-time), e.g. 2026-01-01 or 2026-01-01T12:00:00+0000." + } + }, + { + "name": "pageSize", + "in": "query", + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "description": "Number of results per page. A value of 0 will only return the pagination information.", + "maximum": 500, + "minimum": 0 + } + }, + { + "name": "pageIndex", + "in": "query", + "description": "1-based page index", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1, + "description": "1-based page index", + "exclusiveMinimum": 0, + "minimum": 1 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgenticJobsSearchRestResponse" + } + } + } + } + } + } + }, + "/agentic/jobs/logs": { + "get": { + "tags": [ + "Agentic" + ], + "summary": "Download agent job logs", + "description": "Download the stdout/stderr logs archive for a Hunter/Remediation agent job, as a zip file.\nRequires system administration permission.\n", + "operationId": "downloadJobLogs", + "parameters": [ + { + "name": "jobId", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/zip": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "/a3s/private/a3s-frontend-context/contexts/{id}": { + "get": { + "tags": [ + "Private Contexts" + ], + "summary": "Get a context", + "operationId": "getContext", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Unique identifier of a context", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Context" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "SigV4Auth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/a3s/private/a3s-frontend-context/collection-config": { + "get": { + "tags": [ + "Collection Config" + ], + "summary": "Get collection configuration for an organization/project (private)", + "operationId": "getPrivateCollectionConfig", + "parameters": [ + { + "name": "organizationKey", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "projectKey", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Collection configuration", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CollectionConfig" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "SigV4Auth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/a3s/private/a3s-analysis/org-entitlement/{id}": { + "get": { + "tags": [ + "OrgEntitlement" + ], + "summary": "Get the A3S entitlement for an organization (internal)", + "operationId": "getPrivateOrgEntitlement", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Organization Id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Entitlement check result forwarded from billing", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEntitlement" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "SigV4Auth": [] + } + ] + } + }, + "/a3s/org-entitlement/{id}": { + "get": { + "tags": [ + "OrgEntitlement" + ], + "summary": "Get the A3S entitlement for an organization", + "operationId": "getPublicOrgEntitlement", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Organization Id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Entitlement check result forwarded from billing", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEntitlement" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/a3s/collection-config": { + "get": { + "tags": [ + "Collection Config" + ], + "summary": "Get collection configuration for an organization/project", + "operationId": "getPublicCollectionConfig", + "parameters": [ + { + "name": "organizationKey", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "projectKey", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Collection configuration", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CollectionConfig" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/a3s/analysis-stats/{id}": { + "get": { + "tags": [ + "Analysis Stats" + ], + "summary": "Get analysis statistics for an organization", + "operationId": "getAnalysisStats", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Unique identifier of an organization", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "period", + "in": "query", + "description": "Period for the statistics", + "required": true, + "schema": { + "type": "string", + "enum": [ + "LAST_DAY", + "LAST_7_DAYS", + "LAST_30_DAYS" + ] + } + } + ], + "responses": { + "200": { + "description": "Analysis statistics retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AnalysisStats" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "security": [ + { + "TokenAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/sca/license-profiles/assigned-projects/{project-key}": { + "delete": { + "tags": [ + "License Profiles" + ], + "summary": "Remove the project's assignment to the license profile.", + "description": " Remove the project's assignment to the license profile. If there is a default license profile,\n it will be used to analyze license issues for this project.\n", + "operationId": "delete_2", + "parameters": [ + { + "name": "project-key", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-sonar-internal": "true" + } + }, + "/authorizations/group-memberships/{id}": { + "delete": { + "tags": [ + "Authorizations" + ], + "summary": "Remove a group membership", + "description": "Remove a user from a group", + "operationId": "delete_3", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The ID of the group membership to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + } + }, + "components": { + "schemas": { + "UpdateAssigneeRestRequest": { + "type": "object", + "properties": { + "issueReleaseKey": { + "type": "string", + "description": "Issue release key" + }, + "assigneeLogin": { + "type": "string", + "description": "Assignee login" + } + }, + "required": [ + "issueReleaseKey" + ] + }, + "AffectedPackageResource": { + "type": "object", + "properties": { + "purl": { + "type": "string" + }, + "recommendation": { + "type": "string", + "description": "The maintainer's overall recommendation, if available", + "enum": [ + "ignore", + "upgrade", + "upgrade_or_workaround" + ] + }, + "recommendationDetails": { + "$ref": "#/components/schemas/VulnerabilityRecommendationDetailsResource" + }, + "versionOptions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VersionOptionResource" + } + }, + "affectedVersions": { + "type": "array", + "items": { + "type": "string" + } + }, + "unaffectedVersions": { + "type": "string" + } + } + }, + "CveLocationResource": { + "type": "object", + "properties": { + "signature": { + "type": "string" + }, + "signatureType": { + "type": "string", + "enum": [ + "VULNERABLE_FUNCTION", + "PUBLIC_CALLER", + "ANCESTOR_CALLER" + ] + }, + "filePath": { + "type": "string" + }, + "startLine": { + "type": "integer", + "format": "int32" + }, + "startLineOffset": { + "type": "integer", + "format": "int32" + }, + "endLine": { + "type": "integer", + "format": "int32" + }, + "endLineOffset": { + "type": "integer", + "format": "int32" + } + } + }, + "CweResource": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + } + }, + "IssueReleaseBranchResource": { + "type": "object", + "properties": { + "uuid": { + "type": "string" + }, + "key": { + "type": "string" + }, + "pullRequest": { + "type": "boolean" + }, + "projectKey": { + "type": "string" + }, + "projectName": { + "type": "string" + }, + "legacyProjectUuid": { + "type": "string" + }, + "organizationUuid": { + "type": "string", + "format": "uuid" + } + } + }, + "IssueReleaseDetailsResource": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "severity": { + "type": "string" + }, + "originalSeverity": { + "type": "string" + }, + "manualSeverity": { + "type": "string" + }, + "showIncreasedSeverityWarning": { + "type": "boolean" + }, + "hasReachableLocations": { + "type": "boolean" + }, + "release": { + "$ref": "#/components/schemas/ReleaseSearchResource" + }, + "type": { + "type": "string", + "enum": [ + "VULNERABILITY", + "PROHIBITED_LICENSE", + "MALWARE" + ] + }, + "quality": { + "type": "string", + "enum": [ + "MAINTAINABILITY", + "RELIABILITY", + "SECURITY" + ] + }, + "status": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "assignee": { + "$ref": "#/components/schemas/UserResource" + }, + "commentCount": { + "type": "integer", + "format": "int32" + }, + "vulnerability": { + "$ref": "#/components/schemas/VulnerabilityResource" + }, + "spdxLicenseId": { + "type": "string" + }, + "transitions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "CONFIRM", + "REOPEN", + "SAFE", + "FIXED", + "ACCEPT" + ] + } + }, + "actions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "COMMENT", + "ASSIGN", + "SET_SEVERITY" + ] + }, + "uniqueItems": true + }, + "branch": { + "$ref": "#/components/schemas/IssueReleaseBranchResource" + } + } + }, + "ReleaseSearchResource": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "key": { + "type": "string" + }, + "branchId": { + "type": "string" + }, + "branchUuid": { + "type": "string", + "deprecated": true + }, + "packageUrl": { + "type": "string" + }, + "packageManager": { + "type": "string" + }, + "packageName": { + "type": "string" + }, + "version": { + "type": "string" + }, + "licenseExpression": { + "type": "string" + }, + "known": { + "type": "boolean" + }, + "knownPackage": { + "type": "boolean" + }, + "newlyIntroduced": { + "type": "boolean" + }, + "directSummary": { + "type": "boolean" + }, + "scopeSummary": { + "type": "string" + }, + "productionScopeSummary": { + "type": "boolean" + }, + "scopes": { + "type": "array", + "items": { + "type": "string" + } + }, + "dependencyFilePaths": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "UserResource": { + "type": "object", + "properties": { + "login": { + "type": "string" + }, + "name": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "active": { + "type": "boolean" + } + } + }, + "VersionOptionResource": { + "type": "object", + "properties": { + "version": { + "type": "string", + "description": "The version being presented as an option" + }, + "vulnerabilityIds": { + "type": "array", + "description": "Vulnerability IDs affecting this version", + "items": { + "type": "string" + } + }, + "prerelease": { + "type": "boolean", + "description": "Is this version a pre-release version" + }, + "fixLevel": { + "type": "string", + "description": "Describes which vulnerabilities are fixed", + "enum": [ + "COMPLETE", + "PARTIAL", + "NONE", + "UNKNOWN" + ] + }, + "descriptionCode": { + "type": "string", + "description": "How the frontend should label this version", + "enum": [ + "VERSION_IN_USE", + "NEAREST_PARTIAL", + "NEAREST_COMPLETE", + "LATEST_PARTIAL", + "LATEST_COMPLETE", + "LATEST_STABLE", + "LATEST_PRERELEASE", + "UNKNOWN" + ] + } + } + }, + "VulnerabilityRecommendationDetailsResource": { + "type": "object", + "properties": { + "impactScore": { + "type": "integer", + "format": "int32" + }, + "impactDescription": { + "type": "string" + }, + "realIssue": { + "type": "boolean" + }, + "falsePositiveReason": { + "type": "string" + }, + "includesDev": { + "type": "boolean" + }, + "specificMethodsAffected": { + "type": "boolean" + }, + "specificMethodsDescription": { + "type": "string" + }, + "otherConditions": { + "type": "boolean" + }, + "otherConditionsDescription": { + "type": "string" + }, + "workaroundAvailable": { + "type": "boolean" + }, + "workaroundDescription": { + "type": "string" + }, + "visibility": { + "type": "string" + } + } + }, + "VulnerabilityReportResource": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "url": { + "type": "string" + }, + "type": { + "type": "string" + }, + "cvssScore": { + "type": "string" + }, + "cvssSeverity": { + "type": "string" + }, + "withdrawnAt": { + "type": "string" + } + } + }, + "VulnerabilityResource": { + "type": "object", + "properties": { + "vulnerabilityId": { + "type": "string" + }, + "reports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VulnerabilityReportResource" + } + }, + "description": { + "type": "string" + }, + "epssPercentile": { + "type": "string" + }, + "epssProbability": { + "type": "string" + }, + "knownExploited": { + "type": "boolean" + }, + "cweIds": { + "type": "array", + "deprecated": true, + "items": { + "type": "string", + "deprecated": true + } + }, + "cwes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CweResource" + } + }, + "publishedOn": { + "type": "string" + }, + "affectedPackages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AffectedPackageResource" + } + }, + "withdrawn": { + "type": "boolean" + }, + "reachableLocations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CveLocationResource" + } + } + } + }, + "IssueReleaseStatusTransitionRequest": { + "type": "object", + "properties": { + "issueReleaseKey": { + "type": "string", + "description": "Issue release key" + }, + "transitionKey": { + "type": "string", + "description": "Transition key", + "enum": [ + "CONFIRM", + "REOPEN", + "SAFE", + "FIXED", + "ACCEPT" + ] + }, + "comment": { + "type": "string", + "description": "Transition comment" + } + }, + "required": [ + "issueReleaseKey", + "transitionKey" + ] + }, + "ScheduledAgentConfigUpdateRestRequest": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "frequency": { + "type": "string" + }, + "hour": { + "type": "integer", + "format": "int32" + }, + "dayOfWeek": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "maxIssuesPerRun": { + "type": "integer", + "format": "int32" + }, + "maxOpenPRs": { + "type": "integer", + "format": "int32" + }, + "projectSelectionMode": { + "type": "string" + }, + "selectedProjectKeys": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + } + } + }, + "ScheduledAgentConfigResource": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "projectKey": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "frequency": { + "type": "string" + }, + "hour": { + "type": "integer", + "format": "int32" + }, + "dayOfWeek": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "nextRunUtc": { + "type": "integer", + "format": "int64" + }, + "maxIssuesPerRun": { + "type": "integer", + "format": "int32" + }, + "maxOpenPRs": { + "type": "integer", + "format": "int32" + }, + "projectSelectionMode": { + "type": "string" + }, + "selectedProjectKeys": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + } + } + }, + "BoundProjectCreateRestRequest": { + "type": "object", + "properties": { + "projectKey": { + "type": "string", + "description": "Key of the project to create", + "minLength": 1 + }, + "projectName": { + "type": "string", + "description": "Name of the project to create", + "minLength": 1 + }, + "devOpsPlatformSettingId": { + "type": "string", + "description": "Identifier of DevOps platform configuration to use. Use /dop-translation/dop-settings to retrieve the settings and their ID", + "minLength": 1 + }, + "repositoryIdentifier": { + "type": "string", + "description": "Identifier of the DevOps platform repository to import:\n- repository slug for GitHub and Bitbucket (Cloud and Server)\n- repository id for GitLab\n- repository name for Azure DevOps\n", + "minLength": 1 + }, + "projectIdentifier": { + "type": [ + "string", + "null" + ], + "description": "Identifier of the DevOps platform project in which the repository is located.\nThis is only needed for Azure and BitBucket Server platforms\n" + }, + "newCodeDefinitionType": { + "type": [ + "string", + "null" + ], + "description": " Project New Code Definition Type\n New code definitions of the following types are allowed:\n - PREVIOUS_VERSION\n - NUMBER_OF_DAYS\n - REFERENCE_BRANCH - will default to the main branch.\n" + }, + "newCodeDefinitionValue": { + "type": [ + "string", + "null" + ], + "description": " Project New Code Definition Value\n For each new code definition type, a different value is expected:\n - no value, when the new code definition type is PREVIOUS_VERSION and REFERENCE_BRANCH\n - a number between 1 and 90, when the new code definition type is NUMBER_OF_DAYS\n" + }, + "monorepo": { + "type": "boolean", + "description": "True if project is part of a mono repo." + }, + "summaryCommentEnabled": { + "type": [ + "boolean", + "null" + ], + "description": "For GitHub bindings only: enable/disable posting the analysis summary as a Pull Request comment. When rebinding an existing project, omit to preserve the current value. Defaults to true on new bindings." + }, + "inlineAnnotationsEnabled": { + "type": [ + "boolean", + "null" + ], + "description": "For Azure DevOps bindings only: enable/disable inline annotations during Pull Request decoration. When rebinding an existing project, omit to preserve the current value. Defaults to true on new bindings." + } + }, + "required": [ + "devOpsPlatformSettingId", + "monorepo", + "projectKey", + "projectName", + "repositoryIdentifier" + ] + }, + "BoundProjectCreateRestResponse": { + "type": "object", + "properties": { + "projectId": { + "type": "string", + "description": "The identifier of the created project" + }, + "bindingId": { + "type": "string", + "description": "The identifier of the binding between the created project and the DevOps platform project" + }, + "newProjectCreated": { + "type": "boolean", + "description": "True if a new project was created, false if an existing project was bound" + } + } + }, + "UserCreateRestRequest": { + "type": "object", + "properties": { + "email": { + "type": [ + "string", + "null" + ], + "format": "email", + "description": "User email", + "maxLength": 100, + "minLength": 1 + }, + "local": { + "type": [ + "boolean", + "null" + ], + "default": true, + "description": "Specify if the user should be authenticated from SonarQube server or from an external authentication system. Password should not be set when local is set to false." + }, + "login": { + "type": "string", + "description": "User login", + "maxLength": 100, + "minLength": 2 + }, + "name": { + "type": "string", + "description": "User name", + "maxLength": 200, + "minLength": 0 + }, + "password": { + "type": [ + "string", + "null" + ], + "description": "User password. Only mandatory when creating local user, otherwise it should not be set", + "writeOnly": true + }, + "scmAccounts": { + "type": [ + "array", + "null" + ], + "description": "List of SCM accounts.", + "items": { + "type": "string" + } + } + }, + "required": [ + "login", + "name" + ] + }, + "UserRestResponseForAdmins": { + "type": "object", + "properties": { + "id": { + "type": "string", + "readOnly": true + }, + "login": { + "type": "string" + }, + "name": { + "type": "string" + }, + "email": { + "type": [ + "string", + "null" + ] + }, + "active": { + "type": [ + "boolean", + "null" + ], + "readOnly": true + }, + "local": { + "type": [ + "boolean", + "null" + ], + "readOnly": true + }, + "managed": { + "type": [ + "boolean", + "null" + ], + "readOnly": true + }, + "externalLogin": { + "type": [ + "string", + "null" + ] + }, + "externalProvider": { + "type": [ + "string", + "null" + ] + }, + "externalId": { + "type": [ + "string", + "null" + ] + }, + "avatar": { + "type": [ + "string", + "null" + ] + }, + "sonarQubeLastConnectionDate": { + "type": [ + "string", + "null" + ], + "readOnly": true + }, + "sonarLintLastConnectionDate": { + "type": [ + "string", + "null" + ], + "readOnly": true + }, + "scmAccounts": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + } + }, + "EmailConfigurationCreateRestRequest": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "URL of your SMTP server", + "minLength": 1 + }, + "port": { + "type": "string", + "description": "Port of your SMTP server (usually 25, 587 or 465)", + "minLength": 1 + }, + "securityProtocol": { + "type": "string", + "description": "Security protocol used to connect to your SMTP server (SSLTLS is recommended)", + "enum": [ + "NONE", + "SSLTLS", + "STARTTLS" + ] + }, + "fromAddress": { + "type": "string", + "description": "Address emails will come from", + "minLength": 1 + }, + "fromName": { + "type": "string", + "description": "Name emails will come from (usually \"SonarQube\")", + "minLength": 1 + }, + "subjectPrefix": { + "type": "string", + "description": "Prefix added to email so they can be easily recognized (usually \"[SonarQube]\")", + "minLength": 1 + }, + "authMethod": { + "type": "string", + "description": "Authentication method used to connect to the SMTP server. OAuth is only supported for Microsoft Exchange", + "enum": [ + "BASIC", + "OAUTH" + ] + }, + "username": { + "type": "string", + "description": "For Basic and OAuth authentication: username used to authenticate to the SMTP server", + "minLength": 1 + }, + "basicPassword": { + "type": [ + "string", + "null" + ], + "description": "For basic authentication: password used to authenticate to the SMTP server", + "writeOnly": true + }, + "oauthAuthenticationHost": { + "type": [ + "string", + "null" + ], + "description": "For OAuth authentication: host of the Identity Provider issuing access tokens" + }, + "oauthClientId": { + "type": [ + "string", + "null" + ], + "description": "For OAuth authentication: Client ID provided by Microsoft Exchange when registering the application", + "writeOnly": true + }, + "oauthClientSecret": { + "type": [ + "string", + "null" + ], + "description": "For OAuth authentication: Client secret provided by Microsoft Exchange when registering the application", + "writeOnly": true + }, + "oauthTenant": { + "type": [ + "string", + "null" + ], + "description": "For OAuth authentication: Microsoft tenant" + }, + "oauthScope": { + "type": [ + "string", + "null" + ], + "description": "For OAuth authentication: scope for the OAuth token request. Defaults to 'https://outlook.office365.com/.default'" + } + }, + "required": [ + "authMethod", + "fromAddress", + "fromName", + "host", + "port", + "securityProtocol", + "subjectPrefix", + "username" + ] + }, + "EmailConfigurationResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "readOnly": true + }, + "host": { + "type": "string", + "description": "URL of your SMTP server" + }, + "port": { + "type": "string", + "description": "Port of your SMTP server (usually 25, 587 or 465)" + }, + "securityProtocol": { + "type": "string", + "description": "Security protocol used to connect to your SMTP server (SSLTLS is recommended)", + "enum": [ + "NONE", + "SSLTLS", + "STARTTLS" + ] + }, + "fromAddress": { + "type": "string", + "description": "Address emails will come from" + }, + "fromName": { + "type": "string", + "description": "Name emails will come from (usually \"SonarQube\")" + }, + "subjectPrefix": { + "type": "string", + "description": "Prefix added to email so they can be easily recognized (usually \"[SonarQube]\")" + }, + "authMethod": { + "type": "string", + "description": "Authentication method used to connect to the SMTP server. OAuth is only supported for Microsoft Exchange", + "enum": [ + "BASIC", + "OAUTH" + ] + }, + "username": { + "type": [ + "string", + "null" + ], + "description": "For Basic and OAuth authentication: username used to authenticate to the SMTP server" + }, + "isBasicPasswordSet": { + "type": "boolean", + "description": "For Basic authentication: has the password field been set?" + }, + "oauthAuthenticationHost": { + "type": [ + "string", + "null" + ], + "description": "For OAuth authentication: host of the Identity Provider issuing access tokens" + }, + "isOauthClientIdSet": { + "type": "boolean", + "description": "For OAuth authentication: has the Client ID field been set?" + }, + "isOauthClientSecretSet": { + "type": "boolean", + "description": "For OAuth authentication: has the Client secret field been set?" + }, + "oauthTenant": { + "type": [ + "string", + "null" + ], + "description": "For OAuth authentication: Microsoft tenant" + }, + "oauthScope": { + "type": [ + "string", + "null" + ], + "description": "For OAuth authentication: scope for the OAuth token request" + } + } + }, + "ReleaseResearchRestRequest": { + "type": "object", + "properties": { + "projectKey": { + "type": "string", + "description": "Key of the project to research", + "minLength": 1 + }, + "purls": { + "type": "array", + "description": "Versioned package URLs (PURLs) to research. Maximum 100 per request.", + "items": { + "type": "string" + } + } + }, + "required": [ + "projectKey", + "purls" + ] + }, + "ReleaseResearchEntryResponse": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "packageUrl": { + "type": "string" + }, + "packageManager": { + "type": "string" + }, + "packageName": { + "type": "string" + }, + "version": { + "type": "string" + }, + "licenseExpression": { + "type": "string" + }, + "known": { + "type": "boolean" + }, + "knownPackage": { + "type": "boolean" + }, + "newlyIntroduced": { + "type": "boolean" + }, + "issues": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReleaseResearchIssueResponse" + } + } + } + }, + "ReleaseResearchIssueResponse": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "severity": { + "type": "string" + }, + "showIncreasedSeverityWarning": { + "type": "boolean" + }, + "type": { + "type": "string", + "enum": [ + "VULNERABILITY", + "PROHIBITED_LICENSE", + "MALWARE" + ] + }, + "quality": { + "type": "string", + "enum": [ + "MAINTAINABILITY", + "RELIABILITY", + "SECURITY" + ] + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "ACCEPT", + "CONFIRM", + "SAFE", + "FIXED" + ] + }, + "vulnerabilityId": { + "type": "string" + }, + "cweIds": { + "type": "array", + "deprecated": true, + "items": { + "type": "string", + "deprecated": true + } + }, + "cwes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CweResource" + } + }, + "cvssScore": { + "type": "string" + }, + "spdxLicenseId": { + "type": "string" + }, + "versionOptions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VersionOptionResource" + } + } + } + }, + "ReleaseResearchRestResponse": { + "type": "object", + "properties": { + "releases": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReleaseResearchEntryResponse" + } + } + } + }, + "TriggerReanalysisRestRequest": { + "type": "object", + "properties": { + "projectKey": { + "type": "string", + "description": "Key of the project to re-analyze", + "minLength": 1 + }, + "branchKey": { + "type": "string", + "description": "Key of the branch to re-analyze. If not provided, all branches matching the organization's rescan branch type setting will be queued." + } + }, + "required": [ + "projectKey" + ] + }, + "TriggerReanalysisResource": { + "type": "object", + "properties": { + "branchesQueued": { + "type": "integer", + "format": "int32" + } + } + }, + "PackageInfoRestRequest": { + "type": "object", + "properties": { + "projectKey": { + "type": "string", + "description": "Key of the project to analyze", + "minLength": 1 + }, + "purls": { + "type": "array", + "description": "Package URLs (PURLs) to analyze. Maximum 100 per request. Either 'purls' or the deprecated 'purl' field must be provided.", + "items": { + "type": "string" + } + }, + "purl": { + "type": "string", + "writeOnly": true + }, + "includeVulnerabilityDetails": { + "type": "boolean", + "description": "Whether to include vulnerability details in the response" + } + }, + "required": [ + "projectKey" + ] + }, + "LicenseResponse": { + "type": "object", + "properties": { + "expression": { + "type": "string" + }, + "allowed": { + "type": "boolean" + } + } + }, + "PackageInfoEntryResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/PackageInfoResult" + }, + { + "type": "object", + "properties": { + "purl": { + "type": "string" + }, + "license": { + "$ref": "#/components/schemas/LicenseResponse" + }, + "vulnerabilities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PackageVulnerabilityResponse" + } + }, + "malicious": { + "type": "boolean" + }, + "knownPackage": { + "type": "boolean" + }, + "knownRelease": { + "type": "boolean" + } + } + } + ] + }, + "PackageInfoRestResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/PackageInfoResult" + }, + { + "type": "object", + "properties": { + "packages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PackageInfoEntryResponse" + } + } + } + } + ] + }, + "PackageInfoResult": {}, + "PackageVulnerabilityResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "cvssScore": { + "type": "number" + }, + "cweIds": { + "type": "array", + "deprecated": true, + "items": { + "type": "string", + "deprecated": true + } + }, + "cwes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CweResource" + } + }, + "riskSeverity": { + "type": "string" + }, + "withdrawn": { + "type": "boolean" + }, + "publishedOn": { + "type": "string" + }, + "fixedVersions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VersionFix" + } + }, + "unaffectedVersions": { + "type": "string" + } + } + }, + "VersionFix": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "fixLevel": { + "type": "string" + }, + "descriptionCode": { + "type": "string" + } + } + }, + "LicenseProfilesCreateRestRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the license policy" + }, + "default": { + "type": "boolean", + "description": "Whether this license policy is the default or not" + }, + "organization": { + "type": "string" + } + }, + "required": [ + "name", + "organization" + ] + }, + "LicenseProfileResource": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "default": { + "type": "boolean" + }, + "actions": { + "$ref": "#/components/schemas/LicenseProfileSingleActions" + }, + "updatedAt": { + "type": "string" + } + } + }, + "LicenseProfileSingleActions": { + "type": "object", + "properties": { + "edit": { + "type": "boolean" + }, + "setAsDefault": { + "type": "boolean" + }, + "associateProjects": { + "type": "boolean" + }, + "delete": { + "type": "boolean" + } + } + }, + "IssueReleaseSetSeverityRestRequest": { + "type": "object", + "properties": { + "issueReleaseKey": { + "type": "string", + "description": "Issue release key" + }, + "quality": { + "type": "string", + "description": "Software Quality", + "enum": [ + "MAINTAINABILITY", + "RELIABILITY", + "SECURITY" + ] + }, + "severity": { + "type": "string", + "description": "Severity", + "enum": [ + "INFO", + "LOW", + "MEDIUM", + "HIGH", + "BLOCKER" + ] + } + } + }, + "IssueReleaseAddCommentRestRequest": { + "type": "object", + "properties": { + "issueReleaseKey": { + "type": "string", + "description": "Issue release key" + }, + "comment": { + "type": "string", + "description": "comment" + } + }, + "required": [ + "issueReleaseKey" + ] + }, + "BulkIssueReleaseChangeRequest": { + "type": "object", + "properties": { + "issueReleaseKeys": { + "type": "array", + "description": "Dependency risk identifiers", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "severity": { + "type": "string", + "description": "Severity assignment", + "enum": [ + "BLOCKER", + "HIGH", + "MEDIUM", + "LOW", + "INFO" + ] + }, + "transitionKey": { + "type": "string", + "description": "Transition key", + "enum": [ + "ACCEPT", + "CONFIRM", + "REOPEN", + "SAFE" + ] + }, + "comment": { + "type": "string", + "description": "Transition comment" + }, + "assignee": { + "type": "string", + "description": "Assignee username" + }, + "shouldUnsetAssignee": { + "type": "boolean", + "description": "Whether to unset the current assignee" + } + }, + "required": [ + "issueReleaseKeys" + ] + }, + "AnalysesParseMultipartBody": { + "type": "object", + "description": "Multipart body for the parse endpoint", + "properties": { + "dependencyFiles": { + "type": "string", + "format": "binary", + "description": "Tar-xz archive containing dependency manifests and lockfiles." + } + } + }, + "AnalysisErrorResource": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "UNKNOWN", + "NO_DEPENDENCIES_FOUND", + "DEPENDENCY_FILES_PARSE_ERROR", + "UNSUPPORTED_PLATFORM", + "INEXACT_VERSIONS", + "MISSING_LOCKFILE" + ] + }, + "path": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "ReadDependencyFilesResult": { + "type": "object", + "properties": { + "packages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReadDependencyFilesResultEntry" + } + }, + "parsedFiles": { + "type": "array", + "items": { + "type": "string" + } + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnalysisErrorResource" + } + } + } + }, + "ReadDependencyFilesResultEntry": { + "type": "object", + "properties": { + "packageUrl": { + "type": "string" + }, + "dependencyFilePaths": { + "type": "array", + "items": { + "type": "string" + } + }, + "dependencyChains": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "ScheduledAgentConfigCreateRestRequest": { + "type": "object", + "properties": { + "projectKey": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "frequency": { + "type": "string" + }, + "hour": { + "type": "integer", + "format": "int32" + }, + "dayOfWeek": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "maxIssuesPerRun": { + "type": "integer", + "format": "int32" + }, + "maxOpenPRs": { + "type": "integer", + "format": "int32" + }, + "projectSelectionMode": { + "type": "string" + }, + "selectedProjectKeys": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + } + } + }, + "Job": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "projectId": { + "type": [ + "string", + "null" + ] + }, + "branchId": { + "type": [ + "string", + "null" + ], + "description": "Stable identifier of the branch the job ran on." + }, + "status": { + "type": "string", + "enum": [ + "QUEUED", + "IN_PROGRESS", + "COMPLETED", + "FAILED", + "SKIPPED" + ] + }, + "subStatus": { + "type": [ + "string", + "null" + ], + "description": "Present only when a finer-grained phase is known; status remains the source of truth.", + "enum": [ + "AWAITING_FINDINGS", + "CLONING", + "EXPLORING", + "ANALYZING", + "CONSOLIDATING", + "REPORTING", + "CLONE_FAILED", + "AGENT_FAILED", + "LLM_NOT_ACCESSIBLE", + "ANALYSIS_TIMEOUT", + "RUNTIME_UNAVAILABLE", + "RESULT_PUBLICATION_FAILED", + "INSUFFICIENT_CREDITS", + "NCLOC_UNAVAILABLE", + "UNSUPPORTED_SCM_PROVIDER", + "SCM_CREDENTIALS_REJECTED", + "SCM_BRANCH_NOT_FOUND", + "PRIOR_FINDINGS_UNUSABLE" + ] + }, + "analysisType": { + "type": [ + "string", + "null" + ], + "enum": [ + "FULL", + "INCREMENTAL" + ] + }, + "triggerType": { + "type": [ + "string", + "null" + ], + "enum": [ + "ON_DEMAND", + "SCHEDULED" + ] + }, + "startTime": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "description": "Epoch millis when the job started; null while PENDING." + }, + "durationSeconds": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "consumedCredits": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "description": "Credits submitted to Billing for this run. Zero when the terminal run was not chargeable; null while non-terminal, for legacy runs, or when billing publication failed. ", + "minimum": 0 + }, + "requestTime": { + "type": "integer", + "format": "int64" + }, + "updatedAt": { + "type": [ + "integer", + "null" + ], + "format": "int64" + } + }, + "required": [ + "id", + "requestTime", + "status" + ] + }, + "CreatePullRequestJobRequest": { + "type": "object", + "properties": { + "projectKey": { + "type": "string", + "description": "Key of the SonarQube project to remediate." + }, + "pullRequestKey": { + "type": "string", + "description": "Key of the pull request to scope the remediation to." + }, + "repositoryUrl": { + "type": [ + "string", + "null" + ], + "description": "Clone URL of the repository to remediate. When omitted, it is resolved from the project's configuration. " + }, + "issueKeys": { + "type": "array", + "description": "SonarQube issue keys (scoped to the pull request) selected by the caller for remediation. Every key must belong to projectKey, or the request is rejected. Forwarded to the orchestrator, which enriches them into full issue metadata at dispatch time. ", + "items": { + "type": "string" + }, + "maxItems": 2147483647, + "minItems": 1 + } + }, + "required": [ + "issueKeys", + "projectKey", + "pullRequestKey" + ] + }, + "CreateBacklogJobRequest": { + "type": "object", + "properties": { + "projectKey": { + "type": "string", + "description": "Key of the SonarQube project to remediate." + }, + "repositoryUrl": { + "type": [ + "string", + "null" + ], + "description": "Clone URL of the repository to remediate. When omitted, it is resolved from the project's configuration. " + }, + "branch": { + "type": [ + "string", + "null" + ], + "description": "Branch to remediate. Defaults to the repository's default branch. Required (must be non-blank) when agentTask is HUNTER_FIX, since the finding's branch is not otherwise known. " + }, + "issueKeys": { + "type": "array", + "description": "SonarQube issue keys selected by the caller for remediation. Every key must belong to projectKey, or the request is rejected. Forwarded to the orchestrator, which enriches them into full issue metadata at dispatch time. ", + "items": { + "type": "string" + }, + "maxItems": 2147483647, + "minItems": 1 + }, + "agentTask": { + "type": "string", + "description": "Which agent subcommand to run for this job. SONAR_REPORT (the default, and the value assumed when the field is absent) is the existing multi-issue Sonar-issue flow. HUNTER_FIX runs the hunter-fix subcommand against a single Hunter Agent finding and requires exactly one entry in issueKeys and a non-blank branch. ", + "enum": [ + "SONAR_REPORT", + "HUNTER_FIX" + ] + } + }, + "required": [ + "issueKeys", + "projectKey" + ] + }, + "AzureBillingRestResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "message": { + "type": [ + "string", + "null" + ] + } + } + }, + "LlmProviderCreateRequest": { + "type": "object", + "properties": { + "provider": { + "type": "string", + "enum": [ + "AZURE_OPENAI", + "AWS_BEDROCK", + "AWS_BEDROCK_MANTLE", + "VERTEX_AI", + "CUSTOM_PROXY", + "OPENAI", + "ANTHROPIC" + ] + }, + "label": { + "type": "string" + }, + "configuration": { + "type": "object", + "additionalProperties": {} + } + } + }, + "LlmProviderResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "provider": { + "type": "string", + "enum": [ + "AZURE_OPENAI", + "AWS_BEDROCK", + "AWS_BEDROCK_MANTLE", + "VERTEX_AI", + "CUSTOM_PROXY", + "OPENAI", + "ANTHROPIC" + ] + }, + "label": { + "type": "string" + }, + "configuration": { + "type": "object", + "additionalProperties": {} + } + } + }, + "ProviderValidationCreateRequest": { + "type": "object", + "properties": { + "llmProviderId": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "llmProviderId" + ] + }, + "ProviderValidationResponse": { + "type": "object", + "properties": { + "llmProviderId": { + "type": "string" + }, + "isValid": { + "type": "boolean" + }, + "error": { + "type": [ + "string", + "null" + ] + } + } + }, + "ProviderMappingUpsertRequest": { + "type": "object", + "properties": { + "aiCapability": { + "type": "string", + "enum": [ + "AI_CODEFIX", + "HUNTER_AGENT", + "REMEDIATION_AGENT" + ] + }, + "llmProviderId": { + "type": "string" + }, + "modelIdentifier": { + "type": "string", + "maxLength": 255, + "minLength": 0 + }, + "secondaryModelIdentifier": { + "type": [ + "string", + "null" + ], + "maxLength": 255, + "minLength": 0 + } + }, + "required": [ + "aiCapability", + "llmProviderId", + "modelIdentifier" + ] + }, + "ProviderMappingResponse": { + "type": "object", + "properties": { + "aiCapability": { + "type": "string", + "enum": [ + "AI_CODEFIX", + "HUNTER_AGENT", + "REMEDIATION_AGENT" + ] + }, + "llmProviderId": { + "type": [ + "string", + "null" + ] + }, + "modelIdentifier": { + "type": [ + "string", + "null" + ] + }, + "secondaryModelIdentifier": { + "type": [ + "string", + "null" + ] + } + } + }, + "PostJiraWorkItemRequestResource": { + "type": "object", + "properties": { + "resourceId": { + "type": "string", + "description": "Resource identifier", + "minLength": 1, + "writeOnly": true + }, + "resourceType": { + "type": "string", + "description": "Resource type", + "enum": [ + "SONAR_ISSUE", + "DEPENDENCY_RISK" + ], + "writeOnly": true + }, + "sonarProjectId": { + "type": "string", + "description": "Sonar project identifier", + "minLength": 1, + "writeOnly": true + }, + "workTypeId": { + "type": "string", + "description": "Work type identifier", + "minLength": 1, + "writeOnly": true + }, + "summary": { + "type": [ + "string", + "null" + ], + "description": "Work item summary", + "writeOnly": true + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "Work item description", + "writeOnly": true + } + }, + "required": [ + "resourceId", + "resourceType", + "sonarProjectId", + "workTypeId" + ] + }, + "JiraWorkItemResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Work item identifier", + "readOnly": true + }, + "jiraIssueId": { + "type": "string", + "description": "Jira issue identifier", + "readOnly": true + }, + "jiraIssueKey": { + "type": "string", + "description": "Jira issue key", + "readOnly": true + }, + "jiraIssueUrl": { + "type": "string", + "description": "Jira issue URL", + "readOnly": true + }, + "jiraIssueStatus": { + "type": [ + "string", + "null" + ], + "description": "Jira issue status", + "readOnly": true + } + } + }, + "PostJiraProjectBindingRequestResource": { + "type": "object", + "properties": { + "sonarProjectId": { + "type": "string", + "description": "Sonar project identifier", + "minLength": 1, + "writeOnly": true + }, + "jiraOrganizationId": { + "type": "string", + "description": "Jira instance binding identifier", + "writeOnly": true + }, + "jiraProjectKey": { + "type": "string", + "description": "Jira project key", + "minLength": 1, + "writeOnly": true + } + }, + "required": [ + "jiraOrganizationId", + "jiraProjectKey", + "sonarProjectId" + ] + }, + "JiraProjectBindingResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Jira project binding identifier", + "readOnly": true + }, + "sonarProjectId": { + "type": "string", + "description": "Sonar project identifier", + "readOnly": true + }, + "jiraOrganizationId": { + "type": "string", + "description": "Jira instance binding identifier", + "readOnly": true + }, + "jiraProjectKey": { + "type": "string", + "description": "Jira project key", + "readOnly": true + } + } + }, + "PostJiraOrganizationBindingRequestResource": { + "type": "object", + "properties": { + "state": { + "type": "string", + "description": "Base64-encoded OAuth state parameter", + "minLength": 1, + "writeOnly": true + }, + "authorizationCode": { + "type": "string", + "description": "OAuth authorization code (no whitespace)", + "minLength": 1, + "writeOnly": true + } + }, + "required": [ + "authorizationCode", + "state" + ] + }, + "JiraOrganizationBindingResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Jira instance binding identifier", + "readOnly": true + }, + "sonarOrganizationUuid": { + "type": "string", + "format": "uuid", + "description": "Sonar organization UUID", + "readOnly": true + }, + "jiraInstanceUrl": { + "type": "string", + "description": "Jira instance URL", + "readOnly": true + }, + "createdBy": { + "type": "string", + "description": "User who created or reauthorized the jira connection", + "readOnly": true + }, + "createdAt": { + "type": "integer", + "format": "int64", + "description": "Creation timestamp", + "readOnly": true + }, + "isTokenShared": { + "type": "boolean", + "description": "TODO", + "readOnly": true + } + } + }, + "OAuthResource": { + "type": "object", + "properties": { + "cloudId": { + "type": "string", + "description": "The resource's cloud ID", + "readOnly": true + }, + "url": { + "type": "string", + "description": "The resource's URL", + "readOnly": true + } + } + }, + "PostJiraOrganizationBindingResponseResource": { + "type": "object", + "properties": { + "binding": { + "$ref": "#/components/schemas/JiraOrganizationBindingResource", + "description": "The successfully created binding (present if binding was created)", + "readOnly": true + }, + "resources": { + "type": "array", + "description": "Array of available resources to bind the instance to", + "items": { + "$ref": "#/components/schemas/OAuthResource" + }, + "readOnly": true + } + }, + "required": [ + "resources" + ] + }, + "UserBindingCreationRequest": { + "type": "object", + "description": "User binding creation request", + "properties": { + "bindingData": { + "$ref": "#/components/schemas/UserBindingCreationRequestBindingData" + }, + "userId": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "bindingData", + "userId" + ] + }, + "UserBindingCreationRequestBindingData": { + "type": "object", + "properties": { + "code": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "code" + ] + }, + "UserBindingResponse": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "user_id": { + "type": "string" + }, + "slack_user_id": { + "type": "string" + }, + "slack_workspace_id": { + "type": "string" + }, + "slack_workspace_name": { + "type": "string" + }, + "created_at": { + "type": "integer", + "format": "int64" + } + } + }, + "IntegrationConfigurationPostRequest": { + "type": "object", + "properties": { + "integrationType": { + "type": "string", + "enum": [ + "SLACK" + ] + }, + "clientId": { + "type": "string", + "minLength": 1 + }, + "clientSecret": { + "type": "string", + "minLength": 1 + }, + "signingSecret": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "clientId", + "clientSecret", + "integrationType", + "signingSecret" + ] + }, + "IntegrationConfigurationResponse": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "integrationType": { + "type": "string", + "enum": [ + "SLACK" + ] + }, + "clientId": { + "type": [ + "string", + "null" + ] + }, + "appId": { + "type": [ + "string", + "null" + ] + } + } + }, + "AwarenessBannerClickedRequest": { + "type": "object", + "properties": { + "bannerType": { + "type": "string", + "enum": [ + "ENABLE", + "LEARN_MORE" + ] + } + } + }, + "AwarenessBannerClickedResponse": { + "type": "object", + "properties": { + "id": { + "type": "string", + "readOnly": true + } + } + }, + "FixSuggestionPostRequest": { + "type": "object", + "properties": { + "projectKey": { + "type": [ + "string", + "null" + ] + }, + "issueId": { + "type": [ + "string", + "null" + ], + "description": "Issue key" + }, + "issue": { + "$ref": "#/components/schemas/Issue", + "description": "Issue" + } + } + }, + "Issue": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "startLine": { + "type": "integer", + "format": "int32" + }, + "endLine": { + "type": "integer", + "format": "int32" + }, + "ruleKey": { + "type": "string" + }, + "sourceCode": { + "type": "string" + } + }, + "required": [ + "endLine", + "message", + "ruleKey", + "sourceCode", + "startLine" + ] + }, + "ChangeDto": { + "type": "object", + "properties": { + "startLine": { + "type": "integer", + "format": "int32" + }, + "endLine": { + "type": "integer", + "format": "int32" + }, + "newCode": { + "type": "string" + } + } + }, + "FixSuggestionResponse": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "issueId": { + "type": [ + "string", + "null" + ] + }, + "explanation": { + "type": "string" + }, + "changes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChangeDto" + } + } + } + }, + "LicenseRestRequest": { + "type": "object", + "properties": { + "licenseKey": { + "type": "string", + "description": "New license key" + } + } + }, + "LicenseUploadActivationRequest": { + "type": "object", + "properties": { + "license": { + "type": "string", + "description": "Contents of a valid .lic file" + }, + "licenseKey": { + "type": "string", + "description": "License key for the license" + } + } + }, + "LegacyLicenseRestRequest": { + "type": "object", + "properties": { + "licenseKey": { + "type": "string", + "description": "New license key" + } + } + }, + "ScmAccessTokenRestResponse": { + "type": "object", + "properties": { + "alm": { + "type": "string" + }, + "username": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "expiresAt": { + "type": "string" + } + } + }, + "GitlabSynchronizationRunResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "readOnly": true + } + } + }, + "PermissionMappingsPostRequest": { + "type": "object", + "properties": { + "role": { + "type": "string", + "description": "Custom role name" + }, + "permissions": { + "$ref": "#/components/schemas/RestPermissions", + "description": "Set of SonarQube permissions to apply" + } + }, + "required": [ + "permissions", + "role" + ] + }, + "RestPermissions": { + "type": "object", + "properties": { + "user": { + "type": "boolean", + "description": "Browse" + }, + "codeViewer": { + "type": "boolean", + "description": "See Source Code" + }, + "issueAdmin": { + "type": "boolean", + "description": "Administer Issues" + }, + "securityHotspotAdmin": { + "type": "boolean", + "description": "Administer Security Hotspots" + }, + "admin": { + "type": "boolean", + "description": "Administer" + }, + "scan": { + "type": "boolean", + "description": "Execute Analysis" + } + }, + "required": [ + "admin", + "codeViewer", + "issueAdmin", + "scan", + "securityHotspotAdmin", + "user" + ] + }, + "PermissionMappingsResource": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "role": { + "type": "string" + }, + "baseRole": { + "type": "boolean" + }, + "permissions": { + "$ref": "#/components/schemas/RestPermissions" + } + } + }, + "GitlabConfigurationCreateRestRequest": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable Gitlab authentication" + }, + "applicationId": { + "type": "string", + "description": "Gitlab Application id", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "Url of Gitlab instance for authentication (for instance https://gitlab.com)", + "minLength": 1 + }, + "secret": { + "type": "string", + "description": "Secret of the application", + "minLength": 1, + "writeOnly": true + }, + "synchronizeGroups": { + "type": "boolean", + "description": "Set whether to synchronize groups" + }, + "allowedGroups": { + "type": "array", + "description": "GitLab groups allowed to authenticate.\nSubgroups will automatically be included.\nWhen Auto-provisioning is enabled, members of these groups will be automatically provisioned in SonarQube.\nThis field is required to be non-empty for Auto-provisioning unless allowAllGroups is true.\nIgnored when allowAllGroups is true.\n", + "items": { + "type": "string" + } + }, + "allowAllGroups": { + "type": [ + "boolean", + "null" + ], + "description": "When true with Auto-provisioning, every group visible to the provisioning token is provisioned and the allowedGroups list is ignored. Has no effect with Just-in-Time provisioning. Security risk: any user belonging to any group accessible by the provisioning token will be granted access. Restrict access using allowedGroups unless broad access is intentional. Not supported on GitLab.com (SaaS): the request is rejected when the configured URL is gitlab.com, since the provisioning token may have visibility into a much larger and unbounded set of groups. Performance note: login may be slower for users belonging to a large number of groups, as all their groups must be fetched from GitLab on every authentication." + }, + "provisioningType": { + "type": "string", + "description": "Type of synchronization", + "enum": [ + "JIT", + "AUTO_PROVISIONING" + ] + }, + "provisioningToken": { + "type": [ + "string", + "null" + ], + "description": "Gitlab token for provisioning", + "writeOnly": true + }, + "allowUsersToSignUp": { + "type": [ + "boolean", + "null" + ], + "description": "Allow user to sign up" + } + }, + "required": [ + "allowedGroups", + "applicationId", + "enabled", + "provisioningType", + "secret", + "synchronizeGroups", + "url" + ] + }, + "GitlabConfigurationRestResponseForAdmins": { + "type": "object", + "properties": { + "id": { + "type": "string", + "readOnly": true + }, + "enabled": { + "type": "boolean" + }, + "applicationId": { + "type": "string", + "description": "Gitlab Application id" + }, + "url": { + "type": "string", + "description": "Url of Gitlab instance for authentication (for instance https://gitlab.com/api/v4)" + }, + "synchronizeGroups": { + "type": "boolean" + }, + "allowedGroups": { + "type": "array", + "description": "Root Gitlab groups allowed to authenticate and provisioned. Ignored when allowAllGroups is true.", + "items": { + "type": "string" + } + }, + "allowAllGroups": { + "type": "boolean", + "description": "When true with Auto-provisioning, every group visible to the provisioning token is provisioned and the allowedGroups list is ignored. Has no effect with Just-in-Time provisioning. Security risk: any user belonging to any group accessible by the provisioning token will be granted access. Restrict access using allowedGroups unless broad access is intentional. Not supported on GitLab.com (SaaS): the request is rejected when the configured URL is gitlab.com, since the provisioning token may have visibility into a much larger and unbounded set of groups. Performance note: login may be slower for users belonging to a large number of groups, as all their groups must be fetched from GitLab on every authentication." + }, + "allowUsersToSignUp": { + "type": "boolean" + }, + "provisioningType": { + "type": "string", + "enum": [ + "JIT", + "AUTO_PROVISIONING" + ] + }, + "isProvisioningTokenSet": { + "type": "boolean", + "description": "Whether or not the provisioningToken is defined", + "readOnly": true + }, + "errorMessage": { + "type": [ + "string", + "null" + ], + "description": "In case the GitLab configuration is incorrect, error message", + "readOnly": true + } + } + }, + "GithubInstallationTokenRestResponse": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "expiresAt": { + "type": "string" + } + } + }, + "GithubConfigurationCreateRestRequest": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable GitHub authentication" + }, + "clientId": { + "type": "string", + "description": "Client ID provided by GitHub when registering the application.", + "minLength": 1, + "writeOnly": true + }, + "clientSecret": { + "type": "string", + "description": "Client password provided by GitHub when registering the application.", + "minLength": 1, + "writeOnly": true + }, + "applicationId": { + "type": "string", + "description": "The App ID is found on your GitHub App's page on GitHub at Settings > Developer Settings > GitHub Apps.", + "minLength": 1 + }, + "privateKey": { + "type": "string", + "description": "Your GitHub App's private key. You can generate a .pem file from your GitHub App's page under Private keys.\nCopy and paste the whole contents of the file here.\n", + "minLength": 1, + "writeOnly": true + }, + "synchronizeGroups": { + "type": "boolean", + "description": "Synchronize GitHub team with SonarQube group memberships when users log in to SonarQube.\nFor each GitHub team they belong to, users will be associated to a group of the same name if it exists in SonarQube.\n" + }, + "apiUrl": { + "type": "string", + "description": "The API url for a GitHub instance. https://api.github.com/ for Github.com, https://github.company.com/api/v3/ when using Github Enterprise", + "minLength": 1 + }, + "webUrl": { + "type": "string", + "description": "The WEB url for a GitHub instance. https://github.com/ for Github.com, https://github.company.com/ when using GitHub Enterprise.\n", + "minLength": 1 + }, + "allowedOrganizations": { + "type": "array", + "description": "Only members of these organizations will be able to authenticate to the server.\n⚠ if not set, users from any organization where the GitHub App is installed will be able to login to this SonarQube instance.\n", + "items": { + "type": "string" + } + }, + "provisioningType": { + "type": "string", + "description": "Type of synchronization", + "enum": [ + "JIT", + "AUTO_PROVISIONING" + ] + }, + "allowUsersToSignUp": { + "type": [ + "boolean", + "null" + ], + "description": "Allow user to sign up" + }, + "projectVisibility": { + "type": [ + "boolean", + "null" + ], + "description": "Change project visibility based on GitHub repository visibility.\nIf disabled, every provisioned project will be private in SonarQube and visible only to users with explicit GitHub permissions for the corresponding repository.\nChanges take effect at the next synchronization.\n" + }, + "userConsentRequiredAfterUpgrade": { + "type": [ + "boolean", + "null" + ], + "description": "Admin consent to synchronize permissions from GitHub" + } + }, + "required": [ + "allowedOrganizations", + "apiUrl", + "applicationId", + "clientId", + "clientSecret", + "enabled", + "privateKey", + "provisioningType", + "synchronizeGroups", + "webUrl" + ] + }, + "GithubConfigurationResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "readOnly": true + }, + "enabled": { + "type": "boolean" + }, + "applicationId": { + "type": "string", + "description": "GitHub Application id" + }, + "synchronizeGroups": { + "type": "boolean" + }, + "apiUrl": { + "type": "string", + "description": "Url of GitHub instance for API connectivity (for instance https://api.github.com)" + }, + "webUrl": { + "type": "string", + "description": "Url of GitHub instance for authentication (for instance https://github.com)" + }, + "allowedOrganizations": { + "type": "array", + "description": "GitHub organizations allowed to authenticate and provisioned", + "items": { + "type": "string" + } + }, + "provisioningType": { + "type": "string", + "enum": [ + "JIT", + "AUTO_PROVISIONING" + ] + }, + "allowUsersToSignUp": { + "type": "boolean" + }, + "projectVisibility": { + "type": "boolean" + }, + "userConsentRequiredAfterUpgrade": { + "type": "boolean" + }, + "errorMessage": { + "type": [ + "string", + "null" + ], + "description": "In case the GitHub configuration is incorrect, error message", + "readOnly": true + } + } + }, + "CreateJobRequest": { + "type": "object", + "properties": { + "projectId": { + "type": "string", + "description": "Stable identifier of the SonarQube project to analyse." + }, + "branchId": { + "type": "string", + "description": "Stable identifier of the branch to analyse." + } + }, + "required": [ + "branchId", + "projectId" + ] + }, + "AgenticFinding": { + "type": "object", + "properties": { + "filePath": { + "type": "string" + }, + "line": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "ruleKey": { + "type": "string" + }, + "ruleTitle": { + "type": "string" + }, + "severity": { + "type": "string" + }, + "whyIsThisAnIssue": { + "type": "string" + }, + "howToFix": { + "type": "string" + }, + "agentContext": { + "type": "string" + }, + "flows": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgenticFlow" + } + }, + "status": { + "type": "string" + } + } + }, + "AgenticFlow": { + "type": "object", + "properties": { + "locations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgenticFlowLocation" + } + }, + "description": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "AgenticFlowLocation": { + "type": "object", + "properties": { + "filePath": { + "type": "string" + }, + "line": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + }, + "AgenticJobResult": { + "type": "object", + "properties": { + "jobId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "branchName": { + "type": "string" + }, + "analyzedCommitSha": { + "type": "string" + }, + "analyzedAt": { + "type": "integer", + "format": "int64" + }, + "findings": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgenticFinding" + } + }, + "durationSeconds": { + "type": "integer", + "format": "int64" + }, + "costUsd": { + "type": "number", + "format": "double" + }, + "playbookId": { + "type": "string" + }, + "playbookKey": { + "type": "string" + }, + "playbookVersion": { + "type": "string" + }, + "revalidatesPriors": { + "type": "boolean" + } + } + }, + "DashboardResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string", + "maxLength": 300, + "minLength": 0 + }, + "layout": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 500, + "minLength": 0 + }, + "createdAt": { + "type": "integer", + "format": "int64" + }, + "updatedAt": { + "type": "integer", + "format": "int64" + }, + "createdById": { + "type": "string" + }, + "resourceType": { + "type": "string", + "enum": [ + "project", + "portfolio" + ] + }, + "resourceId": { + "type": "string" + }, + "updatedById": { + "type": "string" + } + }, + "required": [ + "createdAt", + "createdById", + "id", + "layout", + "name", + "resourceId", + "resourceType", + "updatedAt", + "updatedById" + ] + }, + "ErrorResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + }, + "CreateDashboardRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 300, + "minLength": 0 + }, + "layout": { + "type": "string" + }, + "resourceType": { + "type": "string", + "enum": [ + "project", + "portfolio" + ] + }, + "resourceId": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 500, + "minLength": 0 + } + }, + "required": [ + "layout", + "name", + "resourceId", + "resourceType" + ] + }, + "Impact": { + "type": "object", + "properties": { + "softwareQuality": { + "type": "string", + "enum": [ + "MAINTAINABILITY", + "RELIABILITY", + "SECURITY" + ] + }, + "severity": { + "type": "string", + "enum": [ + "INFO", + "LOW", + "MEDIUM", + "HIGH", + "BLOCKER" + ] + } + } + }, + "Parameter": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "htmlDescription": { + "type": "string", + "readOnly": true + }, + "defaultValue": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "STRING", + "TEXT", + "BOOLEAN", + "INTEGER", + "FLOAT" + ], + "readOnly": true + } + } + }, + "RuleCreateRestRequest": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Key of the custom rule to create, must include the repository", + "maxLength": 200, + "minLength": 0 + }, + "templateKey": { + "type": "string", + "description": "Key of the rule template to be used to create the custom rule", + "maxLength": 200, + "minLength": 0 + }, + "name": { + "type": "string", + "description": "Rule name", + "maxLength": 200, + "minLength": 0 + }, + "markdownDescription": { + "type": "string", + "description": "Rule description in markdown format" + }, + "status": { + "type": [ + "string", + "null" + ], + "default": "READY", + "description": "Rule status", + "enum": [ + "BETA", + "DEPRECATED", + "READY", + "REMOVED" + ] + }, + "parameters": { + "type": [ + "array", + "null" + ], + "description": "Custom rule parameters", + "items": { + "$ref": "#/components/schemas/Parameter" + } + }, + "cleanCodeAttribute": { + "type": [ + "string", + "null" + ], + "description": "Clean code attribute", + "enum": [ + "CONVENTIONAL", + "FORMATTED", + "IDENTIFIABLE", + "CLEAR", + "COMPLETE", + "EFFICIENT", + "LOGICAL", + "DISTINCT", + "FOCUSED", + "MODULAR", + "TESTED", + "LAWFUL", + "RESPECTFUL", + "TRUSTWORTHY" + ] + }, + "impacts": { + "type": "array", + "description": "Impacts", + "items": { + "$ref": "#/components/schemas/Impact" + } + }, + "severity": { + "type": [ + "string", + "null" + ], + "description": "Severity" + }, + "type": { + "type": [ + "string", + "null" + ], + "description": "Rule type", + "enum": [ + "CODE_SMELL", + "BUG", + "VULNERABILITY", + "SECURITY_HOTSPOT" + ] + } + }, + "required": [ + "impacts", + "key", + "markdownDescription", + "name", + "templateKey" + ] + }, + "RuleDescriptionSectionContextRestResponse": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "displayName": { + "type": "string" + } + }, + "readOnly": true + }, + "RuleDescriptionSectionRestResponse": { + "type": "object", + "properties": { + "key": { + "type": "string", + "readOnly": true + }, + "content": { + "type": "string", + "readOnly": true + }, + "context": { + "$ref": "#/components/schemas/RuleDescriptionSectionContextRestResponse", + "readOnly": true + } + } + }, + "RuleRestResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "key": { + "type": "string" + }, + "repositoryKey": { + "type": "string" + }, + "name": { + "type": "string" + }, + "severity": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "CODE_SMELL", + "BUG", + "VULNERABILITY", + "SECURITY_HOTSPOT" + ] + }, + "impacts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Impact" + } + }, + "cleanCodeAttribute": { + "type": [ + "string", + "null" + ], + "enum": [ + "CONVENTIONAL", + "FORMATTED", + "IDENTIFIABLE", + "CLEAR", + "COMPLETE", + "EFFICIENT", + "LOGICAL", + "DISTINCT", + "FOCUSED", + "MODULAR", + "TESTED", + "LAWFUL", + "RESPECTFUL", + "TRUSTWORTHY" + ] + }, + "cleanCodeAttributeCategory": { + "type": [ + "string", + "null" + ], + "enum": [ + "ADAPTABLE", + "CONSISTENT", + "INTENTIONAL", + "RESPONSIBLE" + ] + }, + "status": { + "type": [ + "string", + "null" + ], + "enum": [ + "BETA", + "DEPRECATED", + "READY", + "REMOVED" + ] + }, + "external": { + "type": "boolean" + }, + "createdAt": { + "type": [ + "string", + "null" + ] + }, + "descriptionSections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RuleDescriptionSectionRestResponse" + } + }, + "markdownDescription": { + "type": "string" + }, + "gapDescription": { + "type": [ + "string", + "null" + ] + }, + "htmlNote": { + "type": [ + "string", + "null" + ] + }, + "markdownNote": { + "type": [ + "string", + "null" + ] + }, + "educationPrinciples": { + "type": "array", + "items": { + "type": "string" + } + }, + "template": { + "type": "boolean" + }, + "templateId": { + "type": [ + "string", + "null" + ] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "systemTags": { + "type": "array", + "items": { + "type": "string" + } + }, + "languageKey": { + "type": [ + "string", + "null" + ] + }, + "languageName": { + "type": [ + "string", + "null" + ] + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Parameter" + } + }, + "remediationFunctionType": { + "type": "string" + }, + "remediationFunctionGapMultiplier": { + "type": "string" + }, + "remediationFunctionBaseEffort": { + "type": "string" + } + }, + "readOnly": true + }, + "CagUsageEventRequest": { + "type": "object", + "description": "A single CAG tool usage event emitted by the CAG client", + "properties": { + "organizationId": { + "type": "string", + "format": "uuid", + "description": "UUID v4 of the organization. On SonarQube Server, the `@OrganizationId` annotation resolves this value server-side to the caller's default organization id, so callers on that host may pass any syntactically valid UUID as a placeholder — the value must still parse as a UUID, but its content is otherwise ignored. ", + "example": "c713929f-d5ee-4559-bae1-a93ac20cbbb6" + }, + "projectId": { + "type": "string", + "description": "Identifier of the project the tool call was about. Deliberately not `format: uuid`: SonarQube Server project ids are opaque strings, not UUIDs, so a UUID-typed field would reject every real Server project id. SonarQube Cloud sends a UUID string, which this schema accepts unchanged since the JSON wire format is identical either way. ", + "example": "7a2f5c8d-9e3b-4a1c-bf72-83a892472f22", + "maxLength": 40, + "minLength": 1, + "pattern": "^\\S+$" + }, + "invocationId": { + "type": "string", + "format": "uuid", + "description": "UUID v4 of the tool call, generated by the client. Natural deduplication key. ", + "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + } + }, + "required": [ + "invocationId", + "organizationId", + "projectId" + ] + }, + "CagImpactEventRequest": { + "type": "object", + "description": "Vortex impact event envelope.", + "properties": { + "organizationId": { + "type": "string", + "format": "uuid", + "description": "Organization UUID.", + "example": "c713929f-d5ee-4559-bae1-a93ac20cbbb6" + }, + "projectId": { + "type": "string", + "description": "Project identifier. Not format: uuid — Server project ids are opaque strings; Cloud sends a UUID string, accepted unchanged. ", + "example": "d824742e-3a4e-4b4f-a1af-414999bedfaf", + "maxLength": 40, + "minLength": 1, + "pattern": "^\\S+$" + }, + "invocationId": { + "type": "string", + "format": "uuid", + "description": "Invocation identifier. Natural deduplication key.", + "example": "487e0bc5-35a4-4e1d-a3dc-19a4ec185ffe" + }, + "cagInstanceId": { + "type": "string", + "description": "Shared by all calls from the same CAG instance (MCP server or CLI daemon).", + "example": "daemon-7b6411a7", + "maxLength": 255, + "minLength": 1 + }, + "eventType": { + "type": "string", + "enum": [ + "NAVIGATION", + "OUTPUT_COMPRESSION", + "OUTPUT_RESTORE", + "GUIDELINES", + "DEPENDENCY_CHECK", + "INTENDED_ARCHITECTURE" + ] + }, + "eventVersion": { + "type": "integer", + "format": "int32", + "description": "Payload schema version. Only 1 is supported.", + "example": 1 + }, + "success": { + "type": "boolean", + "description": "True when the call completed successfully." + }, + "transport": { + "type": "string", + "enum": [ + "MCP", + "CLI" + ] + }, + "branchName": { + "type": [ + "string", + "null" + ], + "description": "Git branch at invocation time. Optional.", + "example": "feature/roi-dashboard", + "maxLength": 255, + "minLength": 0 + }, + "payload": { + "type": "object", + "additionalProperties": {}, + "description": "Event-specific fields. Schema is selected by (eventType, eventVersion). See NavigationPayloadV1, OutputCompressionPayloadV1, OutputRestorePayloadV1, GuidelinesPayloadV1, DependencyCheckPayloadV1, IntendedArchitecturePayloadV1. " + } + }, + "required": [ + "cagInstanceId", + "eventType", + "eventVersion", + "invocationId", + "organizationId", + "payload", + "projectId", + "success", + "transport" + ] + }, + "OverageRestRequest": { + "type": "object", + "properties": { + "featureKey": { + "type": "string", + "description": "Feature key for the overage-eligible feature", + "minLength": 1 + }, + "enabled": { + "type": "boolean", + "description": "Whether overage is enabled for this feature" + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "description": "Maximum overage units, required when enabling" + }, + "termsAccepted": { + "type": [ + "boolean", + "null" + ], + "description": "Must be true when enabling, confirms acceptance of overage terms" + } + }, + "required": [ + "enabled", + "featureKey" + ] + }, + "LicenseFeatureRestResponse": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "parent": { + "type": [ + "string", + "null" + ] + }, + "startDate": { + "type": [ + "string", + "null" + ] + }, + "endDate": { + "type": [ + "string", + "null" + ] + }, + "featureKey": { + "type": [ + "string", + "null" + ] + }, + "maxConsumption": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "maxOverage": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "overageState": { + "type": [ + "string", + "null" + ], + "enum": [ + "NOT_ELIGIBLE", + "ELIGIBLE", + "NOT_ENABLED", + "ENABLED", + "OFFLINE_BLOCKED" + ] + }, + "overagesStep": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "overageGraceDays": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "overageUnitPrice": { + "type": [ + "number", + "null" + ] + }, + "overageUnitCurrency": { + "type": [ + "string", + "null" + ] + }, + "overageLimit": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "maxConsumptionUnit": { + "type": [ + "string", + "null" + ] + } + } + }, + "CreateConsumptionRecordRestRequest": { + "type": "object", + "properties": { + "featureKey": { + "type": "string", + "description": "Feature key for the consumed CONSUMABLE feature", + "minLength": 1 + }, + "resourceId": { + "type": [ + "string", + "null" + ], + "format": "uuid", + "description": "Organization ID, for compatibility with SQC" + }, + "resourceType": { + "type": [ + "string", + "null" + ], + "description": "Resource type, only 'organization' is supported, for compatibility with SQC", + "enum": [ + "organization" + ] + }, + "consumed": { + "type": "integer", + "format": "int64", + "description": "Units consumed in this event", + "maximum": 10000, + "minimum": 1 + } + }, + "required": [ + "featureKey" + ] + }, + "ConsumptionRecordRestResponse": { + "type": "object", + "properties": { + "recordId": { + "type": "string", + "format": "uuid", + "description": "Transient acceptance/tracing id. Not persisted and not a lookup key; retries receive a new value" + }, + "featureKey": { + "type": "string", + "description": "Feature key that was recorded" + }, + "resourceId": { + "type": "string", + "format": "uuid", + "description": "Organization ID the consumption was recorded against" + }, + "resourceType": { + "type": "string", + "description": "Resource type the consumption was recorded against", + "enum": [ + "organization" + ] + }, + "consumed": { + "type": "integer", + "format": "int64", + "description": "Units consumed in this event" + }, + "consumedAt": { + "type": "string", + "format": "date-time", + "description": "When the consumption was accepted" + } + } + }, + "GroupCreateRestRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name for the new group. Must be unique. The value 'anyone' is reserved and cannot be used.", + "maxLength": 255, + "minLength": 1 + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "Description for the new group.", + "maxLength": 200, + "minLength": 0 + } + }, + "required": [ + "name" + ] + }, + "GroupRestResponse": { + "type": "object", + "properties": { + "id": { + "type": "string", + "readOnly": true + }, + "name": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "managed": { + "type": "boolean", + "readOnly": true + }, + "default": { + "type": "boolean", + "readOnly": true + } + } + }, + "GroupMembershipCreateRestRequest": { + "type": "object", + "properties": { + "userId": { + "type": "string", + "description": "ID of the user to add to group." + }, + "groupId": { + "type": "string", + "description": "ID of the group where a member needs to be added." + } + } + }, + "GroupMembershipRestResponse": { + "type": "object", + "properties": { + "id": { + "type": "string", + "readOnly": true + }, + "groupId": { + "type": "string", + "readOnly": true + }, + "userId": { + "type": "string", + "readOnly": true + } + } + }, + "AtlassianAuthenticationDetailsResource": { + "type": "object", + "properties": { + "clientId": { + "type": "string", + "description": "Atlassian 3LO App Client ID", + "writeOnly": true + }, + "secret": { + "type": "string", + "description": "Atlassian 3LO App Secret", + "writeOnly": true + } + } + }, + "AtlassianAuthenticationDetailsResultResource": { + "type": "object", + "properties": { + "clientId": { + "type": "string", + "description": "Atlassian 3LO App Client ID", + "readOnly": true + } + } + }, + "Sdk": { + "type": "object", + "description": "An organization-scoped SDK: one identity, target and key, grouping N ecosystem-specific exit boundary descriptors.", + "properties": { + "sdkKey": { + "type": "string", + "description": "Unique key identifying this SDK within its organization. What edge matching compares a found exit boundary's key against.", + "maxLength": 255, + "minLength": 1 + }, + "organizationId": { + "type": [ + "string", + "null" + ], + "description": "UUID v4 of the organization." + }, + "id": { + "type": "string", + "format": "uuid" + }, + "targetComponentId": { + "type": "string", + "format": "uuid", + "description": "Id of the organization component the SDK leads into (the provider)." + }, + "sdkBoundaryDescriptors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SdkBoundaryDescriptor" + }, + "maxItems": 2147483647, + "minItems": 1 + }, + "name": { + "type": "string", + "description": "Display name of the SDK." + } + }, + "required": [ + "id", + "name", + "sdkBoundaryDescriptors", + "sdkKey", + "targetComponentId" + ] + }, + "SdkBoundaryDescriptor": { + "type": "object", + "description": "One ecosystem-specific exit boundary descriptor of an SDK. A project's analysis emits a boundary wherever this descriptor matches.", + "properties": { + "ecosystem": { + "type": "string", + "description": "The ecosystem this boundary descriptor applies to.", + "enum": [ + "java", + "js", + "ts", + "py", + "cs" + ] + }, + "query": { + "type": "string", + "description": "Query matching the call into the SDK, in the boundary-descriptor query grammar.", + "maxLength": 2147483647, + "minLength": 1 + } + }, + "required": [ + "ecosystem", + "query" + ] + }, + "Error": { + "type": "object", + "properties": { + "message": { + "type": [ + "string", + "null" + ] + } + } + }, + "PostSdkBody": { + "type": "object", + "properties": { + "targetComponentId": { + "type": "string", + "format": "uuid", + "description": "Id of the organization component the SDK leads into (the provider)." + }, + "sdkBoundaryDescriptors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SdkBoundaryDescriptor" + }, + "maxItems": 2147483647, + "minItems": 1 + }, + "name": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "sdkKey": { + "type": "string", + "description": "Unique key identifying this SDK within its organization.", + "maxLength": 255, + "minLength": 1 + }, + "organizationId": { + "type": [ + "string", + "null" + ], + "description": "UUID v4 of the organization." + } + }, + "required": [ + "name", + "sdkBoundaryDescriptors", + "sdkKey", + "targetComponentId" + ] + }, + "ScannerDataResponse": { + "type": "object", + "properties": { + "uploadUrl": { + "type": [ + "string", + "null" + ], + "description": "Presigned upload URL. Present only on the presigned path (no payload supplied)." + }, + "id": { + "type": [ + "string", + "null" + ], + "format": "uuid" + } + } + }, + "PostScannerDataBody": { + "type": "object", + "properties": { + "analysisId": { + "type": "string", + "description": "Analysis UUID identifying the target project and branch." + } + }, + "required": [ + "analysisId" + ] + }, + "ProjectRelationship": { + "type": "object", + "properties": { + "boundaryKey": { + "type": "string", + "description": "Key identifying the exit boundary (consumer side) this relationship applies to." + }, + "projectId": { + "type": "string", + "description": "Id of the project this relationship belongs to (the consumer)." + }, + "targetEntryPointKey": { + "type": "string", + "description": "Key of the target component's entry point this exit connects to." + }, + "origin": { + "type": "string", + "description": "Whether this relationship was created manually via the API or auto-computed from analysis.", + "enum": [ + "MANUAL", + "AUTOMAPPED" + ] + }, + "targetComponentId": { + "type": "string", + "format": "uuid", + "description": "Id of the target organization component (the provider this exit connects to)." + }, + "organizationId": { + "type": "string", + "description": "UUID v4 of the organization." + }, + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "boundaryKey", + "id", + "organizationId", + "origin", + "projectId", + "targetComponentId", + "targetEntryPointKey" + ] + }, + "PostProjectRelationshipBody": { + "type": "object", + "properties": { + "boundaryKey": { + "type": "string", + "description": "Key identifying the exit boundary (consumer side) this relationship applies to." + }, + "projectId": { + "type": "string", + "description": "Id of the project this relationship belongs to (the consumer)." + }, + "targetComponentId": { + "type": "string", + "format": "uuid", + "description": "Id of the target organization component (the provider this exit connects to)." + }, + "targetEntryPointKey": { + "type": "string", + "description": "Key of the target component's entry point this exit connects to." + } + }, + "required": [ + "boundaryKey", + "projectId", + "targetComponentId", + "targetEntryPointKey" + ] + }, + "ModelConstraint": { + "type": "object", + "properties": { + "to": { + "type": "string", + "description": "Pattern identifying the target group of the dependency." + }, + "from": { + "type": "string", + "description": "Pattern identifying the source group of the dependency." + } + }, + "required": [ + "from", + "to" + ] + }, + "ModelGroup": { + "type": "object", + "description": "A component of the intended model.", + "properties": { + "groups": { + "description": "Subgroups." + }, + "label": { + "type": "string", + "description": "Identifier of this group. Also serves as the display name." + }, + "interface": { + "type": [ + "boolean", + "null" + ], + "description": "If true, this group is an interface. External access to sibling groups that bypasses this interface group will raise an interface issue, regardless of the parent's interfaceAccess setting. To fully disable interface issue generation for a component, this field must be false on all children and interfaceAccess must be false on the parent. " + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "Description of the group." + }, + "interfaceAccess": { + "type": [ + "boolean", + "null" + ], + "description": "Extends interface issue detection beyond what the interface field provides. If true, access to children that are not themselves interfaces (interface: false) from outside this group will raise an interface issue. Additionally, access to unmapped code that resolves to this group's pattern is also considered unauthorized, as it does not go through an interface child, and will raise an interface issue. If false, no additional interface issues are raised by this group — though children with interface: true will still independently raise interface issues for unauthorized sibling access. " + }, + "patternId": { + "type": [ + "string", + "null" + ], + "format": "uuid", + "description": "If set, this group imports a pattern. Its subgroups are taken from the referenced pattern instead of the 'groups' field." + }, + "patterns": { + "type": "array", + "description": "File or namespace pattern for code components associated with this group.", + "items": { + "type": "string" + } + } + }, + "required": [ + "label" + ] + }, + "Pattern": { + "type": "object", + "description": "A pattern resource.", + "properties": { + "organizationId": { + "type": "string", + "description": "UUID v4 of the organization." + }, + "id": { + "type": "string", + "format": "uuid" + }, + "pattern": { + "$ref": "#/components/schemas/PatternData" + }, + "name": { + "type": [ + "string", + "null" + ], + "description": "Display name of the pattern." + } + }, + "required": [ + "id", + "name", + "organizationId" + ] + }, + "PatternData": { + "type": "object", + "description": "Structured pattern definition. Language-agnostic, no perspectives array — represents a single flat pattern. Reuses ModelGroup and ModelConstraint.", + "properties": { + "groups": { + "type": "array", + "description": "Top-level components of the pattern.", + "items": { + "$ref": "#/components/schemas/ModelGroup" + } + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "Description of the pattern definition." + }, + "label": { + "type": [ + "string", + "null" + ], + "description": "Display name of the pattern definition." + }, + "constraints": { + "type": "array", + "description": "Dependency constraints between groups.", + "items": { + "$ref": "#/components/schemas/ModelConstraint" + } + } + } + }, + "PostPattern": { + "type": "object", + "description": "Create a new pattern.", + "properties": { + "organizationId": { + "type": "string", + "description": "UUID v4 of the organization." + }, + "pattern": { + "$ref": "#/components/schemas/PatternData" + }, + "name": { + "type": "string", + "description": "Display name of the pattern." + } + }, + "required": [ + "name", + "organizationId", + "pattern" + ] + }, + "OrganizationPlaceholder": { + "type": "object", + "properties": { + "organizationId": { + "type": [ + "string", + "null" + ], + "description": "UUID v4 of the organization." + }, + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "APP", + "DB", + "QUEUE", + "OTHER" + ] + }, + "displayName": { + "type": "string" + } + }, + "required": [ + "displayName", + "id", + "type" + ] + }, + "PostOrganizationPlaceholderBody": { + "type": "object", + "properties": { + "organizationId": { + "type": [ + "string", + "null" + ], + "description": "UUID v4 of the organization." + }, + "displayName": { + "type": "string", + "maxLength": 256, + "minLength": 1 + }, + "type": { + "type": [ + "string", + "null" + ], + "enum": [ + "APP", + "DB", + "QUEUE", + "OTHER" + ] + } + }, + "required": [ + "displayName" + ] + }, + "Model": { + "type": "object", + "description": "In GET responses, both 'data' and 'model' will be set. 'data' is deprecated, use 'model' instead.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "data": { + "type": [ + "string", + "null" + ], + "deprecated": true, + "description": "Deprecated. Use 'model' instead.", + "maxLength": 50000, + "minLength": 0 + }, + "organizationId": { + "type": [ + "string", + "null" + ], + "description": "UUID v4 of the organization." + }, + "model": { + "$ref": "#/components/schemas/ModelData" + }, + "projectId": { + "type": "string" + } + }, + "required": [ + "id", + "organizationId", + "projectId" + ] + }, + "ModelData": { "type": "object", + "description": "Structured intended model for the architecture analysis, mirroring ModelDto.", "properties": { - "issueReleaseKey": { - "type": "string", - "description": "Issue release key" - }, - "assigneeLogin": { - "type": "string", - "description": "Assignee login" + "perspectives": { + "type": "array", + "description": "Model perspectives. Each perspective provides a specific view of the intended model in terms of groups and constraints.", + "items": { + "$ref": "#/components/schemas/ModelPerspective" + } } }, "required": [ - "issueReleaseKey" + "perspectives" ] }, - "AffectedPackageResource": { + "ModelPerspective": { "type": "object", + "description": "A specific view of the intended model in terms of groups and constraints.", "properties": { - "purl": { - "type": "string" - }, - "recommendation": { + "qualifiers": { "type": "string", - "description": "The maintainer's overall recommendation, if available", "enum": [ - "ignore", - "upgrade", - "upgrade_or_workaround" + "file", + "namespace" ] }, - "recommendationDetails": { - "$ref": "#/components/schemas/VulnerabilityRecommendationDetailsResource" + "description": { + "type": [ + "string", + "null" + ], + "description": "Description of the perspective." }, - "versionOptions": { + "label": { + "type": [ + "string", + "null" + ], + "description": "Display name of the perspective." + }, + "groups": { "type": "array", + "description": "Components of the intended model.", "items": { - "$ref": "#/components/schemas/VersionOptionResource" + "$ref": "#/components/schemas/ModelGroup" } }, - "affectedVersions": { + "constraints": { "type": "array", + "description": "Dependency constraints between groups in this perspective.", "items": { - "type": "string" + "$ref": "#/components/schemas/ModelConstraint" } }, - "unaffectedVersions": { - "type": "string" + "language": { + "type": "string", + "enum": [ + "java", + "js", + "ts", + "py", + "cs" + ] } - } + }, + "required": [ + "constraints", + "language", + "qualifiers" + ] }, - "CveLocationResource": { + "PostModel": { "type": "object", + "description": "Either 'data' (deprecated) or 'model' must be set.", "properties": { - "signature": { - "type": "string" - }, - "signatureType": { - "type": "string", - "enum": [ - "VULNERABLE_FUNCTION", - "PUBLIC_CALLER" - ] - }, - "filePath": { + "projectId": { "type": "string" }, - "startLine": { - "type": "integer", - "format": "int32" - }, - "startLineOffset": { - "type": "integer", - "format": "int32" + "model": { + "$ref": "#/components/schemas/ModelData" }, - "endLine": { - "type": "integer", - "format": "int32" + "organizationId": { + "type": [ + "string", + "null" + ], + "deprecated": true, + "description": "UUID v4 of the organization." }, - "endLineOffset": { - "type": "integer", - "format": "int32" + "data": { + "type": [ + "string", + "null" + ], + "deprecated": true, + "description": "Deprecated. Use 'model' instead.", + "maxLength": 50000, + "minLength": 0 } - } + }, + "required": [ + "projectId" + ] }, - "CweResource": { + "Directive": { "type": "object", "properties": { - "code": { - "type": "string" + "origin": { + "type": [ + "string", + "null" + ], + "description": "Optional provenance tag supplied by the creator of the directive (e.g. the UI component or user action that triggered the creation). Null for directives created before this field was introduced.", + "maxLength": 255, + "minLength": 0 }, - "name": { - "type": "string" + "directiveData": { + "$ref": "#/components/schemas/DirectiveData" }, - "description": { + "projectId": { "type": "string" + }, + "id": { + "type": "string", + "format": "uuid" } - } + }, + "required": [ + "directiveData", + "id", + "projectId" + ] }, - "IssueReleaseBranchResource": { + "DirectiveData": { "type": "object", "properties": { - "uuid": { - "type": "string" - }, - "key": { - "type": "string" + "toNodeKey": { + "type": [ + "string", + "null" + ], + "description": "Key of the dependency target node." }, - "pullRequest": { - "type": "boolean" + "fromNodeKey": { + "type": "string", + "description": "Key of the dependency source node." }, - "projectKey": { - "type": "string" + "graphPerspective": { + "type": [ + "string", + "null" + ], + "description": "Key of the graph perspective the directive's node keys refer to. Syntactically optional for backward compatibility with older payloads, but semantically required: directives without a (parseable, resolvable) value are rejected by the validator as INVALID and are removed by the analysis-time garbage collector. " }, - "projectName": { - "type": "string" + "graphType": { + "type": "string", + "description": "Type of the graph.", + "enum": [ + "file_graph", + "namespace_graph" + ] }, - "legacyProjectUuid": { - "type": "string" + "graphEcoSystem": { + "type": "string", + "description": "Language ecosystem of the graph.", + "enum": [ + "java", + "js", + "ts", + "py", + "cs" + ] }, - "organizationUuid": { + "type": { "type": "string", - "format": "uuid" + "description": "Type of this directive.", + "enum": [ + "REMOVE_EDGE", + "RENAME_NODE", + "MOVE_NODE" + ] + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "Optional human-readable description for this directive." } - } + }, + "required": [ + "fromNodeKey", + "graphEcoSystem", + "graphType", + "toNodeKey", + "type" + ] }, - "IssueReleaseDetailsResource": { + "PostDirectiveBody": { "type": "object", "properties": { - "key": { + "projectId": { "type": "string" }, - "severity": { - "type": "string" + "origin": { + "type": [ + "string", + "null" + ], + "description": "Optional provenance tag for this directive (e.g. the UI component or user action that triggered the creation).", + "maxLength": 255, + "minLength": 0 + }, + "directiveData": { + "$ref": "#/components/schemas/DirectiveData" + } + }, + "required": [ + "directiveData", + "projectId" + ] + }, + "BoundaryDescriptor": { + "type": "object", + "properties": { + "direction": { + "type": "string", + "description": "Direction of the boundary.", + "enum": [ + "EXIT_POINT", + "ENTRY_POINT" + ] }, - "originalSeverity": { - "type": "string" + "name": { + "type": "string", + "description": "Name of the boundary descriptor." }, - "manualSeverity": { - "type": "string" + "keyTemplate": { + "type": "string", + "description": "Template for generating boundary keys." }, - "showIncreasedSeverityWarning": { - "type": "boolean" + "query": { + "type": "string", + "description": "Query to match boundaries." }, - "release": { - "$ref": "#/components/schemas/ReleaseSearchResource" + "id": { + "type": "string", + "format": "uuid" }, - "type": { + "ecosystem": { "type": "string", + "description": "Language ecosystem of the boundary.", "enum": [ - "VULNERABILITY", - "PROHIBITED_LICENSE", - "MALWARE" + "java", + "js", + "ts", + "py", + "cs" ] }, - "quality": { + "projectId": { + "type": "string" + } + }, + "required": [ + "direction", + "ecosystem", + "id", + "keyTemplate", + "name", + "projectId", + "query" + ] + }, + "PostBoundaryDescriptorBody": { + "type": "object", + "properties": { + "ecosystem": { "type": "string", + "description": "Language ecosystem of the boundary.", "enum": [ - "MAINTAINABILITY", - "RELIABILITY", - "SECURITY" + "java", + "js", + "ts", + "py", + "cs" ] }, - "status": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "assignee": { - "$ref": "#/components/schemas/UserResource" + "keyTemplate": { + "type": "string", + "description": "Template for generating boundary keys." }, - "commentCount": { - "type": "integer", - "format": "int32" + "query": { + "type": "string", + "description": "Query to match boundaries." }, - "vulnerability": { - "$ref": "#/components/schemas/VulnerabilityResource" + "direction": { + "type": "string", + "description": "Direction of the boundary.", + "enum": [ + "EXIT_POINT", + "ENTRY_POINT" + ] }, - "spdxLicenseId": { + "projectId": { "type": "string" }, - "transitions": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "CONFIRM", - "REOPEN", - "SAFE", - "FIXED", - "ACCEPT" - ] - } - }, - "actions": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "COMMENT", - "ASSIGN", - "SET_SEVERITY" - ] - }, - "uniqueItems": true + "name": { + "type": "string", + "description": "Name of the boundary descriptor." + } + }, + "required": [ + "direction", + "ecosystem", + "keyTemplate", + "name", + "projectId", + "query" + ] + }, + "PostAnalysisBody": { + "type": "object", + "properties": { + "analysisId": { + "type": [ + "string", + "null" + ], + "description": "Cloud mode: analysis UUID identifying the target project and branch." }, - "branch": { - "$ref": "#/components/schemas/IssueReleaseBranchResource" + "ceTaskId": { + "type": [ + "string", + "null" + ], + "description": "Server mode: Compute Engine task ID, used to resolve the target project and branch. Required when analysisId is absent." } } }, - "ReleaseSearchResource": { + "Analysis": { "type": "object", + "description": "Structure of an Analysis", "properties": { "id": { - "type": "string" - }, - "key": { - "type": "string" - }, - "branchId": { - "type": "string" - }, - "branchUuid": { "type": "string", - "deprecated": true - }, - "packageUrl": { - "type": "string" - }, - "packageManager": { - "type": "string" - }, - "packageName": { - "type": "string" - }, - "version": { - "type": "string" - }, - "licenseExpression": { - "type": "string" - }, - "known": { - "type": "boolean" - }, - "knownPackage": { - "type": "boolean" - }, - "newlyIntroduced": { - "type": "boolean" - }, - "directSummary": { - "type": "boolean" - }, - "scopeSummary": { - "type": "string" - }, - "productionScopeSummary": { - "type": "boolean" + "format": "uuid", + "description": "The unique identifier of an analysis", + "example": "57f08a8b-4a6e-4c64-bf72-83a892472f22" }, - "scopes": { + "issues": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/HubIssue" } }, - "dependencyFilePaths": { + "patchResult": { + "$ref": "#/components/schemas/PatchResult" + }, + "errors": { "type": "array", + "description": "Errors that occurred during analysis from language analysis services", "items": { - "type": "string" + "$ref": "#/components/schemas/AnalysisError" } } - } + }, + "required": [ + "id", + "issues" + ] }, - "UserResource": { + "AnalysisError": { "type": "object", + "description": "Error that occurred during analysis", "properties": { - "login": { - "type": "string" - }, - "name": { - "type": "string" - }, - "avatar": { - "type": "string" + "code": { + "type": "string", + "description": "Error code indicating the type of failure", + "enum": [ + "SERVICE_CALL_ERROR", + "PARSE_ERROR", + "INVALID_CONTEXT" + ] }, - "active": { - "type": "boolean" + "message": { + "type": "string", + "description": "Human-readable error message", + "example": "Error while calling language analysis service" } - } + }, + "required": [ + "code", + "message" + ] }, - "VersionOptionResource": { + "HubIssue": { "type": "object", + "description": "Structure of an Issue", "properties": { - "version": { + "id": { "type": "string", - "description": "The version being presented as an option" - }, - "vulnerabilityIds": { - "type": "array", - "description": "Vulnerability IDs affecting this version", - "items": { - "type": "string" - } + "format": "uuid", + "description": "The unique identifier of the issue", + "example": "7a2f5c8d-9e3b-4a1c-bf72-83a892472f22" }, - "prerelease": { - "type": "boolean", - "description": "Is this version a pre-release version" + "filePath": { + "type": "string", + "description": "Project-relative path of the file containing the issue" }, - "fixLevel": { + "message": { "type": "string", - "description": "Describes which vulnerabilities are fixed", - "enum": [ - "COMPLETE", - "PARTIAL", - "NONE", - "UNKNOWN" - ] + "description": "Primary message of the issue" }, - "descriptionCode": { + "rule": { "type": "string", - "description": "How the frontend should label this version", - "enum": [ - "VERSION_IN_USE", - "NEAREST_PARTIAL", - "NEAREST_COMPLETE", - "LATEST_PARTIAL", - "LATEST_COMPLETE", - "LATEST_STABLE", - "LATEST_PRERELEASE", - "UNKNOWN" + "description": "The rule key (e.g., java:S1854)" + }, + "textRange": { + "$ref": "#/components/schemas/TextRange" + }, + "flows": { + "type": "array", + "description": "Secondary locations and flows for the issue", + "items": { + "$ref": "#/components/schemas/HubIssueFlowsInner" + } + } + }, + "required": [ + "filePath", + "id", + "message", + "rule" + ] + }, + "HubIssueFlowsInner": { + "type": "object", + "properties": { + "type": { + "type": [ + "string", + "null" + ], + "description": "The type of flow. Can be \"UNDEFINED\", \"DATA\", or \"EXECUTION\".", + "enum": [ + "UNDEFINED", + "DATA", + "EXECUTION" ] + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "The description of the flow, if any" + }, + "locations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HubIssueFlowsInnerLocationsInner" + } } } }, - "VulnerabilityRecommendationDetailsResource": { + "HubIssueFlowsInnerLocationsInner": { "type": "object", "properties": { - "impactScore": { - "type": "integer", - "format": "int32" - }, - "impactDescription": { - "type": "string" - }, - "realIssue": { - "type": "boolean" - }, - "falsePositiveReason": { - "type": "string" + "textRange": { + "$ref": "#/components/schemas/TextRange" }, - "includesDev": { - "type": "boolean" - }, - "specificMethodsAffected": { - "type": "boolean" + "message": { + "type": [ + "string", + "null" + ] }, - "specificMethodsDescription": { - "type": "string" + "filePath": { + "type": [ + "string", + "null" + ] + } + } + }, + "PatchResult": { + "type": "object", + "description": "Result of applying a patch to analyze changes", + "properties": { + "newIssues": { + "type": "array", + "description": "Issues that appear only in the patched version", + "items": { + "$ref": "#/components/schemas/HubIssue" + } }, - "otherConditions": { - "type": "boolean" + "matchedIssues": { + "type": "array", + "description": "Issues that exist in both original and patched versions", + "items": { + "$ref": "#/components/schemas/HubIssue" + } }, - "otherConditionsDescription": { - "type": "string" + "closedIssues": { + "type": "array", + "description": "Uuids of issues that existed in the original but are gone in the patch", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + "required": [ + "closedIssues", + "matchedIssues", + "newIssues" + ] + }, + "TextRange": { + "type": "object", + "description": "Location of an issue in a file", + "properties": { + "startLine": { + "type": [ + "integer", + "null" + ], + "format": "int32" }, - "workaroundAvailable": { - "type": "boolean" + "endLine": { + "type": [ + "integer", + "null" + ], + "format": "int32" }, - "workaroundDescription": { - "type": "string" + "startOffset": { + "type": [ + "integer", + "null" + ], + "format": "int32" }, - "visibility": { - "type": "string" + "endOffset": { + "type": [ + "integer", + "null" + ], + "format": "int32" } } }, - "VulnerabilityReportResource": { + "PayloadTooLargeError": { "type": "object", + "description": "Error returned when the request exceeds a size limit, either at the API Gateway level (payload too large) or at the application level (too many files). Use `code` to distinguish between the two cases. The `meta` object provides context about the limits that were exceeded; its fields may vary over time. ", "properties": { - "id": { - "type": "string" - }, - "url": { - "type": "string" - }, - "type": { - "type": "string" + "message": { + "type": [ + "string", + "null" + ], + "description": "Human-readable description of the error" }, - "cvssScore": { - "type": "string" + "code": { + "type": [ + "string", + "null" + ], + "description": "Machine-readable error code. `REQUEST_TOO_LARGE` is returned by the API Gateway when the raw payload exceeds the maximum size. `TOO_MANY_FILES` is returned by the service when the number of files exceeds the allowed maximum. ", + "enum": [ + "REQUEST_TOO_LARGE", + "TOO_MANY_FILES" + ] }, - "cvssSeverity": { - "type": "string" + "meta": { + "$ref": "#/components/schemas/PayloadTooLargeError_meta" + } + } + }, + "PayloadTooLargeError_meta": { + "type": "object", + "description": "Additional context about the limits in effect at the time of the error", + "properties": { + "maxRequestSize": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "description": "Maximum allowed request payload size in bytes", + "example": 10485760 }, - "withdrawnAt": { - "type": "string" + "maxFiles": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "description": "Maximum number of files allowed per request", + "example": 200 } } }, - "VulnerabilityResource": { + "PrivateAnalysisCreationRequest": { "type": "object", + "description": "Structure of a request to create an analysis", "properties": { - "vulnerabilityId": { - "type": "string" + "organizationId": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of the organization", + "example": "c713929f-d5ee-4559-bae1-a93ac20cbbb6" }, - "reports": { - "type": "array", - "items": { - "$ref": "#/components/schemas/VulnerabilityReportResource" - } + "projectId": { + "type": [ + "string", + "null" + ], + "description": "The unique identifier of the project", + "example": "c713929f-d5ee-4559-bae1-a93ac20cbbb6", + "maxLength": 40, + "minLength": 0, + "pattern": "^\\S+$" }, - "description": { - "type": "string" + "projectKey": { + "type": [ + "string", + "null" + ], + "description": "The key of the project (deprecated, use projectId instead)", + "example": "test-project", + "maxLength": 400, + "minLength": 0, + "pattern": "^\\S+$" }, - "epssPercentile": { - "type": "string" + "branchId": { + "type": [ + "string", + "null" + ], + "description": "The unique identifier of the branch to retrieve the latest analysis", + "example": "7a2f5c8d-9e3b-4a1c-bf72-83a892472f22", + "pattern": "^\\S+$" }, - "epssProbability": { - "type": "string" + "branchName": { + "type": [ + "string", + "null" + ], + "description": "The name of the branch to retrieve the latest analysis (used when branchId is not provided)", + "example": "feature/my-branch", + "maxLength": 255, + "minLength": 0, + "pattern": "^\\S+$" }, - "knownExploited": { - "type": "boolean" + "filePath": { + "type": [ + "string", + "null" + ], + "deprecated": true, + "description": "Project-relative path of the file. Deprecated — use `files` instead.", + "example": "directory/File.java", + "pattern": "^\\S([ \\S]*\\S)?$" }, - "cweIds": { - "type": "array", + "fileContent": { + "type": [ + "string", + "null" + ], "deprecated": true, - "items": { - "type": "string", - "deprecated": true - } + "description": "The original content of the file. Deprecated — use `files` instead.", + "example": "class SomeClass {}" }, - "cwes": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CweResource" - } + "patchContent": { + "type": [ + "string", + "null" + ], + "deprecated": true, + "description": "The patch content to apply (unified diff format). Deprecated — use `patchContent` inside `files` instead.", + "example": "++class SomeClass2 {}" }, - "publishedOn": { - "type": "string" + "fileScope": { + "type": [ + "string", + "null" + ], + "enum": [ + "MAIN", + "TEST" + ] }, - "affectedPackages": { + "files": { "type": "array", + "description": "List of files to analyze. When provided, takes precedence over the top-level filePath/fileContent fields. No duplicate paths allowed. ", "items": { - "$ref": "#/components/schemas/AffectedPackageResource" + "$ref": "#/components/schemas/PrivateAnalysisFile" } }, - "withdrawn": { - "type": "boolean" - }, - "reachableLocations": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CveLocationResource" - } + "analysisDepth": { + "type": [ + "string", + "null" + ], + "enum": [ + "STANDARD", + "DEEP" + ] } - } + }, + "required": [ + "organizationId" + ] }, - "IssueReleaseStatusTransitionRequest": { + "PrivateAnalysisFile": { "type": "object", "properties": { - "issueReleaseKey": { + "path": { "type": "string", - "description": "Issue release key" + "description": "Project-relative path of the file", + "example": "directory/File.java", + "pattern": "^\\S([ \\S]*\\S)?$" }, - "transitionKey": { + "content": { "type": "string", - "description": "Transition key", + "description": "The original content of the file", + "example": "class SomeClass {}" + }, + "scope": { + "type": [ + "string", + "null" + ], "enum": [ - "CONFIRM", - "REOPEN", - "SAFE", - "FIXED", - "ACCEPT" + "MAIN", + "TEST" ] }, - "comment": { - "type": "string", - "description": "Transition comment" + "patchContent": { + "type": [ + "string", + "null" + ], + "description": "The patch content to apply (unified diff format). If provided for one file, must be provided for all files in the request.", + "example": "++class SomeClass2 {}" } }, "required": [ - "issueReleaseKey", - "transitionKey" + "content", + "path" ] }, - "BoundProjectCreateRestRequest": { + "ContextItem": { "type": "object", + "description": "Item object stored in a context", "properties": { - "projectKey": { - "type": "string", - "description": "Key of the project to create", - "minLength": 1 - }, - "projectName": { + "id": { "type": "string", - "description": "Name of the project to create", - "minLength": 1 + "description": "The unique identifier of the item in its context", + "example": [ + "550e8400-e29b-11d4-a716-446655440000", + "junit3_14.jar", + "moduleA_classes" + ], + "maxLength": 200, + "minLength": 0, + "pattern": "^\\S+$" }, - "devOpsPlatformSettingId": { + "type": { "type": "string", - "description": "Identifier of DevOps platform configuration to use. Use /dop-translation/dop-settings to retrieve the settings and their ID", - "minLength": 1 + "enum": [ + "FILE" + ] }, - "repositoryIdentifier": { + "sha256": { "type": "string", - "description": "Identifier of the DevOps platform repository to import:\n- repository slug for GitHub and Bitbucket (Cloud and Server)\n- repository id for GitLab\n- repository name for Azure DevOps\n", - "minLength": 1 - }, - "projectIdentifier": { - "type": [ - "string", - "null" + "description": "A sha256 of the item content to detect changes", + "example": [ + "fee9f8802b134dd3f40f06ffd599de7a3434f68c796afa81c6d023db7e7d250e" ], - "description": "Identifier of the DevOps platform project in which the repository is located.\nThis is only needed for Azure and BitBucket Server platforms\n" + "maxLength": 64, + "minLength": 0, + "pattern": "^\\S+$" }, - "newCodeDefinitionType": { + "missing": { "type": [ - "string", + "boolean", "null" ], - "description": " Project New Code Definition Type\n New code definitions of the following types are allowed:\n - PREVIOUS_VERSION\n - NUMBER_OF_DAYS\n - REFERENCE_BRANCH - will default to the main branch.\n" + "description": "Whether or the service has the context item already saved" }, - "newCodeDefinitionValue": { + "downloadUrl": { "type": [ "string", "null" ], - "description": " Project New Code Definition Value\n For each new code definition type, a different value is expected:\n - no value, when the new code definition type is PREVIOUS_VERSION and REFERENCE_BRANCH\n - a number between 1 and 90, when the new code definition type is NUMBER_OF_DAYS\n" - }, - "monorepo": { - "type": "boolean", - "description": "True if project is part of a mono repo." + "description": "The URL to download the item", + "example": "https://s3.us-east-1.amazonaws.com/bucketname/a3s-contexts/550e8400-e29b-11d4-a716-446655440000/itemX.jar", + "maxLength": 200, + "minLength": 0, + "pattern": "^\\S+$" } }, "required": [ - "devOpsPlatformSettingId", - "monorepo", - "projectKey", - "projectName", - "repositoryIdentifier" + "id", + "sha256", + "type" ] }, - "BoundProjectCreateRestResponse": { + "ContextWithUploadUrl": { "type": "object", "properties": { - "projectId": { + "id": { "type": "string", - "description": "The identifier of the created project" + "format": "uuid", + "description": "The unique identifier of a context" }, - "bindingId": { + "analysisId": { "type": "string", - "description": "The identifier of the binding between the created project and the DevOps platform project" - }, - "newProjectCreated": { - "type": "boolean", - "description": "True if a new project was created, false if an existing project was bound" - } - } - }, - "UserCreateRestRequest": { - "type": "object", - "properties": { - "email": { - "type": [ - "string", - "null" - ], - "format": "email", - "description": "User email", - "maxLength": 100, - "minLength": 1 + "description": "The identifier of an analysis on SonarQube (a UUID on SonarQube Cloud, possibly a legacy identifier on SonarQube Server)", + "example": "550e8400-e29b-11d4-a716-446655440000" }, - "local": { - "type": [ - "boolean", - "null" + "kind": { + "type": "string", + "description": "Context kind. Must be unique for a particular analysis", + "example": [ + "java", + "python", + "security", + "jar-libs" ], - "default": true, - "description": "Specify if the user should be authenticated from SonarQube server or from an external authentication system. Password should not be set when local is set to false." + "maxLength": 40, + "minLength": 0, + "pattern": "^\\S+$" }, - "login": { + "metadata": { "type": "string", - "description": "User login", - "maxLength": 100, - "minLength": 2 + "description": "Any JSON-formatted object" }, - "name": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ContextItem" + } + }, + "uploadUrl": { "type": "string", - "description": "User name", + "description": "An URL to upload missing context items", + "example": "https://s3.us-east-1.amazonaws.com/bucketname/a3s-contexts/550e8400-e29b-11d4-a716-446655440000", "maxLength": 200, - "minLength": 0 + "minLength": 0, + "pattern": "^\\S+$" + } + }, + "required": [ + "analysisId", + "id", + "items", + "kind", + "metadata", + "uploadUrl" + ] + }, + "ContextCreationRequest": { + "type": "object", + "description": "Request to create a new context", + "properties": { + "analysisId": { + "type": "string", + "description": "The identifier of an analysis on SonarQube (a UUID on SonarQube Cloud, possibly a legacy identifier on SonarQube Server)", + "example": "550e8400-e29b-11d4-a716-446655440000" }, - "password": { + "branchName": { "type": [ "string", "null" ], - "description": "User password. Only mandatory when creating local user, otherwise it should not be set", - "writeOnly": true + "description": "Optional branch name" }, - "scmAccounts": { - "type": [ - "array", - "null" + "kind": { + "type": "string", + "description": "Context kind. Must be unique for a particular analysis", + "example": [ + "java", + "python", + "security", + "jar-libs" ], - "description": "List of SCM accounts.", + "maxLength": 40, + "minLength": 0, + "pattern": "^\\S+$" + }, + "metadata": { + "type": "string", + "description": "Any JSON-formatted object" + }, + "items": { + "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/ContextItem" } } }, "required": [ - "login", - "name" + "analysisId", + "kind", + "metadata" ] }, - "UserRestResponseForAdmins": { + "PublicAnalysisCreationRequest": { "type": "object", + "description": "Structure of a request to create an analysis", "properties": { - "id": { - "type": "string", - "readOnly": true - }, - "login": { - "type": "string" - }, - "name": { - "type": "string" + "organizationId": { + "type": [ + "string", + "null" + ], + "format": "uuid", + "description": "The unique identifier of the organization", + "example": "c713929f-d5ee-4559-bae1-a93ac20cbbb6" }, - "email": { + "organizationKey": { "type": [ "string", "null" - ] + ], + "description": "The key of the organization", + "example": "my-corporate-org", + "maxLength": 400, + "minLength": 0, + "pattern": "^\\S+$" }, - "active": { + "projectId": { "type": [ - "boolean", + "string", "null" ], - "readOnly": true + "description": "The unique identifier of the project", + "example": "c713929f-d5ee-4559-bae1-a93ac20cbbb6", + "maxLength": 40, + "minLength": 0, + "pattern": "^\\S+$" }, - "local": { + "projectKey": { "type": [ - "boolean", + "string", "null" ], - "readOnly": true + "description": "The key of the project", + "example": "test-project", + "maxLength": 400, + "minLength": 0, + "pattern": "^\\S+$" }, - "managed": { + "branchId": { "type": [ - "boolean", + "string", "null" ], - "readOnly": true + "description": "The unique identifier of the branch to retrieve the latest analysis", + "example": "7a2f5c8d-9e3b-4a1c-bf72-83a892472f22", + "pattern": "^\\S+$" }, - "externalLogin": { + "branchName": { "type": [ "string", "null" - ] + ], + "description": "The branch to retrieve the latest analysis", + "example": "feature/john/BUG-3854/avoid-crash", + "maxLength": 400, + "minLength": 0, + "pattern": "^\\S+$" }, - "externalProvider": { + "filePath": { "type": [ "string", "null" - ] + ], + "deprecated": true, + "description": "Project-relative path of the file. Deprecated — use `files` instead.", + "example": "directory/File.java", + "pattern": "^\\S([ \\S]*\\S)?$" }, - "externalId": { + "fileContent": { "type": [ "string", "null" - ] + ], + "deprecated": true, + "description": "The original content of the file. Deprecated — use `files` instead.", + "example": "class SomeClass {}" }, - "avatar": { + "fileScope": { "type": [ "string", "null" + ], + "enum": [ + "MAIN", + "TEST" ] }, - "sonarQubeLastConnectionDate": { + "files": { + "type": "array", + "description": "List of files to analyze. When provided, takes precedence over the top-level filePath/fileContent fields. No duplicate paths allowed. ", + "items": { + "$ref": "#/components/schemas/PublicAnalysisFile" + } + }, + "analysisDepth": { "type": [ "string", "null" ], - "readOnly": true + "enum": [ + "STANDARD", + "DEEP" + ] + } + } + }, + "PublicAnalysisFile": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Project-relative path of the file", + "example": "directory/File.java", + "pattern": "^\\S([ \\S]*\\S)?$" }, - "sonarLintLastConnectionDate": { + "content": { + "type": "string", + "description": "The original content of the file", + "example": "class SomeClass {}" + }, + "scope": { "type": [ "string", "null" ], - "readOnly": true + "enum": [ + "MAIN", + "TEST" + ] + } + }, + "required": [ + "content", + "path" + ] + }, + "UpdateFieldListString": { + "type": "object", + "properties": { + "value": { + "type": "array", + "items": { + "type": "string" + } + }, + "defined": { + "type": "boolean" + } + } + }, + "UserUpdateRestRequest": { + "type": "object", + "properties": { + "login": { + "type": "string", + "description": "User login", + "maxLength": 100, + "minLength": 2 + }, + "name": { + "type": "string", + "description": "User first name and last name", + "maxLength": 200, + "minLength": 0 + }, + "email": { + "type": "string", + "format": "email", + "description": "Email", + "maxLength": 100, + "minLength": 1 + }, + "scmAccounts": { + "$ref": "#/components/schemas/UpdateFieldListString", + "description": "List of SCM accounts.", + "items": { + "type": "string" + } + }, + "externalProvider": { + "type": "string", + "description": "New identity provider. Only providers configured in your platform are supported. This could be: github, gitlab, bitbucket, saml, LDAP, LDAP_{serverKey}\n(according to your server configuration file).\nWarning: when this is updated, the user will only be able to authenticate using the new identity provider. The account is turned back into a local one\nonly when 'externalProvider', 'externalLogin' and 'externalId' are all null or absent from the request; the resulting local account has no password\n(it was cleared when the account was originally bound to an external identity) and an administrator must set one before it can authenticate locally\nagain. Binding a local account to an external identity provider requires this field to be explicitly set together with 'externalLogin' and/or 'externalId'.\n" + }, + "externalLogin": { + "type": "string", + "description": "New external login, usually the login used in the authentication system.", + "maxLength": 255, + "minLength": 1 }, - "scmAccounts": { - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } + "externalId": { + "type": "string", + "description": "New external id in the authentication system.", + "maxLength": 255, + "minLength": 1 + }, + "local": { + "type": "boolean", + "description": "Expected local status of the user once this update is applied. Provided as a safety check: if the resulting local status of the\nuser does not match this value, the request is rejected instead of applied.\n" } } }, - "EmailConfigurationCreateRestRequest": { + "EmailConfigurationUpdateRestRequest": { "type": "object", "properties": { "host": { "type": "string", - "description": "URL of your SMTP server", - "minLength": 1 + "description": "URL of your SMTP server" }, "port": { "type": "string", - "description": "Port of your SMTP server (usually 25, 587 or 465)", - "minLength": 1 + "description": "Port of your SMTP server (usually 25, 587 or 465)" }, "securityProtocol": { "type": "string", @@ -8657,18 +23467,15 @@ }, "fromAddress": { "type": "string", - "description": "Address emails will come from", - "minLength": 1 + "description": "Address emails will come from" }, "fromName": { "type": "string", - "description": "Name emails will come from (usually \"SonarQube\")", - "minLength": 1 + "description": "Name emails will come from (usually \"SonarQube\")" }, "subjectPrefix": { "type": "string", - "description": "Prefix added to email so they can be easily recognized (usually \"[SonarQube]\")", - "minLength": 1 + "description": "Prefix added to email so they can be easily recognized (usually \"[SonarQube]\")" }, "authMethod": { "type": "string", @@ -8680,3870 +23487,3772 @@ }, "username": { "type": "string", - "description": "For Basic and OAuth authentication: username used to authenticate to the SMTP server", - "minLength": 1 + "description": "For Basic and OAuth authentication: username used to authenticate to the SMTP server" }, "basicPassword": { - "type": [ - "string", - "null" - ], - "description": "For basic authentication: password used to authenticate to the SMTP server", - "writeOnly": true + "type": "string", + "description": "For basic authentication: password used to authenticate to the SMTP server" }, "oauthAuthenticationHost": { - "type": [ - "string", - "null" - ], + "type": "string", "description": "For OAuth authentication: host of the Identity Provider issuing access tokens" }, "oauthClientId": { - "type": [ - "string", - "null" - ], - "description": "For OAuth authentication: Client ID provided by Microsoft Exchange when registering the application", - "writeOnly": true + "type": "string", + "description": "For OAuth authentication: Client ID provided by Microsoft Exchange when registering the application" }, "oauthClientSecret": { - "type": [ - "string", - "null" - ], - "description": "For OAuth authentication: Client secret provided by Microsoft Exchange when registering the application", - "writeOnly": true + "type": "string", + "description": "For OAuth authentication: Client password provided by Microsoft Exchange when registering the application" }, "oauthTenant": { - "type": [ - "string", - "null" - ], + "type": "string", "description": "For OAuth authentication: Microsoft tenant" + }, + "oauthScope": { + "type": "string", + "description": "For OAuth authentication: scope for the OAuth token request" + } + } + }, + "LicenseProfilesUpdateRestRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the license policy" + }, + "default": { + "type": "boolean", + "description": "Whether this license policy is the default or not" + } + } + }, + "LicensePolicyLicenseUpdateRestRequest": { + "type": "object", + "properties": { + "policy": { + "type": "string", + "description": "The new status of this license.", + "enum": [ + "DENY", + "ALLOW" + ] } }, "required": [ - "authMethod", - "fromAddress", - "fromName", - "host", - "port", - "securityProtocol", - "subjectPrefix", - "username" + "policy" ] }, - "EmailConfigurationResource": { + "LicensePolicyLicenseResource": { "type": "object", "properties": { "id": { - "type": "string", - "readOnly": true + "type": "string" }, - "host": { - "type": "string", - "description": "URL of your SMTP server" + "spdxLicenseId": { + "type": "string" }, - "port": { - "type": "string", - "description": "Port of your SMTP server (usually 25, 587 or 465)" + "name": { + "type": "string" }, - "securityProtocol": { + "category": { "type": "string", - "description": "Security protocol used to connect to your SMTP server (SSLTLS is recommended)", "enum": [ - "NONE", - "SSLTLS", - "STARTTLS" + "UNKNOWN", + "COPYLEFT_WEAK", + "COPYLEFT_STRONG", + "COPYLEFT_NETWORK", + "COPYLEFT_MAXIMAL", + "PERMISSIVE_STANDARD", + "PERMISSIVE_AMATEUR" ] }, - "fromAddress": { + "policy": { "type": "string", - "description": "Address emails will come from" - }, - "fromName": { + "description": "The policy status of this license.", + "enum": [ + "DENY", + "ALLOW" + ] + } + } + }, + "LicenseProfileCategoryUpdateRestRequest": { + "type": "object", + "properties": { + "policy": { "type": "string", - "description": "Name emails will come from (usually \"SonarQube\")" + "description": "The new status of this category.", + "enum": [ + "DENY", + "ALLOW" + ] + } + }, + "required": [ + "policy" + ] + }, + "LicenseProfileCategoryResource": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "subjectPrefix": { + "key": { "type": "string", - "description": "Prefix added to email so they can be easily recognized (usually \"[SonarQube]\")" + "enum": [ + "UNKNOWN", + "COPYLEFT_WEAK", + "COPYLEFT_STRONG", + "COPYLEFT_NETWORK", + "COPYLEFT_MAXIMAL", + "PERMISSIVE_STANDARD", + "PERMISSIVE_AMATEUR" + ] }, - "authMethod": { + "policy": { "type": "string", - "description": "Authentication method used to connect to the SMTP server. OAuth is only supported for Microsoft Exchange", + "description": "The policy status of this category.", "enum": [ - "BASIC", - "OAUTH" + "DENY", + "ALLOW" ] - }, - "username": { - "type": [ - "string", - "null" - ], - "description": "For Basic and OAuth authentication: username used to authenticate to the SMTP server" - }, - "isBasicPasswordSet": { - "type": "boolean", - "description": "For Basic authentication: has the password field been set?" - }, - "oauthAuthenticationHost": { - "type": [ - "string", - "null" - ], - "description": "For OAuth authentication: host of the Identity Provider issuing access tokens" - }, - "isOauthClientIdSet": { - "type": "boolean", - "description": "For OAuth authentication: has the Client ID field been set?" - }, - "isOauthClientSecretSet": { - "type": "boolean", - "description": "For OAuth authentication: has the Client secret field been set?" - }, - "oauthTenant": { - "type": [ - "string", - "null" - ], - "description": "For OAuth authentication: Microsoft tenant" } } }, - "ReleaseResearchRestRequest": { + "AssignedProjectsUpdateRestRequest": { "type": "object", "properties": { + "licenseProfileUuid": { + "type": "string", + "description": "(deprecated) The id of the license profile that should be used when analyzing the project for license issues." + }, + "licenseProfileId": { + "type": "string", + "description": "The id of the license profile that should be used when analyzing the project for license issues." + }, "projectKey": { "type": "string", - "description": "Key of the project to research", - "minLength": 1 + "description": "The key of the project that should be assigned to the license profile." + } + }, + "required": [ + "projectKey" + ] + }, + "IssueReleaseUpdateCommentRestRequest": { + "type": "object", + "properties": { + "issueReleaseChangeKey": { + "type": "string", + "description": "Issue release change key" }, - "purls": { - "type": "array", - "description": "Versioned package URLs (PURLs) to research. Maximum 100 per request.", - "items": { - "type": "string" - } + "comment": { + "type": "string", + "description": "Comment text" } }, "required": [ - "projectKey", - "purls" + "comment", + "issueReleaseChangeKey" ] }, - "ReleaseResearchEntryResponse": { + "FeatureEnablementRequest": { "type": "object", "properties": { - "key": { - "type": "string" - }, - "packageUrl": { - "type": "string" - }, - "packageManager": { - "type": "string" - }, - "packageName": { - "type": "string" - }, - "version": { - "type": "string" - }, - "licenseExpression": { - "type": "string" - }, - "known": { - "type": "boolean" - }, - "knownPackage": { - "type": "boolean" - }, - "newlyIntroduced": { - "type": "boolean" - }, - "issues": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ReleaseResearchIssueResponse" - } + "enablement": { + "type": "boolean", + "description": "Whether SCA is enabled or not" } } }, - "ReleaseResearchIssueResponse": { + "FeatureEnablementResource": { "type": "object", "properties": { - "key": { - "type": "string" - }, - "severity": { - "type": "string" - }, - "showIncreasedSeverityWarning": { + "enablement": { "type": "boolean" - }, - "type": { - "type": "string", - "enum": [ - "VULNERABILITY", - "PROHIBITED_LICENSE", - "MALWARE" - ] - }, - "quality": { - "type": "string", - "enum": [ - "MAINTAINABILITY", - "RELIABILITY", - "SECURITY" - ] - }, - "status": { - "type": "string", - "enum": [ - "OPEN", - "ACCEPT", - "CONFIRM", - "SAFE", - "FIXED" + } + } + }, + "LlmProviderUpdateRequest": { + "type": "object", + "properties": { + "label": { + "type": [ + "string", + "null" ] }, - "vulnerabilityId": { - "type": "string" - }, - "cweIds": { - "type": "array", - "deprecated": true, - "items": { - "type": "string", - "deprecated": true - } - }, - "cwes": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CweResource" - } - }, - "cvssScore": { - "type": "string" - }, - "spdxLicenseId": { - "type": "string" - }, - "versionOptions": { - "type": "array", - "items": { - "$ref": "#/components/schemas/VersionOptionResource" - } + "configuration": { + "type": [ + "object", + "null" + ], + "additionalProperties": {} } } }, - "ReleaseResearchRestResponse": { + "JiraWorkTypesSelectionResource": { "type": "object", "properties": { - "releases": { + "sonarProjectId": { + "type": "string", + "description": "The Sonar project ID", + "writeOnly": true + }, + "selectedWorkTypes": { "type": "array", + "description": "An array with the selected work type ids", "items": { - "$ref": "#/components/schemas/ReleaseResearchEntryResponse" - } + "type": "string" + }, + "writeOnly": true } } }, - "TriggerReanalysisRestRequest": { + "PatchJiraProjectBindingRequestResource": { "type": "object", "properties": { - "projectKey": { + "sonarProjectId": { "type": "string", - "description": "Key of the project to re-analyze", - "minLength": 1 + "description": "Sonar project identifier", + "minLength": 1, + "writeOnly": true }, - "branchKey": { + "jiraProjectKey": { "type": "string", - "description": "Key of the branch to re-analyze. If not provided, all branches matching the organization's rescan branch type setting will be queued." + "description": "Jira project key", + "minLength": 1, + "writeOnly": true } }, "required": [ - "projectKey" + "jiraProjectKey", + "sonarProjectId" ] }, - "TriggerReanalysisResource": { - "type": "object", - "properties": { - "branchesQueued": { - "type": "integer", - "format": "int32" - } - } - }, - "PackageInfoRestRequest": { + "PatchJiraOrganizationBindingRequestResource": { "type": "object", "properties": { - "projectKey": { + "sonarOrganizationUuid": { "type": "string", - "description": "Key of the project to analyze", - "minLength": 1 - }, - "purls": { - "type": "array", - "description": "Package URLs (PURLs) to analyze. Maximum 100 per request. Either 'purls' or the deprecated 'purl' field must be provided.", - "items": { - "type": "string" - } + "format": "uuid", + "description": "Sonar organization UUID", + "writeOnly": true }, - "purl": { + "jiraCloudId": { "type": "string", + "description": "Jira cloud ID to bind with the organization", + "minLength": 1, "writeOnly": true }, - "includeVulnerabilityDetails": { - "type": "boolean", - "description": "Whether to include vulnerability details in the response" + "jiraInstanceUrl": { + "type": "string", + "description": "Jira instance URL", + "minLength": 1, + "writeOnly": true } }, "required": [ - "projectKey" + "jiraCloudId", + "jiraInstanceUrl", + "sonarOrganizationUuid" ] }, - "LicenseResponse": { + "PatchJiraOrganizationBindingEditResource": { "type": "object", "properties": { - "expression": { - "type": "string" + "sonarOrganizationUuid": { + "type": "string", + "format": "uuid", + "description": "Sonar organization UUID", + "writeOnly": true }, - "allowed": { - "type": "boolean" + "isTokenShared": { + "type": "boolean", + "description": "Whether the Jira OAuth token is shared across all users in the organization", + "writeOnly": true } - } + }, + "required": [ + "isTokenShared", + "sonarOrganizationUuid" + ] }, - "PackageInfoEntryResponse": { - "allOf": [ - { - "$ref": "#/components/schemas/PackageInfoResult" + "RuleTypeMapping": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Rule type", + "enum": [ + "CODE_SMELL", + "BUG", + "VULNERABILITY", + "SECURITY_HOTSPOT" + ] }, - { - "type": "object", - "properties": { - "purl": { - "type": "string" - }, - "license": { - "$ref": "#/components/schemas/LicenseResponse" - }, - "vulnerabilities": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PackageVulnerabilityResponse" - } - }, - "malicious": { - "type": "boolean" - }, - "knownPackage": { - "type": "boolean" - }, - "knownRelease": { - "type": "boolean" - } + "severities": { + "type": "array", + "description": "List of severities", + "items": { + "type": "string", + "enum": [ + "BLOCKER", + "CRITICAL", + "MAJOR", + "MINOR", + "INFO" + ] } } + }, + "required": [ + "severities", + "type" ] }, - "PackageInfoRestResponse": { - "allOf": [ - { - "$ref": "#/components/schemas/PackageInfoResult" + "SandboxSettingsResource": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether sandbox is enabled globally" }, - { - "type": "object", - "properties": { - "packages": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PackageInfoEntryResponse" - } - } + "defaultValue": { + "type": "boolean", + "description": "Default value for projects" + }, + "allowOverride": { + "type": "boolean", + "description": "Allow projects to override settings" + }, + "softwareQualities": { + "type": [ + "array", + "null" + ], + "description": "Software quality mappings (MQR mode)", + "items": { + "$ref": "#/components/schemas/SoftwareQualityMapping" } + }, + "types": { + "type": [ + "array", + "null" + ], + "description": "Rule type mappings (Standard Experience mode)", + "items": { + "$ref": "#/components/schemas/RuleTypeMapping" + } + }, + "deferralDays": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "description": "Number of days after which a sandboxed issue is automatically reopened. 0 disables auto-reopening. Null on PATCH means unchanged" } - ] + } }, - "PackageInfoResult": {}, - "PackageVulnerabilityResponse": { + "SoftwareQualityMapping": { "type": "object", "properties": { - "id": { - "type": "string" - }, - "cvssScore": { - "type": "number" + "softwareQuality": { + "type": "string", + "description": "Software quality", + "enum": [ + "MAINTAINABILITY", + "RELIABILITY", + "SECURITY" + ] }, - "cweIds": { + "impactSeverities": { "type": "array", - "deprecated": true, + "description": "List of impact severities", "items": { "type": "string", - "deprecated": true + "enum": [ + "INFO", + "LOW", + "MEDIUM", + "HIGH", + "BLOCKER" + ] } + } + }, + "required": [ + "impactSeverities", + "softwareQuality" + ] + }, + "SandboxSettingsProjectResource": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether sandbox is enabled for this project" }, - "cwes": { - "type": "array", + "softwareQualities": { + "type": [ + "array", + "null" + ], + "description": "Software quality mappings (MQR mode)", "items": { - "$ref": "#/components/schemas/CweResource" + "$ref": "#/components/schemas/SoftwareQualityMapping" } }, - "riskSeverity": { - "type": "string" + "types": { + "type": [ + "array", + "null" + ], + "description": "Rule type mappings (Standard Experience mode)", + "items": { + "$ref": "#/components/schemas/RuleTypeMapping" + } }, - "withdrawn": { - "type": "boolean" + "overridden": { + "type": "boolean", + "description": "Whether project settings (software qualities and/or deferral days) override instance settings" }, - "publishedOn": { - "type": "string" + "deferralDays": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "description": "Number of days after which a sandboxed issue is automatically reopened. Null on PATCH means unchanged" + } + } + }, + "IntegrationConfigurationPatchRequest": { + "type": "object", + "properties": { + "clientId": { + "type": [ + "string", + "null" + ] }, - "fixedVersions": { - "type": "array", - "items": { - "$ref": "#/components/schemas/VersionFix" - } + "clientSecret": { + "type": [ + "string", + "null" + ] }, - "unaffectedVersions": { - "type": "string" + "signingSecret": { + "type": [ + "string", + "null" + ] } } }, - "VersionFix": { + "FixSuggestionsFeatureEnablementRequest": { "type": "object", "properties": { - "version": { - "type": "string" + "enablement": { + "type": "string", + "description": "Whether AI CodeFix is enabled or not", + "enum": [ + "DISABLED", + "ENABLED_FOR_ALL_PROJECTS", + "ENABLED_FOR_SOME_PROJECTS" + ] }, - "fixLevel": { - "type": "string" + "enabledProjectKeys": { + "type": [ + "array", + "null" + ], + "description": "Project keys for which AI CodeFix is enabled. Only used when enablement is ENABLED_FOR_SOME_PROJECTS.", + "items": { + "type": "string" + }, + "uniqueItems": true }, - "descriptionCode": { - "type": "string" + "provider": { + "$ref": "#/components/schemas/ProviderDto", + "description": "The AI provider configuration" } } }, - "LicenseProfilesCreateRestRequest": { + "HeaderDto": { "type": "object", + "description": "A custom HTTP header for the provider request", "properties": { "name": { "type": "string", - "description": "The name of the license policy" + "description": "Header name" }, - "default": { - "type": "boolean", - "description": "Whether this license policy is the default or not" + "value": { + "type": "string", + "description": "Header value. Masked as '****' if secret and already saved." }, - "organization": { - "type": "string" + "secret": { + "type": "boolean", + "description": "Whether this header value should be stored encrypted" } - }, - "required": [ - "name", - "organization" - ] + } }, - "LicenseProfileResource": { + "ProviderDto": { "type": "object", "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" + "type": { + "type": "string", + "description": "The provider type" }, - "default": { - "type": "boolean" + "model": { + "type": [ + "string", + "null" + ], + "description": "The selected model (managed providers only)" }, - "actions": { - "$ref": "#/components/schemas/LicenseProfileSingleActions" + "config": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + }, + "description": "Provider-specific configuration (self-hosted providers only)" }, - "updatedAt": { - "type": "string" + "headers": { + "type": [ + "array", + "null" + ], + "description": "Custom HTTP headers (Custom provider only)", + "items": { + "$ref": "#/components/schemas/HeaderDto" + } } } }, - "LicenseProfileSingleActions": { + "DevOpsPermissionMappingUpdateRequest": { "type": "object", "properties": { - "edit": { + "permissions": { + "$ref": "#/components/schemas/PermissionMappingUpdate", + "description": "Set of SonarQube permissions to apply" + } + } + }, + "PermissionMappingUpdate": { + "type": "object", + "properties": { + "user": { "type": "boolean" }, - "setAsDefault": { + "codeViewer": { "type": "boolean" }, - "associateProjects": { + "issueAdmin": { "type": "boolean" }, - "delete": { + "securityHotspotAdmin": { + "type": "boolean" + }, + "admin": { + "type": "boolean" + }, + "scan": { "type": "boolean" } } }, - "IssueReleaseSetSeverityRestRequest": { + "GitlabConfigurationUpdateRestRequest": { "type": "object", "properties": { - "issueReleaseKey": { + "enabled": { + "type": "boolean", + "description": "Enable Gitlab authentication" + }, + "applicationId": { "type": "string", - "description": "Issue release key" + "description": "Gitlab Application id" }, - "quality": { + "url": { "type": "string", - "description": "Software Quality", - "enum": [ - "MAINTAINABILITY", - "RELIABILITY", - "SECURITY" - ] + "description": "Url of Gitlab instance for authentication (for instance https://gitlab.com/api/v4)" }, - "severity": { + "secret": { + "type": [ + "string", + "null" + ], + "description": "Secret of the application" + }, + "synchronizeGroups": { + "type": "boolean", + "description": "Set whether to synchronize groups" + }, + "allowedGroups": { + "$ref": "#/components/schemas/UpdateFieldListString", + "description": "Root Gitlab groups allowed to authenticate and provisioned. Ignored when allowAllGroups is true.", + "items": { + "type": "string" + } + }, + "allowAllGroups": { + "type": "boolean", + "description": "When true with Auto-provisioning, every group visible to the provisioning token is provisioned and the allowedGroups list is ignored. Has no effect with Just-in-Time provisioning. Security risk: any user belonging to any group accessible by the provisioning token will be granted access. Restrict access using allowedGroups unless broad access is intentional. Not supported on GitLab.com (SaaS): the request is rejected when the configured URL is gitlab.com, since the provisioning token may have visibility into a much larger and unbounded set of groups. Performance note: login may be slower for users belonging to a large number of groups, as all their groups must be fetched from GitLab on every authentication." + }, + "provisioningType": { "type": "string", - "description": "Severity", + "description": "Type of synchronization", "enum": [ - "INFO", - "LOW", - "MEDIUM", - "HIGH", - "BLOCKER" + "JIT", + "AUTO_PROVISIONING" ] + }, + "allowUsersToSignUp": { + "type": "boolean", + "description": "Allow user to sign up" + }, + "provisioningToken": { + "type": [ + "string", + "null" + ], + "description": "Gitlab token for provisioning", + "maxLength": 2147483647, + "minLength": 1 } } }, - "IssueReleaseAddCommentRestRequest": { + "GithubConfigurationUpdateRestRequest": { "type": "object", "properties": { - "issueReleaseKey": { - "type": "string", - "description": "Issue release key" + "enabled": { + "type": "boolean", + "description": "Enable GitHub authentication" }, - "comment": { + "clientId": { "type": "string", - "description": "comment" - } - }, - "required": [ - "issueReleaseKey" - ] - }, - "BulkIssueReleaseChangeRequest": { - "type": "object", - "properties": { - "issueReleaseKeys": { - "type": "array", - "description": "Dependency risk identifiers", - "items": { - "type": "string" - }, - "minItems": 1 + "description": "GitHub Client ID" }, - "severity": { + "clientSecret": { "type": "string", - "description": "Severity assignment", - "enum": [ - "BLOCKER", - "HIGH", - "MEDIUM", - "LOW", - "INFO" - ] + "description": "GitHub Client secret" }, - "transitionKey": { + "applicationId": { "type": "string", - "description": "Transition key", - "enum": [ - "ACCEPT", - "CONFIRM", - "REOPEN", - "SAFE" - ] + "description": "GitHub Application id" }, - "comment": { + "privateKey": { "type": "string", - "description": "Transition comment" + "description": "GitHub Private key" }, - "assignee": { + "synchronizeGroups": { + "type": "boolean", + "description": "Set whether to synchronize groups" + }, + "apiUrl": { "type": "string", - "description": "Assignee username" + "description": "Url of GitHub instance for API connectivity (for instance https://api.github.com)" }, - "shouldUnsetAssignee": { - "type": "boolean", - "description": "Whether to unset the current assignee" - } - }, - "required": [ - "issueReleaseKeys" - ] - }, - "AnalysesParseMultipartBody": { - "type": "object", - "description": "Multipart body for the parse endpoint", - "properties": { - "dependencyFiles": { + "webUrl": { "type": "string", - "format": "binary", - "description": "Tar-xz archive containing dependency manifests and lockfiles." - } - } - }, - "AnalysisErrorResource": { - "type": "object", - "properties": { - "id": { - "type": "string" + "description": "Url of GitHub instance for authentication (for instance https://github.com)" }, - "code": { + "allowedOrganizations": { + "$ref": "#/components/schemas/UpdateFieldListString", + "description": "GitHub organizations allowed to authenticate and provisioned", + "items": { + "type": "string" + } + }, + "provisioningType": { "type": "string", + "description": "Type of synchronization", "enum": [ - "UNKNOWN", - "NO_DEPENDENCIES_FOUND", - "DEPENDENCY_FILES_PARSE_ERROR", - "UNSUPPORTED_PLATFORM", - "INEXACT_VERSIONS", - "MISSING_LOCKFILE" + "JIT", + "AUTO_PROVISIONING" ] }, - "path": { - "type": "string" + "allowUsersToSignUp": { + "type": "boolean", + "description": "Allow user to sign up" }, - "message": { - "type": "string" + "projectVisibility": { + "type": "boolean", + "description": "Sync project visibility" + }, + "userConsentRequiredAfterUpgrade": { + "type": "boolean", + "description": "Admin consent to synchronize permissions from GitHub" } } }, - "ReadDependencyFilesResult": { + "ProjectConfiguration": { "type": "object", "properties": { - "packages": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ReadDependencyFilesResultEntry" - } + "projectId": { + "type": "string" }, - "parsedFiles": { + "scannerProperties": { "type": "array", + "description": "Enablement the backend has already computed, for the scanner to inject into the analyzer context under the sonar.architecture.scanner. prefix. Generic on purpose: the scanner prefixes and injects whatever arrives, so a new flag needs no scanner change. Every entry is sent explicitly, false included. Delivery is not guaranteed, though: the scanner skips this call when architecture analysis is disabled and swallows transport errors, so the analyzer keeps its own default for an absent key.", "items": { - "type": "string" + "$ref": "#/components/schemas/ScannerProperty" } }, - "errors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AnalysisErrorResource" - } - } - } - }, - "ReadDependencyFilesResultEntry": { - "type": "object", - "properties": { - "packageUrl": { - "type": "string" + "intendedArchitecture": { + "type": [ + "string", + "null" + ] }, - "dependencyFilePaths": { + "directives": { "type": "array", "items": { "type": "string" } }, - "dependencyChains": { + "boundaryDescriptors": { "type": "array", "items": { - "type": "array", - "items": { - "type": "string" - } + "type": "string" } } - } + }, + "required": [ + "boundaryDescriptors", + "directives", + "projectId", + "scannerProperties" + ] }, - "AzureBillingRestResponse": { + "ScheduleRecurrence": { "type": "object", + "description": "How often the run recurs. WEEKLY requires exactly one dayOfWeek; DAILY and MONTHLY must omit it. MONTHLY always fires on the last day of the month. ", "properties": { - "success": { - "type": "boolean" + "type": { + "type": [ + "string", + "null" + ], + "description": "The recurrence cadence.", + "enum": [ + "DAILY", + "WEEKLY", + "MONTHLY" + ] }, - "message": { + "dayOfWeek": { "type": [ "string", "null" + ], + "description": "Day of week for WEEKLY recurrence; omitted otherwise.", + "enum": [ + "MONDAY", + "TUESDAY", + "WEDNESDAY", + "THURSDAY", + "FRIDAY", + "SATURDAY", + "SUNDAY" ] } } }, - "PostJiraWorkItemRequestResource": { + "ScheduleTime": { "type": "object", + "description": "Time of day of the scheduled run, in the given IANA timezone. Runs fire on the hour.", "properties": { - "resourceId": { - "type": "string", - "description": "Resource identifier", - "minLength": 1, - "writeOnly": true - }, - "resourceType": { - "type": "string", - "description": "Resource type", - "enum": [ - "SONAR_ISSUE", - "DEPENDENCY_RISK" - ], - "writeOnly": true - }, - "sonarProjectId": { - "type": "string", - "description": "Sonar project identifier", - "minLength": 1, - "writeOnly": true - }, - "workTypeId": { - "type": "string", - "description": "Work type identifier", - "minLength": 1, - "writeOnly": true - }, - "summary": { + "hour": { "type": [ - "string", + "integer", "null" ], - "description": "Work item summary", - "writeOnly": true + "format": "int32", + "description": "Hour of day (0-23) in the given timezone.", + "maximum": 23, + "minimum": 0 }, - "description": { + "timezone": { "type": [ "string", "null" ], - "description": "Work item description", - "writeOnly": true + "description": "IANA timezone id the schedule fires in (DST-aware)." } - }, - "required": [ - "resourceId", - "resourceType", - "sonarProjectId", - "workTypeId" - ] + } }, - "JiraWorkItemResource": { + "ProjectConfigurationRequest": { "type": "object", "properties": { - "id": { - "type": "string", - "description": "Work item identifier", - "readOnly": true - }, - "jiraIssueId": { - "type": "string", - "description": "Jira issue identifier", - "readOnly": true - }, - "jiraIssueKey": { - "type": "string", - "description": "Jira issue key", - "readOnly": true - }, - "jiraIssueUrl": { - "type": "string", - "description": "Jira issue URL", - "readOnly": true - }, - "jiraIssueStatus": { - "type": [ - "string", - "null" - ], - "description": "Jira issue status", - "readOnly": true + "scheduling": { + "$ref": "#/components/schemas/SchedulingRequest" } } }, - "PostJiraProjectBindingRequestResource": { + "SchedulingRequest": { "type": "object", + "description": "Scheduled-run configuration to apply to the project. The structured config (branch, time, recurrence) is optional but all-or-nothing: supply a complete config to overwrite the stored schedule, or omit it to keep the stored schedule. Omitting it while enabling a project that has no stored schedule provisions a default one (daily at 02:00 Etc/UTC on the main branch) instead of failing, so enabling never requires the client to pick a schedule. This is a request-only model: the server-derived nextRunTime is never accepted here. ", "properties": { - "sonarProjectId": { - "type": "string", - "description": "Sonar project identifier", - "minLength": 1, - "writeOnly": true + "enabled": { + "type": [ + "boolean", + "null" + ], + "description": "Whether scheduled detection-agent runs are desired for the project. This is the project's own setting, not the effective state: unlike the identically named response field it is never combined with the instance-wide detection-agent setting. Setting it to true while the agent is disabled instance-wide is refused with 403; setting it to false is always accepted. Do not populate it by echoing a SchedulingResponse read back — while the agent is disabled instance-wide that response reports false as a mask, and echoing it turns the project's own scheduling off for real. " }, - "jiraOrganizationId": { - "type": "string", - "description": "Jira instance binding identifier", - "writeOnly": true + "branchId": { + "type": [ + "string", + "null" + ], + "description": "Stable identifier of the long-lived branch to scan on scheduled runs. " }, - "jiraProjectKey": { - "type": "string", - "description": "Jira project key", - "minLength": 1, - "writeOnly": true + "time": { + "$ref": "#/components/schemas/ScheduleTime" + }, + "recurrence": { + "$ref": "#/components/schemas/ScheduleRecurrence" } - }, - "required": [ - "jiraOrganizationId", - "jiraProjectKey", - "sonarProjectId" - ] + } }, - "JiraProjectBindingResource": { + "UpdateDashboardRequest": { "type": "object", "properties": { - "id": { - "type": "string", - "format": "uuid", - "description": "Jira project binding identifier", - "readOnly": true - }, - "sonarProjectId": { - "type": "string", - "description": "Sonar project identifier", - "readOnly": true + "name": { + "type": [ + "string", + "null" + ], + "maxLength": 300, + "minLength": 0 }, - "jiraOrganizationId": { - "type": "string", - "description": "Jira instance binding identifier", - "readOnly": true + "layout": { + "type": [ + "string", + "null" + ] }, - "jiraProjectKey": { - "type": "string", - "description": "Jira project key", - "readOnly": true + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 500, + "minLength": 0 } } }, - "PostJiraOrganizationBindingRequestResource": { + "ModeResource": { "type": "object", "properties": { - "state": { + "mode": { "type": "string", - "description": "Base64-encoded OAuth state parameter", - "minLength": 1, - "writeOnly": true + "enum": [ + "MQR", + "STANDARD_EXPERIENCE" + ] }, - "authorizationCode": { - "type": "string", - "description": "OAuth authorization code (no whitespace)", - "minLength": 1, - "writeOnly": true + "modified": { + "type": "boolean", + "readOnly": true } }, "required": [ - "authorizationCode", - "state" + "mode" ] }, - "JiraOrganizationBindingResource": { - "type": "null", + "GroupUpdateRestRequest": { + "type": "object", "properties": { - "id": { - "type": "string", - "description": "Jira instance binding identifier", - "readOnly": true - }, - "sonarOrganizationUuid": { - "type": "string", - "format": "uuid", - "description": "Sonar organization UUID", - "readOnly": true - }, - "jiraInstanceUrl": { + "name": { "type": "string", - "description": "Jira instance URL", - "readOnly": true + "description": "Group name", + "maxLength": 255, + "minLength": 1 }, - "createdBy": { + "description": { "type": "string", - "description": "User who created or reauthorized the jira connection", - "readOnly": true - }, - "createdAt": { - "type": "integer", - "format": "int64", - "description": "Creation timestamp", - "readOnly": true - }, - "isTokenShared": { - "type": "boolean", - "description": "TODO", - "readOnly": true + "description": "Description of the group", + "maxLength": 200, + "minLength": 0 } } }, - "OAuthResource": { + "PatchSdkBody": { "type": "object", + "description": "Mutable fields of an SDK. Only provided fields are updated; omitted fields are left unchanged. A non-empty sdkBoundaryDescriptors array replaces the whole set. An omitted, empty or null array counts as 'sdkBoundaryDescriptors not provided', since an SDK always has at least one — so a patch whose only field is such an array is an empty patch and is rejected.", "properties": { - "cloudId": { - "type": "string", - "description": "The resource's cloud ID", - "readOnly": true + "targetComponentId": { + "type": [ + "string", + "null" + ], + "format": "uuid", + "description": "Id of the organization component the SDK leads into (the provider)." }, - "url": { - "type": "string", - "description": "The resource's URL", - "readOnly": true + "name": { + "type": [ + "string", + "null" + ], + "maxLength": 255, + "minLength": 1 + }, + "sdkBoundaryDescriptors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SdkBoundaryDescriptor" + } } } }, - "PostJiraOrganizationBindingResponseResource": { + "PatchProjectRelationshipBody": { "type": "object", + "description": "Mutable fields of a project relationship. Only provided fields are updated; omitted fields are left unchanged.", "properties": { - "binding": { - "$ref": "#/components/schemas/JiraOrganizationBindingResource", - "description": "The successfully created binding (present if binding was created)", - "readOnly": true + "boundaryKey": { + "type": [ + "string", + "null" + ], + "description": "Key identifying the exit boundary (consumer side) this relationship applies to." }, - "resources": { - "type": "array", - "description": "Array of available resources to bind the instance to", - "items": { - "$ref": "#/components/schemas/OAuthResource" - }, - "readOnly": true + "targetComponentId": { + "type": [ + "string", + "null" + ], + "format": "uuid", + "description": "Id of the target organization component (the provider this exit connects to)." + }, + "targetEntryPointKey": { + "type": [ + "string", + "null" + ], + "description": "Key of the target component's entry point this exit connects to." } - }, - "required": [ - "resources" - ] + } }, - "UserBindingCreationRequest": { + "PatchPattern": { "type": "object", - "description": "User binding creation request", + "description": "Fields to update. 'name' and 'pattern' can be updated independently.", "properties": { - "bindingData": { - "$ref": "#/components/schemas/UserBindingCreationRequestBindingData" + "pattern": { + "$ref": "#/components/schemas/PatternData" }, - "userId": { - "type": "string", - "minLength": 1 + "name": { + "type": [ + "string", + "null" + ], + "description": "Updated display name." } - }, - "required": [ - "bindingData", - "userId" - ] + } }, - "UserBindingCreationRequestBindingData": { + "PatchOrganizationPlaceholderBody": { "type": "object", + "description": "Mutable fields of a placeholder. Only provided fields are updated; omitted fields are left unchanged.", "properties": { - "code": { - "type": "string", + "displayName": { + "type": [ + "string", + "null" + ], + "maxLength": 256, "minLength": 1 + }, + "type": { + "type": [ + "string", + "null" + ], + "enum": [ + "APP", + "DB", + "QUEUE", + "OTHER" + ] } - }, - "required": [ - "code" - ] + } }, - "UserBindingResponse": { + "PatchModel": { "type": "object", + "description": "Either 'data' (deprecated) or 'model' must be set.", "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "user_id": { - "type": "string" - }, - "slack_user_id": { - "type": "string" - }, - "slack_workspace_id": { - "type": "string" - }, - "slack_workspace_name": { - "type": "string" + "data": { + "type": [ + "string", + "null" + ], + "deprecated": true, + "description": "Deprecated. Use 'model' instead.", + "maxLength": 50000, + "minLength": 0 }, - "created_at": { - "type": "integer", - "format": "int64" + "model": { + "$ref": "#/components/schemas/ModelData" } } }, - "IntegrationConfigurationPostRequest": { + "PatchBoundaryDescriptor": { "type": "object", + "description": "All mutable fields of a boundary descriptor. Every field must be provided.", "properties": { - "integrationType": { + "direction": { "type": "string", + "description": "Direction of the boundary.", "enum": [ - "SLACK" + "EXIT_POINT", + "ENTRY_POINT" ] }, - "clientId": { + "ecosystem": { "type": "string", - "minLength": 1 + "description": "Language ecosystem of the boundary.", + "enum": [ + "java", + "js", + "ts", + "py", + "cs" + ] }, - "clientSecret": { + "keyTemplate": { "type": "string", - "minLength": 1 + "description": "Template for generating boundary keys." }, - "signingSecret": { + "query": { "type": "string", - "minLength": 1 + "description": "Query to match boundaries." + }, + "name": { + "type": "string", + "description": "Name of the boundary descriptor." } }, "required": [ - "clientId", - "clientSecret", - "integrationType", - "signingSecret" + "direction", + "ecosystem", + "keyTemplate", + "name", + "query" ] }, - "IntegrationConfigurationResponse": { + "PageRestResponse": { "type": "object", "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "integrationType": { - "type": "string", - "enum": [ - "SLACK" - ] + "pageIndex": { + "type": "integer", + "format": "int32" }, - "clientId": { - "type": [ - "string", - "null" - ] + "pageSize": { + "type": "integer", + "format": "int32" }, - "appId": { - "type": [ - "string", - "null" - ] - } - } - }, - "AwarenessBannerClickedRequest": { - "type": "object", - "properties": { - "bannerType": { - "type": "string", - "enum": [ - "ENABLE", - "LEARN_MORE" - ] + "total": { + "type": "integer", + "format": "int32" } - } + }, + "required": [ + "pageIndex", + "pageSize", + "total" + ] }, - "AwarenessBannerClickedResponse": { + "UsersSearchRestResponse": { "type": "object", "properties": { - "id": { - "type": "string", - "readOnly": true + "users": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserRestResponseForAdmins" + } + }, + "page": { + "$ref": "#/components/schemas/PageRestResponse" } } }, - "FixSuggestionPostRequest": { + "DatabaseMigrationsResponse": { "type": "object", "properties": { - "projectKey": { + "status": { + "type": "string" + }, + "completedSteps": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "totalSteps": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "startedAt": { "type": [ "string", "null" ] }, - "issueId": { + "message": { "type": [ "string", "null" - ], - "description": "Issue key" + ] }, - "issue": { - "$ref": "#/components/schemas/Issue", - "description": "Issue" + "expectedFinishTimestamp": { + "type": [ + "string", + "null" + ] } } }, - "Issue": { - "type": "null", + "Health": { + "type": "object", "properties": { - "message": { - "type": "string" - }, - "startLine": { - "type": "integer", - "format": "int32" - }, - "endLine": { - "type": "integer", - "format": "int32" - }, - "ruleKey": { - "type": "string" + "status": { + "type": "string", + "enum": [ + "GREEN", + "YELLOW", + "RED" + ] }, - "sourceCode": { - "type": "string" + "causes": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true } - }, - "required": [ - "endLine", - "message", - "ruleKey", - "sourceCode", - "startLine" - ] + } + }, + "EmailConfigurationSearchRestResponse": { + "type": "object", + "properties": { + "emailConfigurations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EmailConfigurationResource" + } + }, + "page": { + "$ref": "#/components/schemas/PageRestResponse" + } + } }, - "ChangeDto": { + "ComplianceCategory": { "type": "object", "properties": { - "startLine": { + "activeRules": { "type": "integer", "format": "int32" }, - "endLine": { + "issues": { "type": "integer", "format": "int32" }, - "newCode": { + "name": { "type": "string" } } }, - "FixSuggestionResponse": { + "GetComplianceReportResponse": { "type": "object", "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "issueId": { - "type": [ - "string", - "null" - ] - }, - "explanation": { - "type": "string" - }, - "changes": { + "categories": { "type": "array", "items": { - "$ref": "#/components/schemas/ChangeDto" + "$ref": "#/components/schemas/ComplianceCategory" } } } }, - "LicenseRestRequest": { - "type": "object", - "properties": { - "licenseKey": { - "type": "string", - "description": "New license key" - } - } - }, - "LicenseUploadActivationRequest": { + "Category": { "type": "object", "properties": { - "license": { - "type": "string", - "description": "Contents of a valid .lic file" + "activeRules": { + "type": "integer", + "format": "int32" }, - "licenseKey": { - "type": "string", - "description": "License key for the license" + "issues": { + "type": "integer", + "format": "int32" + }, + "key": { + "type": "string" } } }, - "LegacyLicenseRestRequest": { + "GetAccessibilityReportResponse": { "type": "object", "properties": { - "licenseKey": { - "type": "string", - "description": "New license key" + "categories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Category" + } } } }, - "GitlabSynchronizationRunResource": { + "AffectedBranchRestResponse": { "type": "object", "properties": { - "id": { + "projectId": { + "type": "string", + "readOnly": true + }, + "projectKey": { + "type": "string", + "readOnly": true + }, + "projectName": { + "type": "string", + "readOnly": true + }, + "branchId": { + "type": "string", + "readOnly": true + }, + "branchKey": { + "type": [ + "string", + "null" + ], + "description": "Not set if the branch has been deleted.", + "readOnly": true + }, + "isMain": { + "type": "boolean", + "description": "Whether this is the project's primary branch", + "readOnly": true + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "RESOLVED" + ], + "readOnly": true + }, + "firstDetectedAt": { "type": "string", + "description": "ISO-8601 UTC timestamp", + "readOnly": true + }, + "lastDetectedAt": { + "type": "string", + "description": "ISO-8601 UTC timestamp", + "readOnly": true + }, + "scaRisks": { + "type": "array", + "description": "SCA risk details per affected package/version", + "items": { + "$ref": "#/components/schemas/ScaRiskSummaryRestResponse" + }, + "readOnly": true + }, + "severity": { + "type": [ + "string", + "null" + ], + "description": "SCA severity on this branch", + "enum": [ + "BLOCKER", + "HIGH", + "MEDIUM", + "LOW", + "INFO" + ], + "readOnly": true + }, + "reachabilityAnalyzed": { + "type": [ + "boolean", + "null" + ], + "description": "Whether reachability has been analyzed", "readOnly": true } } }, - "PermissionMappingsPostRequest": { + "CweInfo": { "type": "object", + "description": "CWE entry with resolved name", "properties": { - "role": { + "code": { "type": "string", - "description": "Custom role name" + "description": "CWE identifier (e.g. CWE-89)", + "readOnly": true }, - "permissions": { - "$ref": "#/components/schemas/RestPermissions", - "description": "Set of SonarQube permissions to apply" + "name": { + "type": "string", + "description": "Human-readable CWE name, or null if not found in the catalog", + "readOnly": true } - }, - "required": [ - "permissions", - "role" - ] + } }, - "RestPermissions": { + "ScaIssue": { "type": "object", + "description": "SCA issue linked to the security alert", "properties": { - "user": { - "type": "boolean", - "description": "Browse" + "scaIssueUuid": { + "type": "string", + "description": "SCA issue UUID", + "readOnly": true }, - "codeViewer": { - "type": "boolean", - "description": "See Source Code" + "vulnerabilityId": { + "type": [ + "string", + "null" + ], + "description": "CVE ID (e.g. CVE-2024-12345)", + "readOnly": true }, - "issueAdmin": { - "type": "boolean", - "description": "Administer Issues" + "packageUrl": { + "type": [ + "string", + "null" + ], + "description": "Package URL (PURL)", + "readOnly": true }, - "securityHotspotAdmin": { - "type": "boolean", - "description": "Administer Security Hotspots" + "issueType": { + "type": "string", + "description": "Issue type (VULNERABILITY, MALWARE, PROHIBITED_LICENSE)", + "readOnly": true }, - "admin": { - "type": "boolean", - "description": "Administer" + "riskId": { + "type": [ + "string", + "null" + ], + "description": "Representative SCA risk ID (highest severity among affected branches, tie-broken by uuid; null if no risk exists on any affected branch; may reference a non-OPEN e.g. FIXED risk)", + "readOnly": true }, - "scan": { - "type": "boolean", - "description": "Execute Analysis" + "cwes": { + "type": "array", + "description": "CWE codes with resolved names", + "items": { + "$ref": "#/components/schemas/CweInfo" + }, + "readOnly": true } - }, - "required": [ - "admin", - "codeViewer", - "issueAdmin", - "scan", - "securityHotspotAdmin", - "user" - ] + } }, - "PermissionMappingsResource": { + "ScaRiskSummaryRestResponse": { "type": "object", "properties": { - "id": { - "type": "string" + "riskId": { + "type": "string", + "readOnly": true }, - "role": { - "type": "string" + "packageName": { + "type": "string", + "readOnly": true }, - "baseRole": { - "type": "boolean" + "packageVersion": { + "type": "string", + "readOnly": true }, - "permissions": { - "$ref": "#/components/schemas/RestPermissions" + "packageEcosystem": { + "type": "string", + "readOnly": true } } }, - "GitlabConfigurationCreateRestRequest": { + "SecurityAlertRestResponse": { "type": "object", "properties": { - "enabled": { - "type": "boolean", - "description": "Enable Gitlab authentication" + "id": { + "type": "string", + "readOnly": true }, - "applicationId": { + "alertType": { "type": "string", - "description": "Gitlab Application id", - "minLength": 1 + "enum": [ + "DEPENDENCY_RISK" + ], + "readOnly": true }, - "url": { + "status": { "type": "string", - "description": "Url of Gitlab instance for authentication (for instance https://gitlab.com)", - "minLength": 1 + "description": "Aggregate status across all affected branches: OPEN if any affected branch is OPEN.", + "enum": [ + "OPEN", + "RESOLVED" + ], + "readOnly": true + }, + "severity": { + "type": "string", + "description": "Highest severity among the affected branches.", + "enum": [ + "BLOCKER", + "HIGH", + "MEDIUM", + "LOW", + "INFO" + ], + "readOnly": true + }, + "affectedAuthorizedBranchesTotal": { + "type": "integer", + "format": "int32", + "description": "Total number of affected branches that the current user is authorized to view.", + "readOnly": true + }, + "firstDetectedAt": { + "type": "string", + "description": "ISO-8601 UTC timestamp", + "readOnly": true }, - "secret": { + "lastDetectedAt": { "type": "string", - "description": "Secret of the application", - "minLength": 1, - "writeOnly": true + "description": "ISO-8601 UTC timestamp", + "readOnly": true }, - "synchronizeGroups": { - "type": "boolean", - "description": "Set whether to synchronize groups" + "scaIssue": { + "$ref": "#/components/schemas/ScaIssue", + "description": "Linked SCA issue details (if any)", + "readOnly": true }, - "allowedGroups": { + "affectedBranches": { "type": "array", - "description": "GitLab groups allowed to authenticate.\nSubgroups will automatically be included.\nWhen Auto-provisioning is enabled, members of these groups will be automatically provisioned in SonarQube.\nThis field is required to be non-empty for Auto-provisioning unless allowAllGroups is true.\nIgnored when allowAllGroups is true.\n", "items": { - "type": "string" + "$ref": "#/components/schemas/AffectedBranchRestResponse" + }, + "readOnly": true + } + } + }, + "SecurityAlertSearchRestResponse": { + "type": "object", + "properties": { + "securityAlerts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SecurityAlertRestResponse" } }, - "allowAllGroups": { - "type": [ - "boolean", - "null" - ], - "description": "When true with Auto-provisioning, every group visible to the provisioning token is provisioned and the allowedGroups list is ignored. Has no effect with Just-in-Time provisioning. Security risk: any user belonging to any group accessible by the provisioning token will be granted access. Restrict access using allowedGroups unless broad access is intentional. Not supported on GitLab.com (SaaS): the request is rejected when the configured URL is gitlab.com, since the provisioning token may have visibility into a much larger and unbounded set of groups. Performance note: login may be slower for users belonging to a large number of groups, as all their groups must be fetched from GitLab on every authentication." - }, - "provisioningType": { - "type": "string", - "description": "Type of synchronization", - "enum": [ - "JIT", - "AUTO_PROVISIONING" - ] - }, - "provisioningToken": { - "type": [ - "string", - "null" - ], - "description": "Gitlab token for provisioning", - "writeOnly": true - }, - "allowUsersToSignUp": { - "type": [ - "boolean", - "null" - ], - "description": "Allow user to sign up" + "page": { + "$ref": "#/components/schemas/PageRestResponse" } - }, - "required": [ - "allowedGroups", - "applicationId", - "enabled", - "provisioningType", - "secret", - "synchronizeGroups", - "url" - ] + } }, - "GitlabConfigurationRestResponseForAdmins": { + "SelfTestHttpCallResource": { "type": "object", "properties": { - "id": { - "type": "string", - "readOnly": true + "attemptedUrl": { + "type": "string" }, - "enabled": { - "type": "boolean" + "attemptedMethod": { + "type": "string" }, - "applicationId": { - "type": "string", - "description": "Gitlab Application id" + "responseCode": { + "type": "integer", + "format": "int32" }, - "url": { - "type": "string", - "description": "Url of Gitlab instance for authentication (for instance https://gitlab.com/api/v4)" + "responseBody": { + "type": "string" }, - "synchronizeGroups": { + "responseBodyAppearsValid": { "type": "boolean" }, - "allowedGroups": { + "responseHeaders": { "type": "array", - "description": "Root Gitlab groups allowed to authenticate and provisioned. Ignored when allowAllGroups is true.", "items": { - "type": "string" + "type": "array", + "items": { + "type": "string" + } } - }, - "allowAllGroups": { - "type": "boolean", - "description": "When true with Auto-provisioning, every group visible to the provisioning token is provisioned and the allowedGroups list is ignored. Has no effect with Just-in-Time provisioning. Security risk: any user belonging to any group accessible by the provisioning token will be granted access. Restrict access using allowedGroups unless broad access is intentional. Not supported on GitLab.com (SaaS): the request is rejected when the configured URL is gitlab.com, since the provisioning token may have visibility into a much larger and unbounded set of groups. Performance note: login may be slower for users belonging to a large number of groups, as all their groups must be fetched from GitLab on every authentication." - }, - "allowUsersToSignUp": { + } + } + }, + "SelfTestResponse": { + "type": "object", + "properties": { + "featureEnabled": { "type": "boolean" }, - "provisioningType": { - "type": "string", - "enum": [ - "JIT", - "AUTO_PROVISIONING" - ] + "selfTestPassed": { + "type": "boolean" }, - "isProvisioningTokenSet": { - "type": "boolean", - "description": "Whether or not the provisioningToken is defined", - "readOnly": true + "cliVersionCheck": { + "$ref": "#/components/schemas/SelfTestHttpCallResource" }, - "errorMessage": { - "type": [ - "string", - "null" - ], - "description": "In case the GitLab configuration is incorrect, error message", - "readOnly": true + "vulnerabilityDetailsCheck": { + "$ref": "#/components/schemas/SelfTestHttpCallResource" } } }, - "GithubConfigurationCreateRestRequest": { + "RiskReportItem": { "type": "object", "properties": { - "enabled": { - "type": "boolean", - "description": "Enable GitHub authentication" + "projectKey": { + "type": "string" }, - "clientId": { - "type": "string", - "description": "Client ID provided by GitHub when registering the application.", - "minLength": 1, - "writeOnly": true + "projectName": { + "type": "string" }, - "clientSecret": { - "type": "string", - "description": "Client password provided by GitHub when registering the application.", - "minLength": 1, - "writeOnly": true + "branchKey": { + "type": "string" }, - "applicationId": { - "type": "string", - "description": "The App ID is found on your GitHub App's page on GitHub at Settings > Developer Settings > GitHub Apps.", - "minLength": 1 + "riskTitle": { + "type": "string" }, - "privateKey": { + "riskType": { "type": "string", - "description": "Your GitHub App's private key. You can generate a .pem file from your GitHub App's page under Private keys.\nCopy and paste the whole contents of the file here.\n", - "minLength": 1, - "writeOnly": true - }, - "synchronizeGroups": { - "type": "boolean", - "description": "Synchronize GitHub team with SonarQube group memberships when users log in to SonarQube.\nFor each GitHub team they belong to, users will be associated to a group of the same name if it exists in SonarQube.\n" + "enum": [ + "VULNERABILITY", + "PROHIBITED_LICENSE", + "MALWARE" + ] }, - "apiUrl": { + "riskSeverity": { "type": "string", - "description": "The API url for a GitHub instance. https://api.github.com/ for Github.com, https://github.company.com/api/v3/ when using Github Enterprise", - "minLength": 1 + "enum": [ + "INFO", + "LOW", + "MEDIUM", + "HIGH", + "BLOCKER" + ] }, - "webUrl": { + "riskStatus": { "type": "string", - "description": "The WEB url for a GitHub instance. https://github.com/ for Github.com, https://github.company.com/ when using GitHub Enterprise.\n", - "minLength": 1 + "enum": [ + "OPEN", + "ACCEPT", + "CONFIRM", + "SAFE", + "FIXED" + ] }, - "allowedOrganizations": { + "statusChanges": { "type": "array", - "description": "Only members of these organizations will be able to authenticate to the server.\n⚠ if not set, users from any organization where the GitHub App is installed will be able to login to this SonarQube instance.\n", "items": { - "type": "string" + "$ref": "#/components/schemas/StatusChange" } }, - "provisioningType": { - "type": "string", - "description": "Type of synchronization", - "enum": [ - "JIT", - "AUTO_PROVISIONING" - ] + "vulnerabilityId": { + "type": "string" }, - "allowUsersToSignUp": { - "type": [ - "boolean", - "null" - ], - "description": "Allow user to sign up" + "cvssScore": { + "type": "number" }, - "projectVisibility": { - "type": [ - "boolean", - "null" - ], - "description": "Change project visibility based on GitHub repository visibility.\nIf disabled, every provisioned project will be private in SonarQube and visible only to users with explicit GitHub permissions for the corresponding repository.\nChanges take effect at the next synchronization.\n" + "epssScore": { + "type": "number" }, - "userConsentRequiredAfterUpgrade": { - "type": [ - "boolean", - "null" - ], - "description": "Admin consent to synchronize permissions from GitHub" - } - }, - "required": [ - "allowedOrganizations", - "apiUrl", - "applicationId", - "clientId", - "clientSecret", - "enabled", - "privateKey", - "provisioningType", - "synchronizeGroups", - "webUrl" - ] - }, - "GithubConfigurationResource": { - "type": "object", - "properties": { - "id": { - "type": "string", - "readOnly": true + "epssPercentile": { + "type": "number" }, - "enabled": { + "knownExploited": { "type": "boolean" }, - "applicationId": { - "type": "string", - "description": "GitHub Application id" + "cweIds": { + "type": "array", + "items": { + "type": "string" + } }, - "synchronizeGroups": { - "type": "boolean" + "publishedOn": { + "type": "string" }, - "apiUrl": { - "type": "string", - "description": "Url of GitHub instance for API connectivity (for instance https://api.github.com)" + "createdAt": { + "type": "string" }, - "webUrl": { - "type": "string", - "description": "Url of GitHub instance for authentication (for instance https://github.com)" + "packageUrl": { + "type": "string" }, - "allowedOrganizations": { + "riskUrl": { + "type": "string" + }, + "dependencyChains": { "type": "array", - "description": "GitHub organizations allowed to authenticate and provisioned", "items": { - "type": "string" + "type": "array", + "items": { + "type": "string" + } } }, - "provisioningType": { - "type": "string", - "enum": [ - "JIT", - "AUTO_PROVISIONING" - ] - }, - "allowUsersToSignUp": { - "type": "boolean" + "scope": { + "type": "string" }, - "projectVisibility": { + "productionScope": { "type": "boolean" }, - "userConsentRequiredAfterUpgrade": { + "confirmedReachable": { "type": "boolean" - }, - "errorMessage": { - "type": [ - "string", - "null" - ], - "description": "In case the GitHub configuration is incorrect, error message", - "readOnly": true } } }, - "Impact": { + "StatusChange": { "type": "object", "properties": { - "softwareQuality": { - "type": "string", - "enum": [ - "MAINTAINABILITY", - "RELIABILITY", - "SECURITY" - ] + "comment": { + "type": "string" }, - "severity": { - "type": "string", - "enum": [ - "INFO", - "LOW", - "MEDIUM", - "HIGH", - "BLOCKER" - ] + "newStatus": { + "type": "string" + }, + "createdAt": { + "type": "string" } } }, - "Parameter": { + "BranchResource": { "type": "object", "properties": { - "key": { + "id": { "type": "string" }, - "htmlDescription": { + "uuid": { "type": "string", - "readOnly": true + "deprecated": true }, - "defaultValue": { - "type": [ - "string", - "null" - ] + "key": { + "type": "string" }, - "type": { - "type": "string", - "enum": [ - "STRING", - "TEXT", - "BOOLEAN", - "INTEGER", - "FLOAT" - ], - "readOnly": true + "pullRequest": { + "type": "boolean" + }, + "projectKey": { + "type": "string" + }, + "projectName": { + "type": "string" } } }, - "RuleCreateRestRequest": { + "PackageManagerCountResource": { "type": "object", "properties": { - "key": { - "type": "string", - "description": "Key of the custom rule to create, must include the repository", - "maxLength": 200, - "minLength": 0 - }, - "templateKey": { - "type": "string", - "description": "Key of the rule template to be used to create the custom rule", - "maxLength": 200, - "minLength": 0 - }, - "name": { - "type": "string", - "description": "Rule name", - "maxLength": 200, - "minLength": 0 - }, - "markdownDescription": { - "type": "string", - "description": "Rule description in markdown format" + "id": { + "type": "string" }, - "status": { - "type": [ - "string", - "null" - ], - "default": "READY", - "description": "Rule status", - "enum": [ - "BETA", - "DEPRECATED", - "READY", - "REMOVED" - ] + "packageManager": { + "type": "string" }, - "parameters": { - "type": [ - "array", - "null" - ], - "description": "Custom rule parameters", + "releaseCount": { + "type": "integer", + "format": "int32" + } + } + }, + "ReleasesSearchRestResponse": { + "type": "object", + "properties": { + "releases": { + "type": "array", "items": { - "$ref": "#/components/schemas/Parameter" + "$ref": "#/components/schemas/ReleaseSearchResource" } }, - "cleanCodeAttribute": { - "type": [ - "string", - "null" - ], - "description": "Clean code attribute", - "enum": [ - "CONVENTIONAL", - "FORMATTED", - "IDENTIFIABLE", - "CLEAR", - "COMPLETE", - "EFFICIENT", - "LOGICAL", - "DISTINCT", - "FOCUSED", - "MODULAR", - "TESTED", - "LAWFUL", - "RESPECTFUL", - "TRUSTWORTHY" - ] + "packageManagerCounts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PackageManagerCountResource" + } }, - "impacts": { + "branches": { "type": "array", - "description": "Impacts", "items": { - "$ref": "#/components/schemas/Impact" + "$ref": "#/components/schemas/BranchResource" } }, - "severity": { - "type": [ - "string", - "null" - ], - "description": "Severity" + "countWithoutFilters": { + "type": "integer", + "format": "int32" }, - "type": { - "type": [ - "string", - "null" - ], - "description": "Rule type", - "enum": [ - "CODE_SMELL", - "BUG", - "VULNERABILITY", - "SECURITY_HOTSPOT" - ] + "page": { + "$ref": "#/components/schemas/PageRestResponse" } - }, - "required": [ - "impacts", - "key", - "markdownDescription", - "name", - "templateKey" - ] + } }, - "RuleDescriptionSectionContextRestResponse": { - "type": "null", + "DependencyResource": { + "type": "object", "properties": { "key": { "type": "string" }, - "displayName": { + "direct": { + "type": "boolean" + }, + "scope": { "type": "string" - } - }, - "readOnly": true - }, - "RuleDescriptionSectionRestResponse": { - "type": "object", - "properties": { - "key": { - "type": "string", - "readOnly": true }, - "content": { - "type": "string", - "readOnly": true + "productionScope": { + "type": "boolean" }, - "context": { - "$ref": "#/components/schemas/RuleDescriptionSectionContextRestResponse", - "readOnly": true + "userDependencyFilePath": { + "type": "string" + }, + "lockfileDependencyFilePath": { + "type": "string" + }, + "chains": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "newlyIntroduced": { + "type": "boolean" } } }, - "RuleRestResponse": { + "IssueResource": { "type": "object", "properties": { - "id": { - "type": "string" - }, "key": { "type": "string" }, - "repositoryKey": { - "type": "string" - }, - "name": { + "severity": { "type": "string" }, - "severity": { - "type": [ - "string", - "null" - ] + "showIncreasedSeverityWarning": { + "type": "boolean" }, "type": { "type": "string", "enum": [ - "CODE_SMELL", - "BUG", "VULNERABILITY", - "SECURITY_HOTSPOT" + "PROHIBITED_LICENSE", + "MALWARE" ] }, - "impacts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Impact" - } - }, - "cleanCodeAttribute": { - "type": [ - "string", - "null" - ], + "quality": { + "type": "string", "enum": [ - "CONVENTIONAL", - "FORMATTED", - "IDENTIFIABLE", - "CLEAR", - "COMPLETE", - "EFFICIENT", - "LOGICAL", - "DISTINCT", - "FOCUSED", - "MODULAR", - "TESTED", - "LAWFUL", - "RESPECTFUL", - "TRUSTWORTHY" + "MAINTAINABILITY", + "RELIABILITY", + "SECURITY" ] }, - "cleanCodeAttributeCategory": { - "type": [ - "string", - "null" - ], - "enum": [ - "ADAPTABLE", - "CONSISTENT", - "INTENTIONAL", - "RESPONSIBLE" - ] + "createdAt": { + "type": "string" }, - "status": { - "type": [ - "string", - "null" - ], - "enum": [ - "BETA", - "DEPRECATED", - "READY", - "REMOVED" - ] + "assignee": { + "$ref": "#/components/schemas/UserResource" }, - "external": { - "type": "boolean" + "vulnerabilityId": { + "type": "string" }, - "createdAt": { - "type": [ - "string", - "null" - ] + "cweIds": { + "type": "array", + "deprecated": true, + "items": { + "type": "string", + "deprecated": true + } }, - "descriptionSections": { + "cwes": { "type": "array", "items": { - "$ref": "#/components/schemas/RuleDescriptionSectionRestResponse" + "$ref": "#/components/schemas/CweResource" } }, - "markdownDescription": { + "cvssScore": { "type": "string" }, - "gapDescription": { - "type": [ - "string", - "null" - ] + "spdxLicenseId": { + "type": "string" + } + } + }, + "ReleaseDetailResource": { + "type": "object", + "properties": { + "key": { + "type": "string" }, - "htmlNote": { - "type": [ - "string", - "null" - ] + "branchId": { + "type": "string" }, - "markdownNote": { - "type": [ - "string", - "null" - ] + "branchUuid": { + "type": "string", + "deprecated": true }, - "educationPrinciples": { - "type": "array", - "items": { - "type": "string" - } + "packageUrl": { + "type": "string" }, - "template": { + "packageManager": { + "type": "string" + }, + "packageName": { + "type": "string" + }, + "version": { + "type": "string" + }, + "licenseExpression": { + "type": "string" + }, + "known": { "type": "boolean" }, - "templateId": { - "type": [ - "string", - "null" - ] + "knownPackage": { + "type": "boolean" }, - "tags": { - "type": "array", - "items": { - "type": "string" - } + "newlyIntroduced": { + "type": "boolean" }, - "systemTags": { - "type": "array", - "items": { - "type": "string" - } + "directSummary": { + "type": "boolean" }, - "languageKey": { - "type": [ - "string", - "null" - ] + "scopeSummary": { + "type": "string" }, - "languageName": { - "type": [ - "string", - "null" - ] + "productionScopeSummary": { + "type": "boolean" }, - "parameters": { + "dependencies": { "type": "array", "items": { - "$ref": "#/components/schemas/Parameter" + "$ref": "#/components/schemas/DependencyResource" } }, - "remediationFunctionType": { - "type": "string" - }, - "remediationFunctionGapMultiplier": { - "type": "string" - }, - "remediationFunctionBaseEffort": { - "type": "string" - } - }, - "readOnly": true - }, - "GroupCreateRestRequest": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name for the new group. Must be unique. The value 'anyone' is reserved and cannot be used.", - "maxLength": 255, - "minLength": 1 + "issues": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IssueResource" + } }, - "description": { - "type": [ - "string", - "null" - ], - "description": "Description for the new group.", - "maxLength": 200, - "minLength": 0 + "branch": { + "$ref": "#/components/schemas/BranchResource" } - }, - "required": [ - "name" - ] + } }, - "GroupRestResponse": { + "ReleaseSearchBranchResource": { "type": "object", "properties": { "id": { - "type": "string", - "readOnly": true + "type": "string" }, - "name": { + "key": { "type": "string" }, - "description": { - "type": [ - "string", - "null" - ] + "pullRequest": { + "type": "boolean" }, - "managed": { - "type": "boolean", - "readOnly": true + "projectKey": { + "type": "string" }, - "default": { - "type": "boolean", - "readOnly": true - } - } - }, - "GroupMembershipCreateRestRequest": { - "type": "object", - "properties": { - "userId": { - "type": "string", - "description": "ID of the user to add to group." + "projectName": { + "type": "string" }, - "groupId": { - "type": "string", - "description": "ID of the group where a member needs to be added." + "packageUrl": { + "type": "string" + }, + "direct": { + "type": "boolean" + }, + "productionScope": { + "type": "boolean" + }, + "dependencyFilePaths": { + "type": "array", + "items": { + "type": "string" + } } } }, - "GroupMembershipRestResponse": { + "ReleaseSearchRestResponse": { "type": "object", "properties": { - "id": { - "type": "string", - "readOnly": true - }, - "groupId": { - "type": "string", - "readOnly": true + "branches": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReleaseSearchBranchResource" + } }, - "userId": { - "type": "string", - "readOnly": true + "page": { + "$ref": "#/components/schemas/PageRestResponse" } } }, - "AtlassianAuthenticationDetailsResource": { + "ReachabilityPathsRestResponse": { "type": "object", "properties": { - "clientId": { - "type": "string", - "description": "Atlassian 3LO App Client ID", - "writeOnly": true + "paths": { + "type": "array", + "items": { + "type": "string" + } }, - "secret": { - "type": "string", - "description": "Atlassian 3LO App Secret", - "writeOnly": true + "page": { + "$ref": "#/components/schemas/PageRestResponse" } } }, - "AtlassianAuthenticationDetailsResultResource": { + "LicenseProfileCollectionActions": { "type": "object", "properties": { - "clientId": { - "type": "string", - "description": "Atlassian 3LO App Client ID", - "readOnly": true + "create": { + "type": "boolean" } } }, - "L": { + "LicenseProfileIndexRestResponse": { "type": "object", "properties": { - "uploadUrl": { - "type": [ - "string", - "null" - ], - "description": "Presigned upload URL. Present only on the presigned path (no payload supplied)." + "licenseProfiles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LicenseProfileResource" + } }, - "id": { - "type": [ - "string", - "null" - ], - "format": "uuid" + "actions": { + "$ref": "#/components/schemas/LicenseProfileCollectionActions" } } }, - "X": { + "LicenseProfileDetailsResource": { "type": "object", "properties": { - "message": { - "type": [ - "string", - "null" - ] + "profile": { + "$ref": "#/components/schemas/LicenseProfileResource" + }, + "categories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LicenseProfileCategoryResource" + } + }, + "licenses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LicensePolicyLicenseResource" + } } } }, - "Bq": { - "type": "object", - "properties": { - "analysisId": { - "type": "string", - "description": "Analysis UUID identifying the target project and branch." - } - }, - "required": [ - "analysisId" - ] - }, - "Model": { + "AssignableProjectResource": { "type": "object", - "description": "In GET responses, both 'data' and 'model' will be set. 'data' is deprecated, use 'model' instead.", "properties": { "id": { - "type": "string", - "format": "uuid" - }, - "data": { - "type": [ - "string", - "null" - ], - "deprecated": true, - "description": "Deprecated. Use 'model' instead.", - "maxLength": 50000, - "minLength": 0 - }, - "organizationId": { - "type": [ - "string", - "null" - ], - "description": "UUID v4 of the organization." + "type": "string" }, - "model": { - "$ref": "#/components/schemas/ModelData" + "projectKey": { + "type": "string" }, - "projectId": { + "projectName": { "type": "string" + }, + "assignedToLicenseProfile": { + "type": "boolean" } - }, - "required": [ - "id", - "organizationId", - "projectId" - ] + } }, - "ModelData": { - "type": "null", - "description": "Structured intended model for the architecture analysis, mirroring ModelDto.", + "AssignableProjectsIndexRestResponse": { + "type": "object", "properties": { - "perspectives": { + "assignableProjects": { "type": "array", - "description": "Model perspectives. Each perspective provides a specific view of the intended model in terms of groups and constraints.", "items": { - "$ref": "#/components/schemas/ModelPerspective" + "$ref": "#/components/schemas/AssignableProjectResource" } + }, + "page": { + "$ref": "#/components/schemas/PageRestResponse" } - }, - "required": [ - "perspectives" - ] + } }, - "ModelGroup": { + "IssueReleaseResource": { "type": "object", - "description": "A component of the intended model.", "properties": { - "groups": { - "description": "Subgroups." + "id": { + "type": "string" + }, + "key": { + "type": "string" + }, + "severity": { + "type": "string" + }, + "originalSeverity": { + "type": "string" + }, + "manualSeverity": { + "type": "string" + }, + "showIncreasedSeverityWarning": { + "type": "boolean" + }, + "hasReachableLocations": { + "type": "boolean" + }, + "release": { + "$ref": "#/components/schemas/ReleaseSearchResource" + }, + "type": { + "type": "string", + "enum": [ + "VULNERABILITY", + "PROHIBITED_LICENSE", + "MALWARE" + ] + }, + "quality": { + "type": "string", + "enum": [ + "MAINTAINABILITY", + "RELIABILITY", + "SECURITY" + ] + }, + "status": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "assignee": { + "$ref": "#/components/schemas/UserResource" + }, + "commentCount": { + "type": "integer", + "format": "int32" + }, + "vulnerabilityId": { + "type": "string" }, - "label": { - "type": "string", - "description": "Identifier of this group. Also serves as the display name." + "cweIds": { + "type": "array", + "deprecated": true, + "items": { + "type": "string", + "deprecated": true + } }, - "interface": { - "type": [ - "boolean", - "null" - ], - "description": "If true, this group is an interface. External access to sibling groups that bypasses this interface group will raise an interface issue, regardless of the parent's interfaceAccess setting. To fully disable interface issue generation for a component, this field must be false on all children and interfaceAccess must be false on the parent. " + "cwes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CweResource" + } }, - "description": { - "type": [ - "string", - "null" - ], - "description": "Description of the group." + "cvssScore": { + "type": "string" }, - "interfaceAccess": { - "type": [ - "boolean", - "null" - ], - "description": "Extends interface issue detection beyond what the interface field provides. If true, access to children that are not themselves interfaces (interface: false) from outside this group will raise an interface issue. Additionally, access to unmapped code that resolves to this group's pattern is also considered unauthorized, as it does not go through an interface child, and will raise an interface issue. If false, no additional interface issues are raised by this group — though children with interface: true will still independently raise interface issues for unauthorized sibling access. " + "withdrawn": { + "type": "boolean" }, - "patternId": { - "type": [ - "string", - "null" - ], - "format": "uuid", - "description": "If set, this group imports a pattern. Its subgroups are taken from the referenced pattern instead of the 'groups' field." + "spdxLicenseId": { + "type": "string" }, - "patterns": { + "transitions": { "type": "array", - "description": "File or namespace pattern for code components associated with this group.", "items": { - "type": "string" + "type": "string", + "enum": [ + "CONFIRM", + "REOPEN", + "SAFE", + "FIXED", + "ACCEPT" + ] } + }, + "actions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "COMMENT", + "ASSIGN", + "SET_SEVERITY" + ] + }, + "uniqueItems": true } - }, - "required": [ - "label" - ] + } }, - "ModelPerspective": { + "IssuesReleasesSearchRestResponse": { "type": "object", - "description": "A specific view of the intended model in terms of groups and constraints.", "properties": { - "qualifiers": { - "type": "string", - "enum": [ - "file", - "namespace" - ] - }, - "description": { - "type": [ - "string", - "null" - ], - "description": "Description of the perspective." - }, - "label": { - "type": [ - "string", - "null" - ], - "description": "Display name of the perspective." - }, - "groups": { + "issuesReleases": { "type": "array", - "description": "Components of the intended model.", "items": { - "$ref": "#/components/schemas/ModelGroup" + "$ref": "#/components/schemas/IssueReleaseResource" } }, - "constraints": { + "branches": { "type": "array", - "description": "Dependency constraints between groups in this perspective.", "items": { - "$ref": "#/components/schemas/T" + "$ref": "#/components/schemas/IssueReleaseBranchResource" } }, - "language": { - "type": "string", - "enum": [ - "java", - "js", - "ts", - "py", - "cs" - ] - } - }, - "required": [ - "constraints", - "language", - "qualifiers" - ] - }, - "T": { - "type": "object", - "properties": { - "to": { - "type": "string", - "description": "Pattern identifying the target group of the dependency." + "countWithoutFilters": { + "type": "integer", + "format": "int32" }, - "from": { - "type": "string", - "description": "Pattern identifying the source group of the dependency." + "page": { + "$ref": "#/components/schemas/PageRestResponse" } - }, - "required": [ - "from", - "to" - ] + } }, - "PostModel": { + "IssueReleaseChangeDiffResource": { "type": "object", - "description": "Either 'data' (deprecated) or 'model' must be set.", "properties": { - "projectId": { + "fieldName": { "type": "string" }, - "model": { - "$ref": "#/components/schemas/ModelData" - }, - "organizationId": { - "type": [ - "string", - "null" - ], - "deprecated": true, - "description": "UUID v4 of the organization." + "oldValue": { + "type": "string" }, - "data": { - "type": [ - "string", - "null" - ], - "deprecated": true, - "description": "Deprecated. Use 'model' instead.", - "maxLength": 50000, - "minLength": 0 + "newValue": { + "type": "string" } - }, - "required": [ - "projectId" - ] + } }, - "a": { + "IssueReleaseChangeResource": { "type": "object", "properties": { - "toNodeKey": { - "type": [ - "string", - "null" - ], - "description": "Key of the dependency target node." + "key": { + "type": "string" }, - "fromNodeKey": { - "type": "string", - "description": "Key of the dependency source node." + "createdAt": { + "type": "string" }, - "graphPerspective": { - "type": [ - "string", - "null" - ], - "description": "Key of the graph perspective the directive's node keys refer to. Syntactically optional for backward compatibility with older payloads, but semantically required: directives without a (parseable, resolvable) value are rejected by the validator as INVALID and are removed by the analysis-time garbage collector. " + "user": { + "$ref": "#/components/schemas/UserResource" }, - "graphType": { - "type": "string", - "description": "Type of the graph.", - "enum": [ - "file_graph", - "namespace_graph" - ] + "markdownComment": { + "type": "string" }, - "graphEcoSystem": { - "type": "string", - "description": "Language ecosystem of the graph.", - "enum": [ - "java", - "js", - "ts", - "py", - "cs" - ] + "htmlComment": { + "type": "string" }, - "type": { - "type": "string", - "description": "Type of this directive.", - "enum": [ - "REMOVE_EDGE", - "RENAME_NODE", - "MOVE_NODE" - ] + "changeData": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IssueReleaseChangeDiffResource" + } }, - "description": { - "type": [ - "string", - "null" - ], - "description": "Optional human-readable description for this directive." + "actions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "EDIT_COMMENT", + "DELETE_COMMENT" + ] + }, + "uniqueItems": true + } + } + }, + "IssuesReleasesChangesRestResponse": { + "type": "object", + "properties": { + "changelog": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IssueReleaseChangeResource" + } } - }, - "required": [ - "fromNodeKey", - "graphEcoSystem", - "graphType", - "toNodeKey", - "type" - ] + } + }, + "ScaFeatureEnabledResource": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + } }, - "y": { + "ScaCliInfoRestResponse": { "type": "object", "properties": { - "origin": { - "type": [ - "string", - "null" - ], - "description": "Optional provenance tag supplied by the creator of the directive (e.g. the UI component or user action that triggered the creation). Null for directives created before this field was introduced.", - "maxLength": 255, - "minLength": 0 + "id": { + "type": "string" }, - "directiveData": { - "$ref": "#/components/schemas/a" + "filename": { + "type": "string" }, - "projectId": { + "sha256": { "type": "string" }, - "id": { - "type": "string", - "format": "uuid" + "os": { + "type": "string" + }, + "arch": { + "type": "string" } - }, - "required": [ - "directiveData", - "id", - "projectId" - ] + } }, - "Dq": { + "AnalysisResource": { "type": "object", "properties": { - "projectId": { + "status": { + "type": "string", + "enum": [ + "FAILED", + "OUTDATED", + "COMPLETED" + ] + }, + "failedReason": { "type": "string" }, - "origin": { - "type": [ - "string", - "null" - ], - "description": "Optional provenance tag for this directive (e.g. the UI component or user action that triggered the creation).", - "maxLength": 255, - "minLength": 0 + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnalysisErrorResource" + } }, - "directiveData": { - "$ref": "#/components/schemas/a" + "parsedFiles": { + "type": "array", + "items": { + "type": "string" + } + }, + "updatedAt": { + "type": "string" } - }, - "required": [ - "directiveData", - "projectId" - ] + } }, - "cq": { + "ScheduledAgentConfigPageRestResponse": { "type": "object", "properties": { - "analysisId": { - "type": [ - "string", - "null" - ], - "description": "Cloud mode: analysis UUID identifying the target project and branch." + "pageIndex": { + "type": "integer", + "format": "int32" }, - "ceTaskId": { - "type": [ - "string", - "null" - ], - "description": "Server mode: Compute Engine task ID, used to resolve the target project and branch. Required when analysisId is absent." + "pageSize": { + "type": "integer", + "format": "int32" + }, + "total": { + "type": "integer", + "format": "int32" } } }, - "UpdateFieldListString": { + "ScheduledAgentConfigSearchRestResponse": { "type": "object", "properties": { - "value": { + "scheduledAgentConfigs": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/ScheduledAgentConfigResource" } }, - "defined": { - "type": "boolean" + "page": { + "$ref": "#/components/schemas/ScheduledAgentConfigPageRestResponse" } } }, - "UserUpdateRestRequest": { + "ScheduledAgentConfigEffectiveRestResponse": { "type": "object", "properties": { - "login": { - "type": "string", - "description": "User login", - "maxLength": 100, - "minLength": 2 - }, - "name": { - "type": "string", - "description": "User first name and last name", - "maxLength": 200, - "minLength": 0 - }, - "email": { - "type": "string", - "format": "email", - "description": "Email", - "maxLength": 100, - "minLength": 1 - }, - "scmAccounts": { - "$ref": "#/components/schemas/UpdateFieldListString", - "description": "List of SCM accounts.", - "items": { - "type": "string" - } - }, - "externalProvider": { - "type": "string", - "description": "New identity provider. Only providers configured in your platform are supported. This could be: github, gitlab, bitbucket, saml, LDAP, LDAP_{serverKey}\n(according to your server configuration file).\nWarning: when this is updated, the user will only be able to authenticate using the new identity provider. Also, it is not possible to remove the identity provider of a user.\n" - }, - "externalLogin": { - "type": "string", - "description": "New external login, usually the login used in the authentication system.", - "maxLength": 255, - "minLength": 1 + "projectConfig": { + "$ref": "#/components/schemas/ScheduledAgentConfigResource" }, - "externalId": { - "type": "string", - "description": "New external id in the authentication system.", - "maxLength": 255, - "minLength": 1 + "organizationDefault": { + "$ref": "#/components/schemas/ScheduledAgentDefaultResource" } } }, - "EmailConfigurationUpdateRestRequest": { + "ScheduledAgentDefaultResource": { "type": "object", "properties": { - "host": { - "type": "string", - "description": "URL of your SMTP server" - }, - "port": { - "type": "string", - "description": "Port of your SMTP server (usually 25, 587 or 465)" - }, - "securityProtocol": { - "type": "string", - "description": "Security protocol used to connect to your SMTP server (SSLTLS is recommended)", - "enum": [ - "NONE", - "SSLTLS", - "STARTTLS" - ] - }, - "fromAddress": { - "type": "string", - "description": "Address emails will come from" + "enabled": { + "type": "boolean" }, - "fromName": { - "type": "string", - "description": "Name emails will come from (usually \"SonarQube\")" + "frequency": { + "type": "string" }, - "subjectPrefix": { - "type": "string", - "description": "Prefix added to email so they can be easily recognized (usually \"[SonarQube]\")" + "hour": { + "type": "integer", + "format": "int32" }, - "authMethod": { - "type": "string", - "description": "Authentication method used to connect to the SMTP server. OAuth is only supported for Microsoft Exchange", - "enum": [ - "BASIC", - "OAUTH" - ] + "dayOfWeek": { + "type": "string" }, - "username": { - "type": "string", - "description": "For Basic and OAuth authentication: username used to authenticate to the SMTP server" + "timezone": { + "type": "string" }, - "basicPassword": { - "type": "string", - "description": "For basic authentication: password used to authenticate to the SMTP server" + "maxIssuesPerRun": { + "type": "integer", + "format": "int32" }, - "oauthAuthenticationHost": { - "type": "string", - "description": "For OAuth authentication: host of the Identity Provider issuing access tokens" + "maxOpenPRs": { + "type": "integer", + "format": "int32" + } + } + }, + "JobPage": { + "type": "object", + "properties": { + "pageIndex": { + "type": "integer", + "format": "int32", + "description": "1-based index of this page." }, - "oauthClientId": { - "type": "string", - "description": "For OAuth authentication: Client ID provided by Microsoft Exchange when registering the application" + "pageSize": { + "type": "integer", + "format": "int32", + "description": "Maximum number of rows per page." }, - "oauthClientSecret": { - "type": "string", - "description": "For OAuth authentication: Client password provided by Microsoft Exchange when registering the application" + "total": { + "type": "integer", + "format": "int64", + "description": "Total number of rows matching the active filter, across all pages." + } + }, + "required": [ + "pageIndex", + "pageSize", + "total" + ] + }, + "JobSearchResponse": { + "type": "object", + "properties": { + "jobs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Job" + } }, - "oauthTenant": { - "type": "string", - "description": "For OAuth authentication: Microsoft tenant" + "page": { + "$ref": "#/components/schemas/JobPage" } - } + }, + "required": [ + "jobs", + "page" + ] }, - "LicenseProfilesUpdateRestRequest": { + "DevopsPlatforms": { "type": "object", "properties": { - "name": { - "type": "string", - "description": "The name of the license policy" + "total": { + "type": "integer", + "format": "int64" }, - "default": { - "type": "boolean", - "description": "Whether this license policy is the default or not" + "shares": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlatformShare" + } } - } + }, + "required": [ + "shares", + "total" + ] }, - "LicensePolicyLicenseUpdateRestRequest": { + "OnboardingStatisticsResponse": { "type": "object", "properties": { - "policy": { - "type": "string", - "description": "The new status of this license.", - "enum": [ - "DENY", - "ALLOW" - ] + "discoveredTotal": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "timeline": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimelinePoint" + } + }, + "devopsPlatforms": { + "$ref": "#/components/schemas/DevopsPlatforms" } }, "required": [ - "policy" + "devopsPlatforms", + "timeline" ] }, - "LicensePolicyLicenseResource": { + "PlatformShare": { "type": "object", "properties": { - "id": { + "platform": { "type": "string" }, - "spdxLicenseId": { - "type": "string" - }, - "name": { - "type": "string" - }, - "category": { - "type": "string", - "enum": [ - "UNKNOWN", - "COPYLEFT_WEAK", - "COPYLEFT_STRONG", - "COPYLEFT_NETWORK", - "COPYLEFT_MAXIMAL", - "PERMISSIVE_STANDARD", - "PERMISSIVE_AMATEUR" - ] + "count": { + "type": "integer", + "format": "int64" }, - "policy": { - "type": "string", - "description": "The policy status of this license.", - "enum": [ - "DENY", - "ALLOW" - ] + "percentage": { + "type": "number", + "format": "double" } - } + }, + "required": [ + "count", + "percentage", + "platform" + ] }, - "LicenseProfileCategoryUpdateRestRequest": { + "TimelinePoint": { "type": "object", "properties": { - "policy": { + "date": { "type": "string", - "description": "The new status of this category.", - "enum": [ - "DENY", - "ALLOW" - ] + "format": "date-time" + }, + "repositoriesImported": { + "type": "integer", + "format": "int64" + }, + "projectsScanned": { + "type": "integer", + "format": "int64" } }, "required": [ - "policy" + "date", + "projectsScanned", + "repositoriesImported" ] }, - "LicenseProfileCategoryResource": { + "OnboardingProjectRow": { "type": "object", "properties": { - "id": { + "key": { "type": "string" }, - "key": { + "name": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "alm": { + "type": [ + "string", + "null" + ] + }, + "scanStatus": { "type": "string", "enum": [ - "UNKNOWN", - "COPYLEFT_WEAK", - "COPYLEFT_STRONG", - "COPYLEFT_NETWORK", - "COPYLEFT_MAXIMAL", - "PERMISSIVE_STANDARD", - "PERMISSIVE_AMATEUR" + "SCANNED", + "NOT_SCANNED" ] }, - "policy": { + "analysisMode": { "type": "string", - "description": "The policy status of this category.", "enum": [ - "DENY", - "ALLOW" + "CI", + "AUTOMATIC", + "NONE" ] + }, + "lastScan": { + "type": [ + "integer", + "null" + ], + "format": "int64" } - } + }, + "required": [ + "analysisMode", + "key", + "name", + "scanStatus" + ] }, - "AssignedProjectsUpdateRestRequest": { + "OnboardingProjectsPage": { "type": "object", "properties": { - "licenseProfileUuid": { - "type": "string", - "description": "(deprecated) The id of the license profile that should be used when analyzing the project for license issues." + "pageIndex": { + "type": "integer", + "format": "int32" }, - "licenseProfileId": { - "type": "string", - "description": "The id of the license profile that should be used when analyzing the project for license issues." + "pageSize": { + "type": "integer", + "format": "int32" }, - "projectKey": { - "type": "string", - "description": "The key of the project that should be assigned to the license profile." + "total": { + "type": "integer", + "format": "int64" } }, "required": [ - "projectKey" + "pageIndex", + "pageSize", + "total" ] }, - "IssueReleaseUpdateCommentRestRequest": { + "OnboardingProjectsResponse": { "type": "object", "properties": { - "issueReleaseChangeKey": { - "type": "string", - "description": "Issue release change key" + "page": { + "$ref": "#/components/schemas/OnboardingProjectsPage" }, - "comment": { - "type": "string", - "description": "Comment text" + "projects": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnboardingProjectRow" + } } }, "required": [ - "comment", - "issueReleaseChangeKey" + "page", + "projects" ] }, - "FeatureEnablementRequest": { + "DevopsPlatformsStep": { "type": "object", "properties": { - "enablement": { - "type": "boolean", - "description": "Whether SCA is enabled or not" + "configured": { + "type": "integer", + "format": "int64" } - } + }, + "required": [ + "configured" + ] }, - "FeatureEnablementResource": { + "OnboardingOverviewResponse": { "type": "object", "properties": { - "enablement": { - "type": "boolean" + "progressPct": { + "type": "number", + "format": "double" + }, + "steps": { + "$ref": "#/components/schemas/OnboardingSteps" } - } + }, + "required": [ + "progressPct", + "steps" + ] }, - "JiraWorkTypesSelectionResource": { + "OnboardingSteps": { "type": "object", "properties": { - "sonarProjectId": { - "type": "string", - "description": "The Sonar project ID", - "writeOnly": true + "devopsPlatforms": { + "$ref": "#/components/schemas/DevopsPlatformsStep" }, - "selectedWorkTypes": { - "type": "array", - "description": "An array with the selected work type ids", - "items": { - "type": "string" - }, - "writeOnly": true + "repositories": { + "$ref": "#/components/schemas/RepositoriesStep" + }, + "projects": { + "$ref": "#/components/schemas/ProjectsStep" } - } + }, + "required": [ + "devopsPlatforms", + "projects", + "repositories" + ] }, - "PatchJiraProjectBindingRequestResource": { + "ProjectsStep": { "type": "object", "properties": { - "sonarProjectId": { - "type": "string", - "description": "Sonar project identifier", - "minLength": 1, - "writeOnly": true + "analyzed": { + "type": "integer", + "format": "int64" }, - "jiraProjectKey": { - "type": "string", - "description": "Jira project key", - "minLength": 1, - "writeOnly": true + "notScanned": { + "type": "integer", + "format": "int64" + }, + "notImported": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "total": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "percent": { + "type": [ + "number", + "null" + ], + "format": "double" } }, "required": [ - "jiraProjectKey", - "sonarProjectId" + "analyzed", + "notScanned" ] }, - "PatchJiraOrganizationBindingRequestResource": { + "RepositoriesStep": { "type": "object", "properties": { - "sonarOrganizationUuid": { - "type": "string", - "format": "uuid", - "description": "Sonar organization UUID", - "writeOnly": true - }, - "jiraCloudId": { - "type": "string", - "description": "Jira cloud ID to bind with the organization", - "minLength": 1, - "writeOnly": true + "imported": { + "type": "integer", + "format": "int64" }, - "jiraInstanceUrl": { - "type": "string", - "description": "Jira instance URL", - "minLength": 1, - "writeOnly": true + "discovered": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "percent": { + "type": [ + "number", + "null" + ], + "format": "double" } }, "required": [ - "jiraCloudId", - "jiraInstanceUrl", - "sonarOrganizationUuid" + "imported" ] }, - "PatchJiraOrganizationBindingEditResource": { + "AlertResource": { "type": "object", "properties": { - "sonarOrganizationUuid": { + "key": { "type": "string", - "format": "uuid", - "description": "Sonar organization UUID", - "writeOnly": true + "description": "Unique identifier of the alert", + "example": "EXAMPLE_COUNTER_REACHED_FIVE" }, - "isTokenShared": { - "type": "boolean", - "description": "Whether the Jira OAuth token is shared across all users in the organization", - "writeOnly": true + "message": { + "type": "string", + "description": "Human-readable message describing the alert condition shown to administrators" + }, + "activeSince": { + "type": "string", + "description": "The timestamp the adminAlert was first triggered, in ISO-8601 format", + "example": "2024-01-01T12:00:00Z" } - }, - "required": [ - "isTokenShared", - "sonarOrganizationUuid" - ] + } }, - "RuleTypeMapping": { + "AlertsResponse": { "type": "object", "properties": { - "type": { - "type": "string", - "description": "Rule type", - "enum": [ - "CODE_SMELL", - "BUG", - "VULNERABILITY", - "SECURITY_HOTSPOT" - ] - }, - "severities": { + "alerts": { "type": "array", - "description": "List of severities", + "description": "List of currently active admin alerts", "items": { - "type": "string", - "enum": [ - "BLOCKER", - "CRITICAL", - "MAJOR", - "MINOR", - "INFO" - ] + "$ref": "#/components/schemas/AlertResource" } } - }, - "required": [ - "severities", - "type" - ] + } }, - "SandboxSettingsResource": { + "SupportedModelResponse": { "type": "object", "properties": { - "enabled": { - "type": "boolean", - "description": "Whether sandbox is enabled globally" - }, - "defaultValue": { - "type": "boolean", - "description": "Default value for projects" + "modelKey": { + "type": "string" }, - "allowOverride": { - "type": "boolean", - "description": "Allow projects to override settings" + "modelDisplayName": { + "type": "string" }, - "softwareQualities": { - "type": [ - "array", - "null" - ], - "description": "Software quality mappings (MQR mode)", + "modelIdentifiers": { + "type": "array", "items": { - "$ref": "#/components/schemas/SoftwareQualityMapping" + "type": "string" } }, - "types": { - "type": [ - "array", - "null" - ], - "description": "Rule type mappings (Standard Experience mode)", + "supportedSecondaryModels": { + "type": "array", "items": { - "$ref": "#/components/schemas/RuleTypeMapping" + "$ref": "#/components/schemas/SupportedModelResponse" } } } }, - "SoftwareQualityMapping": { + "SupportedModelsResponse": { "type": "object", "properties": { - "softwareQuality": { + "supportedModels": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SupportedModelResponse" + } + } + } + }, + "LlmProvidersResponse": { + "type": "object", + "properties": { + "providers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LlmProviderResponse" + } + } + } + }, + "ProviderMappingsResponse": { + "type": "object", + "properties": { + "providerMappings": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProviderMappingResponse" + } + } + } + }, + "LlmProviderDefinitionResponse": { + "type": "object", + "properties": { + "provider": { "type": "string", - "description": "Software quality", "enum": [ - "MAINTAINABILITY", - "RELIABILITY", - "SECURITY" + "AZURE_OPENAI", + "AWS_BEDROCK", + "AWS_BEDROCK_MANTLE", + "VERTEX_AI", + "CUSTOM_PROXY", + "OPENAI", + "ANTHROPIC" ] }, - "impactSeverities": { + "label": { + "type": "string" + }, + "fields": { "type": "array", - "description": "List of impact severities", "items": { - "type": "string", - "enum": [ - "INFO", - "LOW", - "MEDIUM", - "HIGH", - "BLOCKER" - ] + "$ref": "#/components/schemas/LlmProviderFieldResponse" } } - }, - "required": [ - "impactSeverities", - "softwareQuality" - ] + } }, - "SandboxSettingsProjectResource": { + "LlmProviderDefinitionsResponse": { "type": "object", "properties": { - "enabled": { - "type": "boolean", - "description": "Whether sandbox is enabled for this project" - }, - "softwareQualities": { - "type": [ - "array", - "null" - ], - "description": "Software quality mappings (MQR mode)", - "items": { - "$ref": "#/components/schemas/SoftwareQualityMapping" - } - }, - "types": { - "type": [ - "array", - "null" - ], - "description": "Rule type mappings (Standard Experience mode)", + "providerDefinitions": { + "type": "array", "items": { - "$ref": "#/components/schemas/RuleTypeMapping" + "$ref": "#/components/schemas/LlmProviderDefinitionResponse" } - }, - "overridden": { - "type": "boolean", - "description": "Whether project settings override instance settings" } } }, - "IntegrationConfigurationPatchRequest": { + "LlmProviderFieldResponse": { "type": "object", "properties": { - "clientId": { - "type": [ - "string", - "null" - ] + "key": { + "type": "string" }, - "clientSecret": { - "type": [ - "string", - "null" + "label": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "type": { + "type": "string", + "enum": [ + "STRING", + "HTTP_HEADERS" ] }, - "signingSecret": { + "secret": { "type": [ - "string", + "boolean", "null" ] } } }, - "FixSuggestionsFeatureEnablementRequest": { + "JiraWorkTypeFieldResultResource": { "type": "object", "properties": { - "enablement": { + "key": { "type": "string", - "description": "Whether AI CodeFix is enabled or not", - "enum": [ - "DISABLED", - "ENABLED_FOR_ALL_PROJECTS", - "ENABLED_FOR_SOME_PROJECTS" - ] + "description": "The field key", + "readOnly": true + }, + "name": { + "type": "string", + "description": "The field name", + "readOnly": true + }, + "required": { + "type": "boolean", + "description": "Whether the field is required", + "readOnly": true + }, + "hasDefaultValue": { + "type": "boolean", + "description": "Whether the field has a default value", + "readOnly": true + } + } + }, + "JiraWorkTypeResultResource": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "the Jira work type id", + "readOnly": true + }, + "name": { + "type": "string", + "description": "the Jira work type name", + "readOnly": true + }, + "description": { + "type": "string", + "description": "the Jira work type description", + "readOnly": true + }, + "subtask": { + "type": "boolean", + "description": "true, if the Jira work type is a subtask", + "readOnly": true + }, + "hierarchyLevel": { + "type": "integer", + "format": "int32", + "description": "the Jira work type hierarchy", + "readOnly": true }, - "enabledProjectKeys": { - "type": [ - "array", - "null" - ], - "description": "Project keys for which AI CodeFix is enabled. Only used when enablement is ENABLED_FOR_SOME_PROJECTS.", + "selected": { + "type": "boolean", + "description": "true, if the Jira work type is selected", + "readOnly": true + }, + "fields": { + "type": "array", + "description": "Array of field metadata for the specified work type. (Optional)", "items": { - "type": "string" + "$ref": "#/components/schemas/JiraWorkTypeFieldResultResource" }, - "uniqueItems": true - }, - "provider": { - "$ref": "#/components/schemas/ProviderDto", - "description": "The AI provider configuration" + "readOnly": true } } }, - "HeaderDto": { + "JiraProjectResultResource": { "type": "object", - "description": "A custom HTTP header for the provider request", "properties": { "name": { "type": "string", - "description": "Header name" + "description": "the name of the project", + "readOnly": true }, - "value": { + "key": { "type": "string", - "description": "Header value. Masked as '****' if secret and already saved." - }, - "secret": { - "type": "boolean", - "description": "Whether this header value should be stored encrypted" + "description": "the key of the project", + "readOnly": true } } }, - "ProviderDto": { - "type": "null", + "LinkedIssuesCountResource": { + "type": "object", "properties": { - "type": { - "type": "string", - "description": "The provider type" - }, - "model": { - "type": [ - "string", - "null" - ], - "description": "The selected model (managed providers only)" - }, - "config": { - "type": [ - "object", - "null" - ], - "additionalProperties": { - "type": "string" - }, - "description": "Provider-specific configuration (self-hosted providers only)" - }, - "headers": { - "type": [ - "array", - "null" - ], - "description": "Custom HTTP headers (Custom provider only)", - "items": { - "$ref": "#/components/schemas/HeaderDto" - } + "count": { + "type": "integer", + "format": "int32", + "description": "Count of linked Jira issues", + "readOnly": true } } }, - "DevOpsPermissionMappingUpdateRequest": { + "IntegrationConfigurationSearchResponse": { "type": "object", "properties": { - "permissions": { - "$ref": "#/components/schemas/PermissionMappingUpdate", - "description": "Set of SonarQube permissions to apply" + "integrationConfigurations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IntegrationConfigurationResponse" + } } } }, - "PermissionMappingUpdate": { + "ScaResolutionDistribution": { "type": "object", "properties": { - "user": { - "type": "boolean" - }, - "codeViewer": { - "type": "boolean" - }, - "issueAdmin": { - "type": "boolean" - }, - "securityHotspotAdmin": { - "type": "boolean" - }, - "admin": { - "type": "boolean" + "key": { + "type": "string" }, - "scan": { - "type": "boolean" + "value": { + "type": "integer", + "format": "int32" } - } + }, + "required": [ + "key", + "value" + ] }, - "GitlabConfigurationUpdateRestRequest": { + "ScaResolutionHistory": { "type": "object", "properties": { - "enabled": { - "type": "boolean", - "description": "Enable Gitlab authentication" - }, - "applicationId": { + "date": { "type": "string", - "description": "Gitlab Application id" - }, - "url": { - "type": "string", - "description": "Url of Gitlab instance for authentication (for instance https://gitlab.com/api/v4)" - }, - "secret": { - "type": [ - "string", - "null" - ], - "description": "Secret of the application" - }, - "synchronizeGroups": { - "type": "boolean", - "description": "Set whether to synchronize groups" + "format": "date-time", + "description": "Date and time in ISO 8601 format, including timezone offset", + "example": "2024-01-01T12:00Z" }, - "allowedGroups": { - "$ref": "#/components/schemas/UpdateFieldListString", - "description": "Root Gitlab groups allowed to authenticate and provisioned. Ignored when allowAllGroups is true.", + "distribution": { + "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/ScaResolutionDistribution" } - }, - "allowAllGroups": { - "type": "boolean", - "description": "When true with Auto-provisioning, every group visible to the provisioning token is provisioned and the allowedGroups list is ignored. Has no effect with Just-in-Time provisioning. Security risk: any user belonging to any group accessible by the provisioning token will be granted access. Restrict access using allowedGroups unless broad access is intentional. Not supported on GitLab.com (SaaS): the request is rejected when the configured URL is gitlab.com, since the provisioning token may have visibility into a much larger and unbounded set of groups. Performance note: login may be slower for users belonging to a large number of groups, as all their groups must be fetched from GitLab on every authentication." - }, - "provisioningType": { + } + }, + "required": [ + "date", + "distribution" + ] + }, + "ScaResolutionHistoryResponse": { + "type": "object", + "properties": { + "statistic": { "type": "string", - "description": "Type of synchronization", "enum": [ - "JIT", - "AUTO_PROVISIONING" + "SCA_MTTR" ] }, - "allowUsersToSignUp": { - "type": "boolean", - "description": "Allow user to sign up" + "scaResolutionHistory": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ScaResolutionHistory" + } + } + }, + "required": [ + "scaResolutionHistory", + "statistic" + ] + }, + "Pagination": { + "type": "object", + "properties": { + "pageIndex": { + "type": [ + "integer", + "null" + ], + "format": "int32" }, - "provisioningToken": { + "pageSize": { "type": [ - "string", + "integer", "null" ], - "description": "Gitlab token for provisioning", - "maxLength": 2147483647, - "minLength": 1 + "format": "int32" + }, + "total": { + "type": [ + "integer", + "null" + ], + "format": "int64" } } }, - "GithubConfigurationUpdateRestRequest": { + "ProjectScaResolution": { "type": "object", "properties": { - "enabled": { - "type": "boolean", - "description": "Enable GitHub authentication" - }, - "clientId": { - "type": "string", - "description": "GitHub Client ID" - }, - "clientSecret": { - "type": "string", - "description": "GitHub Client secret" - }, - "applicationId": { - "type": "string", - "description": "GitHub Application id" - }, - "privateKey": { - "type": "string", - "description": "GitHub Private key" - }, - "synchronizeGroups": { - "type": "boolean", - "description": "Set whether to synchronize groups" - }, - "apiUrl": { - "type": "string", - "description": "Url of GitHub instance for API connectivity (for instance https://api.github.com)" + "branchId": { + "type": "string" }, - "webUrl": { + "projectName": { "type": "string", - "description": "Url of GitHub instance for authentication (for instance https://github.com)" + "example": "My Project" }, - "allowedOrganizations": { - "$ref": "#/components/schemas/UpdateFieldListString", - "description": "GitHub organizations allowed to authenticate and provisioned", - "items": { - "type": "string" - } - }, - "provisioningType": { + "projectKey": { "type": "string", - "description": "Type of synchronization", - "enum": [ - "JIT", - "AUTO_PROVISIONING" - ] - }, - "allowUsersToSignUp": { - "type": "boolean", - "description": "Allow user to sign up" + "example": "my-project" }, - "projectVisibility": { - "type": "boolean", - "description": "Sync project visibility" + "branchName": { + "type": [ + "string", + "null" + ], + "example": "develop" }, - "userConsentRequiredAfterUpgrade": { - "type": "boolean", - "description": "Admin consent to synchronize permissions from GitHub" + "value": { + "type": "integer", + "format": "int64" + }, + "trend": { + "type": [ + "number", + "null" + ], + "format": "double", + "description": "Percentage change in value relative to the value at trendSince." } - } + }, + "required": [ + "branchId", + "projectKey", + "projectName", + "value" + ] }, - "ModeResource": { + "ProjectScaResolutionResponse": { "type": "object", "properties": { - "mode": { + "hiddenProjectCount": { + "type": "integer", + "format": "int32", + "description": "The number of projects excluded from the response because the user does not have permission to view those projects. " + }, + "statistic": { "type": "string", "enum": [ - "MQR", - "STANDARD_EXPERIENCE" + "SCA_MTTR" ] }, - "modified": { - "type": "boolean", - "readOnly": true + "projectScaResolution": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProjectScaResolution" + } + }, + "page": { + "$ref": "#/components/schemas/Pagination" } }, "required": [ - "mode" + "hiddenProjectCount", + "page", + "projectScaResolution", + "statistic" ] }, - "GroupUpdateRestRequest": { + "ProjectMeasure": { "type": "object", "properties": { - "name": { + "branchId": { + "type": "string" + }, + "branchName": { + "type": [ + "string", + "null" + ], + "example": "develop" + }, + "measure": { + "$ref": "#/components/schemas/ProjectMeasureMetric" + }, + "projectKey": { "type": "string", - "description": "Group name", - "maxLength": 255, - "minLength": 1 + "example": "my-project" }, - "description": { + "projectName": { "type": "string", - "description": "Description of the group", - "maxLength": 200, - "minLength": 0 + "example": "My Project" } - } + }, + "required": [ + "branchId", + "measure", + "projectKey", + "projectName" + ] }, - "PatchModel": { + "ProjectMeasureMetric": { "type": "object", - "description": "Either 'data' (deprecated) or 'model' must be set.", "properties": { - "data": { + "currentValue": { "type": [ "string", "null" ], - "deprecated": true, - "description": "Deprecated. Use 'model' instead.", - "maxLength": 50000, - "minLength": 0 + "description": "Value of the measure encoded as a string. Use the measure's type to interpret the value. The value may be null if the measure has no current value. " }, - "model": { - "$ref": "#/components/schemas/ModelData" - } - } - }, - "PageRestResponse": { - "type": "object", - "properties": { - "pageIndex": { - "type": "integer", - "format": "int32" + "metric": { + "type": "string", + "description": "Metric key", + "example": "coverage" }, - "pageSize": { - "type": "integer", - "format": "int32" + "referenceValue": { + "type": [ + "string", + "null" + ], + "description": "Reference value for the measure encoded as a string, used for trend calculation. Use the measure's type to interpret the value. The value may be null if the measure did not have a value on the reference date. " }, - "total": { - "type": "integer", - "format": "int32" + "type": { + "type": "string", + "description": "Identifies the measure's data type.", + "example": "PERCENT" } - } + }, + "required": [ + "metric", + "type" + ] }, - "UsersSearchRestResponse": { + "ProjectMeasuresResponse": { "type": "object", "properties": { - "users": { + "hiddenProjectCount": { + "type": "integer", + "format": "int32", + "description": "The number of projects excluded from the response because the user does not have permission to view those projects. " + }, + "projectMeasures": { "type": "array", "items": { - "$ref": "#/components/schemas/UserRestResponseForAdmins" + "$ref": "#/components/schemas/ProjectMeasure" } }, "page": { - "$ref": "#/components/schemas/PageRestResponse" + "$ref": "#/components/schemas/Pagination" } - } + }, + "required": [ + "hiddenProjectCount", + "page", + "projectMeasures" + ] }, - "DatabaseMigrationsResponse": { + "ProjectIssueResolution": { "type": "object", "properties": { - "status": { + "branchId": { "type": "string" }, - "completedSteps": { - "type": [ - "integer", - "null" - ], - "format": "int32" + "projectName": { + "type": "string", + "example": "My Project" }, - "totalSteps": { - "type": [ - "integer", - "null" - ], - "format": "int32" + "projectKey": { + "type": "string", + "example": "my-project" }, - "startedAt": { + "branchName": { "type": [ "string", "null" - ] + ], + "example": "develop" }, - "message": { - "type": [ - "string", - "null" - ] + "value": { + "type": "integer", + "format": "int64" }, - "expectedFinishTimestamp": { + "trendPercentage": { "type": [ - "string", + "number", "null" - ] + ], + "format": "double", + "description": "Percentage change in value relative to trendSince." } - } + }, + "required": [ + "branchId", + "projectKey", + "projectName", + "value" + ] }, - "Health": { + "ProjectIssueResolutionResponse": { "type": "object", "properties": { - "status": { + "statistic": { "type": "string", "enum": [ - "GREEN", - "YELLOW", - "RED" + "MTTR", + "RECENT_MTTR", + "RESOLVED_ISSUES" ] }, - "causes": { + "projectIssueResolution": { "type": "array", "items": { - "type": "string" - }, - "uniqueItems": true + "$ref": "#/components/schemas/ProjectIssueResolution" + } + }, + "page": { + "$ref": "#/components/schemas/Pagination" } - } + }, + "required": [ + "page", + "projectIssueResolution", + "statistic" + ] }, - "EmailConfigurationSearchRestResponse": { + "ProjectIssueCount": { "type": "object", "properties": { - "emailConfigurations": { + "branchId": { + "type": "string" + }, + "projectName": { + "type": "string", + "example": "My Project" + }, + "projectKey": { + "type": "string", + "example": "my-project" + }, + "branchName": { + "type": [ + "string", + "null" + ], + "example": "develop" + }, + "issueCount": { + "type": "integer", + "format": "int64" + }, + "referenceIssueCount": { + "type": [ + "integer", + "null" + ], + "format": "int64" + } + }, + "required": [ + "branchId", + "issueCount", + "projectKey", + "projectName" + ] + }, + "ProjectIssueCountsResponse": { + "type": "object", + "properties": { + "hiddenProjectCount": { + "type": "integer", + "format": "int32", + "description": "The number of projects excluded from the response because the user does not have permission to view those projects. " + }, + "projectIssueCounts": { "type": "array", "items": { - "$ref": "#/components/schemas/EmailConfigurationResource" + "$ref": "#/components/schemas/ProjectIssueCount" } }, "page": { - "$ref": "#/components/schemas/PageRestResponse" + "$ref": "#/components/schemas/Pagination" } - } + }, + "required": [ + "hiddenProjectCount", + "page", + "projectIssueCounts" + ] }, - "Category": { + "MeasureHistoryEntry": { "type": "object", "properties": { - "activeRules": { - "type": "integer", - "format": "int32" + "metric": { + "type": [ + "string", + "null" + ], + "description": "Metric whose value is being recorded", + "example": "coverage" }, - "issues": { - "type": "integer", - "format": "int32" + "type": { + "type": [ + "string", + "null" + ], + "description": "Type of value the measure contains", + "example": "PERCENT" }, - "key": { - "type": "string" + "value": { + "type": [ + "string", + "null" + ], + "description": "Value of the measure", + "example": "81.2" } } }, - "GetAccessibilityReportResponse": { + "MeasureHistoryItem": { "type": "object", "properties": { - "categories": { + "date": { + "type": [ + "string", + "null" + ], + "format": "date-time", + "description": "The date the measures were taken" + }, + "measures": { "type": "array", "items": { - "$ref": "#/components/schemas/Category" + "$ref": "#/components/schemas/MeasureHistoryEntry" } } } }, - "SelfTestHttpCallResource": { + "MeasuresHistoryResponse": { "type": "object", "properties": { - "attemptedUrl": { - "type": "string" - }, - "attemptedMethod": { - "type": "string" - }, - "responseCode": { - "type": "integer", - "format": "int32" - }, - "responseBody": { - "type": "string" - }, - "responseBodyAppearsValid": { - "type": "boolean" - }, - "responseHeaders": { + "measuresHistory": { "type": "array", "items": { - "type": "array", - "items": { - "type": "string" - } + "$ref": "#/components/schemas/MeasureHistoryItem" } } } }, - "SelfTestResponse": { + "IssueResolutionDistribution": { "type": "object", "properties": { - "featureEnabled": { - "type": "boolean" - }, - "selfTestPassed": { - "type": "boolean" - }, - "cliVersionCheck": { - "$ref": "#/components/schemas/SelfTestHttpCallResource" + "key": { + "type": [ + "string", + "null" + ] }, - "vulnerabilityDetailsCheck": { - "$ref": "#/components/schemas/SelfTestHttpCallResource" + "value": { + "type": [ + "integer", + "null" + ], + "format": "int32" } } }, - "RiskReportItem": { + "IssueResolutionHistory": { "type": "object", "properties": { - "projectKey": { - "type": "string" - }, - "projectName": { - "type": "string" - }, - "branchKey": { - "type": "string" - }, - "riskTitle": { - "type": "string" - }, - "riskType": { - "type": "string", - "enum": [ - "VULNERABILITY", - "PROHIBITED_LICENSE", - "MALWARE" - ] - }, - "riskSeverity": { - "type": "string", - "enum": [ - "INFO", - "LOW", - "MEDIUM", - "HIGH", - "BLOCKER" - ] - }, - "riskStatus": { - "type": "string", - "enum": [ - "OPEN", - "ACCEPT", - "CONFIRM", - "SAFE", - "FIXED" - ] - }, - "statusChanges": { - "type": "array", - "items": { - "$ref": "#/components/schemas/StatusChange" - } - }, - "vulnerabilityId": { - "type": "string" - }, - "cvssScore": { - "type": "number" - }, - "epssScore": { - "type": "number" - }, - "epssPercentile": { - "type": "number" - }, - "knownExploited": { - "type": "boolean" + "date": { + "type": [ + "string", + "null" + ], + "format": "date-time", + "description": "Date and time in ISO 8601 format, including timezone offset", + "example": "2024-01-01T12:00Z" }, - "cweIds": { + "distribution": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/IssueResolutionDistribution" } + } + } + }, + "IssueResolutionHistoryResponse": { + "type": "object", + "properties": { + "statistic": { + "type": "string", + "enum": [ + "MTTR", + "RECENT_MTTR", + "RESOLVED_ISSUES" + ] }, - "publishedOn": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "packageUrl": { - "type": "string" - }, - "riskUrl": { - "type": "string" - }, - "dependencyChains": { + "issueResolutionHistory": { "type": "array", "items": { - "type": "array", - "items": { - "type": "string" - } + "$ref": "#/components/schemas/IssueResolutionHistory" } + } + }, + "required": [ + "issueResolutionHistory", + "statistic" + ] + }, + "IssueDensityDistribution": { + "type": "object", + "properties": { + "key": { + "type": [ + "string", + "null" + ] }, - "scope": { - "type": "string" - }, - "productionScope": { - "type": "boolean" - }, - "confirmedReachable": { - "type": "boolean" + "value": { + "type": [ + "number", + "null" + ], + "format": "double" } } }, - "StatusChange": { + "IssueDensityHistory": { "type": "object", "properties": { - "comment": { - "type": "string" - }, - "newStatus": { - "type": "string" + "date": { + "type": [ + "string", + "null" + ], + "format": "date-time", + "description": "Date and time in ISO 8601 format, including timezone offset", + "example": "2024-01-01T12:00Z" }, - "createdAt": { - "type": "string" + "distribution": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IssueDensityDistribution" + } } } }, - "BranchResource": { + "IssueDensityHistoryResponse": { "type": "object", "properties": { - "id": { - "type": "string" - }, - "uuid": { - "type": "string" - }, - "key": { - "type": "string" - }, - "pullRequest": { - "type": "boolean" - }, - "projectKey": { - "type": "string" - }, - "projectName": { - "type": "string" + "issueDensityHistory": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IssueDensityHistory" + } } } }, - "PackageManagerCountResource": { + "IssueCountDistribution": { "type": "object", "properties": { - "id": { - "type": "string" - }, - "packageManager": { - "type": "string" + "key": { + "type": [ + "string", + "null" + ] }, - "releaseCount": { - "type": "integer", + "value": { + "type": [ + "integer", + "null" + ], "format": "int32" } } }, - "ReleasesSearchRestResponse": { + "IssueCountHistory": { "type": "object", "properties": { - "releases": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ReleaseSearchResource" - } + "date": { + "type": [ + "string", + "null" + ], + "format": "date-time", + "description": "Date and time in ISO 8601 format, including timezone offset", + "example": "2024-01-01T12:00Z" }, - "packageManagerCounts": { + "distribution": { "type": "array", "items": { - "$ref": "#/components/schemas/PackageManagerCountResource" + "$ref": "#/components/schemas/IssueCountDistribution" } - }, - "branches": { + } + } + }, + "IssueCountHistoryResponse": { + "type": "object", + "properties": { + "issueCountHistory": { "type": "array", "items": { - "$ref": "#/components/schemas/BranchResource" + "$ref": "#/components/schemas/IssueCountHistory" } - }, - "countWithoutFilters": { - "type": "integer", - "format": "int32" - }, - "page": { - "$ref": "#/components/schemas/PageRestResponse" } } }, - "DependencyResource": { + "SupportedRulesDto": { "type": "object", "properties": { - "key": { - "type": "string" - }, - "direct": { - "type": "boolean" - }, - "scope": { - "type": "string" - }, - "productionScope": { - "type": "boolean" - }, - "userDependencyFilePath": { - "type": "string" - }, - "lockfileDependencyFilePath": { - "type": "string" - }, - "chains": { + "rules": { "type": "array", "items": { - "type": "array", - "items": { - "type": "string" - } - } + "type": "string" + }, + "uniqueItems": true + } + } + }, + "LlmModelDto": { + "type": "object", + "properties": { + "key": { + "type": "string" }, - "newlyIntroduced": { + "name": { + "type": "string" + }, + "recommended": { "type": "boolean" } } }, - "IssueResource": { + "LlmProviderResponseDto": { "type": "object", "properties": { "key": { "type": "string" }, - "severity": { + "name": { "type": "string" }, - "showIncreasedSeverityWarning": { + "selfHosted": { "type": "boolean" }, - "type": { + "models": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LlmModelDto" + } + } + } + }, + "ServiceInfo": { + "type": "object", + "properties": { + "status": { "type": "string", "enum": [ - "VULNERABILITY", - "PROHIBITED_LICENSE", - "MALWARE" + "CONNECTION_ERROR", + "SERVICE_ERROR", + "SUCCESS", + "TIMEOUT", + "UNAUTHORIZED" ] + } + } + }, + "FixSuggestionIssueResponse": { + "type": "object", + "properties": { + "issueId": { + "type": "string" }, - "quality": { + "aiSuggestion": { "type": "string", "enum": [ - "MAINTAINABILITY", - "RELIABILITY", - "SECURITY" + "AVAILABLE", + "NOT_AVAILABLE_FILE_LEVEL_ISSUE", + "NOT_AVAILABLE_UNSUPPORTED_RULE", + "NOT_AVAILABLE_FILE_SIZE" + ] + } + } + }, + "FeatureEnablementResponse": { + "type": "object", + "properties": { + "enablement": { + "type": "string", + "enum": [ + "DISABLED", + "ENABLED_FOR_ALL_PROJECTS", + "ENABLED_FOR_SOME_PROJECTS" ] }, - "createdAt": { - "type": "string" - }, - "assignee": { - "$ref": "#/components/schemas/UserResource" - }, - "vulnerabilityId": { - "type": "string" - }, - "cweIds": { - "type": "array", - "deprecated": true, + "enabledProjectKeys": { + "type": [ + "array", + "null" + ], "items": { - "type": "string", - "deprecated": true - } + "type": "string" + }, + "uniqueItems": true }, - "cwes": { + "providers": { "type": "array", "items": { - "$ref": "#/components/schemas/CweResource" + "$ref": "#/components/schemas/ProviderDto" } - }, - "cvssScore": { - "type": "string" - }, - "spdxLicenseId": { - "type": "string" } } }, - "ReleaseDetailResource": { + "PurchasableFeatureRestResponse": { "type": "object", "properties": { - "key": { + "featureKey": { "type": "string" }, - "branchId": { + "parent": { "type": "string" }, - "branchUuid": { - "type": "string", - "deprecated": true + "isEnabled": { + "type": "boolean" }, - "packageUrl": { - "type": "string" + "isAvailable": { + "type": "boolean" }, - "packageManager": { + "url": { "type": "string" - }, - "packageName": { + } + } + }, + "LicenseRestResponse": { + "type": "object", + "properties": { + "expirationDate": { "type": "string" }, - "version": { + "lastRefreshDate": { "type": "string" }, - "licenseExpression": { + "edition": { "type": "string" }, - "known": { - "type": "boolean" + "features": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LicenseFeatureRestResponse" + } }, - "knownPackage": { - "type": "boolean" + "maxLoc": { + "type": "integer", + "format": "int64" }, - "newlyIntroduced": { - "type": "boolean" + "loc": { + "type": "integer", + "format": "int64" }, - "directSummary": { - "type": "boolean" + "maxLocThisPeriod": { + "type": [ + "integer", + "null" + ], + "format": "int64" }, - "scopeSummary": { + "serverId": { "type": "string" }, - "productionScopeSummary": { - "type": "boolean" + "type": { + "type": "string" }, - "dependencies": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DependencyResource" - } + "contactEmail": { + "type": "string" }, - "issues": { - "type": "array", - "items": { - "$ref": "#/components/schemas/IssueResource" - } + "remainingLocThreshold": { + "type": "integer", + "format": "int64" }, - "branch": { - "$ref": "#/components/schemas/BranchResource" - } - } - }, - "ReleaseSearchBranchResource": { - "type": "object", - "properties": { - "id": { - "type": "string" + "canActivateGracePeriod": { + "type": "boolean" }, - "key": { + "gracePeriodEndDate": { "type": "string" }, - "pullRequest": { + "gracePeriodExpired": { "type": "boolean" }, - "projectKey": { - "type": "string" + "extraDays": { + "type": "integer", + "format": "int32" }, - "projectName": { + "startDate": { "type": "string" }, - "packageUrl": { + "activatedOnline": { + "type": "boolean" + }, + "licenseKey": { "type": "string" }, - "direct": { + "supported": { "type": "boolean" }, - "productionScope": { + "expired": { "type": "boolean" }, - "dependencyFilePaths": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "ReleaseSearchRestResponse": { - "type": "object", - "properties": { - "branches": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ReleaseSearchBranchResource" - } + "validEdition": { + "type": "boolean" }, - "page": { - "$ref": "#/components/schemas/PageRestResponse" - } - } - }, - "LicenseProfileCollectionActions": { - "type": "object", - "properties": { - "create": { + "validServerId": { "type": "boolean" - } - } - }, - "LicenseProfileIndexRestResponse": { - "type": "object", - "properties": { - "licenseProfiles": { - "type": "array", - "items": { - "$ref": "#/components/schemas/LicenseProfileResource" - } }, - "actions": { - "$ref": "#/components/schemas/LicenseProfileCollectionActions" - } - } - }, - "LicenseProfileDetailsResource": { - "type": "object", - "properties": { - "profile": { - "$ref": "#/components/schemas/LicenseProfileResource" + "officialDistribution": { + "type": "boolean" }, - "categories": { - "type": "array", - "items": { - "$ref": "#/components/schemas/LicenseProfileCategoryResource" - } + "legacy": { + "type": "boolean" }, - "licenses": { - "type": "array", - "items": { - "$ref": "#/components/schemas/LicensePolicyLicenseResource" - } + "disabled": { + "type": "boolean" } } }, - "AssignableProjectResource": { + "ProjectBinding": { "type": "object", "properties": { "id": { "type": "string" }, - "projectKey": { + "devOpsPlatformSettingId": { "type": "string" }, - "projectName": { + "projectId": { "type": "string" }, - "assignedToLicenseProfile": { - "type": "boolean" + "projectKey": { + "type": "string" + }, + "repository": { + "type": [ + "string", + "null" + ] + }, + "slug": { + "type": [ + "string", + "null" + ] } - } + }, + "required": [ + "devOpsPlatformSettingId", + "id", + "projectId", + "projectKey" + ] }, - "AssignableProjectsIndexRestResponse": { + "ProjectBindingsSearchRestResponse": { "type": "object", "properties": { - "assignableProjects": { + "projectBindings": { "type": "array", "items": { - "$ref": "#/components/schemas/AssignableProjectResource" + "$ref": "#/components/schemas/ProjectBinding" } }, "page": { @@ -12551,803 +27260,1175 @@ } } }, - "IssueReleaseResource": { + "AffectedInstallationResource": { "type": "object", + "description": "A GitHub App installation that has not approved every permission the Remediation Agent requires. Only permissions the\napp itself requests appear here; a permission the app does not request is reported once in 'appMissingPermissions'\ninstead, because no installation owner can approve it. Suspension alone does not put an installation in this list.\n", "properties": { - "id": { - "type": "string" - }, - "key": { - "type": "string" - }, - "severity": { - "type": "string" - }, - "originalSeverity": { - "type": "string" - }, - "manualSeverity": { - "type": "string" + "installationId": { + "type": "string", + "description": "Identifier of the GitHub App installation", + "readOnly": true }, - "showIncreasedSeverityWarning": { - "type": "boolean" + "installationOwner": { + "type": "string", + "description": "Login of whoever the app is installed on. May be an organization or a personal account.", + "readOnly": true }, - "hasReachableLocations": { - "type": "boolean" + "settingsUrl": { + "type": "string", + "description": "URL of the installation settings page, where the missing permissions can be approved", + "readOnly": true }, - "release": { - "$ref": "#/components/schemas/ReleaseSearchResource" + "missingPermissions": { + "type": "array", + "description": "Permissions this installation still has to approve", + "items": { + "$ref": "#/components/schemas/PermissionDeficitResource" + }, + "readOnly": true + } + } + }, + "PermissionCheckResource": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Key of the DevOps Platform configuration", + "readOnly": true }, "type": { "type": "string", - "enum": [ - "VULNERABILITY", - "PROHIBITED_LICENSE", - "MALWARE" - ] + "description": "DevOps Platform type of the configuration (github, gitlab, azure)" }, - "quality": { + "status": { "type": "string", + "description": "Whether the configuration grants the write permissions the Remediation Agent needs to clone and open pull requests", "enum": [ - "MAINTAINABILITY", - "RELIABILITY", - "SECURITY" + "SUFFICIENT", + "INSUFFICIENT", + "UNKNOWN", + "CHECK_FAILED", + "UNSUPPORTED_TOKEN_TYPE" ] }, - "status": { - "type": "string" + "checkedAt": { + "type": "integer", + "format": "int64", + "description": "Epoch milliseconds when this status was last computed. May reflect a cached result.", + "readOnly": true }, - "createdAt": { - "type": "string" + "appMissingPermissions": { + "type": [ + "array", + "null" + ], + "description": "GitHub only. Permissions that the GitHub App does not request. Add these permissions to the app before an\ninstallation can approve them. If the system cannot read the app configuration, 'status' is CHECK_FAILED. In this\ncase, an empty list means that no app permission result is available.\n", + "items": { + "$ref": "#/components/schemas/PermissionDeficitResource" + }, + "readOnly": true }, - "assignee": { - "$ref": "#/components/schemas/UserResource" + "installationCheckStatus": { + "type": [ + "string", + "null" + ], + "description": "GitHub only. Whether the per-installation scan produced a trustworthy answer. The counts and the list are\nauthoritative only when this is COMPLETE. On a project request, NOT_INSTALLED means the app is not installed on\nthat project's repository — a definite answer, unlike FAILED. NOT_RUN means that the system did not check an\ninstallation because the project has no bound repository. In this case, 'status' covers only the app configuration.\n", + "enum": [ + "COMPLETE", + "FAILED", + "NOT_INSTALLED", + "NOT_RUN" + ], + "readOnly": true }, - "commentCount": { - "type": "integer", - "format": "int32" + "totalInstallationCount": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "description": "GitHub administrator requests only. How many installations the app has at all. Tells an app nobody has installed\napart from an app whose installations have all approved, which both report zero affected installations. Omitted\nunless 'installationCheckStatus' is COMPLETE.\n", + "readOnly": true }, - "vulnerabilityId": { - "type": "string" + "affectedInstallationCount": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "description": "GitHub administrator requests only. Exact number of installations with missing permissions. Omitted unless\n'installationCheckStatus' is COMPLETE.\n", + "readOnly": true }, - "cweIds": { - "type": "array", - "deprecated": true, + "affectedInstallations": { + "type": [ + "array", + "null" + ], + "description": "GitHub administrator requests only. Every installation with missing permissions, in a stable order. Omitted unless\n'installationCheckStatus' is COMPLETE.\n", "items": { - "type": "string", - "deprecated": true - } - }, - "cwes": { + "$ref": "#/components/schemas/AffectedInstallationResource" + }, + "readOnly": true + } + } + }, + "PermissionChecksRestResponse": { + "type": "object", + "properties": { + "permissionChecks": { "type": "array", "items": { - "$ref": "#/components/schemas/CweResource" + "$ref": "#/components/schemas/PermissionCheckResource" } + } + } + }, + "PermissionDeficitResource": { + "type": "object", + "description": "A permission the Remediation Agent requires that is not granted at the level it needs", + "properties": { + "permission": { + "type": "string", + "description": "Name of the DevOps Platform permission, e.g. 'contents'", + "readOnly": true }, - "cvssScore": { - "type": "string" + "required": { + "type": "string", + "description": "Access level the Remediation Agent requires, e.g. 'write'", + "readOnly": true }, - "withdrawn": { - "type": "boolean" + "granted": { + "type": [ + "string", + "null" + ], + "description": "Access level actually granted, or null when the permission is not granted at all", + "readOnly": true + } + } + }, + "C": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "QUALITY" + ] }, - "spdxLicenseId": { - "type": "string" + "status": { + "type": "string", + "enum": [ + "OK", + "ERROR", + "WARN", + "NONE" + ] }, - "transitions": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "CONFIRM", - "REOPEN", - "SAFE", - "FIXED", - "ACCEPT" - ] - } + "ignoredConditions": { + "type": "boolean" }, - "actions": { + "conditions": { "type": "array", "items": { - "type": "string", - "enum": [ - "COMMENT", - "ASSIGN", - "SET_SEVERITY" - ] - }, - "uniqueItems": true + "$ref": "#/components/schemas/e" + } } } }, - "IssuesReleasesSearchRestResponse": { + "W": { "type": "object", "properties": { - "issuesReleases": { - "type": "array", - "items": { - "$ref": "#/components/schemas/IssueReleaseResource" - } - }, - "branches": { + "gates": { "type": "array", "items": { - "$ref": "#/components/schemas/IssueReleaseBranchResource" + "$ref": "#/components/schemas/C" } - }, - "countWithoutFilters": { - "type": "integer", - "format": "int32" - }, - "page": { - "$ref": "#/components/schemas/PageRestResponse" } } }, - "IssueReleaseChangeDiffResource": { + "e": { "type": "object", "properties": { - "fieldName": { - "type": "string" + "status": { + "type": "string", + "enum": [ + "OK", + "ERROR", + "WARN", + "NONE" + ] }, - "oldValue": { + "metricKey": { "type": "string" }, - "newValue": { + "comparator": { + "type": "string", + "enum": [ + "LT", + "GT", + "EQ", + "NE" + ] + }, + "errorThreshold": { "type": "string" + }, + "actualValue": { + "type": [ + "string", + "null" + ] } } }, - "IssueReleaseChangeResource": { + "n": { "type": "object", "properties": { - "key": { + "_type": { "type": "string" }, - "createdAt": { + "predicateType": { "type": "string" }, - "user": { - "$ref": "#/components/schemas/UserResource" + "predicate": { + "$ref": "#/components/schemas/W" }, - "markdownComment": { - "type": "string" + "createdAt": { + "type": "string", + "format": "date-time" }, - "htmlComment": { + "createdBy": { "type": "string" }, - "changeData": { + "markdown": { + "type": "string" + } + } + }, + "PermissionMappingsSearchRestResponse": { + "type": "object", + "properties": { + "permissionMappings": { "type": "array", "items": { - "$ref": "#/components/schemas/IssueReleaseChangeDiffResource" + "$ref": "#/components/schemas/PermissionMappingsResource" } - }, - "actions": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "EDIT_COMMENT", - "DELETE_COMMENT" - ] - }, - "uniqueItems": true } } }, - "IssuesReleasesChangesRestResponse": { + "GitlabConfigurationSearchRestResponse": { "type": "object", "properties": { - "changelog": { + "gitlabConfigurations": { "type": "array", "items": { - "$ref": "#/components/schemas/IssueReleaseChangeResource" + "$ref": "#/components/schemas/GitlabConfigurationRestResponseForAdmins" } + }, + "page": { + "$ref": "#/components/schemas/PageRestResponse" } } }, - "ScaFeatureEnabledResource": { + "GithubConfigurationSearchRestResponse": { "type": "object", "properties": { - "enabled": { - "type": "boolean" + "githubConfigurations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GithubConfigurationResource" + } + }, + "page": { + "$ref": "#/components/schemas/PageRestResponse" } } }, - "ScaCliInfoRestResponse": { + "DopSettingsResource": { "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "readOnly": true }, - "filename": { - "type": "string" + "type": { + "type": "string", + "description": "Supported DevOps Platform are: github, gitlab, azure, bitbucketcloud, bitbucket_server" }, - "sha256": { + "key": { "type": "string" }, - "os": { - "type": "string" + "url": { + "type": [ + "string", + "null" + ] }, - "arch": { - "type": "string" + "appId": { + "type": [ + "string", + "null" + ] } } }, - "AnalysisResource": { + "DopSettingsRestResponse": { "type": "object", "properties": { - "status": { - "type": "string", - "enum": [ - "FAILED", - "OUTDATED", - "COMPLETED" - ] - }, - "failedReason": { - "type": "string" - }, - "errors": { + "dopSettings": { "type": "array", "items": { - "$ref": "#/components/schemas/AnalysisErrorResource" + "$ref": "#/components/schemas/DopSettingsResource" } }, - "parsedFiles": { + "page": { + "$ref": "#/components/schemas/PageRestResponse" + } + } + }, + "JobsResponse": { + "type": "object", + "properties": { + "jobs": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/Job" } }, - "updatedAt": { - "type": "string" + "page": { + "$ref": "#/components/schemas/PageInfo" } } }, - "AlertResource": { + "PageInfo": { "type": "object", "properties": { - "key": { - "type": "string", - "description": "Unique identifier of the alert", - "example": "EXAMPLE_COUNTER_REACHED_FIVE" + "pageIndex": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "description": "1-based current page number." }, - "message": { - "type": "string", - "description": "Human-readable message describing the alert condition shown to administrators" + "pageSize": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "description": "Number of items per page." }, - "activeSince": { - "type": "string", - "description": "The timestamp the adminAlert was first triggered, in ISO-8601 format", - "example": "2024-01-01T12:00:00Z" + "total": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "description": "Total number of jobs across all pages." } } }, - "AlertsResponse": { + "AvailableCreditsBreakdown": { "type": "object", "properties": { - "alerts": { - "type": "array", - "description": "List of currently active admin alerts", - "items": { - "$ref": "#/components/schemas/AlertResource" - } + "total": { + "type": "integer", + "format": "int64", + "description": "Total base and active overage credits available after usage and reservations.", + "minimum": 0 + }, + "base": { + "type": "integer", + "format": "int64", + "description": "Base credits available after usage and reservations.", + "minimum": 0 + }, + "overage": { + "type": "integer", + "format": "int64", + "description": "Active overage credits available after usage and reservations. Null when overage is disabled.", + "minimum": 0 } - } + }, + "required": [ + "base", + "overage", + "total" + ] }, - "JiraWorkTypeFieldResultResource": { + "CreditCostBranch": { "type": "object", "properties": { - "key": { + "id": { "type": "string", - "description": "The field key", - "readOnly": true + "description": "Stable identifier of the branch." }, - "name": { + "ncloc": { + "type": "integer", + "format": "int64", + "description": "Latest persisted non-comment lines of code for the branch.", + "minimum": 0 + }, + "size": { "type": "string", - "description": "The field name", - "readOnly": true + "enum": [ + "XS", + "S", + "M", + "L", + "XL", + "XXL" + ] + } + }, + "required": [ + "id", + "ncloc", + "size" + ] + }, + "CreditCostBreakdown": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64", + "description": "Total credits required by the branch.", + "minimum": 1 }, - "required": { - "type": "boolean", - "description": "Whether the field is required", - "readOnly": true + "base": { + "type": "integer", + "format": "int64", + "description": "Branch cost assigned to available base credits.", + "minimum": 0 }, - "hasDefaultValue": { - "type": "boolean", - "description": "Whether the field has a default value", - "readOnly": true + "overage": { + "type": "integer", + "format": "int64", + "description": "Credits needed beyond the available base quota, including when the request will be refused.", + "minimum": 0 } - } + }, + "required": [ + "base", + "overage", + "total" + ] }, - "JiraWorkTypeResultResource": { + "CreditCostCredits": { "type": "object", "properties": { - "id": { - "type": "string", - "description": "the Jira work type id", - "readOnly": true - }, - "name": { - "type": "string", - "description": "the Jira work type name", - "readOnly": true + "branchCost": { + "$ref": "#/components/schemas/CreditCostBreakdown" }, - "description": { - "type": "string", - "description": "the Jira work type description", - "readOnly": true + "available": { + "$ref": "#/components/schemas/AvailableCreditsBreakdown" + } + }, + "required": [ + "available", + "branchCost" + ] + }, + "CreditCostResponse": { + "type": "object", + "properties": { + "branch": { + "$ref": "#/components/schemas/CreditCostBranch" }, - "subtask": { - "type": "boolean", - "description": "true, if the Jira work type is a subtask", - "readOnly": true + "credits": { + "$ref": "#/components/schemas/CreditCostCredits" }, - "hierarchyLevel": { + "creditCost": { "type": "integer", "format": "int32", - "description": "the Jira work type hierarchy", - "readOnly": true - }, - "selected": { - "type": "boolean", - "description": "true, if the Jira work type is selected", - "readOnly": true + "description": "Estimated credit cost for the branch size tier.", + "minimum": 1 }, - "fields": { - "type": "array", - "description": "Array of field metadata for the specified work type. (Optional)", - "items": { - "$ref": "#/components/schemas/JiraWorkTypeFieldResultResource" - }, - "readOnly": true + "availableCredits": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "description": "Available credits, when consumption tracking is configured.", + "minimum": 0 + } + }, + "required": [ + "branch", + "creditCost", + "credits" + ] + }, + "InstanceConfiguration": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "maxConcurrentJobs": { + "type": "integer", + "format": "int32" } - } + }, + "required": [ + "enabled", + "maxConcurrentJobs" + ] }, - "JiraProjectResultResource": { + "DashboardItem": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string", - "description": "the name of the project", - "readOnly": true + "maxLength": 300, + "minLength": 0 }, - "key": { + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 500, + "minLength": 0 + }, + "createdAt": { + "type": "integer", + "format": "int64" + }, + "updatedAt": { + "type": "integer", + "format": "int64" + }, + "createdById": { + "type": "string" + }, + "resourceType": { "type": "string", - "description": "the key of the project", - "readOnly": true + "enum": [ + "project", + "portfolio" + ] + }, + "resourceId": { + "type": "string" + }, + "updatedById": { + "type": "string" } - } + }, + "required": [ + "createdAt", + "createdById", + "id", + "name", + "resourceId", + "resourceType", + "updatedAt", + "updatedById" + ] }, - "LinkedIssuesCountResource": { + "DashboardPage": { "type": "object", "properties": { - "count": { + "pageIndex": { "type": "integer", - "format": "int32", - "description": "Count of linked Jira issues", - "readOnly": true + "format": "int32" + }, + "pageSize": { + "type": "integer", + "format": "int32" + }, + "total": { + "type": "integer", + "format": "int32" } - } + }, + "required": [ + "pageIndex", + "pageSize", + "total" + ] }, - "IntegrationConfigurationSearchResponse": { + "DashboardsResponse": { "type": "object", "properties": { - "integrationConfigurations": { + "dashboards": { "type": "array", "items": { - "$ref": "#/components/schemas/IntegrationConfigurationResponse" + "$ref": "#/components/schemas/DashboardItem" } + }, + "page": { + "$ref": "#/components/schemas/DashboardPage" } - } - }, - "SupportedRulesDto": { - "type": "object", - "properties": { - "rules": { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - } - } + }, + "required": [ + "dashboards", + "page" + ] }, - "LlmModelDto": { + "BuiltInDashboardItem": { "type": "object", "properties": { "key": { "type": "string" }, "name": { - "type": "string" + "type": "string", + "maxLength": 300, + "minLength": 0 }, - "recommended": { - "type": "boolean" + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 500, + "minLength": 0 + }, + "updatedAt": { + "type": "integer", + "format": "int64" + }, + "resourceType": { + "type": "string", + "enum": [ + "project", + "portfolio" + ] } - } + }, + "required": [ + "key", + "name", + "resourceType", + "updatedAt" + ] }, - "LlmProviderResponseDto": { + "BuiltInDashboardsResponse": { "type": "object", "properties": { - "key": { - "type": "string" - }, - "name": { - "type": "string" - }, - "selfHosted": { - "type": "boolean" - }, - "models": { + "dashboards": { "type": "array", "items": { - "$ref": "#/components/schemas/LlmModelDto" + "$ref": "#/components/schemas/BuiltInDashboardItem" } + }, + "page": { + "$ref": "#/components/schemas/DashboardPage" } - } + }, + "required": [ + "dashboards", + "page" + ] }, - "ServiceInfo": { + "BuiltInDashboardResponse": { "type": "object", "properties": { - "status": { + "key": { + "type": "string" + }, + "name": { + "type": "string", + "maxLength": 300, + "minLength": 0 + }, + "layout": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 500, + "minLength": 0 + }, + "updatedAt": { + "type": "integer", + "format": "int64" + }, + "resourceType": { "type": "string", "enum": [ - "CONNECTION_ERROR", - "SERVICE_ERROR", - "SUCCESS", - "TIMEOUT", - "UNAUTHORIZED" + "project", + "portfolio" ] } - } + }, + "required": [ + "key", + "layout", + "name", + "resourceType", + "updatedAt" + ] }, - "FixSuggestionIssueResponse": { + "PingResponse": { "type": "object", "properties": { - "issueId": { - "type": "string" - }, - "aiSuggestion": { + "status": { "type": "string", - "enum": [ - "AVAILABLE", - "NOT_AVAILABLE_FILE_LEVEL_ISSUE", - "NOT_AVAILABLE_UNSUPPORTED_RULE", - "NOT_AVAILABLE_FILE_SIZE" - ] + "description": "Service status", + "example": "ok" } - } + }, + "required": [ + "status" + ] }, - "FeatureEnablementResponse": { + "VerifyMetrics": { "type": "object", + "description": "Verify impact metrics for the requested period.", "properties": { - "enablement": { - "type": "string", - "enum": [ - "DISABLED", - "ENABLED_FOR_ALL_PROJECTS", - "ENABLED_FOR_SOME_PROJECTS" - ] - }, - "enabledProjectKeys": { - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - }, - "uniqueItems": true + "resolvedIssues": { + "type": "integer", + "format": "int64", + "description": "Issues inferred to be resolved across all SQAA analysis sequences. Detected as issue-count decreases between successive successful analyses of the same (file, rule) in inferred sessions. " }, - "providers": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ProviderDto" - } + "engineeringMinutesSaved": { + "type": "number", + "format": "double", + "description": "Verify contribution to estimated engineering time saved in minutes, preserving fractional values. Add this to the guideMetrics value before rounding the combined total for display. " } - } + }, + "required": [ + "engineeringMinutesSaved", + "resolvedIssues" + ] }, - "PurchasableFeatureRestResponse": { + "VortexProjectImpact": { "type": "object", + "description": "Vortex impact metrics for a single project in the requested period. Successful output compression and restoration both establish CAG activity and latest activity, but neither increases the Guide call count. Compression contributes token savings; restoration adds no impact and does not reverse compression savings in V0. Neither contributes engineering savings or resolved issues. A project using only these capabilities can therefore be active with zero tool calls; a restoration-only project also has zero calculated savings. ", "properties": { - "featureKey": { - "type": "string" + "projectId": { + "type": "string", + "description": "Identifier of the project." }, - "parent": { - "type": "string" + "projectName": { + "type": "string", + "description": "Display name of the project." }, - "isEnabled": { - "type": "boolean" + "activityClassification": { + "type": "string", + "enum": [ + "NONE", + "CAG_ONLY", + "VERIFY_ONLY", + "BOTH" + ] }, - "isAvailable": { - "type": "boolean" + "guideCallCount": { + "type": "integer", + "format": "int64", + "description": "Successful CAG tool calls in the period, counted from stored event metadata independently of payload decoding. Compression and restoration count as activity but not as Guide calls. Shell compression is hook-driven and expected to occur much more frequently than model-initiated tool calls, so including it would inflate this count. Successful events with malformed or unsupported payloads remain counted but contribute no calculated impact. " }, - "url": { - "type": "string" - } - } - }, - "LicenseFeatureRestResponse": { - "type": "object", - "properties": { - "name": { - "type": "string" + "verifyCallCount": { + "type": "integer", + "format": "int64", + "description": "Successful public SQAA analyses in the period, counted from stored event metadata independently of payload decoding. Successful events with malformed or unsupported payloads remain counted but contribute no calculated impact. " }, - "parent": { - "type": [ - "string", - "null" - ] + "resolvedIssues": { + "type": "integer", + "format": "int64", + "description": "Issues inferred to be resolved in the period." }, - "startDate": { - "type": [ - "string", - "null" - ] + "engineeringMinutesSaved": { + "type": "number", + "format": "double", + "description": "Combined Guide and Verify contribution to estimated engineering time saved in minutes, preserving fractional values. Aggregate estimates before rounding for display. " }, - "endDate": { + "tokensSaved": { + "type": "number", + "format": "double", + "description": "Estimated tokens saved by navigation tools and output compression for this project, preserving fractional values. Aggregate estimates before rounding for display. Output restoration does not add to or reverse compression savings in V0. " + }, + "latestActivityAt": { "type": [ "string", "null" - ] + ], + "format": "date-time", + "description": "Server-recorded time of the latest successful Vortex event in the period. Null when there is no activity." } - } + }, + "required": [ + "activityClassification", + "engineeringMinutesSaved", + "guideCallCount", + "projectId", + "projectName", + "resolvedIssues", + "tokensSaved", + "verifyCallCount" + ] }, - "LicenseRestResponse": { + "VortexProjectPage": { "type": "object", + "description": "Paginated Vortex impact per project for the requested period.", "properties": { - "expirationDate": { - "type": "string" + "pageIndex": { + "type": "integer", + "format": "int32", + "description": "Current page index (1-based)." }, - "lastRefreshDate": { - "type": "string" + "pageSize": { + "type": "integer", + "format": "int32", + "description": "Number of projects per page as resolved for this response." }, - "edition": { - "type": "string" + "total": { + "type": "integer", + "format": "int32", + "description": "Total number of projects, including those with no Vortex activity." }, - "features": { + "projects": { "type": "array", "items": { - "$ref": "#/components/schemas/LicenseFeatureRestResponse" + "$ref": "#/components/schemas/VortexProjectImpact" } - }, - "maxLoc": { - "type": "integer", - "format": "int64" - }, - "loc": { + } + }, + "required": [ + "pageIndex", + "pageSize", + "projects", + "total" + ] + }, + "VortexProjectActivity": { + "type": "object", + "description": "Vortex usage grouped by project analysis presence during the requested period.", + "properties": { + "totalProjectCount": { "type": "integer", - "format": "int64" + "format": "int32", + "description": "All projects across the four analysis cohorts." }, - "serverId": { - "type": "string" + "withMainBranchAnalysisInPeriod": { + "$ref": "#/components/schemas/VortexProjectAnalysisCohort", + "description": "Projects with at least one processed main-branch analysis in the requested period." }, - "type": { - "type": "string" + "withLongLivedBranchAnalysisInPeriod": { + "$ref": "#/components/schemas/VortexProjectAnalysisCohort", + "description": "Projects with at least one processed long-lived-branch analysis, but no processed main-branch analysis, in the requested period. Pull-request analyses may also be present. " }, - "contactEmail": { - "type": "string" + "withOnlyPullRequestAnalysisInPeriod": { + "$ref": "#/components/schemas/VortexProjectAnalysisCohort", + "description": "Projects with at least one processed pull-request analysis, but no processed main-branch or long-lived-branch analysis, in the requested period. " }, - "remainingLocThreshold": { + "withoutAnalysisInPeriod": { + "$ref": "#/components/schemas/VortexProjectAnalysisCohort", + "description": "Projects without a processed analysis on any branch in the requested period." + } + }, + "required": [ + "totalProjectCount", + "withLongLivedBranchAnalysisInPeriod", + "withMainBranchAnalysisInPeriod", + "withOnlyPullRequestAnalysisInPeriod", + "withoutAnalysisInPeriod" + ] + }, + "VortexProjectAnalysisCohort": { + "type": "object", + "description": "Project count and Vortex usage for one analysis cohort.", + "properties": { + "projectCount": { "type": "integer", - "format": "int64" - }, - "canActivateGracePeriod": { - "type": "boolean" - }, - "gracePeriodEndDate": { - "type": "string" - }, - "gracePeriodExpired": { - "type": "boolean" + "format": "int32", + "description": "Projects in this analysis cohort." }, - "extraDays": { + "vortexUsage": { + "$ref": "#/components/schemas/VortexProjectUsage" + } + }, + "required": [ + "projectCount", + "vortexUsage" + ] + }, + "VortexProjectUsage": { + "type": "object", + "description": "Projects classified by successful Vortex usage during the requested period.", + "properties": { + "withUsageProjectCount": { "type": "integer", - "format": "int32" - }, - "startDate": { - "type": "string" - }, - "activatedOnline": { - "type": "boolean" - }, - "licenseKey": { - "type": "string" + "format": "int32", + "description": "Projects with successful CAG or Verify usage." }, - "supported": { - "type": "boolean" + "withoutUsageProjectCount": { + "type": "integer", + "format": "int32", + "description": "Projects without successful CAG or Verify usage." }, - "expired": { - "type": "boolean" + "cagOnlyProjectCount": { + "type": "integer", + "format": "int32", + "description": "Projects with successful CAG usage but no successful Verify usage." }, - "validEdition": { - "type": "boolean" + "verifyOnlyProjectCount": { + "type": "integer", + "format": "int32", + "description": "Projects with successful Verify usage but no successful CAG usage." }, - "validServerId": { - "type": "boolean" + "cagAndVerifyProjectCount": { + "type": "integer", + "format": "int32", + "description": "Projects with both successful CAG and Verify usage." + } + }, + "required": [ + "cagAndVerifyProjectCount", + "cagOnlyProjectCount", + "verifyOnlyProjectCount", + "withUsageProjectCount", + "withoutUsageProjectCount" + ] + }, + "GuideMetrics": { + "type": "object", + "description": "Guide impact metrics for the requested period.", + "properties": { + "preventedIssues": { + "type": "integer", + "format": "int64", + "description": "Estimated issues prevented by supplied guidelines, rounded to a whole number. Each successful get_guidelines call that returns at least one rule contributes N_prevented. " }, - "officialDistribution": { - "type": "boolean" + "tokensSaved": { + "type": "number", + "format": "double", + "description": "Estimated tokens saved by navigation tools and output compression, preserving fractional values. Aggregate estimates before rounding for display. " }, - "legacy": { - "type": "boolean" + "engineeringMinutesSaved": { + "type": "number", + "format": "double", + "description": "Guide contribution to estimated engineering time saved in minutes, preserving fractional values. Add this to the verifyMetrics value before rounding the combined total for display. " + } + }, + "required": [ + "engineeringMinutesSaved", + "preventedIssues", + "tokensSaved" + ] + }, + "CagUsageStats": { + "type": "object", + "description": "CAG tool usage statistics for an organization", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The organization ID these statistics are about", + "example": "c713929f-d5ee-4559-bae1-a93ac20cbbb6" }, - "disabled": { - "type": "boolean" + "projects": { + "type": "array", + "description": "All projects with their CAG tool call count, ordered by tool call count descending.", + "items": { + "$ref": "#/components/schemas/ProjectCagUsageCount" + } } - } + }, + "required": [ + "id", + "projects" + ] }, - "ProjectBinding": { + "ProjectCagUsageCount": { "type": "object", + "description": "CAG tool call count for a project", "properties": { "id": { - "type": "string" + "type": "string", + "description": "Identifier of the project; an opaque string on SonarQube Server, a UUID string on SonarQube Cloud.", + "example": "7a2f5c8d-9e3b-4a1c-bf72-83a892472f22", + "maxLength": 40, + "minLength": 1, + "pattern": "^\\S+$" }, - "devOpsPlatformSettingId": { - "type": "string" + "name": { + "type": "string", + "description": "The project name", + "example": "My Project" }, - "projectId": { - "type": "string" + "toolCallCount": { + "type": "integer", + "format": "int64", + "description": "Number of CAG tool calls for the project over the requested period", + "example": 42 + } + }, + "required": [ + "id", + "name", + "toolCallCount" + ] + }, + "CagEntitlement": { + "type": "object", + "properties": { + "allowed": { + "type": "boolean", + "description": "Whether the organization is currently allowed to use Context Augmentation." }, - "projectKey": { - "type": "string" + "hasEntitlement": { + "type": "boolean", + "description": "Whether the organization has any valid access path to Context Augmentation. When allowed is false, true means the organization is still entitled but cannot use it right now, typically because it has reached its current limit; false means the organization is not entitled at all, or Context Augmentation is disabled instance-wide (reason disabled), in which case entitlement is not evaluated. See reason for the exact cause. " }, - "repository": { - "type": [ - "string", - "null" - ] + "featureKey": { + "type": "string", + "description": "Feature key to use for consumption reporting when relevant. This is always `contextAugmentation` for the CAG entitlement response. ", + "example": "contextAugmentation" }, - "slug": { + "consumption": { + "$ref": "#/components/schemas/CagEntitlementConsumption" + }, + "reason": { "type": [ "string", "null" + ], + "enum": [ + "not_entitled", + "disabled", + "blocked" ] } }, "required": [ - "devOpsPlatformSettingId", - "id", - "projectId", - "projectKey" + "allowed", + "featureKey", + "hasEntitlement" ] }, - "ProjectBindingsSearchRestResponse": { + "CagEntitlementConsumption": { "type": "object", "properties": { - "projectBindings": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ProjectBinding" - } + "consumed": { + "type": [ + "integer", + "null" + ], + "format": "int64" }, - "page": { - "$ref": "#/components/schemas/PageRestResponse" + "limit": { + "type": [ + "integer", + "null" + ], + "format": "int64" } } }, - "$": { + "ConsumptionRestResponse": { "type": "object", "properties": { - "page": { - "$ref": "#/components/schemas/O" + "allowed": { + "type": "boolean", + "description": "Whether consumption of the feature is allowed, also check for the entitled property" }, - "snapshots": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Snapshot" - } + "limit": { + "$ref": "#/components/schemas/LimitRestResponse", + "description": "Consumption limits for the feature" + }, + "metering": { + "$ref": "#/components/schemas/MeteringRestResponse", + "description": "Consumption metering for the feature" } - }, - "required": [ - "page", - "snapshots" - ] + } }, - "K": { + "EntitlementRestResponse": { "type": "object", "properties": { - "_type": { - "type": "string" - }, - "predicateType": { - "type": "string" + "featureKey": { + "type": "string", + "description": "Internal feature key" }, - "predicate": { - "$ref": "#/components/schemas/$" + "entitled": { + "type": "boolean", + "description": "Whether the feature is entitled for use, also check for the consumption.allowed property if available" }, - "createdAt": { - "type": "string", - "format": "date-time" + "consumption": { + "$ref": "#/components/schemas/ConsumptionRestResponse", + "description": "Consumption information for a given feature, only available for CONSUMABLE features" }, - "createdBy": { - "type": "string" + "value": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "description": "Value for the feature" }, - "markdown": { - "type": "string" - } - } - }, - "PermissionMappingsSearchRestResponse": { - "type": "object", - "properties": { - "permissionMappings": { + "excludedValues": { "type": "array", + "description": "Excluded values for the feature", "items": { - "$ref": "#/components/schemas/PermissionMappingsResource" + "type": "string" } } } }, - "GitlabConfigurationSearchRestResponse": { + "LimitRestResponse": { "type": "object", "properties": { - "gitlabConfigurations": { - "type": "array", - "items": { - "$ref": "#/components/schemas/GitlabConfigurationRestResponseForAdmins" - } + "base": { + "type": "integer", + "format": "int64", + "description": "Base consumption limit for the feature" }, - "page": { - "$ref": "#/components/schemas/PageRestResponse" + "overage": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "description": "Overage consumption limit for the feature" + }, + "overageEnabled": { + "type": "boolean", + "description": "Whether overage is enabled for the feature" } } }, - "GithubConfigurationSearchRestResponse": { + "MeteringRestResponse": { "type": "object", "properties": { - "githubConfigurations": { - "type": "array", - "items": { - "$ref": "#/components/schemas/GithubConfigurationResource" - } + "used": { + "type": "integer", + "format": "int64", + "description": "Amount of credits already used for the feature" }, - "page": { - "$ref": "#/components/schemas/PageRestResponse" + "period": { + "$ref": "#/components/schemas/PeriodRestResponse", + "description": "Period information for the metering" } } }, - "DopSettingsResource": { + "PeriodRestResponse": { "type": "object", "properties": { - "id": { - "type": "string", - "readOnly": true - }, - "type": { + "cadence": { "type": "string", - "description": "Supported DevOps Platform are: github, gitlab, azure, bitbucketcloud, bitbucket_server" - }, - "key": { - "type": "string" - }, - "url": { - "type": [ - "string", - "null" - ] + "description": "Metering cadence (DAILY, WEEKLY, MONTHLY, ANNUAL, PERPETUAL)" }, - "appId": { + "anchor": { "type": [ "string", "null" - ] - } - } - }, - "DopSettingsRestResponse": { - "type": "object", - "properties": { - "dopSettings": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DopSettingsResource" - } - }, - "page": { - "$ref": "#/components/schemas/PageRestResponse" + ], + "description": "Optional period window anchor, e.g. 2026-07 for MONTHLY" } } }, @@ -13379,60 +28460,30 @@ } } }, - "Mq": { + "StructureEdge": { "type": "object", "properties": { - "id": { + "weight": { "type": "integer", "format": "int32" }, - "edges": { - "type": "array", - "items": { - "$ref": "#/components/schemas/m" - } - }, - "mappedKind": { - "type": [ - "string", - "null" - ], - "description": "Architectural category this node represents. For real code nodes this is the same as `kind` and may be omitted. For intended-architecture group nodes (where `kind = \"group\"`), this carries the inferred architectural category — e.g. `\"package\"`, `\"module\"`, `\"namespace\"`, `\"class\"`, `\"folder\"` or `\"file\"`. An absent or empty value means the category could not be inferred; consumers should apply permissive defaults in that case. " - }, - "name": { - "type": "string" - }, - "nodes": { - "type": [ - "array", - "null" - ], - "items": { - "required": [ - "edges", - "id", - "name" - ] - } - }, - "kind": { - "type": "string" + "toId": { + "type": "integer", + "format": "int32" } }, "required": [ - "edges", - "id", - "kind", - "name" + "toId", + "weight" ] }, - "d": { + "StructureGraph": { "type": "object", "properties": { "nodes": { "type": "array", "items": { - "$ref": "#/components/schemas/Mq" + "$ref": "#/components/schemas/StructureNode" } }, "id": { @@ -13455,46 +28506,54 @@ "nodes" ] }, - "m": { + "StructureNode": { "type": "object", "properties": { - "weight": { + "id": { "type": "integer", "format": "int32" }, - "toId": { - "type": "integer", - "format": "int32" - } - }, - "required": [ - "toId", - "weight" - ] - }, - "$q": { - "type": "object", - "properties": { - "values": { - "$ref": "#/components/schemas/SnapshotMeasures" + "edges": { + "type": "array", + "items": { + "$ref": "#/components/schemas/StructureEdge" + } }, - "ecosystem": { - "type": "string", - "enum": [ - "java", - "js", - "ts", - "py", - "cs" - ] + "mappedKind": { + "type": [ + "string", + "null" + ], + "description": "Architectural category this node represents. For real code nodes this is the same as `kind` and may be omitted. For intended-architecture group nodes (where `kind = \"group\"`), this carries the inferred architectural category — e.g. `\"package\"`, `\"module\"`, `\"namespace\"`, `\"class\"`, `\"folder\"` or `\"file\"`. An absent or empty value means the category could not be inferred; consumers should apply permissive defaults in that case. " + }, + "name": { + "type": "string" + }, + "nodes": { + "type": [ + "array", + "null" + ], + "items": { + "required": [ + "edges", + "id", + "name" + ] + } + }, + "kind": { + "type": "string" } }, "required": [ - "ecosystem", - "values" + "edges", + "id", + "kind", + "name" ] }, - "O": { + "PaginatedResponse": { "type": "object", "properties": { "pageSize": { @@ -13550,7 +28609,7 @@ ], "description": "Metrics per ecosystem; each entry identifies the ecosystem and its measures.", "items": { - "$ref": "#/components/schemas/$q" + "$ref": "#/components/schemas/SnapshotMeasuresInner" } }, "id": { @@ -13640,121 +28699,330 @@ "null" ] }, - "numDisallowed": { - "type": [ - "string", - "null" + "numDisallowed": { + "type": [ + "string", + "null" + ] + }, + "numInterfaceIssues": { + "type": [ + "string", + "null" + ] + }, + "numGraphEdges": { + "type": [ + "string", + "null" + ] + }, + "maxWeakTangleSize": { + "type": [ + "string", + "null" + ] + }, + "numStructuralIssues": { + "type": [ + "string", + "null" + ] + } + } + }, + "SnapshotMeasuresInner": { + "type": "object", + "properties": { + "values": { + "$ref": "#/components/schemas/SnapshotMeasures" + }, + "ecosystem": { + "type": "string", + "enum": [ + "java", + "js", + "ts", + "py", + "cs" + ] + } + }, + "required": [ + "ecosystem", + "values" + ] + }, + "SnapshotPagedResponse": { + "type": "object", + "properties": { + "page": { + "$ref": "#/components/schemas/PaginatedResponse" + }, + "snapshots": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Snapshot" + } + } + }, + "required": [ + "page", + "snapshots" + ] + }, + "SdkListResponse": { + "type": "object", + "properties": { + "sdks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Sdk" + } + } + }, + "required": [ + "sdks" + ] + }, + "ProjectRelationshipPagedResponse": { + "type": "object", + "properties": { + "page": { + "$ref": "#/components/schemas/PaginatedResponse" + }, + "projectRelationships": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProjectRelationship" + } + } + }, + "required": [ + "page", + "projectRelationships" + ] + }, + "ScannerProperty": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Key without the sonar.architecture.scanner. prefix, which the scanner adds. \"enterprise.enabled\" becomes sonar.architecture.scanner.enterprise.enabled.", + "example": "enterprise.enabled" + }, + "type": { + "type": "string", + "enum": [ + "BOOLEAN" ] }, - "numInterfaceIssues": { - "type": [ - "string", - "null" - ] + "value": { + "type": "string", + "description": "Always a string on the wire, as scanner properties are.", + "example": "false" + } + }, + "required": [ + "key", + "type", + "value" + ] + }, + "OrganizationPlaceholderListResponse": { + "type": "object", + "properties": { + "placeholders": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrganizationPlaceholder" + } + } + }, + "required": [ + "placeholders" + ] + }, + "OrganizationComponent": { + "type": "object", + "properties": { + "displayName": { + "type": "string" }, - "numGraphEdges": { - "type": [ - "string", - "null" + "id": { + "type": "string", + "format": "uuid", + "description": "Component id. For PROJECT components, this is the project id." + }, + "type": { + "type": "string", + "enum": [ + "PROJECT", + "PLACEHOLDER" ] }, - "maxWeakTangleSize": { + "projectId": { "type": [ "string", "null" - ] + ], + "description": "Set when type=PROJECT. Same value as id. Absent for PLACEHOLDER." }, - "numStructuralIssues": { + "organizationId": { "type": [ "string", "null" - ] + ], + "description": "UUID v4 of the organization." } - } + }, + "required": [ + "displayName", + "id", + "organizationId", + "type" + ] }, - "o": { + "OrganizationComponentListResponse": { "type": "object", "properties": { - "boundaryDescriptors": { + "components": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/OrganizationComponent" + } + } + }, + "required": [ + "components" + ] + }, + "OrganizationArchitecture": { + "type": "object", + "properties": { + "components": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrganizationArchitectureComponent" } }, - "projectId": { - "type": "string" - }, - "intendedArchitecture": { - "type": [ - "string", - "null" - ] - }, - "directives": { + "relationships": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/OrganizationArchitectureRelationship" } } }, "required": [ - "boundaryDescriptors", - "directives", - "projectId" + "components", + "relationships" ] }, - "E": { + "OrganizationArchitectureComponent": { "type": "object", "properties": { + "metadata": { + "$ref": "#/components/schemas/OrganizationArchitectureComponentMetadata" + }, "id": { "type": "string", "format": "uuid" + } + }, + "required": [ + "id", + "metadata" + ] + }, + "OrganizationArchitectureComponentMetadata": { + "type": "object", + "properties": { + "unmatchedExitPoints": { + "type": "array", + "description": "Keys of the component's exit points that are not used by any project relationship.", + "items": { + "type": "string" + } }, - "language": { - "type": [ - "string", - "null" - ], - "description": "Programming language this intended architecture is defined for.", - "enum": [ - "java", - "js", - "ts", - "py", - "cs" - ] - }, - "type": { - "type": [ - "string", - "null" - ], - "description": "Type of components that the intended architecture describes.", - "enum": [ - "file", - "namespace" - ] - }, - "constraints": { + "matchedExitPoints": { "type": "array", + "description": "Keys of the component's exit points that are used by a project relationship.", "items": { - "$ref": "#/components/schemas/v" + "type": "string" } + } + }, + "required": [ + "matchedExitPoints", + "unmatchedExitPoints" + ] + }, + "OrganizationArchitectureRelationship": { + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "uuid" }, - "groups": { + "metadata": { + "$ref": "#/components/schemas/OrganizationArchitectureRelationshipMetadata" + }, + "to": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "from", + "metadata", + "to" + ] + }, + "OrganizationArchitectureRelationshipMetadata": { + "type": "object", + "properties": { + "dependencyCount": { + "type": "integer", + "format": "int32", + "description": "Number of actual dependencies that exist between the 'from' and the 'to' component." + } + }, + "required": [ + "dependencyCount" + ] + }, + "OrganizationArchitectureResponse": { + "type": "object", + "properties": { + "organizationArchitectures": { "type": "array", "items": { - "$ref": "#/components/schemas/Kq" + "$ref": "#/components/schemas/OrganizationArchitecture" } } }, "required": [ - "constraints", - "groups", - "id" + "organizationArchitectures" + ] + }, + "IntendedConstraint": { + "type": "object", + "properties": { + "to": { + "type": "string", + "description": "pattern identifying the target group of the dependency." + }, + "from": { + "type": "string", + "description": "pattern identifying the source group of the dependency." + } + }, + "required": [ + "from", + "to" ] }, - "Kq": { + "IntendedGroup": { "type": "object", "properties": { "label": { @@ -13796,24 +29064,72 @@ "label" ] }, - "v": { + "IntendedModelExport": { "type": "object", "properties": { - "to": { + "id": { "type": "string", - "description": "pattern identifying the target group of the dependency." + "format": "uuid" }, - "from": { - "type": "string", - "description": "pattern identifying the source group of the dependency." + "language": { + "type": [ + "string", + "null" + ], + "description": "Programming language this intended architecture is defined for.", + "enum": [ + "java", + "js", + "ts", + "py", + "cs" + ] + }, + "type": { + "type": [ + "string", + "null" + ], + "description": "Type of components that the intended architecture describes.", + "enum": [ + "file", + "namespace" + ] + }, + "constraints": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IntendedConstraint" + } + }, + "groups": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IntendedGroup" + } } }, "required": [ - "from", - "to" + "constraints", + "groups", + "id" + ] + }, + "GetGraphMetadataResponse": { + "type": "object", + "properties": { + "graphs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GraphMetadata" + } + } + }, + "required": [ + "graphs" ] }, - "J": { + "GraphMetadata": { "type": "object", "properties": { "ecosystem": { @@ -13874,31 +29190,59 @@ "type" ] }, - "k": { + "ExternalInterface": { + "type": "object", + "description": "An external interface of a component. Today this is an analyzer-found boundary; the abstraction will later also cover SDK-declared and default entry points.", + "properties": { + "direction": { + "type": "string", + "description": "Direction of the external interface (entry or exit point).", + "enum": [ + "EXIT_POINT", + "ENTRY_POINT" + ] + }, + "componentId": { + "type": "string", + "format": "uuid", + "description": "Id of the organization component this external interface belongs to." + }, + "key": { + "type": "string", + "description": "Key identifying the external interface." + } + }, + "required": [ + "componentId", + "direction", + "key" + ] + }, + "ExternalInterfaceListResponse": { "type": "object", "properties": { - "graphs": { + "externalInterfaces": { "type": "array", "items": { - "$ref": "#/components/schemas/J" + "$ref": "#/components/schemas/ExternalInterface" } } }, "required": [ - "graphs" + "externalInterfaces" ] }, - "Zq": { + "DirectivePagedResponse": { "type": "object", "properties": { "directives": { "type": "array", "items": { - "$ref": "#/components/schemas/y" + "$ref": "#/components/schemas/Directive" } }, "page": { - "$ref": "#/components/schemas/O" + "$ref": "#/components/schemas/PaginatedResponse" } }, "required": [ @@ -13906,6 +29250,24 @@ "page" ] }, + "BoundaryDescriptorPagedResponse": { + "type": "object", + "properties": { + "boundaryDescriptors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BoundaryDescriptor" + } + }, + "page": { + "$ref": "#/components/schemas/PaginatedResponse" + } + }, + "required": [ + "boundaryDescriptors", + "page" + ] + }, "JreInfoRestResponse": { "type": "object", "properties": { @@ -14024,6 +29386,340 @@ "type": "string" } } + }, + "AgenticJobRestResponse": { + "type": "object", + "properties": { + "id": { + "type": "string", + "readOnly": true + }, + "projectId": { + "type": "string", + "readOnly": true + }, + "projectKey": { + "type": [ + "string", + "null" + ], + "description": "Not set if the project has been deleted.", + "readOnly": true + }, + "projectName": { + "type": [ + "string", + "null" + ], + "description": "Not set if the project has been deleted.", + "readOnly": true + }, + "type": { + "type": "string", + "enum": [ + "HUNTER", + "REMEDIATION" + ], + "readOnly": true + }, + "analysisType": { + "type": "string", + "enum": [ + "FULL", + "INCREMENTAL" + ], + "readOnly": true + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "IN_PROGRESS", + "COMPLETED", + "FAILED" + ], + "readOnly": true + }, + "branch": { + "type": [ + "string", + "null" + ], + "readOnly": true + }, + "repositoryUrl": { + "type": "string", + "readOnly": true + }, + "revision": { + "type": [ + "string", + "null" + ], + "readOnly": true + }, + "workflowType": { + "type": [ + "string", + "null" + ], + "readOnly": true + }, + "findingsCount": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "readOnly": true + }, + "createdAt": { + "type": "integer", + "format": "int64", + "readOnly": true + }, + "updatedAt": { + "type": "integer", + "format": "int64", + "readOnly": true + }, + "startedAt": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "readOnly": true + }, + "finishedAt": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "readOnly": true + }, + "failureReason": { + "type": [ + "string", + "null" + ], + "deprecated": true, + "description": "Deprecated. Use errorKey instead.", + "readOnly": true + }, + "errorKey": { + "type": [ + "string", + "null" + ], + "description": "Stable customer-safe failure identifier. Only set when status is FAILED.", + "readOnly": true + } + } + }, + "AgenticJobsSearchRestResponse": { + "type": "object", + "properties": { + "jobs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgenticJobRestResponse" + } + }, + "page": { + "$ref": "#/components/schemas/PageRestResponse" + } + } + }, + "Contexts": { + "type": "object", + "properties": { + "contexts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizedContext" + } + }, + "page": { + "$ref": "#/components/schemas/PageRestResponse" + } + }, + "required": [ + "contexts", + "page" + ] + }, + "SummarizedContext": { + "type": "object", + "description": "Summarized structure of a Context", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of a context", + "example": "550e8400-e29b-11d4-a716-446655440000" + }, + "analysisId": { + "type": "string", + "description": "The identifier of an analysis on SonarQube (a UUID on SonarQube Cloud, possibly a legacy identifier on SonarQube Server)", + "example": "550e8400-e29b-11d4-a716-446655440000" + }, + "kind": { + "type": "string", + "description": "Context kind. Must be unique for a particular analysis", + "example": [ + "java", + "python", + "security", + "jar-libs" + ], + "maxLength": 40, + "minLength": 0, + "pattern": "^\\S+$" + } + }, + "required": [ + "analysisId", + "id", + "kind" + ] + }, + "Context": { + "type": "object", + "description": "Base structure of a Context", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of a context" + }, + "analysisId": { + "type": "string", + "description": "The identifier of an analysis on SonarQube (a UUID on SonarQube Cloud, possibly a legacy identifier on SonarQube Server)", + "example": "550e8400-e29b-11d4-a716-446655440000" + }, + "kind": { + "type": "string", + "description": "Context kind. Must be unique for a particular analysis", + "example": [ + "java", + "python", + "security", + "jar-libs" + ], + "maxLength": 40, + "minLength": 0, + "pattern": "^\\S+$" + }, + "metadata": { + "type": "string", + "description": "Any JSON-formatted object" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ContextItem" + } + } + }, + "required": [ + "analysisId", + "id", + "items", + "kind", + "metadata" + ] + }, + "CollectionConfig": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether collection is enabled for this project" + } + }, + "required": [ + "enabled" + ] + }, + "OrgEntitlement": { + "type": "object", + "description": "A3S entitlement for an organization", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "An organization id", + "example": "57f08a8b-4a6e-4c64-bf72-83a892472f22" + }, + "allowed": { + "type": "boolean", + "description": "Whether the organization is currently allowed to use A3S" + }, + "hasEntitlement": { + "type": "boolean", + "description": "Whether the organization has any valid access path to A3S. When allowed is false, true means the organization is still entitled but cannot use it right now, typically because it has reached its current limit; false means the organization is not entitled at all." + } + }, + "required": [ + "allowed", + "hasEntitlement", + "id" + ] + }, + "AnalysisStats": { + "type": "object", + "description": "Analysis statistics for an organization", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The organization ID these statistics are about", + "example": "c713929f-d5ee-4559-bae1-a93ac20cbbb6" + }, + "projects": { + "type": "array", + "description": "All allowed projects with their count of analysis over the select period, ordered by analysis count descending.", + "items": { + "$ref": "#/components/schemas/ProjectAnalysisCount" + } + } + }, + "required": [ + "id", + "projects" + ] + }, + "ProjectAnalysisCount": { + "type": "object", + "description": "Analysis count for a project", + "properties": { + "id": { + "type": "string", + "description": "The project ID", + "example": "7a2f5c8d-9e3b-4a1c-bf72-83a892472f22", + "maxLength": 40, + "minLength": 0, + "pattern": "^\\S+$" + }, + "name": { + "type": "string", + "description": "The project name", + "example": "My Project" + }, + "analysisCount": { + "type": "integer", + "format": "int64", + "description": "Number of analyses performed for the project over the requested period", + "example": 100 + } + }, + "required": [ + "analysisCount", + "id", + "name" + ] } } } diff --git a/assets/api.json b/assets/api.json index 6848185..83bf399 100644 --- a/assets/api.json +++ b/assets/api.json @@ -5140,15 +5140,15 @@ "required": false, "internal": false, "possibleValues": [ - "HOTSPOTS", "CREATION_DATE", - "SEVERITY", - "STATUS", - "FILE_LINE", - "QUALITY_SEVERITY", - "CLOSE_DATE", + "HOTSPOTS", + "TYPE_SEVERITY", "UPDATE_DATE", - "TYPE_SEVERITY" + "CLOSE_DATE", + "QUALITY_SEVERITY", + "FILE_LINE", + "STATUS", + "SEVERITY" ] }, { @@ -5982,7 +5982,7 @@ }, { "key": "metricKeys", - "description": "Comma-separated list of metric keys. Types DISTRIB are not allowed. For type DATA only maintainability_issues, security_issues, new_reliability_issues, new_security_issues, new_maintainability_issues, reliability_issues metrics are supported", + "description": "Comma-separated list of metric keys. Types DISTRIB are not allowed. For type DATA only security_issues, maintainability_issues, reliability_issues, new_maintainability_issues, new_security_issues, new_reliability_issues metrics are supported", "required": true, "internal": false, "exampleValue": "ncloc,complexity,violations", @@ -6083,9 +6083,9 @@ "internal": false, "defaultValue": "all", "possibleValues": [ - "leaves", + "all", "children", - "all" + "leaves" ] } ] @@ -9257,7 +9257,7 @@ }, { "key": "metric", - "description": "Condition metric.\u003cbr/\u003e Only metric of the following types are allowed:\u003cul\u003e\u003cli\u003eINT\u003c/li\u003e\u003cli\u003eMILLISEC\u003c/li\u003e\u003cli\u003eRATING\u003c/li\u003e\u003cli\u003eWORK_DUR\u003c/li\u003e\u003cli\u003eFLOAT\u003c/li\u003e\u003cli\u003ePERCENT\u003c/li\u003e\u003cli\u003eLEVEL\u003c/li\u003e\u003c/ul\u003eFollowing metrics are forbidden:\u003cul\u003e\u003cli\u003enew_security_hotspots\u003c/li\u003e\u003cli\u003esecurity_hotspots\u003c/li\u003e\u003cli\u003ealert_status\u003c/li\u003e\u003c/ul\u003e", + "description": "Condition metric.\u003cbr/\u003e Only metric of the following types are allowed:\u003cul\u003e\u003cli\u003eINT\u003c/li\u003e\u003cli\u003eMILLISEC\u003c/li\u003e\u003cli\u003eRATING\u003c/li\u003e\u003cli\u003eWORK_DUR\u003c/li\u003e\u003cli\u003eFLOAT\u003c/li\u003e\u003cli\u003ePERCENT\u003c/li\u003e\u003cli\u003eLEVEL\u003c/li\u003e\u003c/ul\u003eFollowing metrics are forbidden:\u003cul\u003e\u003cli\u003ealert_status\u003c/li\u003e\u003cli\u003esecurity_hotspots\u003c/li\u003e\u003cli\u003enew_security_hotspots\u003c/li\u003e\u003c/ul\u003e", "required": true, "internal": false, "exampleValue": "blocker_violations, vulnerabilities, new_code_smells" @@ -10028,7 +10028,7 @@ }, { "key": "metric", - "description": "Condition metric.\u003cbr/\u003e Only metric of the following types are allowed:\u003cul\u003e\u003cli\u003eINT\u003c/li\u003e\u003cli\u003eMILLISEC\u003c/li\u003e\u003cli\u003eRATING\u003c/li\u003e\u003cli\u003eWORK_DUR\u003c/li\u003e\u003cli\u003eFLOAT\u003c/li\u003e\u003cli\u003ePERCENT\u003c/li\u003e\u003cli\u003eLEVEL\u003c/li\u003e\u003c/ul\u003eFollowing metrics are forbidden:\u003cul\u003e\u003cli\u003enew_security_hotspots\u003c/li\u003e\u003cli\u003esecurity_hotspots\u003c/li\u003e\u003cli\u003ealert_status\u003c/li\u003e\u003c/ul\u003e", + "description": "Condition metric.\u003cbr/\u003e Only metric of the following types are allowed:\u003cul\u003e\u003cli\u003eINT\u003c/li\u003e\u003cli\u003eMILLISEC\u003c/li\u003e\u003cli\u003eRATING\u003c/li\u003e\u003cli\u003eWORK_DUR\u003c/li\u003e\u003cli\u003eFLOAT\u003c/li\u003e\u003cli\u003ePERCENT\u003c/li\u003e\u003cli\u003eLEVEL\u003c/li\u003e\u003c/ul\u003eFollowing metrics are forbidden:\u003cul\u003e\u003cli\u003ealert_status\u003c/li\u003e\u003cli\u003esecurity_hotspots\u003c/li\u003e\u003cli\u003enew_security_hotspots\u003c/li\u003e\u003c/ul\u003e", "required": true, "internal": false, "exampleValue": "blocker_violations, vulnerabilities, new_code_smells" @@ -10426,12 +10426,12 @@ "description": "Sort field", "required": false, "internal": false, - "exampleValue": "key", + "exampleValue": "name", "possibleValues": [ - "key", - "updatedAt", + "name", "createdAt", - "name" + "updatedAt", + "key" ] }, { @@ -11408,12 +11408,12 @@ "description": "Sort field", "required": false, "internal": false, - "exampleValue": "key", + "exampleValue": "name", "possibleValues": [ - "key", - "updatedAt", + "name", "createdAt", - "name" + "updatedAt", + "key" ] }, { @@ -13020,7 +13020,7 @@ "description": "Comma-separated list of additional fields to be returned in the response. All the fields are returned by default, except actives.", "required": false, "internal": false, - "exampleValue": "updatedAt,deprecatedKeys", + "exampleValue": "templateKey,sysTags", "possibleValues": [ "actives", "cleanCodeAttribute", @@ -13278,12 +13278,12 @@ "description": "Sort field", "required": false, "internal": false, - "exampleValue": "key", + "exampleValue": "name", "possibleValues": [ - "key", - "updatedAt", + "name", "createdAt", - "name" + "updatedAt", + "key" ] }, { diff --git a/go.mod b/go.mod index 5656e59..a2b2c0e 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.26.7 require ( github.com/google/go-querystring v1.2.0 github.com/onsi/ginkgo/v2 v2.33.0 - github.com/onsi/gomega v1.43.1 + github.com/onsi/gomega v1.44.0 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.12.1 diff --git a/go.sum b/go.sum index 2604fa5..df56ed6 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3v github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/onsi/ginkgo/v2 v2.33.0 h1:C8gBA6Uc2ZEubiV+SXiu5tZnMTwEmXHgkJwGozKtZf8= github.com/onsi/ginkgo/v2 v2.33.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= -github.com/onsi/gomega v1.43.1 h1:vGIPFuYrIO6/0Z09s0I0QQQgFchiX4+tb1re3MScJYo= -github.com/onsi/gomega v1.43.1/go.mod h1:e/C2HwaZ1DhvjzXXuFhcR7hY7Sh9pl7MmoWKEjzwcdA= +github.com/onsi/gomega v1.44.0 h1:eAiGl3Pw5jz5GQdDff0BcxYpAX1JxW8xD7mFUuwNfZQ= +github.com/onsi/gomega v1.44.0/go.mod h1:e/C2HwaZ1DhvjzXXuFhcR7hY7Sh9pl7MmoWKEjzwcdA= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/integration_testing/architecture_v2_test.go b/integration_testing/architecture_v2_test.go index bee826e..be95be2 100644 --- a/integration_testing/architecture_v2_test.go +++ b/integration_testing/architecture_v2_test.go @@ -339,18 +339,6 @@ var _ = Describe("Architecture V2 Service", Ordered, func() { Expect(result).NotTo(BeNil()) } }) - - It("should return private project configurations or an expected error", func() { - result, resp, err := client.V2.Architecture.GetPrivateProjectConfigurations(context.Background(), &sonar.ArchitectureProjectConfigurationsOptions{ - ProjectKey: "nonexistent-project-arch-v2", - }) - if err != nil { - Expect(resp).NotTo(BeNil()) - } else { - Expect(resp.StatusCode).To(BeNumerically("<", 400)) - Expect(result).NotTo(BeNil()) - } - }) }) }) diff --git a/integration_testing/enterprise/architecture_organization_v2_test.go b/integration_testing/enterprise/architecture_organization_v2_test.go new file mode 100644 index 0000000..d4f79c4 --- /dev/null +++ b/integration_testing/enterprise/architecture_organization_v2_test.go @@ -0,0 +1,300 @@ +package enterprise_test + +import ( + "context" + "net/http" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/boxboxjason/sonarqube-client-go/v2/integration_testing/helpers" + "github.com/boxboxjason/sonarqube-client-go/v2/sonar" +) + +// defaultOrganizationID is the identifier SonarQube Server uses for its single, +// implicit organization in V2 endpoints that take an organizationId. +const defaultOrganizationID = "00000000-0000-4000-0000-000000000000" + +// architectureEnterpriseSetting enables the organization-level architecture +// features (placeholders, patterns, SDKs, boundary descriptors, ...). Without +// it every organization architecture endpoint answers 403. +const architectureEnterpriseSetting = "sonar.architecture.enterprise.enabled" + +// projectUUID returns the project UUID (not the main branch UUID) of a project, +// which is what V2 endpoints expect as "projectId". +func projectUUID(client *sonar.Client, projectKey string) string { + GinkgoHelper() + + result, _, err := client.Projects.Search(context.Background(), &sonar.ProjectsSearchOptions{Projects: []string{projectKey}}) + Expect(err).NotTo(HaveOccurred()) + Expect(result.Components).To(HaveLen(1)) + + return result.Components[0].ProjectUuid +} + +var _ = Describe("Architecture V2 organization features", Ordered, func() { + var ( + client *sonar.Client + cleanup *helpers.CleanupManager + projectKey string + projectID string + ) + + BeforeAll(func() { + var err error + client, err = helpers.NewDefaultClient() + Expect(err).NotTo(HaveOccurred()) + cleanup = helpers.NewCleanupManager(client) + + _, err = client.Settings.Set(context.Background(), &sonar.SettingsSetOptions{Key: architectureEnterpriseSetting, Value: "true"}) + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(func() { + _, _ = client.Settings.Reset(context.Background(), &sonar.SettingsResetOptions{Keys: []string{architectureEnterpriseSetting}}) + }) + + projectKey = helpers.UniqueResourceName("arch-org") + _, _, err = client.Projects.Create(context.Background(), &sonar.ProjectsCreateOptions{Name: projectKey, Project: projectKey}) + Expect(err).NotTo(HaveOccurred()) + cleanup.RegisterCleanup("project", projectKey, func() error { + _, err := client.Projects.Delete(context.Background(), &sonar.ProjectsDeleteOptions{Project: projectKey}) + return err + }) + projectID = projectUUID(client, projectKey) + }) + + AfterAll(func() { + for _, err := range cleanup.Cleanup() { + GinkgoWriter.Printf("Cleanup error: %v\n", err) + } + }) + + Describe("Boundary descriptors", Ordered, func() { + var descriptorID string + + It("should create a boundary descriptor", func() { + result, resp, err := client.V2.Architecture.CreateBoundaryDescriptor(context.Background(), &sonar.ArchitectureCreateBoundaryDescriptorOptions{ + ProjectId: projectID, + Name: "payments-client", + Direction: "EXIT_POINT", + Ecosystem: "java", + KeyTemplate: "payments-{x}", + Query: "call:com.example.Payments#charge", + }) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusCreated)) + Expect(result.Id).NotTo(BeEmpty()) + Expect(result.ProjectId).To(Equal(projectID)) + descriptorID = result.Id + }) + + It("should get and list the boundary descriptor", func() { + result, _, err := client.V2.Architecture.GetBoundaryDescriptor(context.Background(), descriptorID) + Expect(err).NotTo(HaveOccurred()) + Expect(result.Name).To(Equal("payments-client")) + + list, _, err := client.V2.Architecture.ListBoundaryDescriptors(context.Background(), &sonar.ArchitectureListBoundaryDescriptorsOptions{ProjectId: projectID}) + Expect(err).NotTo(HaveOccurred()) + Expect(list.BoundaryDescriptors).To(HaveLen(1)) + Expect(list.Page.Total).To(BeEquivalentTo(1)) + }) + + It("should update the boundary descriptor", func() { + result, resp, err := client.V2.Architecture.UpdateBoundaryDescriptor(context.Background(), descriptorID, &sonar.ArchitectureUpdateBoundaryDescriptorOptions{ + Name: new("payments-client-v2"), + Direction: new("ENTRY_POINT"), + Ecosystem: new("java"), + KeyTemplate: new("payments-{x}"), + Query: new("call:com.example.Payments#refund"), + }) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(result.Name).To(Equal("payments-client-v2")) + Expect(result.Direction).To(Equal("ENTRY_POINT")) + }) + + It("should delete the boundary descriptor", func() { + resp, err := client.V2.Architecture.DeleteBoundaryDescriptor(context.Background(), descriptorID) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) + }) + }) + + Describe("Placeholders, components, SDKs and external interfaces", Ordered, func() { + var placeholderID, sdkID string + + AfterAll(func() { + if sdkID != "" { + _, _ = client.V2.Architecture.DeleteSdk(context.Background(), sdkID) + } + if placeholderID != "" { + _, _ = client.V2.Architecture.DeleteOrganizationPlaceholder(context.Background(), placeholderID) + } + }) + + It("should create a placeholder", func() { + result, resp, err := client.V2.Architecture.CreateOrganizationPlaceholder(context.Background(), &sonar.ArchitectureCreateOrganizationPlaceholderOptions{ + OrganizationId: defaultOrganizationID, + DisplayName: "Payments DB", + Type: "DB", + }) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusCreated)) + Expect(result.Type).To(Equal("DB")) + Expect(result.OrganizationId).To(Equal(defaultOrganizationID)) + placeholderID = result.Id + }) + + It("should get, list and update the placeholder", func() { + result, _, err := client.V2.Architecture.GetOrganizationPlaceholder(context.Background(), placeholderID) + Expect(err).NotTo(HaveOccurred()) + Expect(result.DisplayName).To(Equal("Payments DB")) + + list, _, err := client.V2.Architecture.ListOrganizationPlaceholders(context.Background(), &sonar.ArchitectureListOrganizationPlaceholdersOptions{OrganizationId: defaultOrganizationID}) + Expect(err).NotTo(HaveOccurred()) + Expect(list.Placeholders).NotTo(BeEmpty()) + + updated, _, err := client.V2.Architecture.UpdateOrganizationPlaceholder(context.Background(), placeholderID, &sonar.ArchitectureUpdateOrganizationPlaceholderOptions{ + DisplayName: new("Payments queue"), + Type: new("QUEUE"), + }) + Expect(err).NotTo(HaveOccurred()) + Expect(updated.DisplayName).To(Equal("Payments queue")) + Expect(updated.Type).To(Equal("QUEUE")) + }) + + It("should list the organization components, including the project and the placeholder", func() { + list, resp, err := client.V2.Architecture.ListOrganizationComponents(context.Background(), &sonar.ArchitectureListOrganizationComponentsOptions{OrganizationId: defaultOrganizationID}) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + + var projectComponentID string + ids := make([]string, 0, len(list.Components)) + for _, component := range list.Components { + ids = append(ids, component.Id) + if component.ProjectId == projectID { + Expect(component.Type).To(Equal("PROJECT")) + projectComponentID = component.Id + } + } + Expect(ids).To(ContainElement(placeholderID)) + Expect(projectComponentID).NotTo(BeEmpty()) + + component, _, err := client.V2.Architecture.GetOrganizationComponent(context.Background(), projectComponentID) + Expect(err).NotTo(HaveOccurred()) + Expect(component.ProjectId).To(Equal(projectID)) + }) + + It("should create, get, list and update an SDK", func() { + created, resp, err := client.V2.Architecture.CreateSdk(context.Background(), &sonar.ArchitectureCreateSdkOptions{ + OrganizationId: defaultOrganizationID, + Name: "payments-sdk", + SdkKey: helpers.UniqueResourceName("payments-sdk"), + TargetComponentId: placeholderID, + SdkBoundaryDescriptors: []sonar.ArchitectureSdkBoundaryDescriptor{ + {Ecosystem: "java", Query: "call:com.example.Payments#charge"}, + }, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusCreated)) + Expect(created.SdkBoundaryDescriptors).To(HaveLen(1)) + sdkID = created.Id + + got, _, err := client.V2.Architecture.GetSdk(context.Background(), sdkID) + Expect(err).NotTo(HaveOccurred()) + Expect(got.Name).To(Equal("payments-sdk")) + + list, _, err := client.V2.Architecture.ListSdks(context.Background(), &sonar.ArchitectureListSdksOptions{OrganizationId: defaultOrganizationID}) + Expect(err).NotTo(HaveOccurred()) + Expect(list.Sdks).NotTo(BeEmpty()) + + updated, _, err := client.V2.Architecture.UpdateSdk(context.Background(), sdkID, &sonar.ArchitectureUpdateSdkOptions{Name: new("payments-sdk-v2")}) + Expect(err).NotTo(HaveOccurred()) + Expect(updated.Name).To(Equal("payments-sdk-v2")) + }) + + It("should list external interfaces and the organization architecture", func() { + interfaces, resp, err := client.V2.Architecture.ListExternalInterfaces(context.Background(), &sonar.ArchitectureListExternalInterfacesOptions{OrganizationId: defaultOrganizationID}) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(interfaces).NotTo(BeNil()) + + architecture, resp, err := client.V2.Architecture.GetOrganizationArchitecture(context.Background(), &sonar.ArchitectureGetOrganizationArchitectureOptions{OrganizationId: defaultOrganizationID}) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(architecture.OrganizationArchitectures).NotTo(BeEmpty()) + }) + + It("should delete the SDK and the placeholder", func() { + resp, err := client.V2.Architecture.DeleteSdk(context.Background(), sdkID) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) + sdkID = "" + + resp, err = client.V2.Architecture.DeleteOrganizationPlaceholder(context.Background(), placeholderID) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) + placeholderID = "" + }) + }) + + Describe("Patterns", Ordered, func() { + var patternID string + + It("should create, get, list, update and delete a pattern", func() { + created, resp, err := client.V2.Architecture.CreatePattern(context.Background(), &sonar.ArchitectureCreatePatternOptions{ + OrganizationId: defaultOrganizationID, + Name: "layered", + Pattern: sonar.ArchitecturePatternData{ + Label: "Layered", + Description: "API depends on core", + Groups: []sonar.ArchitectureModelGroup{ + {Label: "api", Patterns: []string{"com.example.api.**"}}, + {Label: "core", Patterns: []string{"com.example.core.**"}}, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusCreated)) + Expect(created.Pattern.Groups).To(HaveLen(2)) + patternID = created.Id + + got, _, err := client.V2.Architecture.GetPattern(context.Background(), patternID) + Expect(err).NotTo(HaveOccurred()) + Expect(got.Name).To(Equal("layered")) + + list, _, err := client.V2.Architecture.ListPatterns(context.Background(), &sonar.ArchitectureListPatternsOptions{OrganizationId: defaultOrganizationID}) + Expect(err).NotTo(HaveOccurred()) + Expect(list).NotTo(BeEmpty()) + + updated, _, err := client.V2.Architecture.UpdatePattern(context.Background(), patternID, &sonar.ArchitectureUpdatePatternOptions{Name: new("layered-v2")}) + Expect(err).NotTo(HaveOccurred()) + Expect(updated.Name).To(Equal("layered-v2")) + + resp, err = client.V2.Architecture.DeletePattern(context.Background(), patternID) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) + }) + }) + + Describe("Project relationships", func() { + It("should list the (empty) relationships of a project", func() { + list, resp, err := client.V2.Architecture.ListProjectRelationships(context.Background(), &sonar.ArchitectureListProjectRelationshipsOptions{ProjectId: projectID}) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(list.ProjectRelationships).To(BeEmpty()) + }) + + // A relationship can only be created from an exit boundary found by an + // analysis; this suite has no scanner, so the server rejects it with 400. + It("should reject a relationship without a matching analysed exit boundary", func() { + _, resp, err := client.V2.Architecture.CreateProjectRelationship(context.Background(), &sonar.ArchitectureCreateProjectRelationshipOptions{ + ProjectId: projectID, + BoundaryKey: "payments-1", + TargetComponentId: projectID, + TargetEntryPointKey: "default", + }) + Expect(err).To(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusBadRequest)) + }) + }) +}) diff --git a/integration_testing/enterprise/hotspots_notifications_test.go b/integration_testing/enterprise/hotspots_notifications_test.go new file mode 100644 index 0000000..abe0670 --- /dev/null +++ b/integration_testing/enterprise/hotspots_notifications_test.go @@ -0,0 +1,138 @@ +package enterprise_test + +import ( + "context" + "net/http" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/boxboxjason/sonarqube-client-go/v2/integration_testing/helpers" + "github.com/boxboxjason/sonarqube-client-go/v2/sonar" +) + +var _ = Describe("Hotspots migration", Ordered, func() { + var ( + client *sonar.Client + cleanup *helpers.CleanupManager + projectKey string + ) + + BeforeAll(func() { + var err error + client, err = helpers.NewDefaultClient() + Expect(err).NotTo(HaveOccurred()) + cleanup = helpers.NewCleanupManager(client) + + projectKey = helpers.UniqueResourceName("hotspots-migration") + _, _, err = client.Projects.Create(context.Background(), &sonar.ProjectsCreateOptions{Name: projectKey, Project: projectKey}) + Expect(err).NotTo(HaveOccurred()) + cleanup.RegisterCleanup("project", projectKey, func() error { + _, err := client.Projects.Delete(context.Background(), &sonar.ProjectsDeleteOptions{Project: projectKey}) + return err + }) + }) + + AfterAll(func() { + for _, err := range cleanup.Cleanup() { + GinkgoWriter.Printf("Cleanup error: %v\n", err) + } + }) + + It("should report the migration status of the instance", func() { + result, resp, err := client.Hotspots.MigrationStatus(context.Background(), nil) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(result).NotTo(BeNil()) + Expect(result.RemainingHotspots).To(BeNumerically(">=", 0)) + }) + + It("should report a complete migration for a project without hotspots", func() { + result, resp, err := client.Hotspots.MigrationStatus(context.Background(), &sonar.HotspotsMigrationStatusOptions{Project: projectKey}) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(result.RemainingHotspots).To(BeZero()) + Expect(result.Complete).To(BeTrue()) + }) + + It("should dry-run a migration without writing anything", func() { + result, resp, err := client.Hotspots.MigrateToIssues(context.Background(), &sonar.HotspotsMigrateToIssuesOptions{ + Project: projectKey, + DryRun: true, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(result.DryRun).To(BeTrue()) + Expect(result.Skipped).To(BeZero()) + Expect(result.Projects).To(BeEmpty()) + }) +}) + +var _ = Describe("Notifications group subscriptions", Ordered, func() { + const notificationType = "security-alert-raised" + + var ( + client *sonar.Client + groupUUID string + groupName string + ) + + BeforeAll(func() { + var err error + client, err = helpers.NewDefaultClient() + Expect(err).NotTo(HaveOccurred()) + + groupName = helpers.UniqueResourceName("notif-group") + group, _, err := client.V2.Authorizations.CreateGroup(context.Background(), &sonar.AuthorizationsCreateGroupOptions{Name: groupName}) + Expect(err).NotTo(HaveOccurred()) + Expect(group).NotTo(BeNil()) + groupUUID = group.Id + DeferCleanup(func() { + _, _ = client.V2.Authorizations.DeleteGroup(context.Background(), groupUUID) + }) + }) + + It("should subscribe a group to a notification type", func() { + resp, err := client.Notifications.AddGroup(context.Background(), &sonar.NotificationsGroupOptions{GroupUuid: groupUUID, Type: notificationType}) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) + }) + + It("should list the group subscription", func() { + result, resp, err := client.Notifications.ListGroups(context.Background()) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(result.Subscriptions).To(ContainElement(sonar.NotificationsGroupSubscription{ + GroupUuid: groupUUID, + GroupName: groupName, + NotificationType: notificationType, + ChannelKey: "EmailNotificationChannel", + })) + }) + + It("should list the current user's group subscriptions", func() { + result, resp, err := client.Notifications.ListGroupSubscriptions(context.Background()) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(result).NotTo(BeNil()) + }) + + It("should list group-subscription notification types", func() { + result, resp, err := client.Notifications.List(context.Background(), &sonar.NotificationsListOptions{Filter: "groupSubscription"}) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(result.GlobalTypes).To(ContainElement(notificationType)) + }) + + It("should remove the group subscription", func() { + resp, err := client.Notifications.RemoveGroup(context.Background(), &sonar.NotificationsGroupOptions{GroupUuid: groupUUID, Type: notificationType}) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) + + result, _, err := client.Notifications.ListGroups(context.Background()) + Expect(err).NotTo(HaveOccurred()) + for _, sub := range result.Subscriptions { + Expect(sub.GroupUuid).NotTo(Equal(groupUUID)) + } + }) +}) diff --git a/integration_testing/enterprise/suite_test.go b/integration_testing/enterprise/suite_test.go index b8e1c4f..057fb1e 100644 --- a/integration_testing/enterprise/suite_test.go +++ b/integration_testing/enterprise/suite_test.go @@ -11,6 +11,7 @@ package enterprise_test import ( "fmt" + "strings" "testing" "time" @@ -36,6 +37,26 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) }) +// AfterSuite fails the run if any API response observed during the suite had +// a JSON field with no corresponding field on its destination Go struct, the +// same strict schema validation the main e2e suite applies. +var _ = AfterSuite(func() { + mismatches := helpers.SchemaMismatches() + if len(mismatches) == 0 { + return + } + + lines := make([]string, 0, len(mismatches)) + for _, mismatch := range mismatches { + lines = append(lines, mismatch.String()) + } + + Fail(fmt.Sprintf( + "strict schema validation found %d field(s) in API responses with no match in their Go struct:\n%s", + len(mismatches), strings.Join(lines, "\n"), + )) +}) + func TestEnterpriseIntegrationTesting(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "SonarQube SDK Enterprise Edition E2E Test Suite") diff --git a/integration_testing/enterprise/v2_2026_5_services_test.go b/integration_testing/enterprise/v2_2026_5_services_test.go new file mode 100644 index 0000000..9f11ee3 --- /dev/null +++ b/integration_testing/enterprise/v2_2026_5_services_test.go @@ -0,0 +1,395 @@ +package enterprise_test + +import ( + "context" + "crypto/rand" + "fmt" + "net/http" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/boxboxjason/sonarqube-client-go/v2/integration_testing/helpers" + "github.com/boxboxjason/sonarqube-client-go/v2/sonar" +) + +// newUUID returns a random RFC 4122 version 4 UUID. +func newUUID() string { + GinkgoHelper() + + b := make([]byte, 16) + _, err := rand.Read(b) + Expect(err).NotTo(HaveOccurred()) + b[6] = (b[6] & 0x0f) | 0x40 + b[8] = (b[8] & 0x3f) | 0x80 + + return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]) +} + +// Specs for the V2 services introduced with SonarQube 2026.5. They run against +// an unlicensed Enterprise Edition instance, so endpoints that need a license, +// a reachable Agent Orchestrator or system-to-system authentication are only +// exercised up to the error they return. +var _ = Describe("SonarQube 2026.5 V2 services", Ordered, func() { + var ( + client *sonar.Client + cleanup *helpers.CleanupManager + projectKey string + projectID string + branchID string + portfolioKey string + portfolioID string + from, to string + ) + + BeforeAll(func() { + var err error + client, err = helpers.NewDefaultClient() + Expect(err).NotTo(HaveOccurred()) + cleanup = helpers.NewCleanupManager(client) + + projectKey = helpers.UniqueResourceName("v2-2026-5") + _, _, err = client.Projects.Create(context.Background(), &sonar.ProjectsCreateOptions{Name: projectKey, Project: projectKey}) + Expect(err).NotTo(HaveOccurred()) + cleanup.RegisterCleanup("project", projectKey, func() error { + _, err := client.Projects.Delete(context.Background(), &sonar.ProjectsDeleteOptions{Project: projectKey}) + return err + }) + projectID = projectUUID(client, projectKey) + + branches, _, err := client.ProjectBranches.List(context.Background(), &sonar.ProjectBranchesListOptions{Project: projectKey}) + Expect(err).NotTo(HaveOccurred()) + Expect(branches.Branches).NotTo(BeEmpty()) + branchID = branches.Branches[0].BranchID + + portfolioKey = helpers.UniqueResourceName("v2-2026-5-pf") + _, err = client.Views.Create(context.Background(), &sonar.ViewsCreateOptions{Key: portfolioKey, Name: portfolioKey}) + Expect(err).NotTo(HaveOccurred()) + cleanup.RegisterCleanup("portfolio", portfolioKey, func() error { + _, err := client.Views.Delete(context.Background(), &sonar.ViewsDeleteOptions{Key: portfolioKey}) + return err + }) + portfolio, _, err := client.Navigation.Component(context.Background(), &sonar.NavigationComponentOptions{Component: portfolioKey}) + Expect(err).NotTo(HaveOccurred()) + portfolioID = portfolio.ID + + now := time.Now().UTC() + from = now.AddDate(0, 0, -7).Format(time.DateOnly) + to = now.Format(time.DateOnly) + }) + + AfterAll(func() { + for _, err := range cleanup.Cleanup() { + GinkgoWriter.Printf("Cleanup error: %v\n", err) + } + }) + + // ========================================================================= + // Dashboards + // ========================================================================= + Describe("Dashboards", Ordered, func() { + var dashboardID string + + It("should list built-in dashboards and get one", func() { + list, resp, err := client.V2.Dashboards.ListBuiltIn(context.Background(), &sonar.DashboardsListBuiltInOptions{ResourceType: "project"}) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(list.Dashboards).NotTo(BeEmpty()) + + builtIn, _, err := client.V2.Dashboards.GetBuiltIn(context.Background(), list.Dashboards[0].Key) + Expect(err).NotTo(HaveOccurred()) + Expect(builtIn.Key).To(Equal(list.Dashboards[0].Key)) + Expect(builtIn.Layout).NotTo(BeEmpty()) + }) + + It("should create, get, list, update and delete a custom dashboard", func() { + created, resp, err := client.V2.Dashboards.Create(context.Background(), &sonar.DashboardsCreateOptions{ + Name: "e2e dashboard", + Layout: `{"widgets":[]}`, + ResourceType: "project", + ResourceId: projectID, + Description: "created by e2e", + }) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusCreated)) + Expect(created.ResourceId).To(Equal(projectID)) + dashboardID = created.Id + + got, _, err := client.V2.Dashboards.Get(context.Background(), dashboardID) + Expect(err).NotTo(HaveOccurred()) + Expect(got.Name).To(Equal("e2e dashboard")) + + list, _, err := client.V2.Dashboards.List(context.Background(), &sonar.DashboardsListOptions{ResourceId: projectID, ResourceType: "project"}) + Expect(err).NotTo(HaveOccurred()) + Expect(list.Dashboards).To(HaveLen(1)) + + updated, _, err := client.V2.Dashboards.Update(context.Background(), dashboardID, &sonar.DashboardsUpdateOptions{Name: new("e2e dashboard v2")}) + Expect(err).NotTo(HaveOccurred()) + Expect(updated.Name).To(Equal("e2e dashboard v2")) + + resp, err = client.V2.Dashboards.Delete(context.Background(), dashboardID) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) + }) + }) + + // ========================================================================= + // Detection agent + // ========================================================================= + Describe("Detection agent", func() { + It("should return the instance configuration", func() { + result, resp, err := client.V2.DetectionAgent.GetInstanceConfiguration(context.Background()) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(result.MaxConcurrentJobs).To(BeNumerically(">", 0)) + }) + + It("should read, enable with a full schedule, and disable the project configuration", func() { + result, _, err := client.V2.DetectionAgent.GetProjectConfiguration(context.Background(), projectID) + Expect(err).NotTo(HaveOccurred()) + Expect(result.ProjectId).To(Equal(projectID)) + Expect(result.Scheduling.Enabled).To(BeFalse()) + + result, resp, err := client.V2.DetectionAgent.UpsertProjectConfiguration(context.Background(), projectID, &sonar.DetectionAgentUpsertProjectConfigurationOptions{ + Scheduling: &sonar.DetectionAgentSchedulingRequest{ + Enabled: new(true), + BranchId: branchID, + Time: &sonar.DetectionAgentScheduleTime{Hour: new(int32(0)), Timezone: "Europe/Paris"}, + Recurrence: &sonar.DetectionAgentScheduleRecurrence{Type: "WEEKLY", DayOfWeek: "MONDAY"}, + }, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(result.Scheduling.Enabled).To(BeTrue()) + Expect(result.Scheduling.BranchId).To(Equal(branchID)) + Expect(result.Scheduling.NextRunTime).NotTo(BeEmpty()) + Expect(result.Scheduling.Recurrence).To(Equal(&sonar.DetectionAgentScheduleRecurrence{Type: "WEEKLY", DayOfWeek: "MONDAY"})) + Expect(result.Scheduling.Time).NotTo(BeNil()) + Expect(*result.Scheduling.Time.Hour).To(BeEquivalentTo(0)) + Expect(result.Scheduling.Time.Timezone).To(Equal("Europe/Paris")) + + result, _, err = client.V2.DetectionAgent.UpsertProjectConfiguration(context.Background(), projectID, &sonar.DetectionAgentUpsertProjectConfigurationOptions{ + Scheduling: &sonar.DetectionAgentSchedulingRequest{Enabled: new(false)}, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(result.Scheduling.Enabled).To(BeFalse()) + }) + }) + + // ========================================================================= + // History + // ========================================================================= + Describe("History", func() { + It("should return issue density and resolution history for a branch", func() { + density, resp, err := client.V2.History.GetIssueDensityHistory(context.Background(), &sonar.HistoryGetIssueDensityHistoryOptions{ + EntityId: branchID, EntityType: "PROJECT_BRANCH", StartDate: from + "T00:00:00Z", SliceBy: "SEVERITY", + }) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(density).NotTo(BeNil()) + + resolution, _, err := client.V2.History.GetIssueResolutionHistory(context.Background(), &sonar.HistoryGetIssueResolutionHistoryOptions{ + EntityId: branchID, EntityType: "PROJECT_BRANCH", Statistic: "MTTR", StartDate: from + "T00:00:00Z", + }) + Expect(err).NotTo(HaveOccurred()) + Expect(resolution.Statistic).To(Equal("MTTR")) + + sca, _, err := client.V2.History.GetScaResolutionHistory(context.Background(), &sonar.HistoryGetScaResolutionHistoryOptions{ + EntityId: branchID, EntityType: "PROJECT_BRANCH", Statistic: "SCA_MTTR", StartDate: from + "T00:00:00Z", + }) + Expect(err).NotTo(HaveOccurred()) + Expect(sca.Statistic).To(Equal("SCA_MTTR")) + Expect(sca.ScaResolutionHistory).NotTo(BeEmpty()) + }) + + It("should return project issue and SCA resolution for a portfolio", func() { + issues, resp, err := client.V2.History.GetProjectIssueResolution(context.Background(), &sonar.HistoryGetProjectIssueResolutionOptions{ + Statistic: "RESOLVED_ISSUES", EntityType: "PORTFOLIO", EntityId: portfolioID, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(issues.Statistic).To(Equal("RESOLVED_ISSUES")) + + sca, _, err := client.V2.History.GetProjectScaResolution(context.Background(), &sonar.HistoryGetProjectScaResolutionOptions{ + Statistic: "SCA_MTTR", EntityType: "PORTFOLIO", EntityId: portfolioID, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(sca.Statistic).To(Equal("SCA_MTTR")) + }) + }) + + // ========================================================================= + // Software quality reports, onboarding, security alerts, agentic jobs + // ========================================================================= + It("should return a WCAG compliance report with nested categories", func() { + report, resp, err := client.V2.SoftwareQualityReports.GetComplianceReport(context.Background(), &sonar.SoftwareQualityReportsGetComplianceReportOptions{ + ComponentKey: projectKey, Standard: "wcag", Version: "2.1", Level: "AA", + }) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(report.Categories).NotTo(BeEmpty()) + Expect(report.Categories[0].Categories).NotTo(BeEmpty()) + }) + + It("should return the onboarding overview, projects and statistics", func() { + overview, resp, err := client.V2.Onboarding.GetOverview(context.Background(), nil) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(overview).NotTo(BeNil()) + + projects, _, err := client.V2.Onboarding.GetProjects(context.Background(), &sonar.OnboardingGetProjectsOptions{Q: projectKey}) + Expect(err).NotTo(HaveOccurred()) + Expect(projects.Projects).To(HaveLen(1)) + Expect(projects.Projects[0].Key).To(Equal(projectKey)) + Expect(projects.Projects[0].ScanStatus).To(Equal("NOT_SCANNED")) + + stats, _, err := client.V2.Onboarding.GetStatistics(context.Background(), nil) + Expect(err).NotTo(HaveOccurred()) + Expect(stats.DevopsPlatforms.Total).To(BeNumerically(">", 0)) + }) + + It("should search security alerts", func() { + result, resp, err := client.V2.SecurityAlerts.Search(context.Background(), &sonar.SecurityAlertsSearchOptions{ + AlertTypes: []string{"DEPENDENCY_RISK"}, Statuses: []string{"OPEN"}, Sort: "FIRST_DETECTED_AT", Direction: "ASC", + }) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(result).NotTo(BeNil()) + + _, resp, err = client.V2.SecurityAlerts.Get(context.Background(), "does-not-exist") + Expect(err).To(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) + }) + + It("should search agentic jobs", func() { + result, resp, err := client.V2.Agentic.SearchJobs(context.Background(), &sonar.AgenticSearchJobsOptions{Type: "HUNTER"}) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(result).NotTo(BeNil()) + }) + + It("should check DevOps Platform permissions", func() { + result, resp, err := client.V2.DopTranslation.CheckPermissions(context.Background(), &sonar.DopTranslationCheckPermissionsOptions{}) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(result).NotTo(BeNil()) + + // The project is not bound to any DevOps Platform, so no token can be minted. + _, resp, err = client.V2.DopTranslation.GenerateScmAccessToken(context.Background(), &sonar.DopTranslationGenerateScmAccessTokenOptions{Project: projectKey}) + Expect(err).To(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) + }) + + // ========================================================================= + // LLM connectivity + // ========================================================================= + It("should list LLM provider definitions, mappings and providers", func() { + definitions, resp, err := client.V2.LlmConnectivity.GetProviderDefinitions(context.Background()) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(definitions.ProviderDefinitions).NotTo(BeEmpty()) + Expect(definitions.ProviderDefinitions[0].Fields).NotTo(BeEmpty()) + + mappings, _, err := client.V2.LlmConnectivity.GetProviderMappings(context.Background(), &sonar.LlmConnectivityGetProviderMappingsOptions{AiCapability: "AI_CODEFIX"}) + Expect(err).NotTo(HaveOccurred()) + Expect(mappings.ProviderMappings).To(HaveLen(1)) + Expect(mappings.ProviderMappings[0].AiCapability).To(Equal("AI_CODEFIX")) + + providers, _, err := client.V2.LlmConnectivity.ListProviders(context.Background()) + Expect(err).NotTo(HaveOccurred()) + Expect(providers).NotTo(BeNil()) + + _, resp, err = client.V2.LlmConnectivity.GetProvider(context.Background(), "does-not-exist") + Expect(err).To(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) + }) + + // ========================================================================= + // CAG, billing and A3S + // ========================================================================= + It("should answer the CAG ping, entitlement and impact endpoints", func() { + ping, resp, err := client.V2.Cag.Ping(context.Background()) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(ping.Status).To(Equal("ok")) + + entitlement, _, err := client.V2.Cag.GetEntitlement(context.Background(), defaultOrganizationID) + Expect(err).NotTo(HaveOccurred()) + Expect(entitlement.FeatureKey).To(Equal("contextAugmentation")) + + usage, _, err := client.V2.Cag.GetUsageStats(context.Background(), defaultOrganizationID, &sonar.CagGetUsageStatsOptions{Period: "LAST_7_DAYS"}) + Expect(err).NotTo(HaveOccurred()) + Expect(usage.Id).To(Equal(defaultOrganizationID)) + + guide, _, err := client.V2.Cag.GetGuideMetrics(context.Background(), &sonar.CagGetGuideMetricsOptions{OrganizationId: defaultOrganizationID, From: from, To: to}) + Expect(err).NotTo(HaveOccurred()) + Expect(guide).NotTo(BeNil()) + + verify, _, err := client.V2.Cag.GetVerifyMetrics(context.Background(), &sonar.CagGetVerifyMetricsOptions{OrganizationId: defaultOrganizationID, From: from, To: to}) + Expect(err).NotTo(HaveOccurred()) + Expect(verify).NotTo(BeNil()) + + activity, _, err := client.V2.Cag.GetProjectActivity(context.Background(), &sonar.CagGetProjectActivityOptions{OrganizationId: defaultOrganizationID, From: from, To: to}) + Expect(err).NotTo(HaveOccurred()) + Expect(activity.TotalProjectCount).To(BeNumerically(">", 0)) + + impact, _, err := client.V2.Cag.GetProjectImpact(context.Background(), &sonar.CagGetProjectImpactOptions{OrganizationId: defaultOrganizationID, From: from, To: to}) + Expect(err).NotTo(HaveOccurred()) + Expect(impact.Projects).NotTo(BeEmpty()) + }) + + It("should record CAG usage and impact events", func() { + resp, err := client.V2.Cag.RecordUsageEvent(context.Background(), &sonar.CagRecordUsageEventOptions{ + OrganizationId: defaultOrganizationID, ProjectId: projectID, InvocationId: newUUID(), + }) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusAccepted)) + + resp, err = client.V2.Cag.RecordImpactEvent(context.Background(), &sonar.CagRecordImpactEventOptions{ + OrganizationId: defaultOrganizationID, + ProjectId: projectID, + InvocationId: newUUID(), + CagInstanceId: "e2e", + EventType: "NAVIGATION", + EventVersion: 1, + Success: true, + Transport: "CLI", + Payload: map[string]any{"toolName": "get_source_code", "resultBytes": 10, "resultCount": 1, "truncated": false}, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusAccepted)) + }) + + It("should check billing entitlements and record consumption", func() { + entitlement, resp, err := client.V2.Billing.GetEntitlementCheck(context.Background(), &sonar.BillingGetEntitlementCheckOptions{FeatureKey: "contextAugmentation"}) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(entitlement.FeatureKey).To(Equal("contextAugmentation")) + + record, resp, err := client.V2.Billing.CreateConsumptionRecord(context.Background(), newUUID(), &sonar.BillingCreateConsumptionRecordOptions{ + FeatureKey: "contextAugmentation", Consumed: new(int64(1)), + }) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusAccepted)) + Expect(record.Consumed).To(BeEquivalentTo(1)) + Expect(record.ResourceId).To(Equal(defaultOrganizationID)) + }) + + It("should answer the public A3S statistics, collection config and entitlement endpoints", func() { + stats, resp, err := client.V2.A3s.GetAnalysisStats(context.Background(), defaultOrganizationID, &sonar.A3sGetAnalysisStatsOptions{Period: "LAST_7_DAYS"}) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(stats.Projects).NotTo(BeEmpty()) + + config, _, err := client.V2.A3s.GetPublicCollectionConfig(context.Background(), &sonar.A3sGetPublicCollectionConfigOptions{ + OrganizationKey: "default-organization", ProjectKey: projectKey, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(config).NotTo(BeNil()) + + entitlement, _, err := client.V2.A3s.GetPublicOrgEntitlement(context.Background(), defaultOrganizationID) + Expect(err).NotTo(HaveOccurred()) + Expect(entitlement.Id).To(Equal(defaultOrganizationID)) + }) +}) diff --git a/integration_testing/sca_v2_test.go b/integration_testing/sca_v2_test.go index 331010d..a805cff 100644 --- a/integration_testing/sca_v2_test.go +++ b/integration_testing/sca_v2_test.go @@ -227,36 +227,4 @@ var _ = Describe("SCA V2 Service", Ordered, func() { }) }) }) - - Describe("ListReachabilityDefinitions", func() { - Context("Parameter Validation", func() { - It("should fail with nil options", func() { - result, resp, err := client.V2.Sca.ListReachabilityDefinitions(context.Background(), nil) - Expect(err).To(HaveOccurred()) - Expect(result).To(BeNil()) - Expect(resp).To(BeNil()) - }) - - It("should fail without a language key", func() { - result, resp, err := client.V2.Sca.ListReachabilityDefinitions(context.Background(), &sonar.ScaReachabilityDefinitionsOptions{}) - Expect(err).To(HaveOccurred()) - Expect(result).To(BeNil()) - Expect(resp).To(BeNil()) - }) - }) - - Context("Functional Tests", func() { - It("should return reachability definitions or an expected error", func() { - result, resp, err := client.V2.Sca.ListReachabilityDefinitions(context.Background(), &sonar.ScaReachabilityDefinitionsOptions{ - LanguageKey: "java", - }) - if err != nil { - Expect(resp).NotTo(BeNil()) - } else { - Expect(resp.StatusCode).To(BeNumerically("<", 400)) - Expect(result).NotTo(BeNil()) - } - }) - }) - }) }) diff --git a/sonar/a3s_v2_service.go b/sonar/a3s_v2_service.go new file mode 100644 index 0000000..38b8477 --- /dev/null +++ b/sonar/a3s_v2_service.go @@ -0,0 +1,899 @@ +package sonar + +import ( + "context" + "fmt" + "io" + "net/http" + "net/url" +) + +// A3sService handles communication with the Advanced Security Analysis (A3S) analyses, contexts and entitlements related methods of the +// SonarQube V2 API. This service is only available in Enterprise Edition. +type A3sService struct { + // client is used to communicate with the SonarQube API. + client *Client +} + +const ( + // A3sValueStandard is the "STANDARD" enumeration value. + A3sValueStandard = "STANDARD" +) + +//nolint:gochecknoglobals // constant sets of allowed values +var ( + // allowedA3sGetAnalysisStatsPeriod is the set of allowed values for the corresponding option. + allowedA3sGetAnalysisStatsPeriod = map[string]struct{}{ + "LAST_DAY": {}, + "LAST_7_DAYS": {}, + "LAST_30_DAYS": {}, + } + // allowedA3sCreatePublicAnalysisFileScope is the set of allowed values for the corresponding option. + allowedA3sCreatePublicAnalysisFileScope = map[string]struct{}{ + "MAIN": {}, + "TEST": {}, + } + // allowedA3sCreatePublicAnalysisAnalysisDepth is the set of allowed values for the corresponding option. + allowedA3sCreatePublicAnalysisAnalysisDepth = map[string]struct{}{ + A3sValueStandard: {}, + "DEEP": {}, + } + // allowedA3sCreatePrivateAnalysisFileScope is the set of allowed values for the corresponding option. + allowedA3sCreatePrivateAnalysisFileScope = map[string]struct{}{ + "MAIN": {}, + "TEST": {}, + } + // allowedA3sCreatePrivateAnalysisAnalysisDepth is the set of allowed values for the corresponding option. + allowedA3sCreatePrivateAnalysisAnalysisDepth = map[string]struct{}{ + A3sValueStandard: {}, + "DEEP": {}, + } +) + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// A3sGetAnalysisStatsOptions contains parameters for the GetAnalysisStats method. +type A3sGetAnalysisStatsOptions struct { + // Period for the statistics. This field is required. Allowed values: LAST_DAY, LAST_7_DAYS, + // LAST_30_DAYS. + Period string `json:"period"` +} + +// A3sProjectAnalysisCount represents a SonarQube V2 API object. +// +// Analysis count for a project. +type A3sProjectAnalysisCount struct { + // Id the project ID. + Id string `json:"id,omitempty"` + // Name the project name. + Name string `json:"name,omitempty"` + // AnalysisCount number of analyses performed for the project over the requested period. + AnalysisCount int64 `json:"analysisCount,omitempty"` +} + +// A3sAnalysisStats represents a SonarQube V2 API object. +// +// Analysis statistics for an organization. +type A3sAnalysisStats struct { + // Id the organization ID these statistics are about. + Id string `json:"id,omitempty"` + // Projects all allowed projects with their count of analysis over the select period, ordered + // by analysis count descending. + Projects []A3sProjectAnalysisCount `json:"projects,omitempty"` +} + +// A3sGetPublicCollectionConfigOptions contains parameters for the GetPublicCollectionConfig method. +type A3sGetPublicCollectionConfigOptions struct { + // OrganizationKey is the organization key. This field is required. + OrganizationKey string `json:"organizationKey"` + // ProjectKey is the project key. This field is required. + ProjectKey string `json:"projectKey"` +} + +// A3sCollectionConfig represents the CollectionConfig object of the SonarQube V2 API. +type A3sCollectionConfig struct { + // Enabled whether collection is enabled for this project. + Enabled bool `json:"enabled,omitempty"` +} + +// A3sOrgEntitlement represents a SonarQube V2 API object. +// +// A3S entitlement for an organization. +type A3sOrgEntitlement struct { + // Id an organization id. + Id string `json:"id,omitempty"` + // Allowed whether the organization is currently allowed to use A3S. + Allowed bool `json:"allowed,omitempty"` + // HasEntitlement whether the organization has any valid access path to A3S. When allowed is + // false, true means the organization is still entitled but cannot use it right now, typically + // because it has reached its current limit; false means the organization is not entitled at + // all. + HasEntitlement bool `json:"hasEntitlement,omitempty"` +} + +// A3sGetPrivateCollectionConfigOptions contains parameters for the GetPrivateCollectionConfig method. +type A3sGetPrivateCollectionConfigOptions struct { + // OrganizationKey is the organization key. This field is required. + OrganizationKey string `json:"organizationKey"` + // ProjectKey is the project key. This field is required. + ProjectKey string `json:"projectKey"` +} + +// A3sSearchPrivateContextsOptions contains parameters for the SearchPrivateContexts method. +type A3sSearchPrivateContextsOptions struct { + // BranchId the identifier of a branch on SonarQube (a UUID on SonarQube Cloud, possibly a + // legacy identifier on SonarQube Server). Must specify either branchId or branchName, but not + // both. + BranchId string `json:"branchId,omitempty"` + // BranchName the name of a branch. Must specify either branchId or branchName, but not both. + // When branchName is specified, projectId is required. + BranchName string `json:"branchName,omitempty"` + // ProjectId the identifier of a project (a UUID on SonarQube Cloud, possibly a legacy + // identifier on SonarQube Server). Required when branchName is specified to disambiguate + // branches with the same name across different projects. + ProjectId string `json:"projectId,omitempty"` +} + +// A3sSummarizedContext represents a SonarQube V2 API object. +// +// Summarized structure of a Context. +type A3sSummarizedContext struct { + // Id the unique identifier of a context. + Id string `json:"id,omitempty"` + // AnalysisId the identifier of an analysis on SonarQube (a UUID on SonarQube Cloud, possibly a + // legacy identifier on SonarQube Server). + AnalysisId string `json:"analysisId,omitempty"` + // Kind context kind. Must be unique for a particular analysis. + Kind string `json:"kind,omitempty"` +} + +// A3sContexts represents the Contexts object of the SonarQube V2 API. +type A3sContexts struct { + // Contexts is the contexts. + Contexts []A3sSummarizedContext `json:"contexts,omitempty"` + // Page is the page. + Page PageResponseV2 `json:"page,omitzero"` +} + +// A3sContextItem represents a SonarQube V2 API object. +// +// Item object stored in a context +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type A3sContextItem struct { + // Id the unique identifier of the item in its context. + Id string `json:"id,omitempty"` + // Type is the type. Allowed values: FILE. + Type string `json:"type,omitempty"` + // Sha256 A sha256 of the item content to detect changes. + Sha256 string `json:"sha256,omitempty"` + // Missing whether or the service has the context item already saved. + Missing bool `json:"missing,omitempty"` + // DownloadUrl the URL to download the item. + DownloadUrl string `json:"downloadUrl,omitempty"` +} + +// A3sContext represents a SonarQube V2 API object. +// +// Base structure of a Context. +type A3sContext struct { + // Id the unique identifier of a context. + Id string `json:"id,omitempty"` + // AnalysisId the identifier of an analysis on SonarQube (a UUID on SonarQube Cloud, possibly a + // legacy identifier on SonarQube Server). + AnalysisId string `json:"analysisId,omitempty"` + // Kind context kind. Must be unique for a particular analysis. + Kind string `json:"kind,omitempty"` + // Metadata any JSON-formatted object. + Metadata string `json:"metadata,omitempty"` + // Items is the items. + Items []A3sContextItem `json:"items,omitempty"` +} + +// A3sPublicAnalysisFile represents the PublicAnalysisFile object of the SonarQube V2 API. +type A3sPublicAnalysisFile struct { + // Path project-relative path of the file. + Path string `json:"path,omitempty"` + // Content the original content of the file. + Content string `json:"content,omitempty"` + // Scope is the scope. Allowed values: MAIN, TEST. + Scope string `json:"scope,omitempty"` +} + +// A3sCreatePublicAnalysisOptions contains the request body for the CreatePublicAnalysis method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type A3sCreatePublicAnalysisOptions struct { + // OrganizationId the unique identifier of the organization. + OrganizationId string `json:"organizationId,omitempty"` + // OrganizationKey the key of the organization. + OrganizationKey string `json:"organizationKey,omitempty"` + // ProjectId the unique identifier of the project. + ProjectId string `json:"projectId,omitempty"` + // ProjectKey the key of the project. + ProjectKey string `json:"projectKey,omitempty"` + // BranchId the unique identifier of the branch to retrieve the latest analysis. + BranchId string `json:"branchId,omitempty"` + // BranchName the branch to retrieve the latest analysis. + BranchName string `json:"branchName,omitempty"` + // FilePath project-relative path of the file. Deprecated — use 'files' instead. + FilePath string `json:"filePath,omitempty"` + // FileContent the original content of the file. Deprecated — use 'files' instead. + FileContent string `json:"fileContent,omitempty"` + // FileScope is the file scope. Allowed values: MAIN, TEST. + FileScope string `json:"fileScope,omitempty"` + // Files list of files to analyze. When provided, takes precedence over the top-level + // filePath/fileContent fields. No duplicate paths allowed. + Files []A3sPublicAnalysisFile `json:"files,omitempty"` + // AnalysisDepth is the analysis depth. Allowed values: STANDARD, DEEP. + AnalysisDepth string `json:"analysisDepth,omitempty"` +} + +// A3sTextRange represents a SonarQube V2 API object. +// +// Location of an issue in a file. +type A3sTextRange struct { + // StartLine is the start line. + StartLine int32 `json:"startLine,omitempty"` + // EndLine is the end line. + EndLine int32 `json:"endLine,omitempty"` + // StartOffset is the start offset. + StartOffset int32 `json:"startOffset,omitempty"` + // EndOffset is the end offset. + EndOffset int32 `json:"endOffset,omitempty"` +} + +// A3sHubIssueFlowsInnerLocationsInner represents the HubIssueFlowsInnerLocationsInner object of the +// SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type A3sHubIssueFlowsInnerLocationsInner struct { + // TextRange is the text range. + TextRange A3sTextRange `json:"textRange,omitzero"` + // Message is the message. + Message string `json:"message,omitempty"` + // FilePath is the file path. + FilePath string `json:"filePath,omitempty"` +} + +// A3sHubIssueFlowsInner represents the HubIssueFlowsInner object of the SonarQube V2 API. +type A3sHubIssueFlowsInner struct { + // Type the type of flow. Can be "UNDEFINED", "DATA", or "EXECUTION". Allowed values: + // UNDEFINED, DATA, EXECUTION. + Type string `json:"type,omitempty"` + // Description the description of the flow, if any. + Description string `json:"description,omitempty"` + // Locations is the locations. + Locations []A3sHubIssueFlowsInnerLocationsInner `json:"locations,omitempty"` +} + +// A3sHubIssue represents a SonarQube V2 API object. +// +// Structure of an Issue +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type A3sHubIssue struct { + // Id the unique identifier of the issue. + Id string `json:"id,omitempty"` + // FilePath project-relative path of the file containing the issue. + FilePath string `json:"filePath,omitempty"` + // Message primary message of the issue. + Message string `json:"message,omitempty"` + // Rule the rule key (e.g., java:S1854). + Rule string `json:"rule,omitempty"` + // TextRange is the text range. + TextRange A3sTextRange `json:"textRange,omitzero"` + // Flows secondary locations and flows for the issue. + Flows []A3sHubIssueFlowsInner `json:"flows,omitempty"` +} + +// A3sPatchResult represents a SonarQube V2 API object. +// +// Result of applying a patch to analyze changes. +type A3sPatchResult struct { + // NewIssues issues that appear only in the patched version. + NewIssues []A3sHubIssue `json:"newIssues,omitempty"` + // MatchedIssues issues that exist in both original and patched versions. + MatchedIssues []A3sHubIssue `json:"matchedIssues,omitempty"` + // ClosedIssues uuids of issues that existed in the original but are gone in the patch. + ClosedIssues []string `json:"closedIssues,omitempty"` +} + +// A3sAnalysisError represents a SonarQube V2 API object. +// +// Error that occurred during analysis. +type A3sAnalysisError struct { + // Code error code indicating the type of failure. Allowed values: SERVICE_CALL_ERROR, + // PARSE_ERROR, INVALID_CONTEXT. + Code string `json:"code,omitempty"` + // Message human-readable error message. + Message string `json:"message,omitempty"` +} + +// A3sAnalysis represents a SonarQube V2 API object. +// +// Structure of an Analysis. +type A3sAnalysis struct { + // Id the unique identifier of an analysis. + Id string `json:"id,omitempty"` + // Issues is the issues. + Issues []A3sHubIssue `json:"issues,omitempty"` + // PatchResult is the patch result. + PatchResult A3sPatchResult `json:"patchResult,omitzero"` + // Errors that occurred during analysis from language analysis services. + Errors []A3sAnalysisError `json:"errors,omitempty"` +} + +// A3sCreatePublicContextOptions contains the request body for the CreatePublicContext method. +type A3sCreatePublicContextOptions struct { + // AnalysisId the identifier of an analysis on SonarQube (a UUID on SonarQube Cloud, possibly a + // legacy identifier on SonarQube Server). This field is required. + AnalysisId string `json:"analysisId"` + // BranchName optional branch name. + BranchName string `json:"branchName,omitempty"` + // Kind context kind. Must be unique for a particular analysis. This field is required. + Kind string `json:"kind"` + // Metadata any JSON-formatted object. This field is required. + Metadata string `json:"metadata"` + // Items is the items. + Items []A3sContextItem `json:"items,omitempty"` +} + +// A3sContextWithUploadUrl represents the ContextWithUploadUrl object of the SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type A3sContextWithUploadUrl struct { + // Id the unique identifier of a context. + Id string `json:"id,omitempty"` + // AnalysisId the identifier of an analysis on SonarQube (a UUID on SonarQube Cloud, possibly a + // legacy identifier on SonarQube Server). + AnalysisId string `json:"analysisId,omitempty"` + // Kind context kind. Must be unique for a particular analysis. + Kind string `json:"kind,omitempty"` + // Metadata any JSON-formatted object. + Metadata string `json:"metadata,omitempty"` + // Items is the items. + Items []A3sContextItem `json:"items,omitempty"` + // UploadUrl an URL to upload missing context items. + UploadUrl string `json:"uploadUrl,omitempty"` +} + +// A3sCreatePrivateContextOptions contains the request body for the CreatePrivateContext method. +type A3sCreatePrivateContextOptions struct { + // AnalysisId the identifier of an analysis on SonarQube (a UUID on SonarQube Cloud, possibly a + // legacy identifier on SonarQube Server). This field is required. + AnalysisId string `json:"analysisId"` + // BranchName optional branch name. + BranchName string `json:"branchName,omitempty"` + // Kind context kind. Must be unique for a particular analysis. This field is required. + Kind string `json:"kind"` + // Metadata any JSON-formatted object. This field is required. + Metadata string `json:"metadata"` + // Items is the items. + Items []A3sContextItem `json:"items,omitempty"` +} + +// A3sPrivateAnalysisFile represents the PrivateAnalysisFile object of the SonarQube V2 API. +type A3sPrivateAnalysisFile struct { + // Path project-relative path of the file. + Path string `json:"path,omitempty"` + // Content the original content of the file. + Content string `json:"content,omitempty"` + // Scope is the scope. Allowed values: MAIN, TEST. + Scope string `json:"scope,omitempty"` + // PatchContent the patch content to apply (unified diff format). If provided for one file, + // must be provided for all files in the request. + PatchContent string `json:"patchContent,omitempty"` +} + +// A3sCreatePrivateAnalysisOptions contains the request body for the CreatePrivateAnalysis method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type A3sCreatePrivateAnalysisOptions struct { + // OrganizationId the unique identifier of the organization. This field is required. + OrganizationId string `json:"organizationId"` + // ProjectId the unique identifier of the project. + ProjectId string `json:"projectId,omitempty"` + // ProjectKey the key of the project (deprecated, use projectId instead). + ProjectKey string `json:"projectKey,omitempty"` + // BranchId the unique identifier of the branch to retrieve the latest analysis. + BranchId string `json:"branchId,omitempty"` + // BranchName the name of the branch to retrieve the latest analysis (used when branchId is not + // provided). + BranchName string `json:"branchName,omitempty"` + // FilePath project-relative path of the file. Deprecated — use 'files' instead. + FilePath string `json:"filePath,omitempty"` + // FileContent the original content of the file. Deprecated — use 'files' instead. + FileContent string `json:"fileContent,omitempty"` + // PatchContent the patch content to apply (unified diff format). Deprecated — use + // 'patchContent' inside 'files' instead. + PatchContent string `json:"patchContent,omitempty"` + // FileScope is the file scope. Allowed values: MAIN, TEST. + FileScope string `json:"fileScope,omitempty"` + // Files list of files to analyze. When provided, takes precedence over the top-level + // filePath/fileContent fields. No duplicate paths allowed. + Files []A3sPrivateAnalysisFile `json:"files,omitempty"` + // AnalysisDepth is the analysis depth. Allowed values: STANDARD, DEEP. + AnalysisDepth string `json:"analysisDepth,omitempty"` +} + +// ----------------------------------------------------------------------------- +// Validation Functions +// ----------------------------------------------------------------------------- + +// ValidateGetAnalysisStatsOpt validates the options for the GetAnalysisStats method. +func (s *A3sService) ValidateGetAnalysisStatsOpt(opt *A3sGetAnalysisStatsOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.Period, "Period") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Period, allowedA3sGetAnalysisStatsPeriod, "Period") + if err != nil { + return err + } + + return nil +} + +// ValidateGetPublicCollectionConfigOpt validates the options for the GetPublicCollectionConfig method. +func (s *A3sService) ValidateGetPublicCollectionConfigOpt(opt *A3sGetPublicCollectionConfigOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.OrganizationKey, "OrganizationKey") + if err != nil { + return err + } + + err = ValidateRequired(opt.ProjectKey, "ProjectKey") + if err != nil { + return err + } + + return nil +} + +// ValidateGetPrivateCollectionConfigOpt validates the options for the GetPrivateCollectionConfig method. +func (s *A3sService) ValidateGetPrivateCollectionConfigOpt(opt *A3sGetPrivateCollectionConfigOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.OrganizationKey, "OrganizationKey") + if err != nil { + return err + } + + err = ValidateRequired(opt.ProjectKey, "ProjectKey") + if err != nil { + return err + } + + return nil +} + +// ValidateCreatePublicAnalysisOpt validates the options for the CreatePublicAnalysis method. +func (s *A3sService) ValidateCreatePublicAnalysisOpt(opt *A3sCreatePublicAnalysisOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := IsValueAuthorized(opt.FileScope, allowedA3sCreatePublicAnalysisFileScope, "FileScope") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.AnalysisDepth, allowedA3sCreatePublicAnalysisAnalysisDepth, "AnalysisDepth") + if err != nil { + return err + } + + return nil +} + +// ValidateCreatePublicContextOpt validates the options for the CreatePublicContext method. +func (s *A3sService) ValidateCreatePublicContextOpt(opt *A3sCreatePublicContextOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.AnalysisId, "AnalysisId") + if err != nil { + return err + } + + err = ValidateRequired(opt.Kind, "Kind") + if err != nil { + return err + } + + err = ValidateRequired(opt.Metadata, "Metadata") + if err != nil { + return err + } + + return nil +} + +// ValidateCreatePrivateContextOpt validates the options for the CreatePrivateContext method. +func (s *A3sService) ValidateCreatePrivateContextOpt(opt *A3sCreatePrivateContextOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.AnalysisId, "AnalysisId") + if err != nil { + return err + } + + err = ValidateRequired(opt.Kind, "Kind") + if err != nil { + return err + } + + err = ValidateRequired(opt.Metadata, "Metadata") + if err != nil { + return err + } + + return nil +} + +// ValidateCreatePrivateAnalysisOpt validates the options for the CreatePrivateAnalysis method. +func (s *A3sService) ValidateCreatePrivateAnalysisOpt(opt *A3sCreatePrivateAnalysisOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.OrganizationId, "OrganizationId") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.FileScope, allowedA3sCreatePrivateAnalysisFileScope, "FileScope") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.AnalysisDepth, allowedA3sCreatePrivateAnalysisAnalysisDepth, "AnalysisDepth") + if err != nil { + return err + } + + return nil +} + +// ----------------------------------------------------------------------------- +// Service Methods +// ----------------------------------------------------------------------------- + +// GetAnalysisStats gets analysis statistics for an organization. +// +// API endpoint: GET /api/v2/a3s/analysis-stats/{id}. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *A3sService) GetAnalysisStats(ctx context.Context, organizationID string, opt *A3sGetAnalysisStatsOptions) (*A3sAnalysisStats, *http.Response, error) { + err := ValidateRequired(organizationID, "organizationID") + if err != nil { + return nil, nil, err + } + + err = s.ValidateGetAnalysisStatsOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "a3s/analysis-stats/"+url.PathEscape(organizationID), opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(A3sAnalysisStats) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetPublicCollectionConfig gets collection configuration for an organization/project. +// +// API endpoint: GET /api/v2/a3s/collection-config. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *A3sService) GetPublicCollectionConfig(ctx context.Context, opt *A3sGetPublicCollectionConfigOptions) (*A3sCollectionConfig, *http.Response, error) { + err := s.ValidateGetPublicCollectionConfigOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "a3s/collection-config", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(A3sCollectionConfig) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetPublicOrgEntitlement gets the A3S entitlement for an organization. +// +// API endpoint: GET /api/v2/a3s/org-entitlement/{id}. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *A3sService) GetPublicOrgEntitlement(ctx context.Context, organizationID string) (*A3sOrgEntitlement, *http.Response, error) { + err := ValidateRequired(organizationID, "organizationID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "a3s/org-entitlement/"+url.PathEscape(organizationID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(A3sOrgEntitlement) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetPrivateOrgEntitlement gets the A3S entitlement for an organization (internal). +// +// API endpoint: GET /api/v2/a3s/private/a3s-analysis/org-entitlement/{id}. +// Enterprise Edition only. +func (s *A3sService) GetPrivateOrgEntitlement(ctx context.Context, organizationID string) (*A3sOrgEntitlement, *http.Response, error) { + err := ValidateRequired(organizationID, "organizationID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "a3s/private/a3s-analysis/org-entitlement/"+url.PathEscape(organizationID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(A3sOrgEntitlement) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetPrivateCollectionConfig gets collection configuration for an organization/project (private). +// +// API endpoint: GET /api/v2/a3s/private/a3s-frontend-context/collection-config. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *A3sService) GetPrivateCollectionConfig(ctx context.Context, opt *A3sGetPrivateCollectionConfigOptions) (*A3sCollectionConfig, *http.Response, error) { + err := s.ValidateGetPrivateCollectionConfigOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "a3s/private/a3s-frontend-context/collection-config", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(A3sCollectionConfig) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// SearchPrivateContexts searches for contexts. +// +// API endpoint: GET /api/v2/a3s/private/a3s-frontend-context/contexts. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *A3sService) SearchPrivateContexts(ctx context.Context, opt *A3sSearchPrivateContextsOptions) (*A3sContexts, *http.Response, error) { + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "a3s/private/a3s-frontend-context/contexts", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(A3sContexts) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetPrivateContext gets a context. +// +// API endpoint: GET /api/v2/a3s/private/a3s-frontend-context/contexts/{id}. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *A3sService) GetPrivateContext(ctx context.Context, contextID string) (*A3sContext, *http.Response, error) { + err := ValidateRequired(contextID, "contextID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "a3s/private/a3s-frontend-context/contexts/"+url.PathEscape(contextID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(A3sContext) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// CreatePublicAnalysis creates a new analysis. +// +// API endpoint: POST /api/v2/a3s/analyses. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *A3sService) CreatePublicAnalysis(ctx context.Context, invocationID string, opt *A3sCreatePublicAnalysisOptions) (*A3sAnalysis, *http.Response, error) { + err := s.ValidateCreatePublicAnalysisOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "a3s/analyses", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + if invocationID != "" { + req.Header.Set("X-Sonar-Invocation-Id", invocationID) + } + + result := new(A3sAnalysis) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// CreatePublicContext creates a new context. +// +// API endpoint: POST /api/v2/a3s/contexts. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *A3sService) CreatePublicContext(ctx context.Context, opt *A3sCreatePublicContextOptions) (*A3sContextWithUploadUrl, *http.Response, error) { + err := s.ValidateCreatePublicContextOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "a3s/contexts", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(A3sContextWithUploadUrl) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// CreatePrivateContext creates a new context. +// +// API endpoint: POST /api/v2/a3s/private/a3s-frontend-context/contexts. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *A3sService) CreatePrivateContext(ctx context.Context, opt *A3sCreatePrivateContextOptions) (*A3sContextWithUploadUrl, *http.Response, error) { + err := s.ValidateCreatePrivateContextOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "a3s/private/a3s-frontend-context/contexts", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(A3sContextWithUploadUrl) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// CreatePrivateAnalysis creates a new analysis. +// +// API endpoint: POST /api/v2/a3s/private/analyses. +// Enterprise Edition only. +func (s *A3sService) CreatePrivateAnalysis(ctx context.Context, opt *A3sCreatePrivateAnalysisOptions) (*A3sAnalysis, *http.Response, error) { + err := s.ValidateCreatePrivateAnalysisOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "a3s/private/analyses", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(A3sAnalysis) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// UploadContext uploads the content of a context created with CreatePublicContext or +// CreatePrivateContext, as a zip archive (the endpoint only accepts application/zip). +// +// API endpoint: PUT /api/v2/a3s/context-uploads/{contextId}. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *A3sService) UploadContext(ctx context.Context, contextID string, content io.Reader) (*http.Response, error) { + err := ValidateRequired(contextID, "contextID") + if err != nil { + return nil, err + } + + if content == nil { + return nil, NewValidationError("content", "is required", ErrMissingRequired) + } + + //nolint:exhaustruct // only the fields relevant to a raw upload are set + req, err := s.client.NewSonarQubeAPIRequest(ctx, SonarAPIRequestParameters{ + Method: http.MethodPut, + Path: v2BasePath + "a3s/context-uploads/" + url.PathEscape(contextID), + Headers: map[string]string{headerContentType: "application/zip"}, + Body: content, + }) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} diff --git a/sonar/a3s_v2_service_test.go b/sonar/a3s_v2_service_test.go new file mode 100644 index 0000000..5316d14 --- /dev/null +++ b/sonar/a3s_v2_service_test.go @@ -0,0 +1,200 @@ +package sonar + +import ( + "context" + "net/http" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestA3sService_GetAnalysisStats(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/a3s/analysis-stats/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.A3s.GetAnalysisStats(context.Background(), "test-id", &A3sGetAnalysisStatsOptions{Period: "LAST_DAY"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestA3sService_GetAnalysisStats_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.A3s.GetAnalysisStats(context.Background(), "", nil) + require.Error(t, err) +} + +func TestA3sService_GetPublicCollectionConfig(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/a3s/collection-config", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.A3s.GetPublicCollectionConfig(context.Background(), &A3sGetPublicCollectionConfigOptions{OrganizationKey: "test", ProjectKey: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestA3sService_GetPublicCollectionConfig_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.A3s.GetPublicCollectionConfig(context.Background(), nil) + require.Error(t, err) +} + +func TestA3sService_GetPublicOrgEntitlement(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/a3s/org-entitlement/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.A3s.GetPublicOrgEntitlement(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestA3sService_GetPublicOrgEntitlement_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.A3s.GetPublicOrgEntitlement(context.Background(), "") + require.Error(t, err) +} + +func TestA3sService_GetPrivateOrgEntitlement(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/a3s/private/a3s-analysis/org-entitlement/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.A3s.GetPrivateOrgEntitlement(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestA3sService_GetPrivateOrgEntitlement_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.A3s.GetPrivateOrgEntitlement(context.Background(), "") + require.Error(t, err) +} + +func TestA3sService_GetPrivateCollectionConfig(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/a3s/private/a3s-frontend-context/collection-config", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.A3s.GetPrivateCollectionConfig(context.Background(), &A3sGetPrivateCollectionConfigOptions{OrganizationKey: "test", ProjectKey: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestA3sService_GetPrivateCollectionConfig_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.A3s.GetPrivateCollectionConfig(context.Background(), nil) + require.Error(t, err) +} + +func TestA3sService_SearchPrivateContexts(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/a3s/private/a3s-frontend-context/contexts", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.A3s.SearchPrivateContexts(context.Background(), &A3sSearchPrivateContextsOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestA3sService_GetPrivateContext(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/a3s/private/a3s-frontend-context/contexts/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.A3s.GetPrivateContext(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestA3sService_GetPrivateContext_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.A3s.GetPrivateContext(context.Background(), "") + require.Error(t, err) +} + +func TestA3sService_CreatePublicAnalysis(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/a3s/analyses", http.StatusCreated, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.A3s.CreatePublicAnalysis(context.Background(), "test-value", &A3sCreatePublicAnalysisOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestA3sService_CreatePublicAnalysis_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.A3s.CreatePublicAnalysis(context.Background(), "", nil) + require.Error(t, err) +} + +func TestA3sService_CreatePublicContext(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/a3s/contexts", http.StatusCreated, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.A3s.CreatePublicContext(context.Background(), &A3sCreatePublicContextOptions{AnalysisId: "test", Kind: "test", Metadata: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestA3sService_CreatePublicContext_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.A3s.CreatePublicContext(context.Background(), nil) + require.Error(t, err) +} + +func TestA3sService_CreatePrivateContext(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/a3s/private/a3s-frontend-context/contexts", http.StatusCreated, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.A3s.CreatePrivateContext(context.Background(), &A3sCreatePrivateContextOptions{AnalysisId: "test", Kind: "test", Metadata: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestA3sService_CreatePrivateContext_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.A3s.CreatePrivateContext(context.Background(), nil) + require.Error(t, err) +} + +func TestA3sService_CreatePrivateAnalysis(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/a3s/private/analyses", http.StatusCreated, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.A3s.CreatePrivateAnalysis(context.Background(), &A3sCreatePrivateAnalysisOptions{OrganizationId: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestA3sService_CreatePrivateAnalysis_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.A3s.CreatePrivateAnalysis(context.Background(), nil) + require.Error(t, err) +} + +func TestA3sService_UploadContext(t *testing.T) { + server := newTestServer(t, mockEmptyHandler(t, http.MethodPut, "/v2/a3s/context-uploads/test-id", http.StatusOK)) + client := newTestClient(t, server.url()) + + resp, err := client.V2.A3s.UploadContext(context.Background(), "test-id", strings.NewReader("payload")) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) +} diff --git a/sonar/agentic_v2_service.go b/sonar/agentic_v2_service.go new file mode 100644 index 0000000..a8f5461 --- /dev/null +++ b/sonar/agentic_v2_service.go @@ -0,0 +1,220 @@ +package sonar + +import ( + "bytes" + "context" + "fmt" + "net/http" +) + +// AgenticService handles communication with the Agentic orchestration jobs (Hunter and Remediation agents) related methods of the +// SonarQube V2 API. This service is only available in Enterprise Edition. +type AgenticService struct { + // client is used to communicate with the SonarQube API. + client *Client +} + +//nolint:gochecknoglobals // constant sets of allowed values +var ( + // allowedAgenticSearchJobsStatus is the set of allowed values for the corresponding option. + allowedAgenticSearchJobsStatus = map[string]struct{}{ + "PENDING": {}, + "IN_PROGRESS": {}, + "COMPLETED": {}, + "FAILED": {}, + } + // allowedAgenticSearchJobsType is the set of allowed values for the corresponding option. + allowedAgenticSearchJobsType = map[string]struct{}{ + "HUNTER": {}, + "REMEDIATION": {}, + } +) + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// AgenticSearchJobsOptions contains parameters for the SearchJobs method. +type AgenticSearchJobsOptions struct { + // Id comma-separated list of job ids to filter on. + Id string `json:"id,omitempty"` + // Status comma-separated list of statuses to filter on. Allowed values: PENDING, IN_PROGRESS, + // COMPLETED, FAILED. + Status string `json:"status,omitempty"` + // Type comma-separated list of agent types to filter on. Allowed values: HUNTER, REMEDIATION. + Type string `json:"type,omitempty"` + // CreatedAfter only return jobs created on or after this date (or date-time), e.g. 2026-01-01 + // or 2026-01-01T12:00:00+0000. + CreatedAfter string `json:"createdAfter,omitempty"` + // CreatedBefore only return jobs created on or before this date (or date-time), e.g. + // 2026-01-01 or 2026-01-01T12:00:00+0000. + CreatedBefore string `json:"createdBefore,omitempty"` + // PageSize number of results per page. A value of 0 will only return the pagination + // information. + // Must be between 0 and 500. Default: 50. + PageSize int32 `json:"pageSize,omitempty"` + // PageIndex 1-based page index. + // Minimum value: 1. Default: 1. + PageIndex int32 `json:"pageIndex,omitempty"` +} + +// AgenticAgenticJobRestResponse represents the AgenticJobRestResponse object of the SonarQube V2 +// API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type AgenticAgenticJobRestResponse struct { + // Id is the id. + Id string `json:"id,omitempty"` + // ProjectId is the project id. + ProjectId string `json:"projectId,omitempty"` + // ProjectKey not set if the project has been deleted. + ProjectKey string `json:"projectKey,omitempty"` + // ProjectName not set if the project has been deleted. + ProjectName string `json:"projectName,omitempty"` + // Type is the type. Allowed values: HUNTER, REMEDIATION. + Type string `json:"type,omitempty"` + // AnalysisType is the analysis type. Allowed values: FULL, INCREMENTAL. + AnalysisType string `json:"analysisType,omitempty"` + // Status is the status. Allowed values: PENDING, IN_PROGRESS, COMPLETED, FAILED. + Status string `json:"status,omitempty"` + // Branch is the branch. + Branch string `json:"branch,omitempty"` + // RepositoryUrl is the repository url. + RepositoryUrl string `json:"repositoryUrl,omitempty"` + // Revision is the revision. + Revision string `json:"revision,omitempty"` + // WorkflowType is the workflow type. + WorkflowType string `json:"workflowType,omitempty"` + // FindingsCount is the findings count. + FindingsCount int32 `json:"findingsCount,omitempty"` + // CreatedAt is the created at. + CreatedAt int64 `json:"createdAt,omitempty"` + // UpdatedAt is the updated at. + UpdatedAt int64 `json:"updatedAt,omitempty"` + // StartedAt is the started at. + StartedAt int64 `json:"startedAt,omitempty"` + // FinishedAt is the finished at. + FinishedAt int64 `json:"finishedAt,omitempty"` + // FailureReason deprecated. Use errorKey instead. + FailureReason string `json:"failureReason,omitempty"` + // ErrorKey stable customer-safe failure identifier. Only set when status is FAILED. + ErrorKey string `json:"errorKey,omitempty"` +} + +// AgenticAgenticJobsSearchRestResponse represents the AgenticJobsSearchRestResponse object of the +// SonarQube V2 API. +type AgenticAgenticJobsSearchRestResponse struct { + // Jobs is the jobs. + Jobs []AgenticAgenticJobRestResponse `json:"jobs,omitempty"` + // Page is the page. + Page PageResponseV2 `json:"page,omitzero"` +} + +// AgenticDownloadJobLogsOptions contains parameters for the DownloadJobLogs method. +type AgenticDownloadJobLogsOptions struct { + // JobId is the job id. This field is required. + JobId string `json:"jobId"` +} + +// ----------------------------------------------------------------------------- +// Validation Functions +// ----------------------------------------------------------------------------- + +// ValidateSearchJobsOpt validates the options for the SearchJobs method. +func (s *AgenticService) ValidateSearchJobsOpt(opt *AgenticSearchJobsOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := IsValueAuthorized(opt.Status, allowedAgenticSearchJobsStatus, "Status") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Type, allowedAgenticSearchJobsType, "Type") + if err != nil { + return err + } + + if opt.PageSize < 0 || opt.PageSize > 500 { + return NewValidationError("PageSize", "must be between 0 and 500", ErrOutOfRange) + } + + if opt.PageIndex < 0 { + return NewValidationError("PageIndex", "must be greater than 0", ErrOutOfRange) + } + + return nil +} + +// ValidateDownloadJobLogsOpt validates the options for the DownloadJobLogs method. +func (s *AgenticService) ValidateDownloadJobLogsOpt(opt *AgenticDownloadJobLogsOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.JobId, "JobId") + if err != nil { + return err + } + + return nil +} + +// ----------------------------------------------------------------------------- +// Service Methods +// ----------------------------------------------------------------------------- + +// SearchJobs searches agentic jobs. List agentic orchestration jobs (Hunter and Remediation +// agents), most recent first. Requires system administration permission. +// +// API endpoint: GET /api/v2/agentic/jobs. +// Enterprise Edition only. +func (s *AgenticService) SearchJobs(ctx context.Context, opt *AgenticSearchJobsOptions) (*AgenticAgenticJobsSearchRestResponse, *http.Response, error) { + err := s.ValidateSearchJobsOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "agentic/jobs", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(AgenticAgenticJobsSearchRestResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// DownloadJobLogs downloads agent job logs. Download the stdout/stderr logs archive for a +// Hunter/Remediation agent job, as a zip file. Requires system administration permission. +// +// API endpoint: GET /api/v2/agentic/jobs/logs. +// Enterprise Edition only. +func (s *AgenticService) DownloadJobLogs(ctx context.Context, opt *AgenticDownloadJobLogsOptions) ([]byte, *http.Response, error) { + err := s.ValidateDownloadJobLogsOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "agentic/jobs/logs", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Accept", "application/zip") + + var buf bytes.Buffer + + resp, err := s.client.Do(req, &buf) + if err != nil { + return nil, resp, err + } + + return buf.Bytes(), resp, nil +} diff --git a/sonar/agentic_v2_service_test.go b/sonar/agentic_v2_service_test.go new file mode 100644 index 0000000..fb25c89 --- /dev/null +++ b/sonar/agentic_v2_service_test.go @@ -0,0 +1,44 @@ +package sonar + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAgenticService_SearchJobs(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/agentic/jobs", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Agentic.SearchJobs(context.Background(), &AgenticSearchJobsOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestAgenticService_SearchJobs_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Agentic.SearchJobs(context.Background(), nil) + require.Error(t, err) +} + +func TestAgenticService_DownloadJobLogs(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/agentic/jobs/logs", http.StatusOK, "zip-bytes")) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Agentic.DownloadJobLogs(context.Background(), &AgenticDownloadJobLogsOptions{JobId: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestAgenticService_DownloadJobLogs_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Agentic.DownloadJobLogs(context.Background(), nil) + require.Error(t, err) +} diff --git a/sonar/alm_settings_service.go b/sonar/alm_settings_service.go index 0e96934..4033e8e 100644 --- a/sonar/alm_settings_service.go +++ b/sonar/alm_settings_service.go @@ -8,6 +8,8 @@ import ( const ( // MaxAlmKeyLength is the maximum length for DevOps Platform setting keys. MaxAlmKeyLength = 200 + // MaxAlmAllowedOrganizationsLength is the maximum length for the GitHub allowed organizations list. + MaxAlmAllowedOrganizationsLength = 4000 // MaxAlmURLLength is the maximum length for DevOps Platform URLs. MaxAlmURLLength = 2000 // MaxPersonalAccessTokenLength is the maximum length for Personal Access Tokens. @@ -274,6 +276,11 @@ type AlmSettingsCreateGithubFromManifestOptions struct { // Allowed values: true, false, yes, no // This field is optional. Default: false. Auth string `url:"auth,omitempty"` + // AllowedOrganizations is a comma-separated list of GitHub organizations whose members are allowed to sign in. + // Only used when Auth is true. When omitted, the value of Organization is used as the allow list. + // An empty allow list lets any GitHub account sign in. + // This field is optional. Maximum length: 4000 characters. Since 2026.5. + AllowedOrganizations string `url:"allowedOrganizations,omitempty"` // Devops indicates whether to create the DevOps Platform integration (project import / PR analysis) for this App. // Allowed values: true, false, yes, no // This field is optional. Default: true. @@ -798,6 +805,11 @@ func (s *AlmSettingsService) ValidateCreateGithubFromManifestOpt(opt *AlmSetting } } + err := ValidateMaxLength(opt.AllowedOrganizations, MaxAlmAllowedOrganizationsLength, "AllowedOrganizations") + if err != nil { + return err + } + return nil } @@ -1545,6 +1557,7 @@ func (s *AlmSettingsService) DeleteBinding(ctx context.Context, opt *AlmSettings // If the project is already bound to a previous Azure DevOps instance, the binding will be updated to the new one. // Requires the 'Administer' permission on the project. // +// Deprecated: Since SonarQube 2026.5. // API endpoint: POST /api/alm_settings/set_azure_binding. // Since: 8.1. func (s *AlmSettingsService) SetAzureBinding(ctx context.Context, opt *AlmSettingsSetAzureBindingOptions) (*http.Response, error) { @@ -1570,6 +1583,7 @@ func (s *AlmSettingsService) SetAzureBinding(ctx context.Context, opt *AlmSettin // If the project is already bound to a previous Bitbucket instance, the binding will be updated to the new one. // Requires the 'Administer' permission on the project. // +// Deprecated: Since SonarQube 2026.5. // API endpoint: POST /api/alm_settings/set_bitbucket_binding. // Since: 8.1. func (s *AlmSettingsService) SetBitbucketBinding(ctx context.Context, opt *AlmSettingsSetBitbucketBindingOptions) (*http.Response, error) { @@ -1595,6 +1609,7 @@ func (s *AlmSettingsService) SetBitbucketBinding(ctx context.Context, opt *AlmSe // If the project is already bound to a different Bitbucket Cloud setting, the binding will be updated to the new one. // Requires the 'Administer' permission on the project. // +// Deprecated: Since SonarQube 2026.5. // API endpoint: POST /api/alm_settings/set_bitbucketcloud_binding. // Since: 8.7. func (s *AlmSettingsService) SetBitbucketCloudBinding(ctx context.Context, opt *AlmSettingsSetBitbucketCloudBindingOptions) (*http.Response, error) { @@ -1620,6 +1635,7 @@ func (s *AlmSettingsService) SetBitbucketCloudBinding(ctx context.Context, opt * // If the project is already bound to a previous GitHub instance, the binding will be updated to the new one. // Requires the 'Administer' permission on the project. // +// Deprecated: Since SonarQube 2026.5. // API endpoint: POST /api/alm_settings/set_github_binding. // Since: 8.1. func (s *AlmSettingsService) SetGithubBinding(ctx context.Context, opt *AlmSettingsSetGithubBindingOptions) (*http.Response, error) { @@ -1645,6 +1661,7 @@ func (s *AlmSettingsService) SetGithubBinding(ctx context.Context, opt *AlmSetti // If the project is already bound to a previous GitLab instance, the binding will be updated to the new one. // Requires the 'Administer' permission on the project. // +// Deprecated: Since SonarQube 2026.5. // API endpoint: POST /api/alm_settings/set_gitlab_binding. // Since: 8.1. func (s *AlmSettingsService) SetGitlabBinding(ctx context.Context, opt *AlmSettingsSetGitlabBindingOptions) (*http.Response, error) { diff --git a/sonar/applications_service.go b/sonar/applications_service.go index 4d6b72d..1048452 100644 --- a/sonar/applications_service.go +++ b/sonar/applications_service.go @@ -82,6 +82,8 @@ type ApplicationProject struct { Enabled bool `json:"enabled,omitempty"` // Selected indicates whether the project is selected in the application. Selected bool `json:"selected,omitempty"` + // Accessible indicates whether the current user can browse the project. + Accessible bool `json:"accessible,omitempty"` } // ApplicationsShow represents the response from the show endpoint. diff --git a/sonar/architecture_organization_v2_service.go b/sonar/architecture_organization_v2_service.go new file mode 100644 index 0000000..8c3c238 --- /dev/null +++ b/sonar/architecture_organization_v2_service.go @@ -0,0 +1,1595 @@ +package sonar + +import ( + "context" + "fmt" + "net/http" + "net/url" +) + +const ( + // ArchitectureValueOther is the "OTHER" enumeration value. + ArchitectureValueOther = "OTHER" +) + +//nolint:gochecknoglobals,goconst // constant sets of allowed values +var ( + // allowedArchitectureListExternalInterfacesDirection is the set of allowed values for the corresponding option. + allowedArchitectureListExternalInterfacesDirection = map[string]struct{}{ + "EXIT_POINT": {}, + "ENTRY_POINT": {}, + } + // allowedArchitectureUpdateBoundaryDescriptorDirection is the set of allowed values for the corresponding option. + allowedArchitectureUpdateBoundaryDescriptorDirection = map[string]struct{}{ + "EXIT_POINT": {}, + "ENTRY_POINT": {}, + } + // allowedArchitectureUpdateBoundaryDescriptorEcosystem is the set of allowed values for the corresponding option. + allowedArchitectureUpdateBoundaryDescriptorEcosystem = map[string]struct{}{ + LanguageJava: {}, + "js": {}, + "ts": {}, + "py": {}, + "cs": {}, + } + // allowedArchitectureUpdateOrganizationPlaceholderType is the set of allowed values for the corresponding option. + allowedArchitectureUpdateOrganizationPlaceholderType = map[string]struct{}{ + ProjectQualifierAPP: {}, + "DB": {}, + "QUEUE": {}, + ArchitectureValueOther: {}, + } + // allowedArchitectureCreateBoundaryDescriptorEcosystem is the set of allowed values for the corresponding option. + allowedArchitectureCreateBoundaryDescriptorEcosystem = map[string]struct{}{ + LanguageJava: {}, + "js": {}, + "ts": {}, + "py": {}, + "cs": {}, + } + // allowedArchitectureCreateBoundaryDescriptorDirection is the set of allowed values for the corresponding option. + allowedArchitectureCreateBoundaryDescriptorDirection = map[string]struct{}{ + "EXIT_POINT": {}, + "ENTRY_POINT": {}, + } + // allowedArchitectureCreateOrganizationPlaceholderType is the set of allowed values for the corresponding option. + allowedArchitectureCreateOrganizationPlaceholderType = map[string]struct{}{ + ProjectQualifierAPP: {}, + "DB": {}, + "QUEUE": {}, + ArchitectureValueOther: {}, + } +) + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// ArchitectureListBoundaryDescriptorsOptions contains parameters for the ListBoundaryDescriptors method. +type ArchitectureListBoundaryDescriptorsOptions struct { + // ProjectId project Id. This field is required. + ProjectId string `json:"projectId"` + // PageIndex page number for pagination (1-based). Specifies which page of results to return. + // Minimum value: 1. Default: 1. + PageIndex int32 `json:"pageIndex,omitempty"` + // PageSize number of items per page. Set to 0 to return only the total count without any items + // (useful for counting). + // Minimum value: 0. Default: 50. + PageSize int32 `json:"pageSize,omitempty"` +} + +// ArchitectureBoundaryDescriptor represents the BoundaryDescriptor object of the SonarQube V2 API. +type ArchitectureBoundaryDescriptor struct { + // Direction of the boundary. Allowed values: EXIT_POINT, ENTRY_POINT. + Direction string `json:"direction,omitempty"` + // Name of the boundary descriptor. + Name string `json:"name,omitempty"` + // KeyTemplate template for generating boundary keys. + KeyTemplate string `json:"keyTemplate,omitempty"` + // Query to match boundaries. + Query string `json:"query,omitempty"` + // Id is the id. + Id string `json:"id,omitempty"` + // Ecosystem language ecosystem of the boundary. Allowed values: java, js, ts, py, cs. + Ecosystem string `json:"ecosystem,omitempty"` + // ProjectId is the project id. + ProjectId string `json:"projectId,omitempty"` +} + +// ArchitectureBoundaryDescriptorPagedResponse represents the BoundaryDescriptorPagedResponse object +// of the SonarQube V2 API. +type ArchitectureBoundaryDescriptorPagedResponse struct { + // BoundaryDescriptors is the boundary descriptors. + BoundaryDescriptors []ArchitectureBoundaryDescriptor `json:"boundaryDescriptors,omitempty"` + // Page is the page. + Page PageResponseV2 `json:"page,omitzero"` +} + +// ArchitectureListExternalInterfacesOptions contains parameters for the ListExternalInterfaces method. +type ArchitectureListExternalInterfacesOptions struct { + // OrganizationId UUID v4 of the organization. This field is required. + OrganizationId string `json:"organizationId"` + // ComponentId the component whose external interfaces are returned. When omitted, the external + // interfaces of every component in the organization are returned. + ComponentId string `json:"componentId,omitempty"` + // Direction when set, only external interfaces of this direction (entry or exit) are returned. + // Allowed values: EXIT_POINT, ENTRY_POINT. + Direction string `json:"direction,omitempty"` +} + +// ArchitectureExternalInterface represents a SonarQube V2 API object. +// +// An external interface of a component. Today this is an analyzer-found boundary; the abstraction +// will later also cover SDK-declared and default entry points. +type ArchitectureExternalInterface struct { + // Direction of the external interface (entry or exit point). Allowed values: EXIT_POINT, + // ENTRY_POINT. + Direction string `json:"direction,omitempty"` + // ComponentId id of the organization component this external interface belongs to. + ComponentId string `json:"componentId,omitempty"` + // Key identifying the external interface. + Key string `json:"key,omitempty"` +} + +// ArchitectureExternalInterfaceListResponse represents the ExternalInterfaceListResponse object of +// the SonarQube V2 API. +type ArchitectureExternalInterfaceListResponse struct { + // ExternalInterfaces is the external interfaces. + ExternalInterfaces []ArchitectureExternalInterface `json:"externalInterfaces,omitempty"` +} + +// ArchitectureGetOrganizationArchitectureOptions contains parameters for the GetOrganizationArchitecture method. +type ArchitectureGetOrganizationArchitectureOptions struct { + // OrganizationId UUID v4 of the organization. This field is required. + OrganizationId string `json:"organizationId"` +} + +// ArchitectureOrganizationArchitectureComponentMetadata represents the +// OrganizationArchitectureComponentMetadata object of the SonarQube V2 API. +type ArchitectureOrganizationArchitectureComponentMetadata struct { + // UnmatchedExitPoints keys of the component's exit points that are not used by any project + // relationship. + UnmatchedExitPoints []string `json:"unmatchedExitPoints,omitempty"` + // MatchedExitPoints keys of the component's exit points that are used by a project + // relationship. + MatchedExitPoints []string `json:"matchedExitPoints,omitempty"` +} + +// ArchitectureOrganizationArchitectureComponent represents the OrganizationArchitectureComponent +// object of the SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type ArchitectureOrganizationArchitectureComponent struct { + // Metadata is the metadata. + Metadata ArchitectureOrganizationArchitectureComponentMetadata `json:"metadata,omitzero"` + // Id is the id. + Id string `json:"id,omitempty"` +} + +// ArchitectureOrganizationArchitectureRelationshipMetadata represents the +// OrganizationArchitectureRelationshipMetadata object of the SonarQube V2 API. +type ArchitectureOrganizationArchitectureRelationshipMetadata struct { + // DependencyCount number of actual dependencies that exist between the 'from' and the 'to' + // component. + DependencyCount int32 `json:"dependencyCount,omitempty"` +} + +// ArchitectureOrganizationArchitectureRelationship represents the +// OrganizationArchitectureRelationship object of the SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type ArchitectureOrganizationArchitectureRelationship struct { + // From is the from. + From string `json:"from,omitempty"` + // Metadata is the metadata. + Metadata ArchitectureOrganizationArchitectureRelationshipMetadata `json:"metadata,omitzero"` + // To is the to. + To string `json:"to,omitempty"` +} + +// ArchitectureOrganizationArchitecture represents the OrganizationArchitecture object of the +// SonarQube V2 API. +type ArchitectureOrganizationArchitecture struct { + // Components is the components. + Components []ArchitectureOrganizationArchitectureComponent `json:"components,omitempty"` + // Relationships is the relationships. + Relationships []ArchitectureOrganizationArchitectureRelationship `json:"relationships,omitempty"` +} + +// ArchitectureOrganizationArchitectureResponse represents the OrganizationArchitectureResponse +// object of the SonarQube V2 API. +type ArchitectureOrganizationArchitectureResponse struct { + // OrganizationArchitectures is the organization architectures. + OrganizationArchitectures []ArchitectureOrganizationArchitecture `json:"organizationArchitectures,omitempty"` +} + +// ArchitectureListOrganizationComponentsOptions contains parameters for the ListOrganizationComponents method. +type ArchitectureListOrganizationComponentsOptions struct { + // OrganizationId UUID v4 of the organization. This field is required. + OrganizationId string `json:"organizationId"` +} + +// ArchitectureOrganizationComponent represents the OrganizationComponent object of the SonarQube V2 +// API. +type ArchitectureOrganizationComponent struct { + // DisplayName is the display name. + DisplayName string `json:"displayName,omitempty"` + // Id component id. For PROJECT components, this is the project id. + Id string `json:"id,omitempty"` + // Type is the type. Allowed values: PROJECT, PLACEHOLDER. + Type string `json:"type,omitempty"` + // ProjectId set when type=PROJECT. Same value as id. Absent for PLACEHOLDER. + ProjectId string `json:"projectId,omitempty"` + // OrganizationId UUID v4 of the organization. + OrganizationId string `json:"organizationId,omitempty"` +} + +// ArchitectureOrganizationComponentListResponse represents the OrganizationComponentListResponse +// object of the SonarQube V2 API. +type ArchitectureOrganizationComponentListResponse struct { + // Components is the components. + Components []ArchitectureOrganizationComponent `json:"components,omitempty"` +} + +// ArchitectureListOrganizationPlaceholdersOptions contains parameters for the ListOrganizationPlaceholders method. +type ArchitectureListOrganizationPlaceholdersOptions struct { + // OrganizationId UUID v4 of the organization. This field is required. + OrganizationId string `json:"organizationId"` +} + +// ArchitectureOrganizationPlaceholder represents the OrganizationPlaceholder object of the +// SonarQube V2 API. +type ArchitectureOrganizationPlaceholder struct { + // OrganizationId UUID v4 of the organization. + OrganizationId string `json:"organizationId,omitempty"` + // Id is the id. + Id string `json:"id,omitempty"` + // Type is the type. Allowed values: APP, DB, QUEUE, OTHER. + Type string `json:"type,omitempty"` + // DisplayName is the display name. + DisplayName string `json:"displayName,omitempty"` +} + +// ArchitectureOrganizationPlaceholderListResponse represents the +// OrganizationPlaceholderListResponse object of the SonarQube V2 API. +type ArchitectureOrganizationPlaceholderListResponse struct { + // Placeholders is the placeholders. + Placeholders []ArchitectureOrganizationPlaceholder `json:"placeholders,omitempty"` +} + +// ArchitectureListPatternsOptions contains parameters for the ListPatterns method. +type ArchitectureListPatternsOptions struct { + // OrganizationId UUID v4 of the organization. This field is required. + OrganizationId string `json:"organizationId"` +} + +// ArchitectureModelConstraint represents the ModelConstraint object of the SonarQube V2 API. +type ArchitectureModelConstraint struct { + // To pattern identifying the target group of the dependency. + To string `json:"to,omitempty"` + // From pattern identifying the source group of the dependency. + From string `json:"from,omitempty"` +} + +// ArchitecturePatternData represents a SonarQube V2 API object. +// +// Structured pattern definition. Language-agnostic, no perspectives array — represents a single +// flat pattern. Reuses ModelGroup and ModelConstraint. +type ArchitecturePatternData struct { + // Groups top-level components of the pattern. + Groups []ArchitectureModelGroup `json:"groups,omitempty"` + // Description of the pattern definition. + Description string `json:"description,omitempty"` + // Label display name of the pattern definition. + Label string `json:"label,omitempty"` + // Constraints dependency constraints between groups. + Constraints []ArchitectureModelConstraint `json:"constraints,omitempty"` +} + +// ArchitecturePattern represents a SonarQube V2 API object. +// +// A pattern resource. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type ArchitecturePattern struct { + // OrganizationId UUID v4 of the organization. + OrganizationId string `json:"organizationId,omitempty"` + // Id is the id. + Id string `json:"id,omitempty"` + // Pattern is the pattern. + Pattern ArchitecturePatternData `json:"pattern,omitzero"` + // Name display name of the pattern. + Name string `json:"name,omitempty"` +} + +// ArchitectureListProjectRelationshipsOptions contains parameters for the ListProjectRelationships method. +type ArchitectureListProjectRelationshipsOptions struct { + // ProjectId project Id. This field is required. + ProjectId string `json:"projectId"` + // PageIndex page number for pagination (1-based). Specifies which page of results to return. + // Minimum value: 1. Default: 1. + PageIndex int32 `json:"pageIndex,omitempty"` + // PageSize number of items per page. Set to 0 to return only the total count without any items + // (useful for counting). + // Minimum value: 0. Default: 50. + PageSize int32 `json:"pageSize,omitempty"` +} + +// ArchitectureProjectRelationship represents the ProjectRelationship object of the SonarQube V2 +// API. +type ArchitectureProjectRelationship struct { + // BoundaryKey key identifying the exit boundary (consumer side) this relationship applies to. + BoundaryKey string `json:"boundaryKey,omitempty"` + // ProjectId id of the project this relationship belongs to (the consumer). + ProjectId string `json:"projectId,omitempty"` + // TargetEntryPointKey key of the target component's entry point this exit connects to. + TargetEntryPointKey string `json:"targetEntryPointKey,omitempty"` + // Origin whether this relationship was created manually via the API or auto-computed from + // analysis. Allowed values: MANUAL, AUTOMAPPED. + Origin string `json:"origin,omitempty"` + // TargetComponentId id of the target organization component (the provider this exit connects + // to). + TargetComponentId string `json:"targetComponentId,omitempty"` + // OrganizationId UUID v4 of the organization. + OrganizationId string `json:"organizationId,omitempty"` + // Id is the id. + Id string `json:"id,omitempty"` +} + +// ArchitectureProjectRelationshipPagedResponse represents the ProjectRelationshipPagedResponse +// object of the SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type ArchitectureProjectRelationshipPagedResponse struct { + // Page is the page. + Page PageResponseV2 `json:"page,omitzero"` + // ProjectRelationships is the project relationships. + ProjectRelationships []ArchitectureProjectRelationship `json:"projectRelationships,omitempty"` +} + +// ArchitectureListSdksOptions contains parameters for the ListSdks method. +type ArchitectureListSdksOptions struct { + // OrganizationId UUID v4 of the organization. This field is required. + OrganizationId string `json:"organizationId"` +} + +// ArchitectureSdkBoundaryDescriptor represents a SonarQube V2 API object. +// +// One ecosystem-specific exit boundary descriptor of an SDK. A project's analysis emits a boundary +// wherever this descriptor matches. +type ArchitectureSdkBoundaryDescriptor struct { + // Ecosystem the ecosystem this boundary descriptor applies to. Allowed values: java, js, ts, + // py, cs. + Ecosystem string `json:"ecosystem,omitempty"` + // Query matching the call into the SDK, in the boundary-descriptor query grammar. + Query string `json:"query,omitempty"` +} + +// ArchitectureSdk represents a SonarQube V2 API object. +// +// An organization-scoped SDK: one identity, target and key, grouping N ecosystem-specific exit +// boundary descriptors. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type ArchitectureSdk struct { + // SdkKey unique key identifying this SDK within its organization. What edge matching compares + // a found exit boundary's key against. + SdkKey string `json:"sdkKey,omitempty"` + // OrganizationId UUID v4 of the organization. + OrganizationId string `json:"organizationId,omitempty"` + // Id is the id. + Id string `json:"id,omitempty"` + // TargetComponentId id of the organization component the SDK leads into (the provider). + TargetComponentId string `json:"targetComponentId,omitempty"` + // SdkBoundaryDescriptors is the sdk boundary descriptors. + SdkBoundaryDescriptors []ArchitectureSdkBoundaryDescriptor `json:"sdkBoundaryDescriptors,omitempty"` + // Name display name of the SDK. + Name string `json:"name,omitempty"` +} + +// ArchitectureSdkListResponse represents the SdkListResponse object of the SonarQube V2 API. +type ArchitectureSdkListResponse struct { + // Sdks is the sdks. + Sdks []ArchitectureSdk `json:"sdks,omitempty"` +} + +// ArchitectureUpdateBoundaryDescriptorOptions contains the request body for the +// UpdateBoundaryDescriptor method. +type ArchitectureUpdateBoundaryDescriptorOptions struct { + // Direction of the boundary. Allowed values: EXIT_POINT, ENTRY_POINT. + Direction *string `json:"direction,omitempty"` + // Ecosystem language ecosystem of the boundary. Allowed values: java, js, ts, py, cs. + Ecosystem *string `json:"ecosystem,omitempty"` + // KeyTemplate template for generating boundary keys. + KeyTemplate *string `json:"keyTemplate,omitempty"` + // Query to match boundaries. + Query *string `json:"query,omitempty"` + // Name of the boundary descriptor. + Name *string `json:"name,omitempty"` +} + +// ArchitectureUpdateOrganizationPlaceholderOptions contains the request body for the +// UpdateOrganizationPlaceholder method. +type ArchitectureUpdateOrganizationPlaceholderOptions struct { + // DisplayName is the display name. + DisplayName *string `json:"displayName,omitempty"` + // Type is the type. Allowed values: APP, DB, QUEUE, OTHER. + Type *string `json:"type,omitempty"` +} + +// ArchitectureUpdatePatternOptions contains the request body for the UpdatePattern method. +type ArchitectureUpdatePatternOptions struct { + // Pattern is the pattern. + Pattern *ArchitecturePatternData `json:"pattern,omitempty"` + // Name updated display name. + Name *string `json:"name,omitempty"` +} + +// ArchitectureUpdateProjectRelationshipOptions contains the request body for the +// UpdateProjectRelationship method. +type ArchitectureUpdateProjectRelationshipOptions struct { + // BoundaryKey key identifying the exit boundary (consumer side) this relationship applies to. + BoundaryKey *string `json:"boundaryKey,omitempty"` + // TargetComponentId id of the target organization component (the provider this exit connects + // to). + TargetComponentId *string `json:"targetComponentId,omitempty"` + // TargetEntryPointKey key of the target component's entry point this exit connects to. + TargetEntryPointKey *string `json:"targetEntryPointKey,omitempty"` +} + +// ArchitectureUpdateSdkOptions contains the request body for the UpdateSdk method. +type ArchitectureUpdateSdkOptions struct { + // TargetComponentId id of the organization component the SDK leads into (the provider). + TargetComponentId *string `json:"targetComponentId,omitempty"` + // Name is the name. + Name *string `json:"name,omitempty"` + // SdkBoundaryDescriptors is the sdk boundary descriptors. + SdkBoundaryDescriptors []ArchitectureSdkBoundaryDescriptor `json:"sdkBoundaryDescriptors,omitempty"` +} + +// ArchitectureCreateBoundaryDescriptorOptions contains the request body for the +// CreateBoundaryDescriptor method. +type ArchitectureCreateBoundaryDescriptorOptions struct { + // Ecosystem language ecosystem of the boundary. This field is required. Allowed values: java, + // js, ts, py, cs. + Ecosystem string `json:"ecosystem"` + // KeyTemplate template for generating boundary keys. This field is required. + KeyTemplate string `json:"keyTemplate"` + // Query to match boundaries. This field is required. + Query string `json:"query"` + // Direction of the boundary. This field is required. Allowed values: EXIT_POINT, ENTRY_POINT. + Direction string `json:"direction"` + // ProjectId is the project id. This field is required. + ProjectId string `json:"projectId"` + // Name of the boundary descriptor. This field is required. + Name string `json:"name"` +} + +// ArchitectureCreateOrganizationPlaceholderOptions contains the request body for the +// CreateOrganizationPlaceholder method. +type ArchitectureCreateOrganizationPlaceholderOptions struct { + // OrganizationId UUID v4 of the organization. + OrganizationId string `json:"organizationId,omitempty"` + // DisplayName is the display name. This field is required. + DisplayName string `json:"displayName"` + // Type is the type. Allowed values: APP, DB, QUEUE, OTHER. + Type string `json:"type,omitempty"` +} + +// ArchitectureCreatePatternOptions contains the request body for the CreatePattern method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type ArchitectureCreatePatternOptions struct { + // OrganizationId UUID v4 of the organization. This field is required. + OrganizationId string `json:"organizationId"` + // Pattern is the pattern. This field is required. + Pattern ArchitecturePatternData `json:"pattern"` + // Name display name of the pattern. This field is required. + Name string `json:"name"` +} + +// ArchitectureCreateProjectRelationshipOptions contains the request body for the +// CreateProjectRelationship method. +type ArchitectureCreateProjectRelationshipOptions struct { + // BoundaryKey key identifying the exit boundary (consumer side) this relationship applies to. + // This field is required. + BoundaryKey string `json:"boundaryKey"` + // ProjectId id of the project this relationship belongs to (the consumer). This field is + // required. + ProjectId string `json:"projectId"` + // TargetComponentId id of the target organization component (the provider this exit connects + // to). This field is required. + TargetComponentId string `json:"targetComponentId"` + // TargetEntryPointKey key of the target component's entry point this exit connects to. This + // field is required. + TargetEntryPointKey string `json:"targetEntryPointKey"` +} + +// ArchitectureCreateSdkOptions contains the request body for the CreateSdk method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type ArchitectureCreateSdkOptions struct { + // TargetComponentId id of the organization component the SDK leads into (the provider). This + // field is required. + TargetComponentId string `json:"targetComponentId"` + // SdkBoundaryDescriptors is the sdk boundary descriptors. This field is required. + SdkBoundaryDescriptors []ArchitectureSdkBoundaryDescriptor `json:"sdkBoundaryDescriptors"` + // Name is the name. This field is required. + Name string `json:"name"` + // SdkKey unique key identifying this SDK within its organization. This field is required. + SdkKey string `json:"sdkKey"` + // OrganizationId UUID v4 of the organization. + OrganizationId string `json:"organizationId,omitempty"` +} + +// ----------------------------------------------------------------------------- +// Validation Functions +// ----------------------------------------------------------------------------- + +// ValidateListBoundaryDescriptorsOpt validates the options for the ListBoundaryDescriptors method. +func (s *ArchitectureService) ValidateListBoundaryDescriptorsOpt(opt *ArchitectureListBoundaryDescriptorsOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.ProjectId, "ProjectId") + if err != nil { + return err + } + + if opt.PageIndex < 0 { + return NewValidationError("PageIndex", "must be greater than 0", ErrOutOfRange) + } + + return nil +} + +// ValidateListExternalInterfacesOpt validates the options for the ListExternalInterfaces method. +func (s *ArchitectureService) ValidateListExternalInterfacesOpt(opt *ArchitectureListExternalInterfacesOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.OrganizationId, "OrganizationId") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Direction, allowedArchitectureListExternalInterfacesDirection, "Direction") + if err != nil { + return err + } + + return nil +} + +// ValidateGetOrganizationArchitectureOpt validates the options for the GetOrganizationArchitecture method. +func (s *ArchitectureService) ValidateGetOrganizationArchitectureOpt(opt *ArchitectureGetOrganizationArchitectureOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.OrganizationId, "OrganizationId") + if err != nil { + return err + } + + return nil +} + +// ValidateListOrganizationComponentsOpt validates the options for the ListOrganizationComponents method. +func (s *ArchitectureService) ValidateListOrganizationComponentsOpt(opt *ArchitectureListOrganizationComponentsOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.OrganizationId, "OrganizationId") + if err != nil { + return err + } + + return nil +} + +// ValidateListOrganizationPlaceholdersOpt validates the options for the ListOrganizationPlaceholders method. +func (s *ArchitectureService) ValidateListOrganizationPlaceholdersOpt(opt *ArchitectureListOrganizationPlaceholdersOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.OrganizationId, "OrganizationId") + if err != nil { + return err + } + + return nil +} + +// ValidateListPatternsOpt validates the options for the ListPatterns method. +func (s *ArchitectureService) ValidateListPatternsOpt(opt *ArchitectureListPatternsOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.OrganizationId, "OrganizationId") + if err != nil { + return err + } + + return nil +} + +// ValidateListProjectRelationshipsOpt validates the options for the ListProjectRelationships method. +func (s *ArchitectureService) ValidateListProjectRelationshipsOpt(opt *ArchitectureListProjectRelationshipsOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.ProjectId, "ProjectId") + if err != nil { + return err + } + + if opt.PageIndex < 0 { + return NewValidationError("PageIndex", "must be greater than 0", ErrOutOfRange) + } + + return nil +} + +// ValidateListSdksOpt validates the options for the ListSdks method. +func (s *ArchitectureService) ValidateListSdksOpt(opt *ArchitectureListSdksOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.OrganizationId, "OrganizationId") + if err != nil { + return err + } + + return nil +} + +// ValidateUpdateBoundaryDescriptorOpt validates the options for the UpdateBoundaryDescriptor method. +func (s *ArchitectureService) ValidateUpdateBoundaryDescriptorOpt(opt *ArchitectureUpdateBoundaryDescriptorOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + var err error + + if opt.Direction != nil { + err = IsValueAuthorized(*opt.Direction, allowedArchitectureUpdateBoundaryDescriptorDirection, "Direction") + if err != nil { + return err + } + } + + if opt.Ecosystem != nil { + err = IsValueAuthorized(*opt.Ecosystem, allowedArchitectureUpdateBoundaryDescriptorEcosystem, "Ecosystem") + if err != nil { + return err + } + } + + return nil +} + +// ValidateUpdateOrganizationPlaceholderOpt validates the options for the UpdateOrganizationPlaceholder method. +func (s *ArchitectureService) ValidateUpdateOrganizationPlaceholderOpt(opt *ArchitectureUpdateOrganizationPlaceholderOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + var err error + + if opt.Type != nil { + err = IsValueAuthorized(*opt.Type, allowedArchitectureUpdateOrganizationPlaceholderType, "Type") + if err != nil { + return err + } + } + + return nil +} + +// ValidateCreateBoundaryDescriptorOpt validates the options for the CreateBoundaryDescriptor method. +func (s *ArchitectureService) ValidateCreateBoundaryDescriptorOpt(opt *ArchitectureCreateBoundaryDescriptorOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.Ecosystem, "Ecosystem") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Ecosystem, allowedArchitectureCreateBoundaryDescriptorEcosystem, "Ecosystem") + if err != nil { + return err + } + + err = ValidateRequired(opt.KeyTemplate, "KeyTemplate") + if err != nil { + return err + } + + err = ValidateRequired(opt.Query, "Query") + if err != nil { + return err + } + + err = ValidateRequired(opt.Direction, "Direction") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Direction, allowedArchitectureCreateBoundaryDescriptorDirection, "Direction") + if err != nil { + return err + } + + err = ValidateRequired(opt.ProjectId, "ProjectId") + if err != nil { + return err + } + + err = ValidateRequired(opt.Name, "Name") + if err != nil { + return err + } + + return nil +} + +// ValidateCreateOrganizationPlaceholderOpt validates the options for the CreateOrganizationPlaceholder method. +func (s *ArchitectureService) ValidateCreateOrganizationPlaceholderOpt(opt *ArchitectureCreateOrganizationPlaceholderOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.DisplayName, "DisplayName") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Type, allowedArchitectureCreateOrganizationPlaceholderType, "Type") + if err != nil { + return err + } + + return nil +} + +// ValidateCreatePatternOpt validates the options for the CreatePattern method. +func (s *ArchitectureService) ValidateCreatePatternOpt(opt *ArchitectureCreatePatternOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.OrganizationId, "OrganizationId") + if err != nil { + return err + } + + err = ValidateRequired(opt.Name, "Name") + if err != nil { + return err + } + + return nil +} + +// ValidateCreateProjectRelationshipOpt validates the options for the CreateProjectRelationship method. +func (s *ArchitectureService) ValidateCreateProjectRelationshipOpt(opt *ArchitectureCreateProjectRelationshipOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.BoundaryKey, "BoundaryKey") + if err != nil { + return err + } + + err = ValidateRequired(opt.ProjectId, "ProjectId") + if err != nil { + return err + } + + err = ValidateRequired(opt.TargetComponentId, "TargetComponentId") + if err != nil { + return err + } + + err = ValidateRequired(opt.TargetEntryPointKey, "TargetEntryPointKey") + if err != nil { + return err + } + + return nil +} + +// ValidateCreateSdkOpt validates the options for the CreateSdk method. +func (s *ArchitectureService) ValidateCreateSdkOpt(opt *ArchitectureCreateSdkOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.TargetComponentId, "TargetComponentId") + if err != nil { + return err + } + + if len(opt.SdkBoundaryDescriptors) == 0 { + return NewValidationError("SdkBoundaryDescriptors", "is required", ErrMissingRequired) + } + + err = ValidateRequired(opt.Name, "Name") + if err != nil { + return err + } + + err = ValidateRequired(opt.SdkKey, "SdkKey") + if err != nil { + return err + } + + return nil +} + +// ----------------------------------------------------------------------------- +// Service Methods +// ----------------------------------------------------------------------------- + +// DeleteBoundaryDescriptor deletes a boundary descriptor. Delete a boundary descriptor by id. +// +// API endpoint: DELETE /api/v2/architecture/boundary-descriptors/{id}. +// Enterprise Edition only. +func (s *ArchitectureService) DeleteBoundaryDescriptor(ctx context.Context, boundaryDescriptorID string) (*http.Response, error) { + err := ValidateRequired(boundaryDescriptorID, "boundaryDescriptorID") + if err != nil { + return nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodDelete, "architecture/boundary-descriptors/"+url.PathEscape(boundaryDescriptorID), nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// DeleteOrganizationPlaceholder deletes a placeholder. Delete the placeholder with the given id. A +// placeholder that is referenced by a project relationship cannot be deleted. +// +// API endpoint: DELETE /api/v2/architecture/organization-placeholders/{id}. +// Enterprise Edition only. +func (s *ArchitectureService) DeleteOrganizationPlaceholder(ctx context.Context, organizationPlaceholderID string) (*http.Response, error) { + err := ValidateRequired(organizationPlaceholderID, "organizationPlaceholderID") + if err != nil { + return nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodDelete, "architecture/organization-placeholders/"+url.PathEscape(organizationPlaceholderID), nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// DeletePattern deletes a pattern. Delete a pattern by id. +// +// API endpoint: DELETE /api/v2/architecture/patterns/{id}. +// Enterprise Edition only. +func (s *ArchitectureService) DeletePattern(ctx context.Context, patternID string) (*http.Response, error) { + err := ValidateRequired(patternID, "patternID") + if err != nil { + return nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodDelete, "architecture/patterns/"+url.PathEscape(patternID), nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// DeleteProjectRelationship deletes a project relationship. Delete a project relationship by id. +// +// API endpoint: DELETE /api/v2/architecture/project-relationships/{id}. +// Enterprise Edition only. +func (s *ArchitectureService) DeleteProjectRelationship(ctx context.Context, projectRelationshipID string) (*http.Response, error) { + err := ValidateRequired(projectRelationshipID, "projectRelationshipID") + if err != nil { + return nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodDelete, "architecture/project-relationships/"+url.PathEscape(projectRelationshipID), nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// DeleteSdk deletes an SDK. Delete the SDK with the given id, together with its boundary +// descriptors. +// +// API endpoint: DELETE /api/v2/architecture/sdks/{id}. +// Enterprise Edition only. +func (s *ArchitectureService) DeleteSdk(ctx context.Context, sdkID string) (*http.Response, error) { + err := ValidateRequired(sdkID, "sdkID") + if err != nil { + return nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodDelete, "architecture/sdks/"+url.PathEscape(sdkID), nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// ListBoundaryDescriptors lists boundary descriptors for a project. Return all boundary descriptors +// for a project sorted by their uuid. May be empty. +// +// API endpoint: GET /api/v2/architecture/boundary-descriptors. +// Enterprise Edition only. +func (s *ArchitectureService) ListBoundaryDescriptors(ctx context.Context, opt *ArchitectureListBoundaryDescriptorsOptions) (*ArchitectureBoundaryDescriptorPagedResponse, *http.Response, error) { + err := s.ValidateListBoundaryDescriptorsOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "architecture/boundary-descriptors", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureBoundaryDescriptorPagedResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetBoundaryDescriptor gets a boundary descriptor by id. Return a single boundary descriptor by +// id. +// +// API endpoint: GET /api/v2/architecture/boundary-descriptors/{id}. +// Enterprise Edition only. +func (s *ArchitectureService) GetBoundaryDescriptor(ctx context.Context, boundaryDescriptorID string) (*ArchitectureBoundaryDescriptor, *http.Response, error) { + err := ValidateRequired(boundaryDescriptorID, "boundaryDescriptorID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "architecture/boundary-descriptors/"+url.PathEscape(boundaryDescriptorID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureBoundaryDescriptor) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// ListExternalInterfaces lists external interfaces. Allow searching for external interfaces within +// an organization, filtering by direction or organization component ID if provided. +// +// API endpoint: GET /api/v2/architecture/external-interfaces. +// Enterprise Edition only. +func (s *ArchitectureService) ListExternalInterfaces(ctx context.Context, opt *ArchitectureListExternalInterfacesOptions) (*ArchitectureExternalInterfaceListResponse, *http.Response, error) { + err := s.ValidateListExternalInterfacesOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "architecture/external-interfaces", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureExternalInterfaceListResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetOrganizationArchitecture gets current architecture for an organization. fetch the current +// architecture for the organization. +// +// API endpoint: GET /api/v2/architecture/organization-architectures. +// Enterprise Edition only. +func (s *ArchitectureService) GetOrganizationArchitecture(ctx context.Context, opt *ArchitectureGetOrganizationArchitectureOptions) (*ArchitectureOrganizationArchitectureResponse, *http.Response, error) { + err := s.ValidateGetOrganizationArchitectureOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "architecture/organization-architectures", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureOrganizationArchitectureResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// ListOrganizationComponents lists organization components. Return the organization components +// (projects and placeholders) for the given organization. Placeholders are included only when the +// placeholder feature is enabled for the organization. +// +// API endpoint: GET /api/v2/architecture/organization-components. +// Enterprise Edition only. +func (s *ArchitectureService) ListOrganizationComponents(ctx context.Context, opt *ArchitectureListOrganizationComponentsOptions) (*ArchitectureOrganizationComponentListResponse, *http.Response, error) { + err := s.ValidateListOrganizationComponentsOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "architecture/organization-components", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureOrganizationComponentListResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetOrganizationComponent gets an organization component by id. Return the organization component +// (project or placeholder) with the given id. +// +// API endpoint: GET /api/v2/architecture/organization-components/{id}. +// Enterprise Edition only. +func (s *ArchitectureService) GetOrganizationComponent(ctx context.Context, organizationComponentID string) (*ArchitectureOrganizationComponent, *http.Response, error) { + err := ValidateRequired(organizationComponentID, "organizationComponentID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "architecture/organization-components/"+url.PathEscape(organizationComponentID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureOrganizationComponent) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// ListOrganizationPlaceholders lists organization placeholders. Return the placeholders for the +// given organization. +// +// API endpoint: GET /api/v2/architecture/organization-placeholders. +// Enterprise Edition only. +func (s *ArchitectureService) ListOrganizationPlaceholders(ctx context.Context, opt *ArchitectureListOrganizationPlaceholdersOptions) (*ArchitectureOrganizationPlaceholderListResponse, *http.Response, error) { + err := s.ValidateListOrganizationPlaceholdersOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "architecture/organization-placeholders", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureOrganizationPlaceholderListResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetOrganizationPlaceholder gets a placeholder by id. Return the placeholder with the given id. +// +// API endpoint: GET /api/v2/architecture/organization-placeholders/{id}. +// Enterprise Edition only. +func (s *ArchitectureService) GetOrganizationPlaceholder(ctx context.Context, organizationPlaceholderID string) (*ArchitectureOrganizationPlaceholder, *http.Response, error) { + err := ValidateRequired(organizationPlaceholderID, "organizationPlaceholderID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "architecture/organization-placeholders/"+url.PathEscape(organizationPlaceholderID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureOrganizationPlaceholder) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// ListPatterns lists patterns for an organization. Return all patterns for an organization. May be +// empty. +// +// API endpoint: GET /api/v2/architecture/patterns. +// Enterprise Edition only. +func (s *ArchitectureService) ListPatterns(ctx context.Context, opt *ArchitectureListPatternsOptions) ([]ArchitecturePattern, *http.Response, error) { + err := s.ValidateListPatternsOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "architecture/patterns", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + var result []ArchitecturePattern + + resp, err := s.client.Do(req, &result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetPattern gets a pattern by id. Return a single pattern by id. +// +// API endpoint: GET /api/v2/architecture/patterns/{id}. +// Enterprise Edition only. +func (s *ArchitectureService) GetPattern(ctx context.Context, patternID string) (*ArchitecturePattern, *http.Response, error) { + err := ValidateRequired(patternID, "patternID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "architecture/patterns/"+url.PathEscape(patternID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitecturePattern) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// ListProjectRelationships lists project relationships for a project. Return all project +// relationships for a project. May be empty. +// +// API endpoint: GET /api/v2/architecture/project-relationships. +// Enterprise Edition only. +func (s *ArchitectureService) ListProjectRelationships(ctx context.Context, opt *ArchitectureListProjectRelationshipsOptions) (*ArchitectureProjectRelationshipPagedResponse, *http.Response, error) { + err := s.ValidateListProjectRelationshipsOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "architecture/project-relationships", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureProjectRelationshipPagedResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetProjectRelationship gets a project relationship by id. Return a single project relationship by +// id. +// +// API endpoint: GET /api/v2/architecture/project-relationships/{id}. +// Enterprise Edition only. +func (s *ArchitectureService) GetProjectRelationship(ctx context.Context, projectRelationshipID string) (*ArchitectureProjectRelationship, *http.Response, error) { + err := ValidateRequired(projectRelationshipID, "projectRelationshipID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "architecture/project-relationships/"+url.PathEscape(projectRelationshipID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureProjectRelationship) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// ListSdks lists SDKs. Return the SDKs defined for the given organization. +// +// API endpoint: GET /api/v2/architecture/sdks. +// Enterprise Edition only. +func (s *ArchitectureService) ListSdks(ctx context.Context, opt *ArchitectureListSdksOptions) (*ArchitectureSdkListResponse, *http.Response, error) { + err := s.ValidateListSdksOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "architecture/sdks", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureSdkListResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetSdk gets an SDK by id. Return the SDK with the given id. +// +// API endpoint: GET /api/v2/architecture/sdks/{id}. +// Enterprise Edition only. +func (s *ArchitectureService) GetSdk(ctx context.Context, sdkID string) (*ArchitectureSdk, *http.Response, error) { + err := ValidateRequired(sdkID, "sdkID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "architecture/sdks/"+url.PathEscape(sdkID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureSdk) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// UpdateBoundaryDescriptor updates a boundary descriptor. Update a boundary descriptor. All mutable +// fields must be present in the body. +// +// API endpoint: PATCH /api/v2/architecture/boundary-descriptors/{id}. +// Enterprise Edition only. +func (s *ArchitectureService) UpdateBoundaryDescriptor(ctx context.Context, boundaryDescriptorID string, opt *ArchitectureUpdateBoundaryDescriptorOptions) (*ArchitectureBoundaryDescriptor, *http.Response, error) { + err := ValidateRequired(boundaryDescriptorID, "boundaryDescriptorID") + if err != nil { + return nil, nil, err + } + + err = s.ValidateUpdateBoundaryDescriptorOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPatch, "architecture/boundary-descriptors/"+url.PathEscape(boundaryDescriptorID), nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureBoundaryDescriptor) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// UpdateOrganizationPlaceholder updates a placeholder. Update the mutable fields of a placeholder. +// +// API endpoint: PATCH /api/v2/architecture/organization-placeholders/{id}. +// Enterprise Edition only. +func (s *ArchitectureService) UpdateOrganizationPlaceholder(ctx context.Context, organizationPlaceholderID string, opt *ArchitectureUpdateOrganizationPlaceholderOptions) (*ArchitectureOrganizationPlaceholder, *http.Response, error) { + err := ValidateRequired(organizationPlaceholderID, "organizationPlaceholderID") + if err != nil { + return nil, nil, err + } + + err = s.ValidateUpdateOrganizationPlaceholderOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPatch, "architecture/organization-placeholders/"+url.PathEscape(organizationPlaceholderID), nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureOrganizationPlaceholder) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// UpdatePattern updates a pattern. Update pattern data and/or name. +// +// API endpoint: PATCH /api/v2/architecture/patterns/{id}. +// Enterprise Edition only. +func (s *ArchitectureService) UpdatePattern(ctx context.Context, patternID string, opt *ArchitectureUpdatePatternOptions) (*ArchitecturePattern, *http.Response, error) { + err := ValidateRequired(patternID, "patternID") + if err != nil { + return nil, nil, err + } + + if opt == nil { + return nil, nil, NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPatch, "architecture/patterns/"+url.PathEscape(patternID), nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitecturePattern) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// UpdateProjectRelationship updates a project relationship. Update project relationship fields. +// +// API endpoint: PATCH /api/v2/architecture/project-relationships/{id}. +// Enterprise Edition only. +func (s *ArchitectureService) UpdateProjectRelationship(ctx context.Context, projectRelationshipID string, opt *ArchitectureUpdateProjectRelationshipOptions) (*ArchitectureProjectRelationship, *http.Response, error) { + err := ValidateRequired(projectRelationshipID, "projectRelationshipID") + if err != nil { + return nil, nil, err + } + + if opt == nil { + return nil, nil, NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPatch, "architecture/project-relationships/"+url.PathEscape(projectRelationshipID), nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureProjectRelationship) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// UpdateSdk updates an SDK. Update the mutable fields of an SDK. +// +// API endpoint: PATCH /api/v2/architecture/sdks/{id}. +// Enterprise Edition only. +func (s *ArchitectureService) UpdateSdk(ctx context.Context, sdkID string, opt *ArchitectureUpdateSdkOptions) (*ArchitectureSdk, *http.Response, error) { + err := ValidateRequired(sdkID, "sdkID") + if err != nil { + return nil, nil, err + } + + if opt == nil { + return nil, nil, NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPatch, "architecture/sdks/"+url.PathEscape(sdkID), nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureSdk) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// CreateBoundaryDescriptor stores a boundary descriptor. +// +// API endpoint: POST /api/v2/architecture/boundary-descriptors. +// Enterprise Edition only. +func (s *ArchitectureService) CreateBoundaryDescriptor(ctx context.Context, opt *ArchitectureCreateBoundaryDescriptorOptions) (*ArchitectureBoundaryDescriptor, *http.Response, error) { + err := s.ValidateCreateBoundaryDescriptorOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "architecture/boundary-descriptors", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureBoundaryDescriptor) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// CreateOrganizationPlaceholder creates a placeholder. Create a new placeholder for the given +// organization. +// +// API endpoint: POST /api/v2/architecture/organization-placeholders. +// Enterprise Edition only. +func (s *ArchitectureService) CreateOrganizationPlaceholder(ctx context.Context, opt *ArchitectureCreateOrganizationPlaceholderOptions) (*ArchitectureOrganizationPlaceholder, *http.Response, error) { + err := s.ValidateCreateOrganizationPlaceholderOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "architecture/organization-placeholders", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureOrganizationPlaceholder) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// CreatePattern creates a pattern. Create a pattern and return it including the generated id. +// +// API endpoint: POST /api/v2/architecture/patterns. +// Enterprise Edition only. +func (s *ArchitectureService) CreatePattern(ctx context.Context, opt *ArchitectureCreatePatternOptions) (*ArchitecturePattern, *http.Response, error) { + err := s.ValidateCreatePatternOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "architecture/patterns", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitecturePattern) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// CreateProjectRelationship stores a project relationship. +// +// API endpoint: POST /api/v2/architecture/project-relationships. +// Enterprise Edition only. +func (s *ArchitectureService) CreateProjectRelationship(ctx context.Context, opt *ArchitectureCreateProjectRelationshipOptions) (*ArchitectureProjectRelationship, *http.Response, error) { + err := s.ValidateCreateProjectRelationshipOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "architecture/project-relationships", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureProjectRelationship) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// CreateSdk creates an SDK. Create a new SDK for the given organization. +// +// API endpoint: POST /api/v2/architecture/sdks. +// Enterprise Edition only. +func (s *ArchitectureService) CreateSdk(ctx context.Context, opt *ArchitectureCreateSdkOptions) (*ArchitectureSdk, *http.Response, error) { + err := s.ValidateCreateSdkOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "architecture/sdks", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ArchitectureSdk) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} diff --git a/sonar/architecture_organization_v2_service_test.go b/sonar/architecture_organization_v2_service_test.go new file mode 100644 index 0000000..f9b4135 --- /dev/null +++ b/sonar/architecture_organization_v2_service_test.go @@ -0,0 +1,498 @@ +package sonar + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestArchitectureService_DeleteBoundaryDescriptor(t *testing.T) { + server := newTestServer(t, mockEmptyHandler(t, http.MethodDelete, "/v2/architecture/boundary-descriptors/test-id", http.StatusNoContent)) + client := newTestClient(t, server.url()) + + resp, err := client.V2.Architecture.DeleteBoundaryDescriptor(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusNoContent, resp.StatusCode) +} + +func TestArchitectureService_DeleteBoundaryDescriptor_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, err := client.V2.Architecture.DeleteBoundaryDescriptor(context.Background(), "") + require.Error(t, err) +} + +func TestArchitectureService_DeleteOrganizationPlaceholder(t *testing.T) { + server := newTestServer(t, mockEmptyHandler(t, http.MethodDelete, "/v2/architecture/organization-placeholders/test-id", http.StatusNoContent)) + client := newTestClient(t, server.url()) + + resp, err := client.V2.Architecture.DeleteOrganizationPlaceholder(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusNoContent, resp.StatusCode) +} + +func TestArchitectureService_DeleteOrganizationPlaceholder_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, err := client.V2.Architecture.DeleteOrganizationPlaceholder(context.Background(), "") + require.Error(t, err) +} + +func TestArchitectureService_DeletePattern(t *testing.T) { + server := newTestServer(t, mockEmptyHandler(t, http.MethodDelete, "/v2/architecture/patterns/test-id", http.StatusNoContent)) + client := newTestClient(t, server.url()) + + resp, err := client.V2.Architecture.DeletePattern(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusNoContent, resp.StatusCode) +} + +func TestArchitectureService_DeletePattern_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, err := client.V2.Architecture.DeletePattern(context.Background(), "") + require.Error(t, err) +} + +func TestArchitectureService_DeleteProjectRelationship(t *testing.T) { + server := newTestServer(t, mockEmptyHandler(t, http.MethodDelete, "/v2/architecture/project-relationships/test-id", http.StatusNoContent)) + client := newTestClient(t, server.url()) + + resp, err := client.V2.Architecture.DeleteProjectRelationship(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusNoContent, resp.StatusCode) +} + +func TestArchitectureService_DeleteProjectRelationship_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, err := client.V2.Architecture.DeleteProjectRelationship(context.Background(), "") + require.Error(t, err) +} + +func TestArchitectureService_DeleteSdk(t *testing.T) { + server := newTestServer(t, mockEmptyHandler(t, http.MethodDelete, "/v2/architecture/sdks/test-id", http.StatusNoContent)) + client := newTestClient(t, server.url()) + + resp, err := client.V2.Architecture.DeleteSdk(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusNoContent, resp.StatusCode) +} + +func TestArchitectureService_DeleteSdk_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, err := client.V2.Architecture.DeleteSdk(context.Background(), "") + require.Error(t, err) +} + +func TestArchitectureService_ListBoundaryDescriptors(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/architecture/boundary-descriptors", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.ListBoundaryDescriptors(context.Background(), &ArchitectureListBoundaryDescriptorsOptions{ProjectId: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_ListBoundaryDescriptors_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.ListBoundaryDescriptors(context.Background(), nil) + require.Error(t, err) +} + +func TestArchitectureService_GetBoundaryDescriptor(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/architecture/boundary-descriptors/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.GetBoundaryDescriptor(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_GetBoundaryDescriptor_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.GetBoundaryDescriptor(context.Background(), "") + require.Error(t, err) +} + +func TestArchitectureService_ListExternalInterfaces(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/architecture/external-interfaces", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.ListExternalInterfaces(context.Background(), &ArchitectureListExternalInterfacesOptions{OrganizationId: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_ListExternalInterfaces_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.ListExternalInterfaces(context.Background(), nil) + require.Error(t, err) +} + +func TestArchitectureService_GetOrganizationArchitecture(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/architecture/organization-architectures", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.GetOrganizationArchitecture(context.Background(), &ArchitectureGetOrganizationArchitectureOptions{OrganizationId: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_GetOrganizationArchitecture_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.GetOrganizationArchitecture(context.Background(), nil) + require.Error(t, err) +} + +func TestArchitectureService_ListOrganizationComponents(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/architecture/organization-components", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.ListOrganizationComponents(context.Background(), &ArchitectureListOrganizationComponentsOptions{OrganizationId: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_ListOrganizationComponents_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.ListOrganizationComponents(context.Background(), nil) + require.Error(t, err) +} + +func TestArchitectureService_GetOrganizationComponent(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/architecture/organization-components/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.GetOrganizationComponent(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_GetOrganizationComponent_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.GetOrganizationComponent(context.Background(), "") + require.Error(t, err) +} + +func TestArchitectureService_ListOrganizationPlaceholders(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/architecture/organization-placeholders", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.ListOrganizationPlaceholders(context.Background(), &ArchitectureListOrganizationPlaceholdersOptions{OrganizationId: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_ListOrganizationPlaceholders_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.ListOrganizationPlaceholders(context.Background(), nil) + require.Error(t, err) +} + +func TestArchitectureService_GetOrganizationPlaceholder(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/architecture/organization-placeholders/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.GetOrganizationPlaceholder(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_GetOrganizationPlaceholder_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.GetOrganizationPlaceholder(context.Background(), "") + require.Error(t, err) +} + +func TestArchitectureService_ListPatterns(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/architecture/patterns", http.StatusOK, []any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.ListPatterns(context.Background(), &ArchitectureListPatternsOptions{OrganizationId: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_ListPatterns_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.ListPatterns(context.Background(), nil) + require.Error(t, err) +} + +func TestArchitectureService_GetPattern(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/architecture/patterns/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.GetPattern(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_GetPattern_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.GetPattern(context.Background(), "") + require.Error(t, err) +} + +func TestArchitectureService_ListProjectRelationships(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/architecture/project-relationships", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.ListProjectRelationships(context.Background(), &ArchitectureListProjectRelationshipsOptions{ProjectId: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_ListProjectRelationships_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.ListProjectRelationships(context.Background(), nil) + require.Error(t, err) +} + +func TestArchitectureService_GetProjectRelationship(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/architecture/project-relationships/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.GetProjectRelationship(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_GetProjectRelationship_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.GetProjectRelationship(context.Background(), "") + require.Error(t, err) +} + +func TestArchitectureService_ListSdks(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/architecture/sdks", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.ListSdks(context.Background(), &ArchitectureListSdksOptions{OrganizationId: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_ListSdks_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.ListSdks(context.Background(), nil) + require.Error(t, err) +} + +func TestArchitectureService_GetSdk(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/architecture/sdks/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.GetSdk(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_GetSdk_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.GetSdk(context.Background(), "") + require.Error(t, err) +} + +func TestArchitectureService_UpdateBoundaryDescriptor(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPatch, "/v2/architecture/boundary-descriptors/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.UpdateBoundaryDescriptor(context.Background(), "test-id", &ArchitectureUpdateBoundaryDescriptorOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_UpdateBoundaryDescriptor_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.UpdateBoundaryDescriptor(context.Background(), "", nil) + require.Error(t, err) +} + +func TestArchitectureService_UpdateOrganizationPlaceholder(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPatch, "/v2/architecture/organization-placeholders/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.UpdateOrganizationPlaceholder(context.Background(), "test-id", &ArchitectureUpdateOrganizationPlaceholderOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_UpdateOrganizationPlaceholder_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.UpdateOrganizationPlaceholder(context.Background(), "", nil) + require.Error(t, err) +} + +func TestArchitectureService_UpdatePattern(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPatch, "/v2/architecture/patterns/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.UpdatePattern(context.Background(), "test-id", &ArchitectureUpdatePatternOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_UpdatePattern_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.UpdatePattern(context.Background(), "", nil) + require.Error(t, err) +} + +func TestArchitectureService_UpdateProjectRelationship(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPatch, "/v2/architecture/project-relationships/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.UpdateProjectRelationship(context.Background(), "test-id", &ArchitectureUpdateProjectRelationshipOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_UpdateProjectRelationship_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.UpdateProjectRelationship(context.Background(), "", nil) + require.Error(t, err) +} + +func TestArchitectureService_UpdateSdk(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPatch, "/v2/architecture/sdks/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.UpdateSdk(context.Background(), "test-id", &ArchitectureUpdateSdkOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_UpdateSdk_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.UpdateSdk(context.Background(), "", nil) + require.Error(t, err) +} + +func TestArchitectureService_CreateBoundaryDescriptor(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/architecture/boundary-descriptors", http.StatusCreated, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.CreateBoundaryDescriptor(context.Background(), &ArchitectureCreateBoundaryDescriptorOptions{Ecosystem: "java", KeyTemplate: "test", Query: "test", Direction: "EXIT_POINT", ProjectId: "test", Name: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_CreateBoundaryDescriptor_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.CreateBoundaryDescriptor(context.Background(), nil) + require.Error(t, err) +} + +func TestArchitectureService_CreateOrganizationPlaceholder(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/architecture/organization-placeholders", http.StatusCreated, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.CreateOrganizationPlaceholder(context.Background(), &ArchitectureCreateOrganizationPlaceholderOptions{DisplayName: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_CreateOrganizationPlaceholder_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.CreateOrganizationPlaceholder(context.Background(), nil) + require.Error(t, err) +} + +func TestArchitectureService_CreatePattern(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/architecture/patterns", http.StatusCreated, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.CreatePattern(context.Background(), &ArchitectureCreatePatternOptions{OrganizationId: "test", Pattern: ArchitecturePatternData{}, Name: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_CreatePattern_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.CreatePattern(context.Background(), nil) + require.Error(t, err) +} + +func TestArchitectureService_CreateProjectRelationship(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/architecture/project-relationships", http.StatusCreated, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.CreateProjectRelationship(context.Background(), &ArchitectureCreateProjectRelationshipOptions{BoundaryKey: "test", ProjectId: "test", TargetComponentId: "test", TargetEntryPointKey: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_CreateProjectRelationship_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.CreateProjectRelationship(context.Background(), nil) + require.Error(t, err) +} + +func TestArchitectureService_CreateSdk(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/architecture/sdks", http.StatusCreated, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Architecture.CreateSdk(context.Background(), &ArchitectureCreateSdkOptions{TargetComponentId: "test", SdkBoundaryDescriptors: []ArchitectureSdkBoundaryDescriptor{{}}, Name: "test", SdkKey: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestArchitectureService_CreateSdk_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Architecture.CreateSdk(context.Background(), nil) + require.Error(t, err) +} diff --git a/sonar/architecture_v2_service.go b/sonar/architecture_v2_service.go index 9960cd6..7e26620 100644 --- a/sonar/architecture_v2_service.go +++ b/sonar/architecture_v2_service.go @@ -799,7 +799,7 @@ type ArchitectureProjectConfiguration struct { } // ArchitectureProjectConfigurationsOptions contains parameters for the -// GetProjectConfigurations and GetPrivateProjectConfigurations methods. At least +// GetProjectConfigurations method. At least // one of ProjectId or ProjectKey should be provided. type ArchitectureProjectConfigurationsOptions struct { // ProjectId filters by project identifier. Optional. @@ -816,16 +816,6 @@ func (s *ArchitectureService) GetProjectConfigurations(ctx context.Context, opt return s.getProjectConfigurations(ctx, "architecture/project-configurations", opt) } -// GetPrivateProjectConfigurations returns architecture configurations for one or -// more projects via the internal variant of the endpoint. -// -// API endpoint: GET /api/v2/architecture/private/architecture/project-configurations. -// Enterprise Edition only. Marked internal by SonarQube and subject to change -// without notice. -func (s *ArchitectureService) GetPrivateProjectConfigurations(ctx context.Context, opt *ArchitectureProjectConfigurationsOptions) ([]ArchitectureProjectConfiguration, *http.Response, error) { - return s.getProjectConfigurations(ctx, "architecture/private/architecture/project-configurations", opt) -} - // ----------------------------------------------------------------------------- // Snapshots // ----------------------------------------------------------------------------- diff --git a/sonar/architecture_v2_service_test.go b/sonar/architecture_v2_service_test.go index 14c480c..f707fc6 100644 --- a/sonar/architecture_v2_service_test.go +++ b/sonar/architecture_v2_service_test.go @@ -483,18 +483,6 @@ func TestArchitectureService_GetProjectConfigurations(t *testing.T) { assert.Equal(t, []string{"b1"}, result[0].BoundaryDescriptors) } -func TestArchitectureService_GetPrivateProjectConfigurations(t *testing.T) { - server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/architecture/private/architecture/project-configurations", http.StatusOK, - []map[string]any{{"projectId": "p1"}})) - client := newTestClient(t, server.URL) - - result, resp, err := client.V2.Architecture.GetPrivateProjectConfigurations(context.Background(), &ArchitectureProjectConfigurationsOptions{ProjectId: "p1"}) - require.NoError(t, err) - assert.Equal(t, http.StatusOK, resp.StatusCode) - require.Len(t, result, 1) - assert.Equal(t, "p1", result[0].ProjectId) -} - // ----------------------------------------------------------------------------- // Snapshots // ----------------------------------------------------------------------------- diff --git a/sonar/billing_v2_service.go b/sonar/billing_v2_service.go new file mode 100644 index 0000000..f9f428d --- /dev/null +++ b/sonar/billing_v2_service.go @@ -0,0 +1,336 @@ +package sonar + +import ( + "context" + "fmt" + "net/http" +) + +// BillingService handles communication with the billing, entitlement checks and consumption related methods of the +// SonarQube V2 API. This service is only available in Enterprise Edition. +type BillingService struct { + // client is used to communicate with the SonarQube API. + client *Client +} + +//nolint:gochecknoglobals // constant sets of allowed values +var ( + // allowedBillingGetEntitlementCheckResourceType is the set of allowed values for the corresponding option. + allowedBillingGetEntitlementCheckResourceType = map[string]struct{}{ + "organization": {}, + } + // allowedBillingCreateConsumptionRecordResourceType is the set of allowed values for the corresponding option. + allowedBillingCreateConsumptionRecordResourceType = map[string]struct{}{ + "organization": {}, + } +) + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// BillingGetEntitlementCheckOptions contains parameters for the GetEntitlementCheck method. +type BillingGetEntitlementCheckOptions struct { + // FeatureKey feature key to check entitlement for. This field is required. + FeatureKey string `json:"featureKey"` + // ResourceId organization ID, for compatibility with SQC. + ResourceId string `json:"resourceId,omitempty"` + // ResourceType resource type, only 'organization' is supported, for compatibility with SQC. + // Allowed values: organization. + // Default: organization. + ResourceType string `json:"resourceType,omitempty"` +} + +// BillingLimitRestResponse represents the LimitRestResponse object of the SonarQube V2 API. +type BillingLimitRestResponse struct { + // Base consumption limit for the feature. + Base int64 `json:"base,omitempty"` + // Overage consumption limit for the feature. + Overage int64 `json:"overage,omitempty"` + // OverageEnabled whether overage is enabled for the feature. + OverageEnabled bool `json:"overageEnabled,omitempty"` +} + +// BillingPeriodRestResponse represents the PeriodRestResponse object of the SonarQube V2 API. +type BillingPeriodRestResponse struct { + // Cadence metering cadence (DAILY, WEEKLY, MONTHLY, ANNUAL, PERPETUAL). + Cadence string `json:"cadence,omitempty"` + // Anchor optional period window anchor, e.g. 2026-07 for MONTHLY. + Anchor string `json:"anchor,omitempty"` +} + +// BillingMeteringRestResponse represents the MeteringRestResponse object of the SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type BillingMeteringRestResponse struct { + // Used amount of credits already used for the feature. + Used int64 `json:"used,omitempty"` + // Period information for the metering. + Period BillingPeriodRestResponse `json:"period,omitzero"` +} + +// BillingConsumptionRestResponse represents the ConsumptionRestResponse object of the SonarQube V2 +// API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type BillingConsumptionRestResponse struct { + // Allowed whether consumption of the feature is allowed, also check for the entitled property. + Allowed bool `json:"allowed,omitempty"` + // Limit consumption limits for the feature. + Limit BillingLimitRestResponse `json:"limit,omitzero"` + // Metering consumption metering for the feature. + Metering BillingMeteringRestResponse `json:"metering,omitzero"` +} + +// BillingEntitlementRestResponse represents the EntitlementRestResponse object of the SonarQube V2 +// API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type BillingEntitlementRestResponse struct { + // FeatureKey internal feature key. + FeatureKey string `json:"featureKey,omitempty"` + // Entitled whether the feature is entitled for use, also check for the consumption.allowed + // property if available. + Entitled bool `json:"entitled,omitempty"` + // Consumption information for a given feature, only available for CONSUMABLE features. + Consumption BillingConsumptionRestResponse `json:"consumption,omitzero"` + // Value for the feature. + Value int64 `json:"value,omitempty"` + // ExcludedValues excluded values for the feature. + ExcludedValues []string `json:"excludedValues,omitempty"` +} + +// BillingCreateConsumptionRecordOptions contains the request body for the CreateConsumptionRecord +// method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type BillingCreateConsumptionRecordOptions struct { + // FeatureKey feature key for the consumed CONSUMABLE feature. This field is required. + FeatureKey string `json:"featureKey"` + // ResourceId organization ID, for compatibility with SQC. + ResourceId string `json:"resourceId,omitempty"` + // ResourceType resource type, only 'organization' is supported, for compatibility with SQC. + // Allowed values: organization. + ResourceType string `json:"resourceType,omitempty"` + // Consumed units consumed in this event. + Consumed *int64 `json:"consumed,omitempty"` +} + +// BillingConsumptionRecordRestResponse represents the ConsumptionRecordRestResponse object of the +// SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type BillingConsumptionRecordRestResponse struct { + // RecordId transient acceptance/tracing id. Not persisted and not a lookup key; retries + // receive a new value. + RecordId string `json:"recordId,omitempty"` + // FeatureKey feature key that was recorded. + FeatureKey string `json:"featureKey,omitempty"` + // ResourceId organization ID the consumption was recorded against. + ResourceId string `json:"resourceId,omitempty"` + // ResourceType resource type the consumption was recorded against. Allowed values: + // organization. + ResourceType string `json:"resourceType,omitempty"` + // Consumed units consumed in this event. + Consumed int64 `json:"consumed,omitempty"` + // ConsumedAt when the consumption was accepted. + ConsumedAt string `json:"consumedAt,omitempty"` +} + +// BillingSetOverageOptions contains the request body for the SetOverage method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type BillingSetOverageOptions struct { + // FeatureKey feature key for the overage-eligible feature. This field is required. + FeatureKey string `json:"featureKey"` + // Enabled whether overage is enabled for this feature. This field is required. + Enabled bool `json:"enabled"` + // Limit maximum overage units, required when enabling. + Limit *int64 `json:"limit,omitempty"` + // TermsAccepted must be true when enabling, confirms acceptance of overage terms. + TermsAccepted *bool `json:"termsAccepted,omitempty"` +} + +// BillingLicenseFeatureRestResponse represents the LicenseFeatureRestResponse object of the +// SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type BillingLicenseFeatureRestResponse struct { + // Name is the name. + Name string `json:"name,omitempty"` + // Parent is the parent. + Parent string `json:"parent,omitempty"` + // StartDate is the start date. + StartDate string `json:"startDate,omitempty"` + // EndDate is the end date. + EndDate string `json:"endDate,omitempty"` + // FeatureKey is the feature key. + FeatureKey string `json:"featureKey,omitempty"` + // MaxConsumption is the max consumption. + MaxConsumption int64 `json:"maxConsumption,omitempty"` + // MaxOverage is the max overage. + MaxOverage int64 `json:"maxOverage,omitempty"` + // OverageState is the overage state. Allowed values: NOT_ELIGIBLE, ELIGIBLE, NOT_ENABLED, + // ENABLED, OFFLINE_BLOCKED. + OverageState string `json:"overageState,omitempty"` + // OveragesStep is the overages step. + OveragesStep int64 `json:"overagesStep,omitempty"` + // OverageGraceDays is the overage grace days. + OverageGraceDays int32 `json:"overageGraceDays,omitempty"` + // OverageUnitPrice is the overage unit price. + OverageUnitPrice float64 `json:"overageUnitPrice,omitempty"` + // OverageUnitCurrency is the overage unit currency. + OverageUnitCurrency string `json:"overageUnitCurrency,omitempty"` + // OverageLimit is the overage limit. + OverageLimit int64 `json:"overageLimit,omitempty"` + // MaxConsumptionUnit is the max consumption unit. + MaxConsumptionUnit string `json:"maxConsumptionUnit,omitempty"` +} + +// ----------------------------------------------------------------------------- +// Validation Functions +// ----------------------------------------------------------------------------- + +// ValidateGetEntitlementCheckOpt validates the options for the GetEntitlementCheck method. +func (s *BillingService) ValidateGetEntitlementCheckOpt(opt *BillingGetEntitlementCheckOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.FeatureKey, "FeatureKey") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.ResourceType, allowedBillingGetEntitlementCheckResourceType, "ResourceType") + if err != nil { + return err + } + + return nil +} + +// ValidateCreateConsumptionRecordOpt validates the options for the CreateConsumptionRecord method. +func (s *BillingService) ValidateCreateConsumptionRecordOpt(opt *BillingCreateConsumptionRecordOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.FeatureKey, "FeatureKey") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.ResourceType, allowedBillingCreateConsumptionRecordResourceType, "ResourceType") + if err != nil { + return err + } + + return nil +} + +// ValidateSetOverageOpt validates the options for the SetOverage method. +func (s *BillingService) ValidateSetOverageOpt(opt *BillingSetOverageOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.FeatureKey, "FeatureKey") + if err != nil { + return err + } + + return nil +} + +// ----------------------------------------------------------------------------- +// Service Methods +// ----------------------------------------------------------------------------- + +// GetEntitlementCheck returns the entitlement status for a given feature. Returns live entitlement +// status for a single feature. Used to get entitlement status, including consumption and remaining +// quota for CONSUMABLE features. Requires authentication. +// +// API endpoint: GET /api/v2/billing/entitlement-checks. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *BillingService) GetEntitlementCheck(ctx context.Context, opt *BillingGetEntitlementCheckOptions) (*BillingEntitlementRestResponse, *http.Response, error) { + err := s.ValidateGetEntitlementCheckOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "billing/entitlement-checks", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(BillingEntitlementRestResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// CreateConsumptionRecord records a consumption event for a CONSUMABLE feature. Records usage after +// a billable action. Does not enforce caps — use entitlement-checks first. Returns 202 because +// drain and LicenseSpring reporting remain asynchronous. Requires authentication. +// +// API endpoint: POST /api/v2/billing/consumption-records. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *BillingService) CreateConsumptionRecord(ctx context.Context, idempotencyKey string, opt *BillingCreateConsumptionRecordOptions) (*BillingConsumptionRecordRestResponse, *http.Response, error) { + err := s.ValidateCreateConsumptionRecordOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "billing/consumption-records", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + if idempotencyKey != "" { + req.Header.Set("Idempotency-Key", idempotencyKey) + } + + result := new(BillingConsumptionRecordRestResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// SetOverage enables,s updates, or disables the admin's overage arrangement for a feature. Enables, +// updates, or disables the overage arrangement for a feature. A single POST covers all three +// actions. Requires system administrator privileges. +// +// API endpoint: POST /api/v2/billing/overage. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *BillingService) SetOverage(ctx context.Context, opt *BillingSetOverageOptions) (*BillingLicenseFeatureRestResponse, *http.Response, error) { + err := s.ValidateSetOverageOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "billing/overage", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(BillingLicenseFeatureRestResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} diff --git a/sonar/billing_v2_service_test.go b/sonar/billing_v2_service_test.go new file mode 100644 index 0000000..e8fe1b4 --- /dev/null +++ b/sonar/billing_v2_service_test.go @@ -0,0 +1,61 @@ +package sonar + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBillingService_GetEntitlementCheck(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/billing/entitlement-checks", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Billing.GetEntitlementCheck(context.Background(), &BillingGetEntitlementCheckOptions{FeatureKey: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestBillingService_GetEntitlementCheck_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Billing.GetEntitlementCheck(context.Background(), nil) + require.Error(t, err) +} + +func TestBillingService_CreateConsumptionRecord(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/billing/consumption-records", http.StatusAccepted, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Billing.CreateConsumptionRecord(context.Background(), "test-value", &BillingCreateConsumptionRecordOptions{FeatureKey: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusAccepted, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestBillingService_CreateConsumptionRecord_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Billing.CreateConsumptionRecord(context.Background(), "", nil) + require.Error(t, err) +} + +func TestBillingService_SetOverage(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/billing/overage", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Billing.SetOverage(context.Background(), &BillingSetOverageOptions{FeatureKey: "test", Enabled: true}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestBillingService_SetOverage_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Billing.SetOverage(context.Background(), nil) + require.Error(t, err) +} diff --git a/sonar/cag_v2_service.go b/sonar/cag_v2_service.go new file mode 100644 index 0000000..72ed57e --- /dev/null +++ b/sonar/cag_v2_service.go @@ -0,0 +1,820 @@ +package sonar + +import ( + "context" + "fmt" + "net/http" + "net/url" +) + +// CagService handles communication with the Context Augmentation (CAG) related methods of the +// SonarQube V2 API. This service is only available in Enterprise Edition. +type CagService struct { + // client is used to communicate with the SonarQube API. + client *Client +} + +//nolint:gochecknoglobals // constant sets of allowed values +var ( + // allowedCagGetUsageStatsPeriod is the set of allowed values for the corresponding option. + allowedCagGetUsageStatsPeriod = map[string]struct{}{ + "LAST_DAY": {}, + "LAST_7_DAYS": {}, + "LAST_30_DAYS": {}, + } + // allowedCagRecordImpactEventEventType is the set of allowed values for the corresponding option. + allowedCagRecordImpactEventEventType = map[string]struct{}{ + "NAVIGATION": {}, + "OUTPUT_COMPRESSION": {}, + "OUTPUT_RESTORE": {}, + "GUIDELINES": {}, + "DEPENDENCY_CHECK": {}, + "INTENDED_ARCHITECTURE": {}, + } + // allowedCagRecordImpactEventTransport is the set of allowed values for the corresponding option. + allowedCagRecordImpactEventTransport = map[string]struct{}{ + "MCP": {}, + "CLI": {}, + } +) + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// CagCagEntitlementConsumption represents the CagEntitlementConsumption object of the SonarQube V2 +// API. +type CagCagEntitlementConsumption struct { + // Consumed is the consumed. + Consumed int64 `json:"consumed,omitempty"` + // Limit is the limit. + Limit int64 `json:"limit,omitempty"` +} + +// CagCagEntitlement represents the CagEntitlement object of the SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type CagCagEntitlement struct { + // Allowed whether the organization is currently allowed to use Context Augmentation. + Allowed bool `json:"allowed,omitempty"` + // HasEntitlement whether the organization has any valid access path to Context Augmentation. + // When allowed is false, true means the organization is still entitled but cannot use it right + // now, typically because it has reached its current limit; false means the organization is not + // entitled at all, or Context Augmentation is disabled instance-wide (reason disabled), in + // which case entitlement is not evaluated. See reason for the exact cause. + HasEntitlement bool `json:"hasEntitlement,omitempty"` + // FeatureKey feature key to use for consumption reporting when relevant. This is always + // 'contextAugmentation' for the CAG entitlement response. + FeatureKey string `json:"featureKey,omitempty"` + // Consumption is the consumption. + Consumption CagCagEntitlementConsumption `json:"consumption,omitzero"` + // Reason is the reason. Allowed values: not_entitled, disabled, blocked. + Reason string `json:"reason,omitempty"` +} + +// CagGetUsageStatsOptions contains parameters for the GetUsageStats method. +type CagGetUsageStatsOptions struct { + // Period for the usage statistics. This field is required. Allowed values: LAST_DAY, + // LAST_7_DAYS, LAST_30_DAYS. + Period string `json:"period"` +} + +// CagProjectCagUsageCount represents a SonarQube V2 API object. +// +// CAG tool call count for a project. +type CagProjectCagUsageCount struct { + // Identifier of the project; an opaque string on SonarQube Server, a UUID string on SonarQube + // Cloud. + Id string `json:"id,omitempty"` + // Name the project name. + Name string `json:"name,omitempty"` + // ToolCallCount number of CAG tool calls for the project over the requested period. + ToolCallCount int64 `json:"toolCallCount,omitempty"` +} + +// CagCagUsageStats represents a SonarQube V2 API object. +// +// CAG tool usage statistics for an organization. +type CagCagUsageStats struct { + // Id the organization ID these statistics are about. + Id string `json:"id,omitempty"` + // Projects all projects with their CAG tool call count, ordered by tool call count descending. + Projects []CagProjectCagUsageCount `json:"projects,omitempty"` +} + +// CagGetGuideMetricsOptions contains parameters for the GetGuideMetrics method. +type CagGetGuideMetricsOptions struct { + // OrganizationId UUID v4 of the organization. This field is required. + OrganizationId string `json:"organizationId"` + // From inclusive start of the calendar date range (YYYY-MM-DD, UTC). This field is required. + From string `json:"from"` + // To inclusive end of the calendar date range (YYYY-MM-DD, UTC). Must not precede from. This + // field is required. + To string `json:"to"` +} + +// CagGuideMetrics represents a SonarQube V2 API object. +// +// Guide impact metrics for the requested period. +type CagGuideMetrics struct { + // PreventedIssues estimated issues prevented by supplied guidelines, rounded to a whole + // number. Each successful get_guidelines call that returns at least one rule contributes + // N_prevented. + PreventedIssues int64 `json:"preventedIssues,omitempty"` + // TokensSaved estimated tokens saved by navigation tools and output compression, preserving + // fractional values. Aggregate estimates before rounding for display. + TokensSaved float64 `json:"tokensSaved,omitempty"` + // EngineeringMinutesSaved guide contribution to estimated engineering time saved in minutes, + // preserving fractional values. Add this to the verifyMetrics value before rounding the + // combined total for display. + EngineeringMinutesSaved float64 `json:"engineeringMinutesSaved,omitempty"` +} + +// CagGetProjectActivityOptions contains parameters for the GetProjectActivity method. +type CagGetProjectActivityOptions struct { + // OrganizationId UUID v4 of the organization. This field is required. + OrganizationId string `json:"organizationId"` + // From inclusive start of the calendar date range (YYYY-MM-DD, UTC). This field is required. + From string `json:"from"` + // To inclusive end of the calendar date range (YYYY-MM-DD, UTC). Must not precede from. This + // field is required. + To string `json:"to"` +} + +// CagVortexProjectUsage represents a SonarQube V2 API object. +// +// Projects classified by successful Vortex usage during the requested period. +type CagVortexProjectUsage struct { + // WithUsageProjectCount projects with successful CAG or Verify usage. + WithUsageProjectCount int32 `json:"withUsageProjectCount,omitempty"` + // WithoutUsageProjectCount projects without successful CAG or Verify usage. + WithoutUsageProjectCount int32 `json:"withoutUsageProjectCount,omitempty"` + // CagOnlyProjectCount projects with successful CAG usage but no successful Verify usage. + CagOnlyProjectCount int32 `json:"cagOnlyProjectCount,omitempty"` + // VerifyOnlyProjectCount projects with successful Verify usage but no successful CAG usage. + VerifyOnlyProjectCount int32 `json:"verifyOnlyProjectCount,omitempty"` + // CagAndVerifyProjectCount projects with both successful CAG and Verify usage. + CagAndVerifyProjectCount int32 `json:"cagAndVerifyProjectCount,omitempty"` +} + +// CagVortexProjectAnalysisCohort represents a SonarQube V2 API object. +// +// Project count and Vortex usage for one analysis cohort. +type CagVortexProjectAnalysisCohort struct { + // ProjectCount projects in this analysis cohort. + ProjectCount int32 `json:"projectCount,omitempty"` + // VortexUsage is the vortex usage. + VortexUsage CagVortexProjectUsage `json:"vortexUsage,omitzero"` +} + +// CagVortexProjectActivity represents a SonarQube V2 API object. +// +// Vortex usage grouped by project analysis presence during the requested period. +type CagVortexProjectActivity struct { + // TotalProjectCount all projects across the four analysis cohorts. + TotalProjectCount int32 `json:"totalProjectCount,omitempty"` + // WithMainBranchAnalysisInPeriod projects with at least one processed main-branch analysis in + // the requested period. + WithMainBranchAnalysisInPeriod CagVortexProjectAnalysisCohort `json:"withMainBranchAnalysisInPeriod,omitzero"` + // WithLongLivedBranchAnalysisInPeriod projects with at least one processed long-lived-branch + // analysis, but no processed main-branch analysis, in the requested period. Pull-request + // analyses may also be present. + WithLongLivedBranchAnalysisInPeriod CagVortexProjectAnalysisCohort `json:"withLongLivedBranchAnalysisInPeriod,omitzero"` + // WithOnlyPullRequestAnalysisInPeriod projects with at least one processed pull-request + // analysis, but no processed main-branch or long-lived-branch analysis, in the requested + // period. + WithOnlyPullRequestAnalysisInPeriod CagVortexProjectAnalysisCohort `json:"withOnlyPullRequestAnalysisInPeriod,omitzero"` + // WithoutAnalysisInPeriod projects without a processed analysis on any branch in the requested + // period. + WithoutAnalysisInPeriod CagVortexProjectAnalysisCohort `json:"withoutAnalysisInPeriod,omitzero"` +} + +// CagGetProjectImpactOptions contains parameters for the GetProjectImpact method. +type CagGetProjectImpactOptions struct { + // OrganizationId UUID v4 of the organization. This field is required. + OrganizationId string `json:"organizationId"` + // From inclusive start of the calendar date range (YYYY-MM-DD, UTC). This field is required. + From string `json:"from"` + // To inclusive end of the calendar date range (YYYY-MM-DD, UTC). Must not precede from. This + // field is required. + To string `json:"to"` + // PageIndex page to retrieve, 1-based. Defaults to 1. + // Minimum value: 1. Default: 1. + PageIndex int32 `json:"pageIndex,omitempty"` + // PageSize number of projects per page. Defaults to 50. Use 0 to return only the total count. + // Must be between 0 and 500. Default: 50. + PageSize int32 `json:"pageSize,omitempty"` +} + +// CagVortexProjectImpact represents a SonarQube V2 API object. +// +// Vortex impact metrics for a single project in the requested period. Successful output compression +// and restoration both establish CAG activity and latest activity, but neither increases the Guide +// call count. Compression contributes token savings; restoration adds no impact and does not +// reverse compression savings in V0. Neither contributes engineering savings or resolved issues. A +// project using only these capabilities can therefore be active with zero tool calls; a +// restoration-only project also has zero calculated savings. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type CagVortexProjectImpact struct { + // ProjectId identifier of the project. + ProjectId string `json:"projectId,omitempty"` + // ProjectName display name of the project. + ProjectName string `json:"projectName,omitempty"` + // ActivityClassification is the activity classification. Allowed values: NONE, CAG_ONLY, + // VERIFY_ONLY, BOTH. + ActivityClassification string `json:"activityClassification,omitempty"` + // GuideCallCount successful CAG tool calls in the period, counted from stored event metadata + // independently of payload decoding. Compression and restoration count as activity but not as + // Guide calls. Shell compression is hook-driven and expected to occur much more frequently + // than model-initiated tool calls, so including it would inflate this count. Successful events + // with malformed or unsupported payloads remain counted but contribute no calculated impact. + GuideCallCount int64 `json:"guideCallCount,omitempty"` + // VerifyCallCount successful public SQAA analyses in the period, counted from stored event + // metadata independently of payload decoding. Successful events with malformed or unsupported + // payloads remain counted but contribute no calculated impact. + VerifyCallCount int64 `json:"verifyCallCount,omitempty"` + // ResolvedIssues issues inferred to be resolved in the period. + ResolvedIssues int64 `json:"resolvedIssues,omitempty"` + // EngineeringMinutesSaved combined Guide and Verify contribution to estimated engineering time + // saved in minutes, preserving fractional values. Aggregate estimates before rounding for + // display. + EngineeringMinutesSaved float64 `json:"engineeringMinutesSaved,omitempty"` + // TokensSaved estimated tokens saved by navigation tools and output compression for this + // project, preserving fractional values. Aggregate estimates before rounding for display. + // Output restoration does not add to or reverse compression savings in V0. + TokensSaved float64 `json:"tokensSaved,omitempty"` + // LatestActivityAt server-recorded time of the latest successful Vortex event in the period. + // Null when there is no activity. + LatestActivityAt string `json:"latestActivityAt,omitempty"` +} + +// CagVortexProjectPage represents a SonarQube V2 API object. +// +// Paginated Vortex impact per project for the requested period. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type CagVortexProjectPage struct { + // PageIndex current page index (1-based). + PageIndex int32 `json:"pageIndex,omitempty"` + // PageSize number of projects per page as resolved for this response. + PageSize int32 `json:"pageSize,omitempty"` + // Total number of projects, including those with no Vortex activity. + Total int32 `json:"total,omitempty"` + // Projects is the projects. + Projects []CagVortexProjectImpact `json:"projects,omitempty"` +} + +// CagGetVerifyMetricsOptions contains parameters for the GetVerifyMetrics method. +type CagGetVerifyMetricsOptions struct { + // OrganizationId UUID v4 of the organization. This field is required. + OrganizationId string `json:"organizationId"` + // From inclusive start of the calendar date range (YYYY-MM-DD, UTC). This field is required. + From string `json:"from"` + // To inclusive end of the calendar date range (YYYY-MM-DD, UTC). Must not precede from. This + // field is required. + To string `json:"to"` +} + +// CagVerifyMetrics represents a SonarQube V2 API object. +// +// Verify impact metrics for the requested period. +type CagVerifyMetrics struct { + // ResolvedIssues issues inferred to be resolved across all SQAA analysis sequences. Detected + // as issue-count decreases between successive successful analyses of the same (file, rule) in + // inferred sessions. + ResolvedIssues int64 `json:"resolvedIssues,omitempty"` + // EngineeringMinutesSaved verify contribution to estimated engineering time saved in minutes, + // preserving fractional values. Add this to the guideMetrics value before rounding the + // combined total for display. + EngineeringMinutesSaved float64 `json:"engineeringMinutesSaved,omitempty"` +} + +// CagPingResponse represents the PingResponse object of the SonarQube V2 API. +type CagPingResponse struct { + // Status service status. + Status string `json:"status,omitempty"` +} + +// CagRecordImpactEventOptions contains the request body for the RecordImpactEvent method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type CagRecordImpactEventOptions struct { + // OrganizationId organization UUID. This field is required. + OrganizationId string `json:"organizationId"` + // ProjectId project identifier. Not format: uuid — Server project ids are opaque strings; + // Cloud sends a UUID string, accepted unchanged. This field is required. + ProjectId string `json:"projectId"` + // InvocationId invocation identifier. Natural deduplication key. This field is required. + InvocationId string `json:"invocationId"` + // CagInstanceId shared by all calls from the same CAG instance (MCP server or CLI daemon). + // This field is required. + CagInstanceId string `json:"cagInstanceId"` + // EventType is the event type. This field is required. Allowed values: NAVIGATION, + // OUTPUT_COMPRESSION, OUTPUT_RESTORE, GUIDELINES, DEPENDENCY_CHECK, INTENDED_ARCHITECTURE. + EventType string `json:"eventType"` + // EventVersion payload schema version. Only 1 is supported. This field is required. + EventVersion int32 `json:"eventVersion"` + // Success true when the call completed successfully. This field is required. + Success bool `json:"success"` + // Transport is the transport. This field is required. Allowed values: MCP, CLI. + Transport string `json:"transport"` + // BranchName git branch at invocation time. Optional. + BranchName string `json:"branchName,omitempty"` + // Payload event-specific fields. Schema is selected by (eventType, eventVersion). See + // NavigationPayloadV1, OutputCompressionPayloadV1, OutputRestorePayloadV1, + // GuidelinesPayloadV1, DependencyCheckPayloadV1, IntendedArchitecturePayloadV1. This field is + // required. + Payload map[string]any `json:"payload"` +} + +// CagRecordUsageEventOptions contains the request body for the RecordUsageEvent method. +type CagRecordUsageEventOptions struct { + // OrganizationId UUID v4 of the organization. On SonarQube Server, the '@OrganizationId' + // annotation resolves this value server-side to the caller's default organization id, so + // callers on that host may pass any syntactically valid UUID as a placeholder — the value must + // still parse as a UUID, but its content is otherwise ignored. This field is required. + OrganizationId string `json:"organizationId"` + // ProjectId identifier of the project the tool call was about. Deliberately not 'format: + // uuid': SonarQube Server project ids are opaque strings, not UUIDs, so a UUID-typed field + // would reject every real Server project id. SonarQube Cloud sends a UUID string, which this + // schema accepts unchanged since the JSON wire format is identical either way. This field is + // required. + ProjectId string `json:"projectId"` + // InvocationId UUID v4 of the tool call, generated by the client. Natural deduplication key. + // This field is required. + InvocationId string `json:"invocationId"` +} + +// ----------------------------------------------------------------------------- +// Validation Functions +// ----------------------------------------------------------------------------- + +// ValidateGetUsageStatsOpt validates the options for the GetUsageStats method. +func (s *CagService) ValidateGetUsageStatsOpt(opt *CagGetUsageStatsOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.Period, "Period") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Period, allowedCagGetUsageStatsPeriod, "Period") + if err != nil { + return err + } + + return nil +} + +// ValidateGetGuideMetricsOpt validates the options for the GetGuideMetrics method. +func (s *CagService) ValidateGetGuideMetricsOpt(opt *CagGetGuideMetricsOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.OrganizationId, "OrganizationId") + if err != nil { + return err + } + + err = ValidateRequired(opt.From, "From") + if err != nil { + return err + } + + err = ValidateRequired(opt.To, "To") + if err != nil { + return err + } + + return nil +} + +// ValidateGetProjectActivityOpt validates the options for the GetProjectActivity method. +func (s *CagService) ValidateGetProjectActivityOpt(opt *CagGetProjectActivityOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.OrganizationId, "OrganizationId") + if err != nil { + return err + } + + err = ValidateRequired(opt.From, "From") + if err != nil { + return err + } + + err = ValidateRequired(opt.To, "To") + if err != nil { + return err + } + + return nil +} + +// ValidateGetProjectImpactOpt validates the options for the GetProjectImpact method. +func (s *CagService) ValidateGetProjectImpactOpt(opt *CagGetProjectImpactOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.OrganizationId, "OrganizationId") + if err != nil { + return err + } + + err = ValidateRequired(opt.From, "From") + if err != nil { + return err + } + + err = ValidateRequired(opt.To, "To") + if err != nil { + return err + } + + if opt.PageIndex < 0 { + return NewValidationError("PageIndex", "must be greater than 0", ErrOutOfRange) + } + + if opt.PageSize < 0 || opt.PageSize > 500 { + return NewValidationError("PageSize", "must be between 0 and 500", ErrOutOfRange) + } + + return nil +} + +// ValidateGetVerifyMetricsOpt validates the options for the GetVerifyMetrics method. +func (s *CagService) ValidateGetVerifyMetricsOpt(opt *CagGetVerifyMetricsOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.OrganizationId, "OrganizationId") + if err != nil { + return err + } + + err = ValidateRequired(opt.From, "From") + if err != nil { + return err + } + + err = ValidateRequired(opt.To, "To") + if err != nil { + return err + } + + return nil +} + +// ValidateRecordImpactEventOpt validates the options for the RecordImpactEvent method. +func (s *CagService) ValidateRecordImpactEventOpt(opt *CagRecordImpactEventOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.OrganizationId, "OrganizationId") + if err != nil { + return err + } + + err = ValidateRequired(opt.ProjectId, "ProjectId") + if err != nil { + return err + } + + err = ValidateRequired(opt.InvocationId, "InvocationId") + if err != nil { + return err + } + + err = ValidateRequired(opt.CagInstanceId, "CagInstanceId") + if err != nil { + return err + } + + err = ValidateRequired(opt.EventType, "EventType") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.EventType, allowedCagRecordImpactEventEventType, "EventType") + if err != nil { + return err + } + + err = ValidateRequired(opt.Transport, "Transport") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Transport, allowedCagRecordImpactEventTransport, "Transport") + if err != nil { + return err + } + + return nil +} + +// ValidateRecordUsageEventOpt validates the options for the RecordUsageEvent method. +func (s *CagService) ValidateRecordUsageEventOpt(opt *CagRecordUsageEventOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.OrganizationId, "OrganizationId") + if err != nil { + return err + } + + err = ValidateRequired(opt.ProjectId, "ProjectId") + if err != nil { + return err + } + + err = ValidateRequired(opt.InvocationId, "InvocationId") + if err != nil { + return err + } + + return nil +} + +// ----------------------------------------------------------------------------- +// Service Methods +// ----------------------------------------------------------------------------- + +// GetEntitlement gets the CAG entitlement for an organization. Returns the Context Augmentation +// entitlement decision for an organization. Response shape (allowed / hasEntitlement / featureKey / +// consumption) matches the existing SonarQube Cloud contract so clients are unaffected by which +// host answers the request. An unresolvable check (license unreadable, billing unreachable) is 503, +// not a not-entitled 200. +// +// API endpoint: GET /api/v2/cag/cag-entitlement/{id}. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *CagService) GetEntitlement(ctx context.Context, organizationID string) (*CagCagEntitlement, *http.Response, error) { + err := ValidateRequired(organizationID, "organizationID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "cag/cag-entitlement/"+url.PathEscape(organizationID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(CagCagEntitlement) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetUsageStats gets CAG tool usage statistics for an organization. Returns, for every project, the +// number of Context Augmentation tool calls recorded over the requested period. Response shape (id +// / projects[].id,name,toolCallCount) matches the existing SonarQube Cloud contract so clients are +// unaffected by which host answers the request. Projects with no usage in the period are still +// returned, with a toolCallCount of zero, and the list is ordered by toolCallCount descending. +// +// API endpoint: GET /api/v2/cag/cag-usage-stats/{id}. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *CagService) GetUsageStats(ctx context.Context, organizationID string, opt *CagGetUsageStatsOptions) (*CagCagUsageStats, *http.Response, error) { + err := ValidateRequired(organizationID, "organizationID") + if err != nil { + return nil, nil, err + } + + err = s.ValidateGetUsageStatsOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "cag/cag-usage-stats/"+url.PathEscape(organizationID), opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(CagCagUsageStats) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetGuideMetrics gets Guide impact metrics for the organization. Returns prevented issues, tokens +// saved, and the Guide contribution to engineering time saved for the requested calendar date +// range. Covers navigation tools, output compression, get_guidelines, check_dependency, and +// get_intended_architecture events. Only successful events contribute to calculated values. Dates +// are inclusive UTC calendar boundaries: from=2026-08-01 to=2026-08-31 selects [2026-08-01T00:00Z, +// 2026-09-01T00:00Z). +// +// API endpoint: GET /api/v2/cag/impact/guide-metrics. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *CagService) GetGuideMetrics(ctx context.Context, opt *CagGetGuideMetricsOptions) (*CagGuideMetrics, *http.Response, error) { + err := s.ValidateGetGuideMetricsOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "cag/impact/guide-metrics", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(CagGuideMetrics) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetProjectActivity gets Vortex usage by project analysis cohort for the organization. Groups +// projects into mutually exclusive main-branch, long-lived-branch, pull-request-only, and no- +// analysis cohorts for the requested period, then reports Vortex usage within each cohort. Analysis +// precedence follows that order. Successful output-compression and restoration events establish CAG +// usage; failed events do not. Date boundaries follow the same UTC-calendar interpretation as +// /cag/impact/guide-metrics. Only periods ending on the current date are supported. To tolerate +// browser and Server timezone differences, a 'to' value within one day of the current UTC date is +// accepted and normalized to the current UTC date. +// +// API endpoint: GET /api/v2/cag/impact/project-activity. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *CagService) GetProjectActivity(ctx context.Context, opt *CagGetProjectActivityOptions) (*CagVortexProjectActivity, *http.Response, error) { + err := s.ValidateGetProjectActivityOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "cag/impact/project-activity", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(CagVortexProjectActivity) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetProjectImpact gets paginated Vortex impact per project for the organization. Returns every +// project, including projects with no Vortex activity in the period. Each row combines Guide and +// Verify impact. The page is ordered by total Guide plus Verify calls descending, then latest +// successful activity descending with no activity last, then project key ascending. Successful +// compression and restoration establish activity without increasing Guide calls. Sorting and +// filtering by calculated impact values are outside the V0 contract. Date boundaries follow the +// same UTC-calendar interpretation as /cag/impact/guide-metrics. +// +// API endpoint: GET /api/v2/cag/impact/projects. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *CagService) GetProjectImpact(ctx context.Context, opt *CagGetProjectImpactOptions) (*CagVortexProjectPage, *http.Response, error) { + err := s.ValidateGetProjectImpactOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "cag/impact/projects", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(CagVortexProjectPage) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetVerifyMetrics gets Verify impact metrics for the organization. Returns resolved issues, and +// the Verify contribution to engineering time saved, for the requested calendar date range. Groups +// public SQAA analysis events into inferred sessions and detects issue-count decreases between +// successive successful analyses. Date boundaries follow the same UTC-calendar interpretation as +// /cag/impact/guide-metrics. +// +// API endpoint: GET /api/v2/cag/impact/verify-metrics. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *CagService) GetVerifyMetrics(ctx context.Context, opt *CagGetVerifyMetricsOptions) (*CagVerifyMetrics, *http.Response, error) { + err := s.ValidateGetVerifyMetricsOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "cag/impact/verify-metrics", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(CagVerifyMetrics) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// Ping authenticateds liveness probe for the CAG Hub service. Validates the caller's SonarCloud / +// SonarQube Server user token and returns a generic success payload. Zero-dependency authenticated +// liveness probe shared by both hosts. No business logic. +// +// API endpoint: GET /api/v2/cag/ping. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *CagService) Ping(ctx context.Context) (*CagPingResponse, *http.Response, error) { + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "cag/ping", nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(CagPingResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// RecordImpactEvent records a CAG Vortex impact event. Records one versioned Vortex impact event. +// Null or missing eventType and eventVersion return 400. The payload must conform to the named +// payload schema for the (eventType, eventVersion) pair: NavigationPayloadV1, +// OutputCompressionPayloadV1, OutputRestorePayloadV1, GuidelinesPayloadV1, +// DependencyCheckPayloadV1, or IntendedArchitecturePayloadV1 (all in components/schemas). +// +// API endpoint: POST /api/v2/cag/cag-impact-events. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *CagService) RecordImpactEvent(ctx context.Context, opt *CagRecordImpactEventOptions) (*http.Response, error) { + err := s.ValidateRecordImpactEventOpt(opt) + if err != nil { + return nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "cag/cag-impact-events", nil, opt) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// RecordUsageEvent records a CAG tool usage event. Records a single Context Augmentation tool-call +// event. The event is acknowledged with 202 once accepted; a storage failure answers 500. Request +// shape (organizationId / projectId / invocationId) matches the existing SonarQube Cloud contract +// so clients are unaffected by which host answers the request. +// +// API endpoint: POST /api/v2/cag/cag-usage-events. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *CagService) RecordUsageEvent(ctx context.Context, opt *CagRecordUsageEventOptions) (*http.Response, error) { + err := s.ValidateRecordUsageEventOpt(opt) + if err != nil { + return nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "cag/cag-usage-events", nil, opt) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} diff --git a/sonar/cag_v2_service_test.go b/sonar/cag_v2_service_test.go new file mode 100644 index 0000000..232a4b5 --- /dev/null +++ b/sonar/cag_v2_service_test.go @@ -0,0 +1,154 @@ +package sonar + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCagService_GetEntitlement(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/cag/cag-entitlement/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Cag.GetEntitlement(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestCagService_GetEntitlement_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Cag.GetEntitlement(context.Background(), "") + require.Error(t, err) +} + +func TestCagService_GetUsageStats(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/cag/cag-usage-stats/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Cag.GetUsageStats(context.Background(), "test-id", &CagGetUsageStatsOptions{Period: "LAST_DAY"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestCagService_GetUsageStats_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Cag.GetUsageStats(context.Background(), "", nil) + require.Error(t, err) +} + +func TestCagService_GetGuideMetrics(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/cag/impact/guide-metrics", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Cag.GetGuideMetrics(context.Background(), &CagGetGuideMetricsOptions{OrganizationId: "test", From: "test", To: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestCagService_GetGuideMetrics_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Cag.GetGuideMetrics(context.Background(), nil) + require.Error(t, err) +} + +func TestCagService_GetProjectActivity(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/cag/impact/project-activity", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Cag.GetProjectActivity(context.Background(), &CagGetProjectActivityOptions{OrganizationId: "test", From: "test", To: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestCagService_GetProjectActivity_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Cag.GetProjectActivity(context.Background(), nil) + require.Error(t, err) +} + +func TestCagService_GetProjectImpact(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/cag/impact/projects", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Cag.GetProjectImpact(context.Background(), &CagGetProjectImpactOptions{OrganizationId: "test", From: "test", To: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestCagService_GetProjectImpact_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Cag.GetProjectImpact(context.Background(), nil) + require.Error(t, err) +} + +func TestCagService_GetVerifyMetrics(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/cag/impact/verify-metrics", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Cag.GetVerifyMetrics(context.Background(), &CagGetVerifyMetricsOptions{OrganizationId: "test", From: "test", To: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestCagService_GetVerifyMetrics_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Cag.GetVerifyMetrics(context.Background(), nil) + require.Error(t, err) +} + +func TestCagService_Ping(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/cag/ping", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Cag.Ping(context.Background()) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestCagService_RecordImpactEvent(t *testing.T) { + server := newTestServer(t, mockEmptyHandler(t, http.MethodPost, "/v2/cag/cag-impact-events", http.StatusAccepted)) + client := newTestClient(t, server.url()) + + resp, err := client.V2.Cag.RecordImpactEvent(context.Background(), &CagRecordImpactEventOptions{OrganizationId: "test", ProjectId: "test", InvocationId: "test", CagInstanceId: "test", EventType: "NAVIGATION", EventVersion: 1, Success: true, Transport: "MCP", Payload: nil}) + require.NoError(t, err) + assert.Equal(t, http.StatusAccepted, resp.StatusCode) +} + +func TestCagService_RecordImpactEvent_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, err := client.V2.Cag.RecordImpactEvent(context.Background(), nil) + require.Error(t, err) +} + +func TestCagService_RecordUsageEvent(t *testing.T) { + server := newTestServer(t, mockEmptyHandler(t, http.MethodPost, "/v2/cag/cag-usage-events", http.StatusAccepted)) + client := newTestClient(t, server.url()) + + resp, err := client.V2.Cag.RecordUsageEvent(context.Background(), &CagRecordUsageEventOptions{OrganizationId: "test", ProjectId: "test", InvocationId: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusAccepted, resp.StatusCode) +} + +func TestCagService_RecordUsageEvent_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, err := client.V2.Cag.RecordUsageEvent(context.Background(), nil) + require.Error(t, err) +} diff --git a/sonar/client.go b/sonar/client.go index 0956f3a..4b041a5 100644 --- a/sonar/client.go +++ b/sonar/client.go @@ -162,6 +162,36 @@ type ServicesV2 struct { // SoftwareQualityReports provides methods for the Software Quality Reports V2 API. SoftwareQualityReports *SoftwareQualityReportsService + + // A3s provides methods for the A3S V2 API. + A3s *A3sService + + // Agentic provides methods for the Agentic V2 API. + Agentic *AgenticService + + // Billing provides methods for the Billing V2 API. + Billing *BillingService + + // Cag provides methods for the Context Augmentation (CAG) V2 API. + Cag *CagService + + // Dashboards provides methods for the Dashboards V2 API. + Dashboards *DashboardsService + + // DetectionAgent provides methods for the Detection Agent V2 API. + DetectionAgent *DetectionAgentService + + // LlmConnectivity provides methods for the LLM Connectivity V2 API. + LlmConnectivity *LlmConnectivityService + + // Onboarding provides methods for the Onboarding V2 API. + Onboarding *OnboardingService + + // RemediationAgent provides methods for the Remediation Agent V2 API. + RemediationAgent *RemediationAgentService + + // SecurityAlerts provides methods for the Security Alerts V2 API. + SecurityAlerts *SecurityAlertsService } // ClientCreateOptions contains options for creating a new Client. @@ -610,6 +640,16 @@ func initServicesV2(client *Client) { SoftwareQualityReports: &SoftwareQualityReportsService{client: client}, System: &SystemServiceV2{client: client}, UsersManagement: &UsersManagementService{client: client}, + A3s: &A3sService{client: client}, + Agentic: &AgenticService{client: client}, + Billing: &BillingService{client: client}, + Cag: &CagService{client: client}, + Dashboards: &DashboardsService{client: client}, + DetectionAgent: &DetectionAgentService{client: client}, + LlmConnectivity: &LlmConnectivityService{client: client}, + Onboarding: &OnboardingService{client: client}, + RemediationAgent: &RemediationAgentService{client: client}, + SecurityAlerts: &SecurityAlertsService{client: client}, } } diff --git a/sonar/common.go b/sonar/common.go index 797c1ee..f3be577 100644 --- a/sonar/common.go +++ b/sonar/common.go @@ -33,6 +33,8 @@ const ( LanguageCS = "cs" // LanguageCSS is the language key for CSS. LanguageCSS = "css" + // LanguageDataWeave is the language key for DataWeave. + LanguageDataWeave = "dataweave" // LanguageDocker is the language key for Docker. LanguageDocker = "docker" // LanguageFlex is the language key for Flex. @@ -63,6 +65,8 @@ const ( LanguagePowershell = "powershell" // LanguagePython is the language key for Python. LanguagePython = "py" + // LanguageR is the language key for R. + LanguageR = "r" // LanguageRuby is the language key for Ruby On Rails. LanguageRuby = "ruby" // LanguageRust is the language key for Rust. diff --git a/sonar/components_service.go b/sonar/components_service.go index 3745853..97fcc67 100644 --- a/sonar/components_service.go +++ b/sonar/components_service.go @@ -76,20 +76,21 @@ var ( // allowedSearchProjectsFacets is the set of allowed facets for search projects. allowedSearchProjectsFacets = map[string]struct{}{ - "alert_status": {}, - "coverage": {}, - "duplicated_lines_density": {}, - "languages": {}, - "ncloc": {}, - "new_coverage": {}, - "new_duplicated_lines_density": {}, - "new_lines": {}, - "new_maintainability_rating": {}, - "new_reliability_rating": {}, - "new_sca_rating_any_issue": {}, - "new_security_hotspots_reviewed": {}, - "new_security_rating": {}, - "new_security_review_rating": {}, + "alert_status": {}, + "coverage": {}, + "duplicated_lines_density": {}, + "languages": {}, + "ncloc": {}, + "new_coverage": {}, + "new_duplicated_lines_density": {}, + "new_lines": {}, + "new_ncloc": {}, + "new_maintainability_rating": {}, + "new_reliability_rating": {}, + "new_sca_rating_any_issue": {}, + "new_security_hotspots_reviewed": {}, + "new_security_rating": {}, + "new_security_review_rating": {}, "new_software_quality_maintainability_rating": {}, "new_software_quality_reliability_rating": {}, "new_software_quality_security_rating": {}, @@ -120,6 +121,7 @@ var ( "new_coverage": {}, "new_duplicated_lines_density": {}, "new_lines": {}, + "new_ncloc": {}, "new_maintainability_rating": {}, "new_reliability_rating": {}, "new_sca_rating_any_issue": {}, diff --git a/sonar/dashboards_v2_service.go b/sonar/dashboards_v2_service.go new file mode 100644 index 0000000..61c53c4 --- /dev/null +++ b/sonar/dashboards_v2_service.go @@ -0,0 +1,470 @@ +package sonar + +import ( + "context" + "fmt" + "net/http" + "net/url" +) + +// DashboardsService handles communication with the custom and built-in dashboards related methods of the +// SonarQube V2 API. This service is only available in Enterprise Edition. +type DashboardsService struct { + // client is used to communicate with the SonarQube API. + client *Client +} + +//nolint:gochecknoglobals,goconst // constant sets of allowed values +var ( + // allowedDashboardsListResourceType is the set of allowed values for the corresponding option. + allowedDashboardsListResourceType = map[string]struct{}{ + "project": {}, + "portfolio": {}, + } + // allowedDashboardsListBuiltInResourceType is the set of allowed values for the corresponding option. + allowedDashboardsListBuiltInResourceType = map[string]struct{}{ + "project": {}, + "portfolio": {}, + } + // allowedDashboardsCreateResourceType is the set of allowed values for the corresponding option. + allowedDashboardsCreateResourceType = map[string]struct{}{ + "project": {}, + "portfolio": {}, + } +) + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// DashboardsListOptions contains parameters for the List method. +type DashboardsListOptions struct { + // ResourceId is the resource id. This field is required. + ResourceId string `json:"resourceId"` + // ResourceType is the resource type. This field is required. Allowed values: project, + // portfolio. + ResourceType string `json:"resourceType"` + // Q is the q. + Q string `json:"q,omitempty"` + // PageIndex is the page index. + // Minimum value: 1. Default: 1. + PageIndex int32 `json:"pageIndex,omitempty"` + // PageSize is the page size. + // Must be between 0 and 5000. Default: 50. + PageSize int32 `json:"pageSize,omitempty"` +} + +// DashboardsDashboardItem represents the DashboardItem object of the SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type DashboardsDashboardItem struct { + // Id is the id. + Id string `json:"id,omitempty"` + // Name is the name. + Name string `json:"name,omitempty"` + // Description is the description. + Description string `json:"description,omitempty"` + // CreatedAt is the created at. + CreatedAt int64 `json:"createdAt,omitempty"` + // UpdatedAt is the updated at. + UpdatedAt int64 `json:"updatedAt,omitempty"` + // CreatedById is the created by id. + CreatedById string `json:"createdById,omitempty"` + // ResourceType is the resource type. Allowed values: project, portfolio. + ResourceType string `json:"resourceType,omitempty"` + // ResourceId is the resource id. + ResourceId string `json:"resourceId,omitempty"` + // UpdatedById is the updated by id. + UpdatedById string `json:"updatedById,omitempty"` +} + +// DashboardsDashboardsResponse represents the DashboardsResponse object of the SonarQube V2 API. +type DashboardsDashboardsResponse struct { + // Dashboards is the dashboards. + Dashboards []DashboardsDashboardItem `json:"dashboards,omitempty"` + // Page is the page. + Page PageResponseV2 `json:"page,omitzero"` +} + +// DashboardsListBuiltInOptions contains parameters for the ListBuiltIn method. +type DashboardsListBuiltInOptions struct { + // Q is the q. + Q string `json:"q,omitempty"` + // ResourceType is the resource type. Allowed values: project, portfolio. + ResourceType string `json:"resourceType,omitempty"` + // PageIndex is the page index. + // Minimum value: 1. Default: 1. + PageIndex int32 `json:"pageIndex,omitempty"` + // PageSize is the page size. + // Must be between 0 and 5000. Default: 50. + PageSize int32 `json:"pageSize,omitempty"` +} + +// DashboardsBuiltInDashboardItem represents the BuiltInDashboardItem object of the SonarQube V2 +// API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type DashboardsBuiltInDashboardItem struct { + // Key is the key. + Key string `json:"key,omitempty"` + // Name is the name. + Name string `json:"name,omitempty"` + // Description is the description. + Description string `json:"description,omitempty"` + // UpdatedAt is the updated at. + UpdatedAt int64 `json:"updatedAt,omitempty"` + // ResourceType is the resource type. Allowed values: project, portfolio. + ResourceType string `json:"resourceType,omitempty"` +} + +// DashboardsBuiltInDashboardsResponse represents the BuiltInDashboardsResponse object of the +// SonarQube V2 API. +type DashboardsBuiltInDashboardsResponse struct { + // Dashboards is the dashboards. + Dashboards []DashboardsBuiltInDashboardItem `json:"dashboards,omitempty"` + // Page is the page. + Page PageResponseV2 `json:"page,omitzero"` +} + +// DashboardsBuiltInDashboardResponse represents the BuiltInDashboardResponse object of the +// SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type DashboardsBuiltInDashboardResponse struct { + // Key is the key. + Key string `json:"key,omitempty"` + // Name is the name. + Name string `json:"name,omitempty"` + // Layout is the layout. + Layout string `json:"layout,omitempty"` + // Description is the description. + Description string `json:"description,omitempty"` + // UpdatedAt is the updated at. + UpdatedAt int64 `json:"updatedAt,omitempty"` + // ResourceType is the resource type. Allowed values: project, portfolio. + ResourceType string `json:"resourceType,omitempty"` +} + +// DashboardsDashboardResponse represents the DashboardResponse object of the SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type DashboardsDashboardResponse struct { + // Id is the id. + Id string `json:"id,omitempty"` + // Name is the name. + Name string `json:"name,omitempty"` + // Layout is the layout. + Layout string `json:"layout,omitempty"` + // Description is the description. + Description string `json:"description,omitempty"` + // CreatedAt is the created at. + CreatedAt int64 `json:"createdAt,omitempty"` + // UpdatedAt is the updated at. + UpdatedAt int64 `json:"updatedAt,omitempty"` + // CreatedById is the created by id. + CreatedById string `json:"createdById,omitempty"` + // ResourceType is the resource type. Allowed values: project, portfolio. + ResourceType string `json:"resourceType,omitempty"` + // ResourceId is the resource id. + ResourceId string `json:"resourceId,omitempty"` + // UpdatedById is the updated by id. + UpdatedById string `json:"updatedById,omitempty"` +} + +// DashboardsUpdateOptions contains the request body for the Update method. +type DashboardsUpdateOptions struct { + // Name is the name. + Name *string `json:"name,omitempty"` + // Layout is the layout. + Layout *string `json:"layout,omitempty"` + // Description is the description. + Description *string `json:"description,omitempty"` +} + +// DashboardsCreateOptions contains the request body for the Create method. +type DashboardsCreateOptions struct { + // Name is the name. This field is required. + Name string `json:"name"` + // Layout is the layout. This field is required. + Layout string `json:"layout"` + // ResourceType is the resource type. This field is required. Allowed values: project, + // portfolio. + ResourceType string `json:"resourceType"` + // ResourceId is the resource id. This field is required. + ResourceId string `json:"resourceId"` + // Description is the description. + Description string `json:"description,omitempty"` +} + +// ----------------------------------------------------------------------------- +// Validation Functions +// ----------------------------------------------------------------------------- + +// ValidateListOpt validates the options for the List method. +func (s *DashboardsService) ValidateListOpt(opt *DashboardsListOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.ResourceId, "ResourceId") + if err != nil { + return err + } + + err = ValidateRequired(opt.ResourceType, "ResourceType") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.ResourceType, allowedDashboardsListResourceType, "ResourceType") + if err != nil { + return err + } + + if opt.PageIndex < 0 { + return NewValidationError("PageIndex", "must be greater than 0", ErrOutOfRange) + } + + if opt.PageSize < 0 || opt.PageSize > 5000 { + return NewValidationError("PageSize", "must be between 0 and 5000", ErrOutOfRange) + } + + return nil +} + +// ValidateListBuiltInOpt validates the options for the ListBuiltIn method. +func (s *DashboardsService) ValidateListBuiltInOpt(opt *DashboardsListBuiltInOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := IsValueAuthorized(opt.ResourceType, allowedDashboardsListBuiltInResourceType, "ResourceType") + if err != nil { + return err + } + + if opt.PageIndex < 0 { + return NewValidationError("PageIndex", "must be greater than 0", ErrOutOfRange) + } + + if opt.PageSize < 0 || opt.PageSize > 5000 { + return NewValidationError("PageSize", "must be between 0 and 5000", ErrOutOfRange) + } + + return nil +} + +// ValidateCreateOpt validates the options for the Create method. +func (s *DashboardsService) ValidateCreateOpt(opt *DashboardsCreateOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.Name, "Name") + if err != nil { + return err + } + + err = ValidateRequired(opt.Layout, "Layout") + if err != nil { + return err + } + + err = ValidateRequired(opt.ResourceType, "ResourceType") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.ResourceType, allowedDashboardsCreateResourceType, "ResourceType") + if err != nil { + return err + } + + err = ValidateRequired(opt.ResourceId, "ResourceId") + if err != nil { + return err + } + + return nil +} + +// ----------------------------------------------------------------------------- +// Service Methods +// ----------------------------------------------------------------------------- + +// Delete deletes a custom dashboard. +// +// API endpoint: DELETE /api/v2/dashboards/{id}. +// Enterprise Edition only. +func (s *DashboardsService) Delete(ctx context.Context, dashboardID string) (*http.Response, error) { + err := ValidateRequired(dashboardID, "dashboardID") + if err != nil { + return nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodDelete, "dashboards/"+url.PathEscape(dashboardID), nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// List lists dashboards for a resource. +// +// API endpoint: GET /api/v2/dashboards. +// Enterprise Edition only. +func (s *DashboardsService) List(ctx context.Context, opt *DashboardsListOptions) (*DashboardsDashboardsResponse, *http.Response, error) { + err := s.ValidateListOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "dashboards", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(DashboardsDashboardsResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// ListBuiltIn lists built-in dashboards. +// +// API endpoint: GET /api/v2/dashboards/built-ins. +// Enterprise Edition only. +func (s *DashboardsService) ListBuiltIn(ctx context.Context, opt *DashboardsListBuiltInOptions) (*DashboardsBuiltInDashboardsResponse, *http.Response, error) { + err := s.ValidateListBuiltInOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "dashboards/built-ins", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(DashboardsBuiltInDashboardsResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetBuiltIn gets a built-in dashboard. +// +// API endpoint: GET /api/v2/dashboards/built-ins/{key}. +// Enterprise Edition only. +func (s *DashboardsService) GetBuiltIn(ctx context.Context, key string) (*DashboardsBuiltInDashboardResponse, *http.Response, error) { + err := ValidateRequired(key, "key") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "dashboards/built-ins/"+url.PathEscape(key), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(DashboardsBuiltInDashboardResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// Get gets a custom dashboard. +// +// API endpoint: GET /api/v2/dashboards/{id}. +// Enterprise Edition only. +func (s *DashboardsService) Get(ctx context.Context, dashboardID string) (*DashboardsDashboardResponse, *http.Response, error) { + err := ValidateRequired(dashboardID, "dashboardID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "dashboards/"+url.PathEscape(dashboardID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(DashboardsDashboardResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// Update updates a custom dashboard. +// +// API endpoint: PATCH /api/v2/dashboards/{id}. +// Enterprise Edition only. +func (s *DashboardsService) Update(ctx context.Context, dashboardID string, opt *DashboardsUpdateOptions) (*DashboardsDashboardResponse, *http.Response, error) { + err := ValidateRequired(dashboardID, "dashboardID") + if err != nil { + return nil, nil, err + } + + if opt == nil { + return nil, nil, NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPatch, "dashboards/"+url.PathEscape(dashboardID), nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(DashboardsDashboardResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// Create creates a custom dashboard. +// +// API endpoint: POST /api/v2/dashboards. +// Enterprise Edition only. +func (s *DashboardsService) Create(ctx context.Context, opt *DashboardsCreateOptions) (*DashboardsDashboardResponse, *http.Response, error) { + err := s.ValidateCreateOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "dashboards", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(DashboardsDashboardResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} diff --git a/sonar/dashboards_v2_service_test.go b/sonar/dashboards_v2_service_test.go new file mode 100644 index 0000000..08d5840 --- /dev/null +++ b/sonar/dashboards_v2_service_test.go @@ -0,0 +1,128 @@ +package sonar + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDashboardsService_Delete(t *testing.T) { + server := newTestServer(t, mockEmptyHandler(t, http.MethodDelete, "/v2/dashboards/test-id", http.StatusNoContent)) + client := newTestClient(t, server.url()) + + resp, err := client.V2.Dashboards.Delete(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusNoContent, resp.StatusCode) +} + +func TestDashboardsService_Delete_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, err := client.V2.Dashboards.Delete(context.Background(), "") + require.Error(t, err) +} + +func TestDashboardsService_List(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/dashboards", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Dashboards.List(context.Background(), &DashboardsListOptions{ResourceId: "test", ResourceType: "project"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestDashboardsService_List_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Dashboards.List(context.Background(), nil) + require.Error(t, err) +} + +func TestDashboardsService_ListBuiltIn(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/dashboards/built-ins", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Dashboards.ListBuiltIn(context.Background(), &DashboardsListBuiltInOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestDashboardsService_ListBuiltIn_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Dashboards.ListBuiltIn(context.Background(), nil) + require.Error(t, err) +} + +func TestDashboardsService_GetBuiltIn(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/dashboards/built-ins/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Dashboards.GetBuiltIn(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestDashboardsService_GetBuiltIn_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Dashboards.GetBuiltIn(context.Background(), "") + require.Error(t, err) +} + +func TestDashboardsService_Get(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/dashboards/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Dashboards.Get(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestDashboardsService_Get_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Dashboards.Get(context.Background(), "") + require.Error(t, err) +} + +func TestDashboardsService_Update(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPatch, "/v2/dashboards/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Dashboards.Update(context.Background(), "test-id", &DashboardsUpdateOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestDashboardsService_Update_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Dashboards.Update(context.Background(), "", nil) + require.Error(t, err) +} + +func TestDashboardsService_Create(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/dashboards", http.StatusCreated, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Dashboards.Create(context.Background(), &DashboardsCreateOptions{Name: "test", Layout: "test", ResourceType: "project", ResourceId: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestDashboardsService_Create_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Dashboards.Create(context.Background(), nil) + require.Error(t, err) +} diff --git a/sonar/detection_agent_v2_service.go b/sonar/detection_agent_v2_service.go new file mode 100644 index 0000000..0dc2e80 --- /dev/null +++ b/sonar/detection_agent_v2_service.go @@ -0,0 +1,592 @@ +package sonar + +import ( + "context" + "fmt" + "net/http" + "net/url" +) + +// DetectionAgentService handles communication with the detection agent related methods of the +// SonarQube V2 API. This service is only available in Enterprise Edition. +type DetectionAgentService struct { + // client is used to communicate with the SonarQube API. + client *Client +} + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// DetectionAgentInstanceConfiguration represents the InstanceConfiguration object of the SonarQube +// V2 API. +type DetectionAgentInstanceConfiguration struct { + // Enabled is the enabled. + Enabled bool `json:"enabled,omitempty"` + // MaxConcurrentJobs is the max concurrent jobs. + MaxConcurrentJobs int32 `json:"maxConcurrentJobs,omitempty"` +} + +// DetectionAgentListJobsOptions contains parameters for the ListJobs method. +type DetectionAgentListJobsOptions struct { + // ProjectId restrict to jobs of this project. This field is required. + ProjectId string `json:"projectId"` + // BranchId restrict to jobs of this branch. + BranchId string `json:"branchId,omitempty"` + // Status job statuses to include. Repeat the parameter to provide multiple statuses. Allowed + // values: QUEUED, IN_PROGRESS, COMPLETED, FAILED, SKIPPED. + Status []string `json:"status,omitempty"` + // PageIndex 1-based page index. + // Minimum value: 1. Default: 1. + PageIndex int32 `json:"pageIndex,omitempty"` + // PageSize number of items per page. + // Must be between 1 and 500. Default: 50. + PageSize int32 `json:"pageSize,omitempty"` +} + +// DetectionAgentJob represents the Job object of the SonarQube V2 API. +type DetectionAgentJob struct { + // Id is the id. + Id string `json:"id,omitempty"` + // ProjectId is the project id. + ProjectId string `json:"projectId,omitempty"` + // BranchId stable identifier of the branch the job ran on. + BranchId string `json:"branchId,omitempty"` + // Status is the status. Allowed values: QUEUED, IN_PROGRESS, COMPLETED, FAILED, SKIPPED. + Status string `json:"status,omitempty"` + // SubStatus present only when a finer-grained phase is known; status remains the source of + // truth. Allowed values: AWAITING_FINDINGS, CLONING, EXPLORING, ANALYZING, CONSOLIDATING, + // REPORTING, CLONE_FAILED, AGENT_FAILED, LLM_NOT_ACCESSIBLE, ANALYSIS_TIMEOUT, + // RUNTIME_UNAVAILABLE, RESULT_PUBLICATION_FAILED, INSUFFICIENT_CREDITS, NCLOC_UNAVAILABLE, + // UNSUPPORTED_SCM_PROVIDER, SCM_CREDENTIALS_REJECTED, SCM_BRANCH_NOT_FOUND, + // PRIOR_FINDINGS_UNUSABLE. + SubStatus string `json:"subStatus,omitempty"` + // AnalysisType is the analysis type. Allowed values: FULL, INCREMENTAL. + AnalysisType string `json:"analysisType,omitempty"` + // TriggerType is the trigger type. Allowed values: ON_DEMAND, SCHEDULED. + TriggerType string `json:"triggerType,omitempty"` + // StartTime epoch millis when the job started; null while PENDING. + StartTime int64 `json:"startTime,omitempty"` + // DurationSeconds is the duration seconds. + DurationSeconds int64 `json:"durationSeconds,omitempty"` + // ConsumedCredits credits submitted to Billing for this run. Zero when the terminal run was + // not chargeable; null while non-terminal, for legacy runs, or when billing publication + // failed. + ConsumedCredits int32 `json:"consumedCredits,omitempty"` + // RequestTime is the request time. + RequestTime int64 `json:"requestTime,omitempty"` + // UpdatedAt is the updated at. + UpdatedAt int64 `json:"updatedAt,omitempty"` +} + +// DetectionAgentJobsResponse represents the JobsResponse object of the SonarQube V2 API. +type DetectionAgentJobsResponse struct { + // Jobs is the jobs. + Jobs []DetectionAgentJob `json:"jobs,omitempty"` + // Page is the page. + Page PageResponseV2 `json:"page,omitzero"` +} + +// DetectionAgentGetCreditCostOptions contains parameters for the GetCreditCost method. +type DetectionAgentGetCreditCostOptions struct { + // BranchId stable identifier of the branch to estimate. This field is required. + BranchId string `json:"branchId"` +} + +// DetectionAgentCreditCostBranch represents the CreditCostBranch object of the SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type DetectionAgentCreditCostBranch struct { + // Id stable identifier of the branch. + Id string `json:"id,omitempty"` + // Ncloc latest persisted non-comment lines of code for the branch. + Ncloc int64 `json:"ncloc,omitempty"` + // Size is the size. Allowed values: XS, S, M, L, XL, XXL. + Size string `json:"size,omitempty"` +} + +// DetectionAgentCreditCostBreakdown represents the CreditCostBreakdown object of the SonarQube V2 +// API. +type DetectionAgentCreditCostBreakdown struct { + // Total credits required by the branch. + Total int64 `json:"total,omitempty"` + // Base branch cost assigned to available base credits. + Base int64 `json:"base,omitempty"` + // Overage credits needed beyond the available base quota, including when the request will be + // refused. + Overage int64 `json:"overage,omitempty"` +} + +// DetectionAgentAvailableCreditsBreakdown represents the AvailableCreditsBreakdown object of the +// SonarQube V2 API. +type DetectionAgentAvailableCreditsBreakdown struct { + // Total base and active overage credits available after usage and reservations. + Total int64 `json:"total,omitempty"` + // Base credits available after usage and reservations. + Base int64 `json:"base,omitempty"` + // Overage active overage credits available after usage and reservations. Null when overage is + // disabled. + Overage int64 `json:"overage,omitempty"` +} + +// DetectionAgentCreditCostCredits represents the CreditCostCredits object of the SonarQube V2 API. +type DetectionAgentCreditCostCredits struct { + // BranchCost is the branch cost. + BranchCost DetectionAgentCreditCostBreakdown `json:"branchCost,omitzero"` + // Available is the available. + Available DetectionAgentAvailableCreditsBreakdown `json:"available,omitzero"` +} + +// DetectionAgentCreditCostResponse represents the CreditCostResponse object of the SonarQube V2 +// API. +type DetectionAgentCreditCostResponse struct { + // Branch is the branch. + Branch DetectionAgentCreditCostBranch `json:"branch,omitzero"` + // Credits is the credits. + Credits DetectionAgentCreditCostCredits `json:"credits,omitzero"` + // CreditCost estimated credit cost for the branch size tier. + CreditCost int32 `json:"creditCost,omitempty"` + // AvailableCredits available credits, when consumption tracking is configured. + AvailableCredits int64 `json:"availableCredits,omitempty"` +} + +// DetectionAgentScheduling represents the scheduled-run configuration of a project as returned +// by the detection-agent project configuration endpoints. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type DetectionAgentScheduling struct { + // BranchId is the identifier of the long-lived branch scanned on scheduled runs. + BranchId string `json:"branchId,omitempty"` + // Enabled indicates whether scheduled runs are effectively enabled. It reads as false while + // the detection agent is disabled instance-wide. + Enabled bool `json:"enabled,omitempty"` + // NextRunTime is the server-computed date and time of the next scheduled run. + NextRunTime string `json:"nextRunTime,omitempty"` + // Recurrence is how often the run recurs. + Recurrence *DetectionAgentScheduleRecurrence `json:"recurrence,omitempty"` + // Time is the time of day of the scheduled run. + Time *DetectionAgentScheduleTime `json:"time,omitempty"` +} + +// DetectionAgentProjectConfiguration represents the detection-agent configuration of a project. +// +// Note: the SonarQube V2 API spec references a colliding "ProjectConfiguration" schema (the +// architecture scanner configuration); this type reflects what the endpoint actually returns. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type DetectionAgentProjectConfiguration struct { + // ProjectId is the project identifier. + ProjectId string `json:"projectId,omitempty"` + // Scheduling is the scheduled-run configuration of the project. + Scheduling DetectionAgentScheduling `json:"scheduling,omitzero"` +} + +// DetectionAgentScheduleTime represents a SonarQube V2 API object. +// +// Time of day of the scheduled run, in the given IANA timezone. Runs fire on the hour. +type DetectionAgentScheduleTime struct { + // Hour of day (0-23) in the given timezone. + Hour *int32 `json:"hour,omitempty"` + // Timezone IANA timezone id the schedule fires in (DST-aware). + Timezone string `json:"timezone,omitempty"` +} + +// DetectionAgentScheduleRecurrence represents a SonarQube V2 API object. +// +// How often the run recurs. WEEKLY requires exactly one dayOfWeek; DAILY and MONTHLY must omit it. +// MONTHLY always fires on the last day of the month. +type DetectionAgentScheduleRecurrence struct { + // Type the recurrence cadence. Allowed values: DAILY, WEEKLY, MONTHLY. + Type string `json:"type,omitempty"` + // DayOfWeek day of week for WEEKLY recurrence; omitted otherwise. Allowed values: MONDAY, + // TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY. + DayOfWeek string `json:"dayOfWeek,omitempty"` +} + +// DetectionAgentSchedulingRequest represents a SonarQube V2 API object. +// +// Scheduled-run configuration to apply to the project. The structured config (branch, time, +// recurrence) is optional but all-or-nothing: supply a complete config to overwrite the stored +// schedule, or omit it to keep the stored schedule. Omitting it while enabling a project that has +// no stored schedule provisions a default one (daily at 02:00 Etc/UTC on the main branch) instead +// of failing, so enabling never requires the client to pick a schedule. This is a request-only +// model: the server-derived nextRunTime is never accepted here. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type DetectionAgentSchedulingRequest struct { + // Enabled whether scheduled detection-agent runs are desired for the project. This is the + // project's own setting, not the effective state: unlike the identically named response field + // it is never combined with the instance-wide detection-agent setting. Setting it to true + // while the agent is disabled instance-wide is refused with 403; setting it to false is always + // accepted. Do not populate it by echoing a SchedulingResponse read back — while the agent is + // disabled instance-wide that response reports false as a mask, and echoing it turns the + // project's own scheduling off for real. + Enabled *bool `json:"enabled,omitempty"` + // BranchId stable identifier of the long-lived branch to scan on scheduled runs. + BranchId string `json:"branchId,omitempty"` + // Time is the time of day of the scheduled run. + Time *DetectionAgentScheduleTime `json:"time,omitempty"` + // Recurrence is how often the run recurs. + Recurrence *DetectionAgentScheduleRecurrence `json:"recurrence,omitempty"` +} + +// DetectionAgentUpsertProjectConfigurationOptions contains the request body for the +// UpsertProjectConfiguration method. +type DetectionAgentUpsertProjectConfigurationOptions struct { + // Scheduling is the scheduling. + Scheduling *DetectionAgentSchedulingRequest `json:"scheduling,omitempty"` +} + +// DetectionAgentCreateJobOptions contains the request body for the CreateJob method. +type DetectionAgentCreateJobOptions struct { + // ProjectId stable identifier of the SonarQube project to analyse. This field is required. + ProjectId string `json:"projectId"` + // BranchId stable identifier of the branch to analyse. This field is required. + BranchId string `json:"branchId"` +} + +// DetectionAgentAgenticFlowLocation represents the AgenticFlowLocation object of the SonarQube V2 +// API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type DetectionAgentAgenticFlowLocation struct { + // FilePath is the file path. + FilePath string `json:"filePath,omitempty"` + // Line is the line. + Line int32 `json:"line,omitempty"` + // Message is the message. + Message string `json:"message,omitempty"` +} + +// DetectionAgentAgenticFlow represents the AgenticFlow object of the SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type DetectionAgentAgenticFlow struct { + // Locations is the locations. + Locations []DetectionAgentAgenticFlowLocation `json:"locations,omitempty"` + // Description is the description. + Description string `json:"description,omitempty"` + // Type is the type. + Type string `json:"type,omitempty"` +} + +// DetectionAgentAgenticFinding represents the AgenticFinding object of the SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type DetectionAgentAgenticFinding struct { + // FilePath is the file path. + FilePath string `json:"filePath,omitempty"` + // Line is the line. + Line int32 `json:"line,omitempty"` + // Message is the message. + Message string `json:"message,omitempty"` + // RuleKey is the rule key. + RuleKey string `json:"ruleKey,omitempty"` + // RuleTitle is the rule title. + RuleTitle string `json:"ruleTitle,omitempty"` + // Severity is the severity. + Severity string `json:"severity,omitempty"` + // WhyIsThisAnIssue is the why is this an issue. + WhyIsThisAnIssue string `json:"whyIsThisAnIssue,omitempty"` + // HowToFix is the how to fix. + HowToFix string `json:"howToFix,omitempty"` + // AgentContext is the agent context. + AgentContext string `json:"agentContext,omitempty"` + // Flows is the flows. + Flows []DetectionAgentAgenticFlow `json:"flows,omitempty"` + // Status is the status. + Status string `json:"status,omitempty"` +} + +// DetectionAgentSubmitJobResultOptions contains the request body for the SubmitJobResult method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type DetectionAgentSubmitJobResultOptions struct { + // JobId is the job id. + JobId string `json:"jobId,omitempty"` + // ProjectId is the project id. + ProjectId string `json:"projectId,omitempty"` + // BranchName is the branch name. + BranchName string `json:"branchName,omitempty"` + // AnalyzedCommitSha is the analyzed commit sha. + AnalyzedCommitSha string `json:"analyzedCommitSha,omitempty"` + // AnalyzedAt is the analyzed at. + AnalyzedAt *int64 `json:"analyzedAt,omitempty"` + // Findings is the findings. + Findings []DetectionAgentAgenticFinding `json:"findings,omitempty"` + // DurationSeconds is the duration seconds. + DurationSeconds *int64 `json:"durationSeconds,omitempty"` + // CostUsd is the cost usd. + CostUsd *float64 `json:"costUsd,omitempty"` + // PlaybookId is the playbook id. + PlaybookId string `json:"playbookId,omitempty"` + // PlaybookKey is the playbook key. + PlaybookKey string `json:"playbookKey,omitempty"` + // PlaybookVersion is the playbook version. + PlaybookVersion string `json:"playbookVersion,omitempty"` + // RevalidatesPriors is the revalidates priors. + RevalidatesPriors *bool `json:"revalidatesPriors,omitempty"` +} + +// ----------------------------------------------------------------------------- +// Validation Functions +// ----------------------------------------------------------------------------- + +// ValidateListJobsOpt validates the options for the ListJobs method. +func (s *DetectionAgentService) ValidateListJobsOpt(opt *DetectionAgentListJobsOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.ProjectId, "ProjectId") + if err != nil { + return err + } + + if opt.PageIndex < 0 { + return NewValidationError("PageIndex", "must be greater than 0", ErrOutOfRange) + } + + if opt.PageSize < 0 || opt.PageSize > 500 { + return NewValidationError("PageSize", "must be between 0 and 500", ErrOutOfRange) + } + + return nil +} + +// ValidateGetCreditCostOpt validates the options for the GetCreditCost method. +func (s *DetectionAgentService) ValidateGetCreditCostOpt(opt *DetectionAgentGetCreditCostOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.BranchId, "BranchId") + if err != nil { + return err + } + + return nil +} + +// ValidateCreateJobOpt validates the options for the CreateJob method. +func (s *DetectionAgentService) ValidateCreateJobOpt(opt *DetectionAgentCreateJobOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.ProjectId, "ProjectId") + if err != nil { + return err + } + + err = ValidateRequired(opt.BranchId, "BranchId") + if err != nil { + return err + } + + return nil +} + +// ----------------------------------------------------------------------------- +// Service Methods +// ----------------------------------------------------------------------------- + +// GetInstanceConfiguration gets instance configuration. Retrieve the instance-wide detection-agent +// configuration. +// +// API endpoint: GET /api/v2/detection-agent/instance-config. +// Enterprise Edition only. +func (s *DetectionAgentService) GetInstanceConfiguration(ctx context.Context) (*DetectionAgentInstanceConfiguration, *http.Response, error) { + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "detection-agent/instance-config", nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(DetectionAgentInstanceConfiguration) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// ListJobs lists detection jobs. Returns a paginated list of detection jobs for a project. +// +// API endpoint: GET /api/v2/detection-agent/jobs. +// Enterprise Edition only. +func (s *DetectionAgentService) ListJobs(ctx context.Context, opt *DetectionAgentListJobsOptions) (*DetectionAgentJobsResponse, *http.Response, error) { + err := s.ValidateListJobsOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "detection-agent/jobs", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(DetectionAgentJobsResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetCreditCost estimates detection job credit cost. Returns the LOC-based credit cost for the +// latest persisted NCLOC of a branch. +// +// API endpoint: GET /api/v2/detection-agent/jobs/credit-cost. +// Enterprise Edition only. +func (s *DetectionAgentService) GetCreditCost(ctx context.Context, opt *DetectionAgentGetCreditCostOptions) (*DetectionAgentCreditCostResponse, *http.Response, error) { + err := s.ValidateGetCreditCostOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "detection-agent/jobs/credit-cost", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(DetectionAgentCreditCostResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetProjectConfiguration gets project configuration. Retrieve the detection-agent configuration +// for a project. Scheduling reflects the instance-wide detection-agent setting, so it reads as +// disabled while the detection agent is disabled instance-wide. Entitlement and credit availability +// are not reflected here: a scheduled run also requires the instance to be entitled and to hold +// enough credits at fire time, so scheduling can read as enabled on an instance where a run would +// be refused for one of those reasons. +// +// API endpoint: GET /api/v2/detection-agent/project-configs/{projectId}. +// Enterprise Edition only. +func (s *DetectionAgentService) GetProjectConfiguration(ctx context.Context, projectID string) (*DetectionAgentProjectConfiguration, *http.Response, error) { + err := ValidateRequired(projectID, "projectID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "detection-agent/project-configs/"+url.PathEscape(projectID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(DetectionAgentProjectConfiguration) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// UpsertProjectConfiguration upserts project configuration. Create or update the detection-agent +// configuration (including scheduling) for a project. Enabling scheduling with no schedule +// available — neither supplied in the request nor already stored — provisions a default one rather +// than failing: a daily run at 02:00 Etc/UTC on the project's main branch, persisted and returned +// in the response. Each missing piece is defaulted independently; a partial structured config is +// still refused with 400. While the detection agent is disabled instance-wide, a request that sets +// scheduling.enabled to true is refused with 403: nothing can dispatch, so scheduling cannot be +// switched on. Disabling is always accepted, and a request that carries no scheduling intent writes +// nothing and behaves as a read. The instance-wide setting never touches a project's stored +// schedule, which takes effect again once that setting is turned back on. +// +// API endpoint: PATCH /api/v2/detection-agent/project-configs/{projectId}. +// Enterprise Edition only. +func (s *DetectionAgentService) UpsertProjectConfiguration(ctx context.Context, projectID string, opt *DetectionAgentUpsertProjectConfigurationOptions) (*DetectionAgentProjectConfiguration, *http.Response, error) { + err := ValidateRequired(projectID, "projectID") + if err != nil { + return nil, nil, err + } + + if opt == nil { + return nil, nil, NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + // Unlike most V2 PATCH endpoints, this one only accepts application/json (it answers 415 to + // application/merge-patch+json), so the default PATCH content type is overridden. + //nolint:exhaustruct // only the fields relevant to this request are set + req, err := s.client.NewSonarQubeAPIRequest(ctx, SonarAPIRequestParameters{ + Method: http.MethodPatch, + Path: v2BasePath + "detection-agent/project-configs/" + url.PathEscape(projectID), + Headers: map[string]string{headerContentType: "application/json"}, + Body: opt, + }) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(DetectionAgentProjectConfiguration) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// CreateJob creates a detection job. Triggers a new agentic detection job for a project. +// +// API endpoint: POST /api/v2/detection-agent/jobs. +// Enterprise Edition only. +func (s *DetectionAgentService) CreateJob(ctx context.Context, opt *DetectionAgentCreateJobOptions) (*DetectionAgentJob, *http.Response, error) { + err := s.ValidateCreateJobOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "detection-agent/jobs", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(DetectionAgentJob) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// SubmitJobResult calls POST /api/v2/detection-agent/jobs/results. +// +// API endpoint: POST /api/v2/detection-agent/jobs/results. +// Enterprise Edition only. +func (s *DetectionAgentService) SubmitJobResult(ctx context.Context, jobID string, opt *DetectionAgentSubmitJobResultOptions) (*http.Response, error) { + err := ValidateRequired(jobID, "jobID") + if err != nil { + return nil, err + } + + if opt == nil { + return nil, NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "detection-agent/jobs/results", map[string]string{"jobId": jobID}, opt) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} diff --git a/sonar/detection_agent_v2_service_test.go b/sonar/detection_agent_v2_service_test.go new file mode 100644 index 0000000..56a4905 --- /dev/null +++ b/sonar/detection_agent_v2_service_test.go @@ -0,0 +1,121 @@ +package sonar + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDetectionAgentService_GetInstanceConfiguration(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/detection-agent/instance-config", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.DetectionAgent.GetInstanceConfiguration(context.Background()) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestDetectionAgentService_ListJobs(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/detection-agent/jobs", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.DetectionAgent.ListJobs(context.Background(), &DetectionAgentListJobsOptions{ProjectId: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestDetectionAgentService_ListJobs_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.DetectionAgent.ListJobs(context.Background(), nil) + require.Error(t, err) +} + +func TestDetectionAgentService_GetCreditCost(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/detection-agent/jobs/credit-cost", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.DetectionAgent.GetCreditCost(context.Background(), &DetectionAgentGetCreditCostOptions{BranchId: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestDetectionAgentService_GetCreditCost_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.DetectionAgent.GetCreditCost(context.Background(), nil) + require.Error(t, err) +} + +func TestDetectionAgentService_GetProjectConfiguration(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/detection-agent/project-configs/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.DetectionAgent.GetProjectConfiguration(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestDetectionAgentService_GetProjectConfiguration_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.DetectionAgent.GetProjectConfiguration(context.Background(), "") + require.Error(t, err) +} + +func TestDetectionAgentService_UpsertProjectConfiguration(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPatch, "/v2/detection-agent/project-configs/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.DetectionAgent.UpsertProjectConfiguration(context.Background(), "test-id", &DetectionAgentUpsertProjectConfigurationOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestDetectionAgentService_UpsertProjectConfiguration_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.DetectionAgent.UpsertProjectConfiguration(context.Background(), "", nil) + require.Error(t, err) +} + +func TestDetectionAgentService_CreateJob(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/detection-agent/jobs", http.StatusCreated, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.DetectionAgent.CreateJob(context.Background(), &DetectionAgentCreateJobOptions{ProjectId: "test", BranchId: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestDetectionAgentService_CreateJob_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.DetectionAgent.CreateJob(context.Background(), nil) + require.Error(t, err) +} + +func TestDetectionAgentService_SubmitJobResult(t *testing.T) { + server := newTestServer(t, mockEmptyHandler(t, http.MethodPost, "/v2/detection-agent/jobs/results", http.StatusOK)) + client := newTestClient(t, server.url()) + + resp, err := client.V2.DetectionAgent.SubmitJobResult(context.Background(), "test-value", &DetectionAgentSubmitJobResultOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) +} + +func TestDetectionAgentService_SubmitJobResult_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, err := client.V2.DetectionAgent.SubmitJobResult(context.Background(), "", nil) + require.Error(t, err) +} diff --git a/sonar/dop_translation_v2_agents_service.go b/sonar/dop_translation_v2_agents_service.go new file mode 100644 index 0000000..54f2fc8 --- /dev/null +++ b/sonar/dop_translation_v2_agents_service.go @@ -0,0 +1,212 @@ +package sonar + +import ( + "context" + "fmt" + "net/http" +) + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// DopTranslationCheckPermissionsOptions contains parameters for the CheckPermissions method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type DopTranslationCheckPermissionsOptions struct { + // Project key of the project whose bound DevOps Platform should be checked. + Project string `json:"project,omitempty"` + // Configuration key of a single DevOps Platform configuration to check, instead of all of + // them. Cannot be combined with 'project'. + Configuration string `json:"configuration,omitempty"` + // Refresh bypass the cache and run a live check, then store its result. Requires + // 'configuration' and the 'Administer System' permission. + // Default: False. + Refresh *bool `json:"refresh,omitempty"` +} + +// DopTranslationPermissionDeficitResource represents a SonarQube V2 API object. +// +// A permission the Remediation Agent requires that is not granted at the level it needs. +type DopTranslationPermissionDeficitResource struct { + // Permission name of the DevOps Platform permission, e.g. 'contents'. + Permission string `json:"permission,omitempty"` + // Required access level the Remediation Agent requires, e.g. 'write'. + Required string `json:"required,omitempty"` + // Granted access level actually granted, or null when the permission is not granted at all. + Granted string `json:"granted,omitempty"` +} + +// DopTranslationAffectedInstallationResource represents a SonarQube V2 API object. +// +// A GitHub App installation that has not approved every permission the Remediation Agent requires. +// Only permissions the app itself requests appear here; a permission the app does not request is +// reported once in 'appMissingPermissions' instead, because no installation owner can approve it. +// Suspension alone does not put an installation in this list. +type DopTranslationAffectedInstallationResource struct { + // InstallationId identifier of the GitHub App installation. + InstallationId string `json:"installationId,omitempty"` + // InstallationOwner login of whoever the app is installed on. May be an organization or a + // personal account. + InstallationOwner string `json:"installationOwner,omitempty"` + // SettingsUrl URL of the installation settings page, where the missing permissions can be + // approved. + SettingsUrl string `json:"settingsUrl,omitempty"` + // MissingPermissions permissions this installation still has to approve. + MissingPermissions []DopTranslationPermissionDeficitResource `json:"missingPermissions,omitempty"` +} + +// DopTranslationPermissionCheckResource represents the PermissionCheckResource object of the +// SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type DopTranslationPermissionCheckResource struct { + // Key of the DevOps Platform configuration. + Key string `json:"key,omitempty"` + // Type devOps Platform type of the configuration (github, gitlab, azure). + Type string `json:"type,omitempty"` + // Status whether the configuration grants the write permissions the Remediation Agent needs to + // clone and open pull requests. Allowed values: SUFFICIENT, INSUFFICIENT, UNKNOWN, + // CHECK_FAILED, UNSUPPORTED_TOKEN_TYPE. + Status string `json:"status,omitempty"` + // CheckedAt epoch milliseconds when this status was last computed. May reflect a cached + // result. + CheckedAt int64 `json:"checkedAt,omitempty"` + // AppMissingPermissions gitHub only. Permissions that the GitHub App does not request. Add + // these permissions to the app before an installation can approve them. If the system cannot + // read the app configuration, 'status' is CHECK_FAILED. In this case, an empty list means that + // no app permission result is available. + AppMissingPermissions []DopTranslationPermissionDeficitResource `json:"appMissingPermissions,omitempty"` + // InstallationCheckStatus gitHub only. Whether the per-installation scan produced a + // trustworthy answer. The counts and the list are authoritative only when this is COMPLETE. On + // a project request, NOT_INSTALLED means the app is not installed on that project's repository + // — a definite answer, unlike FAILED. NOT_RUN means that the system did not check an + // installation because the project has no bound repository. In this case, 'status' covers only + // the app configuration. Allowed values: COMPLETE, FAILED, NOT_INSTALLED, NOT_RUN. + InstallationCheckStatus string `json:"installationCheckStatus,omitempty"` + // TotalInstallationCount gitHub administrator requests only. How many installations the app + // has at all. Tells an app nobody has installed apart from an app whose installations have all + // approved, which both report zero affected installations. Omitted unless + // 'installationCheckStatus' is COMPLETE. + TotalInstallationCount int32 `json:"totalInstallationCount,omitempty"` + // AffectedInstallationCount gitHub administrator requests only. Exact number of installations + // with missing permissions. Omitted unless 'installationCheckStatus' is COMPLETE. + AffectedInstallationCount int32 `json:"affectedInstallationCount,omitempty"` + // AffectedInstallations gitHub administrator requests only. Every installation with missing + // permissions, in a stable order. Omitted unless 'installationCheckStatus' is COMPLETE. + AffectedInstallations []DopTranslationAffectedInstallationResource `json:"affectedInstallations,omitempty"` +} + +// DopTranslationPermissionChecksRestResponse represents the PermissionChecksRestResponse object of +// the SonarQube V2 API. +type DopTranslationPermissionChecksRestResponse struct { + // PermissionChecks is the permission checks. + PermissionChecks []DopTranslationPermissionCheckResource `json:"permissionChecks,omitempty"` +} + +// DopTranslationGenerateScmAccessTokenOptions contains parameters for the GenerateScmAccessToken method. +type DopTranslationGenerateScmAccessTokenOptions struct { + // Project key. This field is required. + Project string `json:"project"` +} + +// DopTranslationScmAccessTokenRestResponse represents the ScmAccessTokenRestResponse object of the +// SonarQube V2 API. +type DopTranslationScmAccessTokenRestResponse struct { + // Alm is the alm. + Alm string `json:"alm,omitempty"` + // Username is the username. + Username string `json:"username,omitempty"` + // Secret is the secret. + Secret string `json:"secret,omitempty"` + // ExpiresAt is the expires at. + ExpiresAt string `json:"expiresAt,omitempty"` +} + +// ----------------------------------------------------------------------------- +// Validation Functions +// ----------------------------------------------------------------------------- + +// ValidateGenerateScmAccessTokenOpt validates the options for the GenerateScmAccessToken method. +func (s *DopTranslationService) ValidateGenerateScmAccessTokenOpt(opt *DopTranslationGenerateScmAccessTokenOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.Project, "Project") + if err != nil { + return err + } + + return nil +} + +// ----------------------------------------------------------------------------- +// Service Methods +// ----------------------------------------------------------------------------- + +// CheckPermissions checks DevOps Platform permissions for the Remediation Agent. Validates whether +// the configured DevOps Platforms (GitHub, GitLab, Azure DevOps) grant the write permissions the +// SonarQube Remediation Agent needs to clone a repository, push a branch and open a pull/merge +// request. Results are cached for a short time. Internal endpoint used by the Remediation Agent UI. +// Without 'project' it checks configurations instance-wide and requires the 'Administer System' +// permission or trusted privileged-service authentication; 'configuration' narrows that to a single +// configuration. With 'project' it checks the platform bound to that project and requires 'Browse' +// permission on the project; a GitHub project is checked against the installation covering its own +// repository, so two projects sharing one configuration can get different results. 'project' and +// 'configuration' cannot be combined. For GitHub, the response also reports permissions the app +// itself is not configured to request, and — for administrator requests — every installation that +// has not approved the permissions it does request, whether an organization or a personal account +// owns it. The counts and list are valid only when 'installationCheckStatus' is COMPLETE. If the +// scan cannot finish, 'installationCheckStatus' is FAILED and 'status' is CHECK_FAILED. The +// response does not include the counts or list. +// +// API endpoint: GET /api/v2/dop-translation/permission-checks. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *DopTranslationService) CheckPermissions(ctx context.Context, opt *DopTranslationCheckPermissionsOptions) (*DopTranslationPermissionChecksRestResponse, *http.Response, error) { + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "dop-translation/permission-checks", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(DopTranslationPermissionChecksRestResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GenerateScmAccessToken provides a scoped SCM access token. Provide a scoped git credential for +// the given project's bound DevOps Platform (GitHub or GitLab). GitHub credentials are short-lived +// and minted per call; GitLab credentials are cached in memory per SonarQube Server node and +// refreshed before expiry. Internal endpoint used by the agentic-workflows remediation orchestrator +// (SONAR-31165). Requires the 'Administer System' permission or trusted privileged-service +// authentication. +// +// API endpoint: POST /api/v2/dop-translation/scm-access-tokens. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *DopTranslationService) GenerateScmAccessToken(ctx context.Context, opt *DopTranslationGenerateScmAccessTokenOptions) (*DopTranslationScmAccessTokenRestResponse, *http.Response, error) { + err := s.ValidateGenerateScmAccessTokenOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "dop-translation/scm-access-tokens", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(DopTranslationScmAccessTokenRestResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} diff --git a/sonar/dop_translation_v2_agents_service_test.go b/sonar/dop_translation_v2_agents_service_test.go new file mode 100644 index 0000000..fba92d7 --- /dev/null +++ b/sonar/dop_translation_v2_agents_service_test.go @@ -0,0 +1,37 @@ +package sonar + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDopTranslationService_CheckPermissions(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/dop-translation/permission-checks", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.DopTranslation.CheckPermissions(context.Background(), &DopTranslationCheckPermissionsOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestDopTranslationService_GenerateScmAccessToken(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/dop-translation/scm-access-tokens", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.DopTranslation.GenerateScmAccessToken(context.Background(), &DopTranslationGenerateScmAccessTokenOptions{Project: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestDopTranslationService_GenerateScmAccessToken_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.DopTranslation.GenerateScmAccessToken(context.Background(), nil) + require.Error(t, err) +} diff --git a/sonar/history_v2_resolution_service.go b/sonar/history_v2_resolution_service.go new file mode 100644 index 0000000..483809f --- /dev/null +++ b/sonar/history_v2_resolution_service.go @@ -0,0 +1,725 @@ +package sonar + +import ( + "context" + "fmt" + "net/http" +) + +//nolint:gochecknoglobals,goconst // constant sets of allowed values +var ( + // allowedHistoryGetIssueDensityHistoryEntityType is the set of allowed values for the corresponding option. + allowedHistoryGetIssueDensityHistoryEntityType = map[string]struct{}{ + "PORTFOLIO": {}, + "PROJECT_BRANCH": {}, + "APPLICATION": {}, + } + // allowedHistoryGetIssueDensityHistorySliceBy is the set of allowed values for the corresponding option. + allowedHistoryGetIssueDensityHistorySliceBy = map[string]struct{}{ + "RULE_KEY": {}, + "SEVERITY": {}, + "TYPE_SEVERITY": {}, + "SOFTWARE_QUALITY": {}, + "STATUS": {}, + "TYPE": {}, + } + // allowedHistoryGetIssueResolutionHistoryEntityType is the set of allowed values for the corresponding option. + allowedHistoryGetIssueResolutionHistoryEntityType = map[string]struct{}{ + "PORTFOLIO": {}, + "PROJECT_BRANCH": {}, + "APPLICATION": {}, + } + // allowedHistoryGetIssueResolutionHistoryStatistic is the set of allowed values for the corresponding option. + allowedHistoryGetIssueResolutionHistoryStatistic = map[string]struct{}{ + "MTTR": {}, + "RECENT_MTTR": {}, + "RESOLVED_ISSUES": {}, + } + // allowedHistoryGetIssueResolutionHistorySliceBy is the set of allowed values for the corresponding option. + allowedHistoryGetIssueResolutionHistorySliceBy = map[string]struct{}{ + "SEVERITY": {}, + "TYPE_SEVERITY": {}, + "SOFTWARE_QUALITY": {}, + "TYPE": {}, + } + // allowedHistoryGetProjectIssueResolutionStatistic is the set of allowed values for the corresponding option. + allowedHistoryGetProjectIssueResolutionStatistic = map[string]struct{}{ + "MTTR": {}, + "RECENT_MTTR": {}, + "RESOLVED_ISSUES": {}, + } + // allowedHistoryGetProjectIssueResolutionEntityType is the set of allowed values for the corresponding option. + allowedHistoryGetProjectIssueResolutionEntityType = map[string]struct{}{ + "PORTFOLIO": {}, + "APPLICATION": {}, + } + // allowedHistoryGetProjectScaResolutionStatistic is the set of allowed values for the corresponding option. + allowedHistoryGetProjectScaResolutionStatistic = map[string]struct{}{ + "SCA_MTTR": {}, + } + // allowedHistoryGetProjectScaResolutionEntityType is the set of allowed values for the corresponding option. + allowedHistoryGetProjectScaResolutionEntityType = map[string]struct{}{ + "PORTFOLIO": {}, + "APPLICATION": {}, + } + // allowedHistoryGetScaResolutionHistoryEntityType is the set of allowed values for the corresponding option. + allowedHistoryGetScaResolutionHistoryEntityType = map[string]struct{}{ + "PORTFOLIO": {}, + "PROJECT_BRANCH": {}, + "APPLICATION": {}, + } + // allowedHistoryGetScaResolutionHistoryStatistic is the set of allowed values for the corresponding option. + allowedHistoryGetScaResolutionHistoryStatistic = map[string]struct{}{ + "SCA_MTTR": {}, + } + // allowedHistoryGetScaResolutionHistorySliceBy is the set of allowed values for the corresponding option. + allowedHistoryGetScaResolutionHistorySliceBy = map[string]struct{}{ + "SEVERITY": {}, + } +) + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// HistoryGetIssueDensityHistoryOptions contains parameters for the GetIssueDensityHistory method. +type HistoryGetIssueDensityHistoryOptions struct { + // EntityId the ID of the entity to retrieve issue density history for. This field is required. + EntityId string `json:"entityId"` + // EntityType the entity's type. This field is required. Allowed values: PORTFOLIO, + // PROJECT_BRANCH, APPLICATION. + EntityType string `json:"entityType"` + // StartDate the date and time to begin returning issue density history in ISO 8601 format + // (inclusive). The start date may not be more than 1 year in the past. This field is required. + StartDate string `json:"startDate"` + // EndDate the date and time to stop returning issue density history in ISO 8601 format + // (inclusive). If not specified, the current date and time at UTC offset 0 will be used. + EndDate string `json:"endDate,omitempty"` + // Impacts limit results to issues with specific impacts (comma-separated). An impact is a + // combination of software quality and severity. + Impacts []string `json:"impacts,omitempty"` + // IssueTypes limit results to issues with specific types (comma-separated). Allowed values are + // the SonarQube issue types. + IssueTypes []string `json:"issueTypes,omitempty"` + // RuleKeys comma separated list of ruleKeys. + RuleKeys []string `json:"ruleKeys,omitempty"` + // Severities limit results to issues with specific impact severities (comma-separated). + // Allowed values: BLOCKER, HIGH, INFO, LOW, MEDIUM. + Severities []string `json:"severities,omitempty"` + // TypeSeverities limit results to issues with specific stored issue severities (comma- + // separated). Allowed values: INFO, MINOR, MAJOR, CRITICAL, BLOCKER. + TypeSeverities []string `json:"typeSeverities,omitempty"` + // SliceBy optionally group issue counts by a specific dimension. If not provided, issue counts + // will contain a single distribution with the count of all issues. Allowed values: RULE_KEY, + // SEVERITY, TYPE_SEVERITY, SOFTWARE_QUALITY, STATUS, TYPE. + SliceBy string `json:"sliceBy,omitempty"` + // Statuses limit results to issues with specific statuses (comma-separated). Allowed values: + // ACCEPTED, CONFIRMED, FALSE_POSITIVE, FIXED, OPEN. + Statuses []string `json:"statuses,omitempty"` +} + +// HistoryIssueDensityDistribution represents the IssueDensityDistribution object of the SonarQube +// V2 API. +type HistoryIssueDensityDistribution struct { + // Key is the key. + Key string `json:"key,omitempty"` + // Value is the value. + Value float64 `json:"value,omitempty"` +} + +// HistoryIssueDensityHistory represents the IssueDensityHistory object of the SonarQube V2 API. +type HistoryIssueDensityHistory struct { + // Date and time in ISO 8601 format, including timezone offset. + Date string `json:"date,omitempty"` + // Distribution is the distribution. + Distribution []HistoryIssueDensityDistribution `json:"distribution,omitempty"` +} + +// HistoryIssueDensityHistoryResponse represents the IssueDensityHistoryResponse object of the +// SonarQube V2 API. +type HistoryIssueDensityHistoryResponse struct { + // IssueDensityHistory is the issue density history. + IssueDensityHistory []HistoryIssueDensityHistory `json:"issueDensityHistory,omitempty"` +} + +// HistoryGetIssueResolutionHistoryOptions contains parameters for the GetIssueResolutionHistory method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type HistoryGetIssueResolutionHistoryOptions struct { + // EntityId the ID of the entity to retrieve issue resolution history for. This field is + // required. + EntityId string `json:"entityId"` + // EntityType the entity's type. This field is required. Allowed values: PORTFOLIO, + // PROJECT_BRANCH, APPLICATION. + EntityType string `json:"entityType"` + // Statistic the issue resolution statistic to return. This field is required. Allowed values: + // MTTR, RECENT_MTTR, RESOLVED_ISSUES. + Statistic string `json:"statistic"` + // StartDate the inclusive start of the history range in ISO 8601 format. This field is + // required. + StartDate string `json:"startDate"` + // EndDate the inclusive end of the history range. Defaults to the current date. + EndDate string `json:"endDate,omitempty"` + // Impacts limit results to issues with specific impacts (comma-separated). An impact is a + // combination of software quality and severity. + Impacts []string `json:"impacts,omitempty"` + // IssueTypes limit results to issues with specific types (comma-separated). Allowed values are + // the SonarQube issue types. + IssueTypes []string `json:"issueTypes,omitempty"` + // Severities limit results to issues with specific impact severities (comma-separated). + // Allowed values: BLOCKER, HIGH, INFO, LOW, MEDIUM. + Severities []string `json:"severities,omitempty"` + // TypeSeverities limit results to issues with specific stored issue severities (comma- + // separated). Allowed values: INFO, MINOR, MAJOR, CRITICAL, BLOCKER. + TypeSeverities []string `json:"typeSeverities,omitempty"` + // SliceBy optionally group issue resolution metrics by a dimension. Allowed values: SEVERITY, + // TYPE_SEVERITY, SOFTWARE_QUALITY, TYPE. + SliceBy string `json:"sliceBy,omitempty"` +} + +// HistoryIssueResolutionDistribution represents the IssueResolutionDistribution object of the +// SonarQube V2 API. +type HistoryIssueResolutionDistribution struct { + // Key is the key. + Key string `json:"key,omitempty"` + // Value is the value. + Value int32 `json:"value,omitempty"` +} + +// HistoryIssueResolutionHistory represents the IssueResolutionHistory object of the SonarQube V2 +// API. +type HistoryIssueResolutionHistory struct { + // Date and time in ISO 8601 format, including timezone offset. + Date string `json:"date,omitempty"` + // Distribution is the distribution. + Distribution []HistoryIssueResolutionDistribution `json:"distribution,omitempty"` +} + +// HistoryIssueResolutionHistoryResponse represents the IssueResolutionHistoryResponse object of the +// SonarQube V2 API. +type HistoryIssueResolutionHistoryResponse struct { + // Statistic is the statistic. Allowed values: MTTR, RECENT_MTTR, RESOLVED_ISSUES. + Statistic string `json:"statistic,omitempty"` + // IssueResolutionHistory is the issue resolution history. + IssueResolutionHistory []HistoryIssueResolutionHistory `json:"issueResolutionHistory,omitempty"` +} + +// HistoryGetProjectIssueResolutionOptions contains parameters for the GetProjectIssueResolution method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type HistoryGetProjectIssueResolutionOptions struct { + // Statistic the project issue resolution statistic to return. This field is required. Allowed + // values: MTTR, RECENT_MTTR, RESOLVED_ISSUES. + Statistic string `json:"statistic"` + // PortfolioId legacy portfolio ID. Either portfolioId or both entityType and entityId must be + // provided, but not both forms. + PortfolioId string `json:"portfolioId,omitempty"` + // EntityType type of the selection whose projects are returned as a flattened list. Must be + // provided together with entityId and cannot be combined with portfolioId. Allowed values: + // PORTFOLIO, APPLICATION. + EntityType string `json:"entityType,omitempty"` + // EntityId portfolio ID or application branch ID, according to entityType. Must be provided + // together with entityType and cannot be combined with portfolioId. + EntityId string `json:"entityId,omitempty"` + // Severities limit results to issues with specific impact severities (comma-separated). + // Allowed values: BLOCKER, HIGH, INFO, LOW, MEDIUM. + Severities []string `json:"severities,omitempty"` + // TypeSeverities limit results to issues with specific stored issue severities (comma- + // separated). Allowed values: INFO, MINOR, MAJOR, CRITICAL, BLOCKER. + TypeSeverities []string `json:"typeSeverities,omitempty"` + // IssueTypes limit results to issues with specific types (comma-separated). Allowed values are + // the SonarQube issue types. + IssueTypes []string `json:"issueTypes,omitempty"` + // Impacts limit results to issues with specific impacts (comma-separated). An impact is a + // combination of software quality and severity. + Impacts []string `json:"impacts,omitempty"` + // NameContains search query for project names, case insensitive. + NameContains string `json:"nameContains,omitempty"` + // TrendSince date used as the trend comparison point. + TrendSince string `json:"trendSince,omitempty"` + // PageIndex index of the page to fetch. + // Minimum value: 1. Default: 1. + PageIndex int32 `json:"pageIndex,omitempty"` + // PageSize number of projects to return. A value of 0 returns pagination only. + // Must be between 0 and 5000. Default: 50. + PageSize int32 `json:"pageSize,omitempty"` +} + +// HistoryProjectIssueResolution represents the ProjectIssueResolution object of the SonarQube V2 +// API. +type HistoryProjectIssueResolution struct { + // BranchId is the branch id. + BranchId string `json:"branchId,omitempty"` + // ProjectName is the project name. + ProjectName string `json:"projectName,omitempty"` + // ProjectKey is the project key. + ProjectKey string `json:"projectKey,omitempty"` + // BranchName is the branch name. + BranchName string `json:"branchName,omitempty"` + // Value is the value. + Value int64 `json:"value,omitempty"` + // TrendPercentage percentage change in value relative to trendSince. + TrendPercentage float64 `json:"trendPercentage,omitempty"` +} + +// HistoryProjectIssueResolutionResponse represents the ProjectIssueResolutionResponse object of the +// SonarQube V2 API. +type HistoryProjectIssueResolutionResponse struct { + // Statistic is the statistic. Allowed values: MTTR, RECENT_MTTR, RESOLVED_ISSUES. + Statistic string `json:"statistic,omitempty"` + // ProjectIssueResolution is the project issue resolution. + ProjectIssueResolution []HistoryProjectIssueResolution `json:"projectIssueResolution,omitempty"` + // Page is the page. + Page PageResponseV2 `json:"page,omitzero"` +} + +// HistoryGetProjectScaResolutionOptions contains parameters for the GetProjectScaResolution method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type HistoryGetProjectScaResolutionOptions struct { + // Statistic the SCA resolution statistic to return. This field is required. Allowed values: + // SCA_MTTR. + Statistic string `json:"statistic"` + // PortfolioId legacy portfolio ID. Either portfolioId or both entityType and entityId must be + // provided, but not both forms. + PortfolioId string `json:"portfolioId,omitempty"` + // EntityType type of the selection whose projects are returned as a flattened list. Must be + // provided together with entityId and cannot be combined with portfolioId. Allowed values: + // PORTFOLIO, APPLICATION. + EntityType string `json:"entityType,omitempty"` + // EntityId portfolio ID or application branch ID, according to entityType. Must be provided + // together with entityType and cannot be combined with portfolioId. + EntityId string `json:"entityId,omitempty"` + // Severities limit results to issues with specific impact severities (comma-separated). + // Allowed values: BLOCKER, HIGH, INFO, LOW, MEDIUM. + Severities []string `json:"severities,omitempty"` + // NameContains search query for project names, case insensitive. + NameContains string `json:"nameContains,omitempty"` + // TrendSince the date from which to compare project SCA resolution values. Past values are + // unbounded. The value must be before the current date in UTC; values on or after the current + // date are rejected. If omitted, trend is not returned in the response. + TrendSince string `json:"trendSince,omitempty"` + // PageIndex index of the page to fetch. + // Minimum value: 1. Default: 1. + PageIndex int32 `json:"pageIndex,omitempty"` + // PageSize size of the page to fetch. + // Must be between 0 and 5000. Default: 50. + PageSize int32 `json:"pageSize,omitempty"` +} + +// HistoryProjectScaResolution represents the ProjectScaResolution object of the SonarQube V2 API. +type HistoryProjectScaResolution struct { + // BranchId is the branch id. + BranchId string `json:"branchId,omitempty"` + // ProjectName is the project name. + ProjectName string `json:"projectName,omitempty"` + // ProjectKey is the project key. + ProjectKey string `json:"projectKey,omitempty"` + // BranchName is the branch name. + BranchName string `json:"branchName,omitempty"` + // Value is the value. + Value int64 `json:"value,omitempty"` + // Trend percentage change in value relative to the value at trendSince. + Trend float64 `json:"trend,omitempty"` +} + +// HistoryProjectScaResolutionResponse represents the ProjectScaResolutionResponse object of the +// SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type HistoryProjectScaResolutionResponse struct { + // HiddenProjectCount the number of projects excluded from the response because the user does + // not have permission to view those projects. + HiddenProjectCount int32 `json:"hiddenProjectCount,omitempty"` + // Statistic is the statistic. Allowed values: SCA_MTTR. + Statistic string `json:"statistic,omitempty"` + // ProjectScaResolution is the project sca resolution. + ProjectScaResolution []HistoryProjectScaResolution `json:"projectScaResolution,omitempty"` + // Page is the page. + Page PageResponseV2 `json:"page,omitzero"` +} + +// HistoryGetScaResolutionHistoryOptions contains parameters for the GetScaResolutionHistory method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type HistoryGetScaResolutionHistoryOptions struct { + // EntityId the ID of the entity to retrieve SCA resolution history for. This field is + // required. + EntityId string `json:"entityId"` + // EntityType the entity's type. This field is required. Allowed values: PORTFOLIO, + // PROJECT_BRANCH, APPLICATION. + EntityType string `json:"entityType"` + // Statistic the SCA resolution statistic to return. This field is required. Allowed values: + // SCA_MTTR. + Statistic string `json:"statistic"` + // StartDate inclusive start of the date range. Past values are unbounded. This field is + // required. + StartDate string `json:"startDate"` + // EndDate inclusive end of the date range. Defaults to the current date and time in UTC. + // Future values are clamped to the current time. + EndDate string `json:"endDate,omitempty"` + // Severities limit results to issues with specific impact severities (comma-separated). + // Allowed values: BLOCKER, HIGH, INFO, LOW, MEDIUM. + Severities []string `json:"severities,omitempty"` + // SliceBy optionally group SCA resolution metrics by severity. Allowed values: SEVERITY. + SliceBy string `json:"sliceBy,omitempty"` +} + +// HistoryScaResolutionDistribution represents the ScaResolutionDistribution object of the SonarQube +// V2 API. +type HistoryScaResolutionDistribution struct { + // Key is the key. + Key string `json:"key,omitempty"` + // Value is the value. + Value int32 `json:"value,omitempty"` +} + +// HistoryScaResolutionHistory represents the ScaResolutionHistory object of the SonarQube V2 API. +type HistoryScaResolutionHistory struct { + // Date and time in ISO 8601 format, including timezone offset. + Date string `json:"date,omitempty"` + // Distribution is the distribution. + Distribution []HistoryScaResolutionDistribution `json:"distribution,omitempty"` +} + +// HistoryScaResolutionHistoryResponse represents the ScaResolutionHistoryResponse object of the +// SonarQube V2 API. +type HistoryScaResolutionHistoryResponse struct { + // Statistic is the statistic. Allowed values: SCA_MTTR. + Statistic string `json:"statistic,omitempty"` + // ScaResolutionHistory is the sca resolution history. + ScaResolutionHistory []HistoryScaResolutionHistory `json:"scaResolutionHistory,omitempty"` +} + +// ----------------------------------------------------------------------------- +// Validation Functions +// ----------------------------------------------------------------------------- + +// ValidateGetIssueDensityHistoryOpt validates the options for the GetIssueDensityHistory method. +func (s *HistoryService) ValidateGetIssueDensityHistoryOpt(opt *HistoryGetIssueDensityHistoryOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.EntityId, "EntityId") + if err != nil { + return err + } + + err = ValidateRequired(opt.EntityType, "EntityType") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.EntityType, allowedHistoryGetIssueDensityHistoryEntityType, "EntityType") + if err != nil { + return err + } + + err = ValidateRequired(opt.StartDate, "StartDate") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.SliceBy, allowedHistoryGetIssueDensityHistorySliceBy, "SliceBy") + if err != nil { + return err + } + + return nil +} + +// ValidateGetIssueResolutionHistoryOpt validates the options for the GetIssueResolutionHistory method. +func (s *HistoryService) ValidateGetIssueResolutionHistoryOpt(opt *HistoryGetIssueResolutionHistoryOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.EntityId, "EntityId") + if err != nil { + return err + } + + err = ValidateRequired(opt.EntityType, "EntityType") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.EntityType, allowedHistoryGetIssueResolutionHistoryEntityType, "EntityType") + if err != nil { + return err + } + + err = ValidateRequired(opt.Statistic, "Statistic") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Statistic, allowedHistoryGetIssueResolutionHistoryStatistic, "Statistic") + if err != nil { + return err + } + + err = ValidateRequired(opt.StartDate, "StartDate") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.SliceBy, allowedHistoryGetIssueResolutionHistorySliceBy, "SliceBy") + if err != nil { + return err + } + + return nil +} + +// ValidateGetProjectIssueResolutionOpt validates the options for the GetProjectIssueResolution method. +func (s *HistoryService) ValidateGetProjectIssueResolutionOpt(opt *HistoryGetProjectIssueResolutionOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.Statistic, "Statistic") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Statistic, allowedHistoryGetProjectIssueResolutionStatistic, "Statistic") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.EntityType, allowedHistoryGetProjectIssueResolutionEntityType, "EntityType") + if err != nil { + return err + } + + if opt.PageIndex < 0 { + return NewValidationError("PageIndex", "must be greater than 0", ErrOutOfRange) + } + + if opt.PageSize < 0 || opt.PageSize > 5000 { + return NewValidationError("PageSize", "must be between 0 and 5000", ErrOutOfRange) + } + + return nil +} + +// ValidateGetProjectScaResolutionOpt validates the options for the GetProjectScaResolution method. +func (s *HistoryService) ValidateGetProjectScaResolutionOpt(opt *HistoryGetProjectScaResolutionOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.Statistic, "Statistic") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Statistic, allowedHistoryGetProjectScaResolutionStatistic, "Statistic") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.EntityType, allowedHistoryGetProjectScaResolutionEntityType, "EntityType") + if err != nil { + return err + } + + if opt.PageIndex < 0 { + return NewValidationError("PageIndex", "must be greater than 0", ErrOutOfRange) + } + + if opt.PageSize < 0 || opt.PageSize > 5000 { + return NewValidationError("PageSize", "must be between 0 and 5000", ErrOutOfRange) + } + + return nil +} + +// ValidateGetScaResolutionHistoryOpt validates the options for the GetScaResolutionHistory method. +func (s *HistoryService) ValidateGetScaResolutionHistoryOpt(opt *HistoryGetScaResolutionHistoryOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.EntityId, "EntityId") + if err != nil { + return err + } + + err = ValidateRequired(opt.EntityType, "EntityType") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.EntityType, allowedHistoryGetScaResolutionHistoryEntityType, "EntityType") + if err != nil { + return err + } + + err = ValidateRequired(opt.Statistic, "Statistic") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Statistic, allowedHistoryGetScaResolutionHistoryStatistic, "Statistic") + if err != nil { + return err + } + + err = ValidateRequired(opt.StartDate, "StartDate") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.SliceBy, allowedHistoryGetScaResolutionHistorySliceBy, "SliceBy") + if err != nil { + return err + } + + return nil +} + +// ----------------------------------------------------------------------------- +// Service Methods +// ----------------------------------------------------------------------------- + +// GetIssueDensityHistory provides issue density history data for an entity. Returns the history of +// issue density (issues per 1000 NCLOC) for an entity. The maximum resolution for history data is 1 +// day. All history is recorded in UTC timezone. Issue density values carry-forward for days with no +// changes. Density is calculated as (issue count / NCLOC) * 1000. +// +// API endpoint: GET /api/v2/history/issue-density-history. +// Enterprise Edition only. +func (s *HistoryService) GetIssueDensityHistory(ctx context.Context, opt *HistoryGetIssueDensityHistoryOptions) (*HistoryIssueDensityHistoryResponse, *http.Response, error) { + err := s.ValidateGetIssueDensityHistoryOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "history/issue-density-history", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(HistoryIssueDensityHistoryResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetIssueResolutionHistory provides issue resolution history data for an entity. Returns issue +// resolution metrics for an entity. All history is recorded at UTC midnight. MTTR and RECENT_MTTR +// use a trailing 30-day window ending on each returned day. SQS deliberately permits dates older +// than one year under the approved history date-range policy. +// +// API endpoint: GET /api/v2/history/issue-resolution-history. +// Enterprise Edition only. +func (s *HistoryService) GetIssueResolutionHistory(ctx context.Context, opt *HistoryGetIssueResolutionHistoryOptions) (*HistoryIssueResolutionHistoryResponse, *http.Response, error) { + err := s.ValidateGetIssueResolutionHistoryOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "history/issue-resolution-history", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(HistoryIssueResolutionHistoryResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetProjectIssueResolution gets project issue resolution. Get issue resolution values for the +// flattened list of projects in a portfolio or application. Identify the selection with either the +// legacy portfolioId parameter, or with both entityType and entityId. The two selector forms are +// mutually exclusive. +// +// API endpoint: GET /api/v2/history/project-issue-resolution. +// Enterprise Edition only. +func (s *HistoryService) GetProjectIssueResolution(ctx context.Context, opt *HistoryGetProjectIssueResolutionOptions) (*HistoryProjectIssueResolutionResponse, *http.Response, error) { + err := s.ValidateGetProjectIssueResolutionOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "history/project-issue-resolution", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(HistoryProjectIssueResolutionResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetProjectScaResolution gets project SCA resolution. Get SCA resolution values for the flattened +// list of projects in a portfolio or application. Identify the selection with either the legacy +// portfolioId parameter, or with both entityType and entityId. The two selector forms are mutually +// exclusive. +// +// API endpoint: GET /api/v2/history/project-sca-resolution. +// Enterprise Edition only. +func (s *HistoryService) GetProjectScaResolution(ctx context.Context, opt *HistoryGetProjectScaResolutionOptions) (*HistoryProjectScaResolutionResponse, *http.Response, error) { + err := s.ValidateGetProjectScaResolutionOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "history/project-sca-resolution", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(HistoryProjectScaResolutionResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetScaResolutionHistory provides SCA resolution history data for an entity. Returns the history +// of SCA resolution metrics for an entity. All history is recorded in UTC. SCA MTTR values are +// calculated using a trailing 30-day window ending on each returned day. +// +// API endpoint: GET /api/v2/history/sca-resolution-history. +// Enterprise Edition only. +func (s *HistoryService) GetScaResolutionHistory(ctx context.Context, opt *HistoryGetScaResolutionHistoryOptions) (*HistoryScaResolutionHistoryResponse, *http.Response, error) { + err := s.ValidateGetScaResolutionHistoryOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "history/sca-resolution-history", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(HistoryScaResolutionHistoryResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} diff --git a/sonar/history_v2_resolution_service_test.go b/sonar/history_v2_resolution_service_test.go new file mode 100644 index 0000000..4c563ff --- /dev/null +++ b/sonar/history_v2_resolution_service_test.go @@ -0,0 +1,95 @@ +package sonar + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHistoryService_GetIssueDensityHistory(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/history/issue-density-history", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.History.GetIssueDensityHistory(context.Background(), &HistoryGetIssueDensityHistoryOptions{EntityId: "test", EntityType: "PORTFOLIO", StartDate: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestHistoryService_GetIssueDensityHistory_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.History.GetIssueDensityHistory(context.Background(), nil) + require.Error(t, err) +} + +func TestHistoryService_GetIssueResolutionHistory(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/history/issue-resolution-history", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.History.GetIssueResolutionHistory(context.Background(), &HistoryGetIssueResolutionHistoryOptions{EntityId: "test", EntityType: "PORTFOLIO", Statistic: "MTTR", StartDate: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestHistoryService_GetIssueResolutionHistory_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.History.GetIssueResolutionHistory(context.Background(), nil) + require.Error(t, err) +} + +func TestHistoryService_GetProjectIssueResolution(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/history/project-issue-resolution", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.History.GetProjectIssueResolution(context.Background(), &HistoryGetProjectIssueResolutionOptions{Statistic: "MTTR"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestHistoryService_GetProjectIssueResolution_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.History.GetProjectIssueResolution(context.Background(), nil) + require.Error(t, err) +} + +func TestHistoryService_GetProjectScaResolution(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/history/project-sca-resolution", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.History.GetProjectScaResolution(context.Background(), &HistoryGetProjectScaResolutionOptions{Statistic: "SCA_MTTR"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestHistoryService_GetProjectScaResolution_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.History.GetProjectScaResolution(context.Background(), nil) + require.Error(t, err) +} + +func TestHistoryService_GetScaResolutionHistory(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/history/sca-resolution-history", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.History.GetScaResolutionHistory(context.Background(), &HistoryGetScaResolutionHistoryOptions{EntityId: "test", EntityType: "PORTFOLIO", Statistic: "SCA_MTTR", StartDate: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestHistoryService_GetScaResolutionHistory_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.History.GetScaResolutionHistory(context.Background(), nil) + require.Error(t, err) +} diff --git a/sonar/hotspots_service.go b/sonar/hotspots_service.go index 158479e..060cd1b 100644 --- a/sonar/hotspots_service.go +++ b/sonar/hotspots_service.go @@ -206,6 +206,36 @@ type HotspotRule struct { // Response Types // ============================================================================= +// HotspotsMigratedProject represents the migration result of a single project. +type HotspotsMigratedProject struct { + // ProjectKey is the key of the project. + ProjectKey string `json:"projectKey,omitempty"` + // Migrated is the number of hotspots migrated (or that would be migrated for a dry run). + Migrated int64 `json:"migrated,omitempty"` +} + +// HotspotsMigrateToIssues represents the response from migrating Security Hotspots to Issues. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type HotspotsMigrateToIssues struct { + // DryRun indicates whether the call only counted hotspots without performing any writes. + DryRun bool `json:"dryRun,omitempty"` + // Skipped is the scope-wide number of findings that cannot be migrated because their rule is still a Security Hotspot. + Skipped int64 `json:"skipped,omitempty"` + // Projects is the per-project migration result. + Projects []HotspotsMigratedProject `json:"projects,omitempty"` +} + +// HotspotsMigrationStatus represents the response from the migration status endpoint. +type HotspotsMigrationStatus struct { + // RemainingHotspots is the number of hotspots that remain to be migrated. + RemainingHotspots int64 `json:"remainingHotspots,omitempty"` + // NotConvertedHotspots is the number of findings whose rule is still a Security Hotspot and cannot be migrated yet. + NotConvertedHotspots int64 `json:"notConvertedHotspots,omitempty"` + // Complete indicates whether the migration is complete for the requested scope. + Complete bool `json:"complete,omitempty"` +} + // HotspotsEditComment represents the response from editing a hotspot comment. type HotspotsEditComment struct { // CreatedAt is the timestamp when the comment was created. @@ -288,6 +318,20 @@ type HotspotsShow struct { // Option Types // ============================================================================= +// HotspotsMigrateToIssuesOptions contains parameters for the MigrateToIssues method. +type HotspotsMigrateToIssuesOptions struct { + // Project is the project key. If empty, all projects are migrated. + Project string `url:"project,omitempty"` + // DryRun only counts the hotspots to migrate without performing any writes. + DryRun bool `url:"dryRun,omitempty"` +} + +// HotspotsMigrationStatusOptions contains parameters for the MigrationStatus method. +type HotspotsMigrationStatusOptions struct { + // Project is the project key. If empty, the status is reported for the whole instance. + Project string `url:"project,omitempty"` +} + // HotspotsAddCommentOptions contains parameters for the AddComment method. type HotspotsAddCommentOptions struct { // Comment is the comment text. @@ -730,6 +774,52 @@ func (s *HotspotsService) ValidateShowOpt(opt *HotspotsShowOptions) error { // Service Methods // ============================================================================= +// MigrateToIssues migrates Security Hotspots to Issues. +// Requires the 'Administer System' permission. +// Findings whose rule is still a Security Hotspot cannot be migrated and are reported as skipped. +// +// API endpoint: POST /api/hotspots/migrate_to_issues. +// Since: 2026.4. +// Internal: true. +func (s *HotspotsService) MigrateToIssues(ctx context.Context, opt *HotspotsMigrateToIssuesOptions) (*HotspotsMigrateToIssues, *http.Response, error) { + req, err := s.client.NewSonarQubeV1APIRequest(ctx, http.MethodPost, "hotspots/migrate_to_issues", opt) + if err != nil { + return nil, nil, err + } + + result := new(HotspotsMigrateToIssues) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// MigrationStatus reports how many Security Hotspots remain to be migrated to Issues. +// The migration is complete for the scope once RemainingHotspots reaches zero. +// Requires the 'Administer System' permission. +// +// API endpoint: GET /api/hotspots/migration_status. +// Since: 2026.4. +// Internal: true. +func (s *HotspotsService) MigrationStatus(ctx context.Context, opt *HotspotsMigrationStatusOptions) (*HotspotsMigrationStatus, *http.Response, error) { + req, err := s.client.NewSonarQubeV1APIRequest(ctx, http.MethodGet, "hotspots/migration_status", opt) + if err != nil { + return nil, nil, err + } + + result := new(HotspotsMigrationStatus) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + // AddComment adds a comment to a Security Hotspot. // Requires authentication and the 'Browse' permission on the project. // diff --git a/sonar/hotspots_service_test.go b/sonar/hotspots_service_test.go index ba35f06..44e74a7 100644 --- a/sonar/hotspots_service_test.go +++ b/sonar/hotspots_service_test.go @@ -1008,3 +1008,34 @@ func TestHotspotsService_SearchAll(t *testing.T) { assert.Error(t, err) }) } + +func TestHotspots_MigrateToIssues(t *testing.T) { + server := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "/hotspots/migrate_to_issues", r.URL.Path) + assert.Equal(t, "true", r.URL.Query().Get("dryRun")) + assert.Equal(t, "my-project", r.URL.Query().Get("project")) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"dryRun":true,"skipped":2,"projects":[{"projectKey":"my-project","migrated":12}]}`)) + }) + client := newTestClient(t, server.URL) + + result, _, err := client.Hotspots.MigrateToIssues(context.Background(), &HotspotsMigrateToIssuesOptions{DryRun: true, Project: "my-project"}) + require.NoError(t, err) + assert.Equal(t, &HotspotsMigrateToIssues{ + DryRun: true, + Skipped: 2, + Projects: []HotspotsMigratedProject{{ProjectKey: "my-project", Migrated: 12}}, + }, result) +} + +func TestHotspots_MigrationStatus(t *testing.T) { + // Payload captured from a live SonarQube 2026.5 Enterprise instance. + response := `{"remainingHotspots":3,"notConvertedHotspots":1,"complete":false}` + server := newTestServer(t, mockHandlerWithParams(t, http.MethodGet, "/hotspots/migration_status", http.StatusOK, map[string]string{"project": "my-project"}, response)) + client := newTestClient(t, server.URL) + + result, _, err := client.Hotspots.MigrationStatus(context.Background(), &HotspotsMigrationStatusOptions{Project: "my-project"}) + require.NoError(t, err) + assert.Equal(t, &HotspotsMigrationStatus{RemainingHotspots: 3, NotConvertedHotspots: 1}, result) +} diff --git a/sonar/issues_service.go b/sonar/issues_service.go index 13da4b5..8ba1206 100644 --- a/sonar/issues_service.go +++ b/sonar/issues_service.go @@ -715,7 +715,7 @@ type IssuesSearchOptions struct { // Rules is the list of rule keys to filter by. Rules []string `url:"rules,omitempty,comma"` // Sort is the sort field. - // Allowed values: CREATION_DATE, CLOSE_DATE, SEVERITY, STATUS, FILE_LINE, HOTSPOTS, UPDATE_DATE + // Allowed values: CREATION_DATE, CLOSE_DATE, SEVERITY, STATUS, FILE_LINE, HOTSPOTS, UPDATE_DATE, IMPACT_RANK Sort string `url:"s,omitempty"` // SansTop25 is the list of SANS Top 25 categories. // Allowed values: insecure-interaction, risky-resource, porous-defenses diff --git a/sonar/llm_connectivity_v2_service.go b/sonar/llm_connectivity_v2_service.go new file mode 100644 index 0000000..de7b48f --- /dev/null +++ b/sonar/llm_connectivity_v2_service.go @@ -0,0 +1,579 @@ +package sonar + +import ( + "context" + "fmt" + "net/http" + "net/url" +) + +// LlmConnectivityService handles communication with the LLM provider connectivity related methods of the +// SonarQube V2 API. This service is only available in Enterprise Edition. +type LlmConnectivityService struct { + // client is used to communicate with the SonarQube API. + client *Client +} + +//nolint:gochecknoglobals,goconst // constant sets of allowed values +var ( + // allowedLlmConnectivityGetProviderMappingsAiCapability is the set of allowed values for the corresponding option. + allowedLlmConnectivityGetProviderMappingsAiCapability = map[string]struct{}{ + "AI_CODEFIX": {}, + "HUNTER_AGENT": {}, + "REMEDIATION_AGENT": {}, + } + // allowedLlmConnectivityGetSupportedModelsAiCapability is the set of allowed values for the corresponding option. + allowedLlmConnectivityGetSupportedModelsAiCapability = map[string]struct{}{ + "AI_CODEFIX": {}, + "HUNTER_AGENT": {}, + "REMEDIATION_AGENT": {}, + } + // allowedLlmConnectivityUpsertProviderMappingAiCapability is the set of allowed values for the corresponding option. + allowedLlmConnectivityUpsertProviderMappingAiCapability = map[string]struct{}{ + "AI_CODEFIX": {}, + "HUNTER_AGENT": {}, + "REMEDIATION_AGENT": {}, + } + // allowedLlmConnectivityCreateProviderProvider is the set of allowed values for the corresponding option. + allowedLlmConnectivityCreateProviderProvider = map[string]struct{}{ + "AZURE_OPENAI": {}, + "AWS_BEDROCK": {}, + "AWS_BEDROCK_MANTLE": {}, + "VERTEX_AI": {}, + "CUSTOM_PROXY": {}, + "OPENAI": {}, + "ANTHROPIC": {}, + } +) + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// LlmConnectivityLlmProviderFieldResponse represents the LlmProviderFieldResponse object of the +// SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type LlmConnectivityLlmProviderFieldResponse struct { + // Key is the key. + Key string `json:"key,omitempty"` + // Label is the label. + Label string `json:"label,omitempty"` + // Required is the required. + Required bool `json:"required,omitempty"` + // Type is the type. Allowed values: STRING, HTTP_HEADERS. + Type string `json:"type,omitempty"` + // Secret is the secret. + Secret bool `json:"secret,omitempty"` +} + +// LlmConnectivityLlmProviderDefinitionResponse represents the LlmProviderDefinitionResponse object +// of the SonarQube V2 API. +type LlmConnectivityLlmProviderDefinitionResponse struct { + // Provider is the provider. Allowed values: AZURE_OPENAI, AWS_BEDROCK, AWS_BEDROCK_MANTLE, + // VERTEX_AI, CUSTOM_PROXY, OPENAI, ANTHROPIC. + Provider string `json:"provider,omitempty"` + // Label is the label. + Label string `json:"label,omitempty"` + // Fields is the fields. + Fields []LlmConnectivityLlmProviderFieldResponse `json:"fields,omitempty"` +} + +// LlmConnectivityLlmProviderDefinitionsResponse represents the LlmProviderDefinitionsResponse +// object of the SonarQube V2 API. +type LlmConnectivityLlmProviderDefinitionsResponse struct { + // ProviderDefinitions is the provider definitions. + ProviderDefinitions []LlmConnectivityLlmProviderDefinitionResponse `json:"providerDefinitions,omitempty"` +} + +// LlmConnectivityGetProviderMappingsOptions contains parameters for the GetProviderMappings method. +type LlmConnectivityGetProviderMappingsOptions struct { + // AiCapability is the ai capability. Allowed values: AI_CODEFIX, HUNTER_AGENT, + // REMEDIATION_AGENT. + AiCapability string `json:"aiCapability,omitempty"` +} + +// LlmConnectivityProviderMappingResponse represents the ProviderMappingResponse object of the +// SonarQube V2 API. +type LlmConnectivityProviderMappingResponse struct { + // AiCapability is the ai capability. Allowed values: AI_CODEFIX, HUNTER_AGENT, + // REMEDIATION_AGENT. + AiCapability string `json:"aiCapability,omitempty"` + // LlmProviderId is the llm provider id. + LlmProviderId string `json:"llmProviderId,omitempty"` + // ModelIdentifier is the model identifier. + ModelIdentifier string `json:"modelIdentifier,omitempty"` + // SecondaryModelIdentifier is the secondary model identifier. + SecondaryModelIdentifier string `json:"secondaryModelIdentifier,omitempty"` +} + +// LlmConnectivityProviderMappingsResponse represents the ProviderMappingsResponse object of the +// SonarQube V2 API. +type LlmConnectivityProviderMappingsResponse struct { + // ProviderMappings is the provider mappings. + ProviderMappings []LlmConnectivityProviderMappingResponse `json:"providerMappings,omitempty"` +} + +// LlmConnectivityLlmProviderResponse represents the LlmProviderResponse object of the SonarQube V2 +// API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type LlmConnectivityLlmProviderResponse struct { + // Id is the id. + Id string `json:"id,omitempty"` + // Provider is the provider. Allowed values: AZURE_OPENAI, AWS_BEDROCK, AWS_BEDROCK_MANTLE, + // VERTEX_AI, CUSTOM_PROXY, OPENAI, ANTHROPIC. + Provider string `json:"provider,omitempty"` + // Label is the label. + Label string `json:"label,omitempty"` + // Configuration is the configuration. + Configuration map[string]any `json:"configuration,omitempty"` +} + +// LlmConnectivityLlmProvidersResponse represents the LlmProvidersResponse object of the SonarQube +// V2 API. +type LlmConnectivityLlmProvidersResponse struct { + // Providers is the providers. + Providers []LlmConnectivityLlmProviderResponse `json:"providers,omitempty"` +} + +// LlmConnectivityGetSupportedModelsOptions contains parameters for the GetSupportedModels method. +type LlmConnectivityGetSupportedModelsOptions struct { + // AiCapability is the ai capability. This field is required. Allowed values: AI_CODEFIX, + // HUNTER_AGENT, REMEDIATION_AGENT. + AiCapability string `json:"aiCapability"` +} + +// LlmConnectivitySupportedModelResponse represents the SupportedModelResponse object of the +// SonarQube V2 API. +type LlmConnectivitySupportedModelResponse struct { + // ModelKey is the model key. + ModelKey string `json:"modelKey,omitempty"` + // ModelDisplayName is the model display name. + ModelDisplayName string `json:"modelDisplayName,omitempty"` + // ModelIdentifiers is the model identifiers. + ModelIdentifiers []string `json:"modelIdentifiers,omitempty"` + // SupportedSecondaryModels is the supported secondary models. + SupportedSecondaryModels []LlmConnectivitySupportedModelResponse `json:"supportedSecondaryModels,omitempty"` +} + +// LlmConnectivitySupportedModelsResponse represents the SupportedModelsResponse object of the +// SonarQube V2 API. +type LlmConnectivitySupportedModelsResponse struct { + // SupportedModels is the supported models. + SupportedModels []LlmConnectivitySupportedModelResponse `json:"supportedModels,omitempty"` +} + +// LlmConnectivityUpdateProviderOptions contains the request body for the UpdateProvider method. +type LlmConnectivityUpdateProviderOptions struct { + // Label is the label. + Label *string `json:"label,omitempty"` + // Configuration is the configuration. + Configuration map[string]any `json:"configuration,omitempty"` +} + +// LlmConnectivityUpsertProviderMappingOptions contains the request body for the +// UpsertProviderMapping method. +type LlmConnectivityUpsertProviderMappingOptions struct { + // AiCapability is the ai capability. This field is required. Allowed values: AI_CODEFIX, + // HUNTER_AGENT, REMEDIATION_AGENT. + AiCapability string `json:"aiCapability"` + // LlmProviderId is the llm provider id. This field is required. + LlmProviderId string `json:"llmProviderId"` + // ModelIdentifier is the model identifier. This field is required. + ModelIdentifier string `json:"modelIdentifier"` + // SecondaryModelIdentifier is the secondary model identifier. + SecondaryModelIdentifier string `json:"secondaryModelIdentifier,omitempty"` +} + +// LlmConnectivityValidateProviderOptions contains the request body for the ValidateProvider method. +type LlmConnectivityValidateProviderOptions struct { + // LlmProviderId is the llm provider id. This field is required. + LlmProviderId string `json:"llmProviderId"` +} + +// LlmConnectivityProviderValidationResponse represents the ProviderValidationResponse object of the +// SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type LlmConnectivityProviderValidationResponse struct { + // LlmProviderId is the llm provider id. + LlmProviderId string `json:"llmProviderId,omitempty"` + // IsValid is the is valid. + IsValid bool `json:"isValid,omitempty"` + // Error is the error. + Error string `json:"error,omitempty"` +} + +// LlmConnectivityCreateProviderOptions contains the request body for the CreateProvider method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type LlmConnectivityCreateProviderOptions struct { + // Provider is the provider. Allowed values: AZURE_OPENAI, AWS_BEDROCK, AWS_BEDROCK_MANTLE, + // VERTEX_AI, CUSTOM_PROXY, OPENAI, ANTHROPIC. + Provider string `json:"provider,omitempty"` + // Label is the label. + Label string `json:"label,omitempty"` + // Configuration is the configuration. + Configuration map[string]any `json:"configuration,omitempty"` +} + +// ----------------------------------------------------------------------------- +// Validation Functions +// ----------------------------------------------------------------------------- + +// ValidateGetProviderMappingsOpt validates the options for the GetProviderMappings method. +func (s *LlmConnectivityService) ValidateGetProviderMappingsOpt(opt *LlmConnectivityGetProviderMappingsOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := IsValueAuthorized(opt.AiCapability, allowedLlmConnectivityGetProviderMappingsAiCapability, "AiCapability") + if err != nil { + return err + } + + return nil +} + +// ValidateGetSupportedModelsOpt validates the options for the GetSupportedModels method. +func (s *LlmConnectivityService) ValidateGetSupportedModelsOpt(opt *LlmConnectivityGetSupportedModelsOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.AiCapability, "AiCapability") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.AiCapability, allowedLlmConnectivityGetSupportedModelsAiCapability, "AiCapability") + if err != nil { + return err + } + + return nil +} + +// ValidateUpsertProviderMappingOpt validates the options for the UpsertProviderMapping method. +func (s *LlmConnectivityService) ValidateUpsertProviderMappingOpt(opt *LlmConnectivityUpsertProviderMappingOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.AiCapability, "AiCapability") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.AiCapability, allowedLlmConnectivityUpsertProviderMappingAiCapability, "AiCapability") + if err != nil { + return err + } + + err = ValidateRequired(opt.LlmProviderId, "LlmProviderId") + if err != nil { + return err + } + + err = ValidateRequired(opt.ModelIdentifier, "ModelIdentifier") + if err != nil { + return err + } + + return nil +} + +// ValidateValidateProviderOpt validates the options for the ValidateProvider method. +func (s *LlmConnectivityService) ValidateValidateProviderOpt(opt *LlmConnectivityValidateProviderOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.LlmProviderId, "LlmProviderId") + if err != nil { + return err + } + + return nil +} + +// ValidateCreateProviderOpt validates the options for the CreateProvider method. +func (s *LlmConnectivityService) ValidateCreateProviderOpt(opt *LlmConnectivityCreateProviderOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := IsValueAuthorized(opt.Provider, allowedLlmConnectivityCreateProviderProvider, "Provider") + if err != nil { + return err + } + + return nil +} + +// ----------------------------------------------------------------------------- +// Service Methods +// ----------------------------------------------------------------------------- + +// DeleteProvider deletes an LLM provider. Blocked when the provider is selected by an AI +// capability. Requires system administration. +// +// API endpoint: DELETE /api/v2/llm-connectivity/llm-providers/{id}. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *LlmConnectivityService) DeleteProvider(ctx context.Context, providerID string) (*http.Response, error) { + err := ValidateRequired(providerID, "providerID") + if err != nil { + return nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodDelete, "llm-connectivity/llm-providers/"+url.PathEscape(providerID), nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// GetProviderDefinitions lists available LLM provider types. Returns the provider types and the +// configuration fields each requires. Requires system administration. +// +// API endpoint: GET /api/v2/llm-connectivity/llm-provider-definitions. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *LlmConnectivityService) GetProviderDefinitions(ctx context.Context) (*LlmConnectivityLlmProviderDefinitionsResponse, *http.Response, error) { + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "llm-connectivity/llm-provider-definitions", nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(LlmConnectivityLlmProviderDefinitionsResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetProviderMappings lists per-capability provider mappings. Optionally filtered to a single AI +// capability. Requires system administration. +// +// API endpoint: GET /api/v2/llm-connectivity/llm-provider-mappings. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *LlmConnectivityService) GetProviderMappings(ctx context.Context, opt *LlmConnectivityGetProviderMappingsOptions) (*LlmConnectivityProviderMappingsResponse, *http.Response, error) { + err := s.ValidateGetProviderMappingsOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "llm-connectivity/llm-provider-mappings", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(LlmConnectivityProviderMappingsResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// ListProviders lists configured LLM providers. Requires system administration. +// +// API endpoint: GET /api/v2/llm-connectivity/llm-providers. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *LlmConnectivityService) ListProviders(ctx context.Context) (*LlmConnectivityLlmProvidersResponse, *http.Response, error) { + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "llm-connectivity/llm-providers", nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(LlmConnectivityLlmProvidersResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetProvider gets one LLM provider. Requires system administration. +// +// API endpoint: GET /api/v2/llm-connectivity/llm-providers/{id}. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *LlmConnectivityService) GetProvider(ctx context.Context, providerID string) (*LlmConnectivityLlmProviderResponse, *http.Response, error) { + err := ValidateRequired(providerID, "providerID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "llm-connectivity/llm-providers/"+url.PathEscape(providerID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(LlmConnectivityLlmProviderResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetSupportedModels lists supported models for an AI capability. Proxies the agent-orchestrator's +// supported-models endpoint for the given AI capability. Requires system administration. +// +// API endpoint: GET /api/v2/llm-connectivity/supported-models. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *LlmConnectivityService) GetSupportedModels(ctx context.Context, opt *LlmConnectivityGetSupportedModelsOptions) (*LlmConnectivitySupportedModelsResponse, *http.Response, error) { + err := s.ValidateGetSupportedModelsOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "llm-connectivity/supported-models", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(LlmConnectivitySupportedModelsResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// UpdateProvider updates an LLM provider. The connection is re-tested before changes are persisted. +// Requires system administration. +// +// API endpoint: PATCH /api/v2/llm-connectivity/llm-providers/{id}. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *LlmConnectivityService) UpdateProvider(ctx context.Context, providerID string, opt *LlmConnectivityUpdateProviderOptions) (*LlmConnectivityLlmProviderResponse, *http.Response, error) { + err := ValidateRequired(providerID, "providerID") + if err != nil { + return nil, nil, err + } + + if opt == nil { + return nil, nil, NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPatch, "llm-connectivity/llm-providers/"+url.PathEscape(providerID), nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(LlmConnectivityLlmProviderResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// UpsertProviderMapping creates or replace the provider mapping for an AI capability. Creates or +// replaces the provider mapping for the given AI capability. Rejected when the provider or +// configured models cannot be validated, or when Hunter does not have both models. Requires system +// administration. +// +// API endpoint: POST /api/v2/llm-connectivity/llm-provider-mappings. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *LlmConnectivityService) UpsertProviderMapping(ctx context.Context, opt *LlmConnectivityUpsertProviderMappingOptions) (*LlmConnectivityProviderMappingResponse, *http.Response, error) { + err := s.ValidateUpsertProviderMappingOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "llm-connectivity/llm-provider-mappings", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(LlmConnectivityProviderMappingResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// ValidateProvider runs a connection test for an existing provider. Runs a fresh live connection +// test. Nothing is persisted. Requires system administration. +// +// API endpoint: POST /api/v2/llm-connectivity/llm-provider-validations. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *LlmConnectivityService) ValidateProvider(ctx context.Context, opt *LlmConnectivityValidateProviderOptions) (*LlmConnectivityProviderValidationResponse, *http.Response, error) { + err := s.ValidateValidateProviderOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "llm-connectivity/llm-provider-validations", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(LlmConnectivityProviderValidationResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// CreateProvider creates an LLM provider. The connection is tested before the provider is +// persisted. Requires system administration. +// +// API endpoint: POST /api/v2/llm-connectivity/llm-providers. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *LlmConnectivityService) CreateProvider(ctx context.Context, opt *LlmConnectivityCreateProviderOptions) (*LlmConnectivityLlmProviderResponse, *http.Response, error) { + err := s.ValidateCreateProviderOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "llm-connectivity/llm-providers", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(LlmConnectivityLlmProviderResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} diff --git a/sonar/llm_connectivity_v2_service_test.go b/sonar/llm_connectivity_v2_service_test.go new file mode 100644 index 0000000..4a085a3 --- /dev/null +++ b/sonar/llm_connectivity_v2_service_test.go @@ -0,0 +1,165 @@ +package sonar + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLlmConnectivityService_DeleteProvider(t *testing.T) { + server := newTestServer(t, mockEmptyHandler(t, http.MethodDelete, "/v2/llm-connectivity/llm-providers/test-id", http.StatusNoContent)) + client := newTestClient(t, server.url()) + + resp, err := client.V2.LlmConnectivity.DeleteProvider(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusNoContent, resp.StatusCode) +} + +func TestLlmConnectivityService_DeleteProvider_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, err := client.V2.LlmConnectivity.DeleteProvider(context.Background(), "") + require.Error(t, err) +} + +func TestLlmConnectivityService_GetProviderDefinitions(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/llm-connectivity/llm-provider-definitions", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.LlmConnectivity.GetProviderDefinitions(context.Background()) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestLlmConnectivityService_GetProviderMappings(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/llm-connectivity/llm-provider-mappings", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.LlmConnectivity.GetProviderMappings(context.Background(), &LlmConnectivityGetProviderMappingsOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestLlmConnectivityService_GetProviderMappings_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.LlmConnectivity.GetProviderMappings(context.Background(), nil) + require.Error(t, err) +} + +func TestLlmConnectivityService_ListProviders(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/llm-connectivity/llm-providers", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.LlmConnectivity.ListProviders(context.Background()) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestLlmConnectivityService_GetProvider(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/llm-connectivity/llm-providers/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.LlmConnectivity.GetProvider(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestLlmConnectivityService_GetProvider_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.LlmConnectivity.GetProvider(context.Background(), "") + require.Error(t, err) +} + +func TestLlmConnectivityService_GetSupportedModels(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/llm-connectivity/supported-models", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.LlmConnectivity.GetSupportedModels(context.Background(), &LlmConnectivityGetSupportedModelsOptions{AiCapability: "AI_CODEFIX"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestLlmConnectivityService_GetSupportedModels_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.LlmConnectivity.GetSupportedModels(context.Background(), nil) + require.Error(t, err) +} + +func TestLlmConnectivityService_UpdateProvider(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPatch, "/v2/llm-connectivity/llm-providers/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.LlmConnectivity.UpdateProvider(context.Background(), "test-id", &LlmConnectivityUpdateProviderOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestLlmConnectivityService_UpdateProvider_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.LlmConnectivity.UpdateProvider(context.Background(), "", nil) + require.Error(t, err) +} + +func TestLlmConnectivityService_UpsertProviderMapping(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/llm-connectivity/llm-provider-mappings", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.LlmConnectivity.UpsertProviderMapping(context.Background(), &LlmConnectivityUpsertProviderMappingOptions{AiCapability: "AI_CODEFIX", LlmProviderId: "test", ModelIdentifier: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestLlmConnectivityService_UpsertProviderMapping_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.LlmConnectivity.UpsertProviderMapping(context.Background(), nil) + require.Error(t, err) +} + +func TestLlmConnectivityService_ValidateProvider(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/llm-connectivity/llm-provider-validations", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.LlmConnectivity.ValidateProvider(context.Background(), &LlmConnectivityValidateProviderOptions{LlmProviderId: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestLlmConnectivityService_ValidateProvider_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.LlmConnectivity.ValidateProvider(context.Background(), nil) + require.Error(t, err) +} + +func TestLlmConnectivityService_CreateProvider(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/llm-connectivity/llm-providers", http.StatusCreated, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.LlmConnectivity.CreateProvider(context.Background(), &LlmConnectivityCreateProviderOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestLlmConnectivityService_CreateProvider_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.LlmConnectivity.CreateProvider(context.Background(), nil) + require.Error(t, err) +} diff --git a/sonar/notifications_service.go b/sonar/notifications_service.go index 310b5d2..b5512f5 100644 --- a/sonar/notifications_service.go +++ b/sonar/notifications_service.go @@ -5,6 +5,20 @@ import ( "net/http" ) +//nolint:gochecknoglobals // Allowed-value sets +var ( + // allowedNotificationFilters is the set of allowed notification list filters. + allowedNotificationFilters = map[string]struct{}{ + "user": {}, + "groupSubscription": {}, + "all": {}, + } + // allowedNotificationGroupTypes is the set of allowed notification types for group subscriptions. + allowedNotificationGroupTypes = map[string]struct{}{ + "security-alert-raised": {}, + } +) + // NotificationsService handles communication with the notifications related methods // of the SonarQube API. // This service manages notifications for the authenticated user. @@ -29,6 +43,38 @@ type NotificationsList struct { PerProjectTypes []string `json:"perProjectTypes,omitempty"` } +// NotificationsGroupSubscription represents a group subscribed to a notification type. +type NotificationsGroupSubscription struct { + // GroupUuid is the UUID of the subscribed group. + GroupUuid string `json:"groupUuid,omitempty"` + // GroupName is the name of the subscribed group. + GroupName string `json:"groupName,omitempty"` + // NotificationType is the notification type (e.g. security-alert-raised). + NotificationType string `json:"notificationType,omitempty"` + // ChannelKey is the key of the notification channel (e.g. EmailNotificationChannel). + ChannelKey string `json:"channelKey,omitempty"` +} + +// NotificationsListGroups represents the response from listing all group notification subscriptions. +type NotificationsListGroups struct { + // Subscriptions is the list of group notification subscriptions. + Subscriptions []NotificationsGroupSubscription `json:"subscriptions,omitempty"` +} + +// NotificationsUserGroupSubscription represents a notification subscription of one of the current user's groups. +type NotificationsUserGroupSubscription struct { + // GroupName is the name of the subscribed group. + GroupName string `json:"groupName,omitempty"` + // NotificationType is the notification type (e.g. security-alert-raised). + NotificationType string `json:"notificationType,omitempty"` +} + +// NotificationsListGroupSubscriptions represents the response from listing the current user's group subscriptions. +type NotificationsListGroupSubscriptions struct { + // GroupSubscriptions is the list of notification subscriptions of the current user's groups. + GroupSubscriptions []NotificationsUserGroupSubscription `json:"groupSubscriptions,omitempty"` +} + // Notification represents a configured notification. type Notification struct { // Channel is the notification channel (e.g., email). @@ -61,8 +107,22 @@ type NotificationsAddOptions struct { Type string `url:"type"` } +// NotificationsGroupOptions contains parameters for the AddGroup and RemoveGroup methods. +type NotificationsGroupOptions struct { + // GroupUuid is the UUID of the group. + // This field is required. + GroupUuid string `url:"groupUuid"` + // Type is the notification type. + // This field is required. + // Allowed values: security-alert-raised. + Type string `url:"type"` +} + // NotificationsListOptions contains parameters for the List method. type NotificationsListOptions struct { + // Filter is the category of notification types to return. + // Allowed values: user, groupSubscription, all. Default: all. Since 2026.5. + Filter string `url:"filter,omitempty"` // Login is the user login. If not provided, the authenticated user is used. Login string `url:"login,omitempty"` } @@ -99,10 +159,37 @@ func (s *NotificationsService) ValidateAddOpt(opt *NotificationsAddOptions) erro return nil } +// ValidateGroupOpt validates the options for the AddGroup and RemoveGroup methods. +func (s *NotificationsService) ValidateGroupOpt(opt *NotificationsGroupOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.GroupUuid, "GroupUuid") + if err != nil { + return err + } + + err = ValidateRequired(opt.Type, "Type") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Type, allowedNotificationGroupTypes, "Type") + if err != nil { + return err + } + + return nil +} + // ValidateListOpt validates the options for the List method. func (s *NotificationsService) ValidateListOpt(opt *NotificationsListOptions) error { - // No required fields - return nil + if opt == nil { + return nil + } + + return IsValueAuthorized(opt.Filter, allowedNotificationFilters, "Filter") } // ValidateRemoveOpt validates the options for the Remove method. @@ -200,3 +287,98 @@ func (s *NotificationsService) Remove(ctx context.Context, opt *NotificationsRem return resp, nil } + +// AddGroup subscribes a group to a notification type. +// Requires system administration permission. +// +// API endpoint: POST /api/notifications/add_group. +// Since: 2026.5. +// Internal: true. +func (s *NotificationsService) AddGroup(ctx context.Context, opt *NotificationsGroupOptions) (*http.Response, error) { + err := s.ValidateGroupOpt(opt) + if err != nil { + return nil, err + } + + req, err := s.client.NewSonarQubeV1APIRequest(ctx, http.MethodPost, "notifications/add_group", opt) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// ListGroupSubscriptions lists the notification group subscriptions for the current user's groups. +// Requires authentication. +// +// API endpoint: GET /api/notifications/list_group_subscriptions. +// Since: 2026.5. +// Internal: true. +func (s *NotificationsService) ListGroupSubscriptions(ctx context.Context) (*NotificationsListGroupSubscriptions, *http.Response, error) { + req, err := s.client.NewSonarQubeV1APIRequest(ctx, http.MethodGet, "notifications/list_group_subscriptions", nil) + if err != nil { + return nil, nil, err + } + + result := new(NotificationsListGroupSubscriptions) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// ListGroups lists the group notification subscriptions. +// Requires system administration permission. +// +// API endpoint: GET /api/notifications/list_groups. +// Since: 2026.5. +// Internal: true. +func (s *NotificationsService) ListGroups(ctx context.Context) (*NotificationsListGroups, *http.Response, error) { + req, err := s.client.NewSonarQubeV1APIRequest(ctx, http.MethodGet, "notifications/list_groups", nil) + if err != nil { + return nil, nil, err + } + + result := new(NotificationsListGroups) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// RemoveGroup removes a group notification subscription. +// This is a no-op if the subscription is not found. +// Requires system administration permission. +// +// API endpoint: POST /api/notifications/remove_group. +// Since: 2026.5. +// Internal: true. +func (s *NotificationsService) RemoveGroup(ctx context.Context, opt *NotificationsGroupOptions) (*http.Response, error) { + err := s.ValidateGroupOpt(opt) + if err != nil { + return nil, err + } + + req, err := s.client.NewSonarQubeV1APIRequest(ctx, http.MethodPost, "notifications/remove_group", opt) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} diff --git a/sonar/notifications_service_test.go b/sonar/notifications_service_test.go index 7a0b327..3ef4e7a 100644 --- a/sonar/notifications_service_test.go +++ b/sonar/notifications_service_test.go @@ -188,3 +188,92 @@ func TestNotifications_ValidateRemoveOpt(t *testing.T) { err = client.Notifications.ValidateRemoveOpt(&NotificationsRemoveOptions{}) assert.Error(t, err) } + +func TestNotifications_AddGroup(t *testing.T) { + server := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "/notifications/add_group", r.URL.Path) + assert.Equal(t, "grp-1", r.URL.Query().Get("groupUuid")) + assert.Equal(t, "security-alert-raised", r.URL.Query().Get("type")) + w.WriteHeader(http.StatusNoContent) + }) + + client := newTestClient(t, server.URL) + + resp, err := client.Notifications.AddGroup(context.Background(), &NotificationsGroupOptions{ + GroupUuid: "grp-1", + Type: "security-alert-raised", + }) + require.NoError(t, err) + assert.Equal(t, http.StatusNoContent, resp.StatusCode) +} + +func TestNotifications_RemoveGroup(t *testing.T) { + server := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "/notifications/remove_group", r.URL.Path) + w.WriteHeader(http.StatusNoContent) + }) + + client := newTestClient(t, server.URL) + + resp, err := client.Notifications.RemoveGroup(context.Background(), &NotificationsGroupOptions{ + GroupUuid: "grp-1", + Type: "security-alert-raised", + }) + require.NoError(t, err) + assert.Equal(t, http.StatusNoContent, resp.StatusCode) +} + +func TestNotifications_Group_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, err := client.Notifications.AddGroup(context.Background(), nil) + require.Error(t, err) + + _, err = client.Notifications.AddGroup(context.Background(), &NotificationsGroupOptions{Type: "security-alert-raised"}) + require.Error(t, err) + + _, err = client.Notifications.RemoveGroup(context.Background(), &NotificationsGroupOptions{GroupUuid: "g", Type: "bogus"}) + require.Error(t, err) +} + +func TestNotifications_ListGroups(t *testing.T) { + // Payload captured from a live SonarQube 2026.5 Enterprise instance. + response := `{"subscriptions":[{"groupUuid":"2eaac77e-c62e-4295-873f-d281fec20337","groupName":"sonar-users",` + + `"notificationType":"security-alert-raised","channelKey":"EmailNotificationChannel"}]}` + server := newTestServer(t, mockHandler(t, http.MethodGet, "/notifications/list_groups", http.StatusOK, response)) + client := newTestClient(t, server.URL) + + result, resp, err := client.Notifications.ListGroups(context.Background()) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.Equal(t, &NotificationsListGroups{ + Subscriptions: []NotificationsGroupSubscription{{ + GroupUuid: "2eaac77e-c62e-4295-873f-d281fec20337", + GroupName: "sonar-users", + NotificationType: "security-alert-raised", + ChannelKey: "EmailNotificationChannel", + }}, + }, result) +} + +func TestNotifications_ListGroupSubscriptions(t *testing.T) { + // Payload captured from a live SonarQube 2026.5 Enterprise instance. + response := `{"groupSubscriptions":[{"groupName":"sonar-users","notificationType":"security-alert-raised"}]}` + server := newTestServer(t, mockHandler(t, http.MethodGet, "/notifications/list_group_subscriptions", http.StatusOK, response)) + client := newTestClient(t, server.URL) + + result, _, err := client.Notifications.ListGroupSubscriptions(context.Background()) + require.NoError(t, err) + assert.Equal(t, &NotificationsListGroupSubscriptions{ + GroupSubscriptions: []NotificationsUserGroupSubscription{{GroupName: "sonar-users", NotificationType: "security-alert-raised"}}, + }, result) +} + +func TestNotifications_List_InvalidFilter(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.Notifications.List(context.Background(), &NotificationsListOptions{Filter: "bogus"}) + require.Error(t, err) +} diff --git a/sonar/onboarding_v2_service.go b/sonar/onboarding_v2_service.go new file mode 100644 index 0000000..e4b96b8 --- /dev/null +++ b/sonar/onboarding_v2_service.go @@ -0,0 +1,289 @@ +package sonar + +import ( + "context" + "fmt" + "net/http" +) + +// OnboardingService handles communication with the onboarding related methods of the +// SonarQube V2 API. This service is only available in Enterprise Edition. +type OnboardingService struct { + // client is used to communicate with the SonarQube API. + client *Client +} + +//nolint:gochecknoglobals // constant sets of allowed values +var ( + // allowedOnboardingGetProjectsScanStatus is the set of allowed values for the corresponding option. + allowedOnboardingGetProjectsScanStatus = map[string]struct{}{ + "SCANNED": {}, + "NOT_SCANNED": {}, + } + // allowedOnboardingGetProjectsAnalysisMode is the set of allowed values for the corresponding option. + allowedOnboardingGetProjectsAnalysisMode = map[string]struct{}{ + "CI": {}, + "AUTOMATIC": {}, + "NONE": {}, + } +) + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// OnboardingGetOverviewOptions contains parameters for the GetOverview method. +type OnboardingGetOverviewOptions struct { + // OrganizationKey is the organization key. + OrganizationKey string `json:"organizationKey,omitempty"` +} + +// OnboardingDevopsPlatformsStep represents the DevopsPlatformsStep object of the SonarQube V2 API. +type OnboardingDevopsPlatformsStep struct { + // Configured is the configured. + Configured int64 `json:"configured,omitempty"` +} + +// OnboardingRepositoriesStep represents the RepositoriesStep object of the SonarQube V2 API. +type OnboardingRepositoriesStep struct { + // Imported is the imported. + Imported int64 `json:"imported,omitempty"` + // Discovered is the discovered. + Discovered int64 `json:"discovered,omitempty"` + // Percent is the percent. + Percent float64 `json:"percent,omitempty"` +} + +// OnboardingProjectsStep represents the ProjectsStep object of the SonarQube V2 API. +type OnboardingProjectsStep struct { + // Analyzed is the analyzed. + Analyzed int64 `json:"analyzed,omitempty"` + // NotScanned is the not scanned. + NotScanned int64 `json:"notScanned,omitempty"` + // NotImported is the not imported. + NotImported int64 `json:"notImported,omitempty"` + // Total is the total. + Total int64 `json:"total,omitempty"` + // Percent is the percent. + Percent float64 `json:"percent,omitempty"` +} + +// OnboardingOnboardingSteps represents the OnboardingSteps object of the SonarQube V2 API. +type OnboardingOnboardingSteps struct { + // DevopsPlatforms is the devops platforms. + DevopsPlatforms OnboardingDevopsPlatformsStep `json:"devopsPlatforms,omitzero"` + // Repositories is the repositories. + Repositories OnboardingRepositoriesStep `json:"repositories,omitzero"` + // Projects is the projects. + Projects OnboardingProjectsStep `json:"projects,omitzero"` +} + +// OnboardingOnboardingOverviewResponse represents the OnboardingOverviewResponse object of the +// SonarQube V2 API. +type OnboardingOnboardingOverviewResponse struct { + // ProgressPct is the progress pct. + ProgressPct float64 `json:"progressPct,omitempty"` + // Steps is the steps. + Steps OnboardingOnboardingSteps `json:"steps,omitzero"` +} + +// OnboardingGetProjectsOptions contains parameters for the GetProjects method. +type OnboardingGetProjectsOptions struct { + // OrganizationKey is the organization key. + OrganizationKey string `json:"organizationKey,omitempty"` + // Q is the q. + Q string `json:"q,omitempty"` + // ScanStatus is the scan status. Allowed values: SCANNED, NOT_SCANNED. + ScanStatus string `json:"scanStatus,omitempty"` + // AnalysisMode is the analysis mode. Allowed values: CI, AUTOMATIC, NONE. + AnalysisMode string `json:"analysisMode,omitempty"` + // PageIndex is the page index. + // Minimum value: 1. Default: 1. + PageIndex int32 `json:"pageIndex,omitempty"` + // PageSize is the page size. + // Must be between 1 and 500. Default: 100. + PageSize int32 `json:"pageSize,omitempty"` +} + +// OnboardingOnboardingProjectRow represents the OnboardingProjectRow object of the SonarQube V2 +// API. +type OnboardingOnboardingProjectRow struct { + // Key is the key. + Key string `json:"key,omitempty"` + // Name is the name. + Name string `json:"name,omitempty"` + // Path is the path. + Path string `json:"path,omitempty"` + // Alm is the alm. + Alm string `json:"alm,omitempty"` + // ScanStatus is the scan status. Allowed values: SCANNED, NOT_SCANNED. + ScanStatus string `json:"scanStatus,omitempty"` + // AnalysisMode is the analysis mode. Allowed values: CI, AUTOMATIC, NONE. + AnalysisMode string `json:"analysisMode,omitempty"` + // LastScan is the last scan. + LastScan int64 `json:"lastScan,omitempty"` +} + +// OnboardingOnboardingProjectsResponse represents the OnboardingProjectsResponse object of the +// SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type OnboardingOnboardingProjectsResponse struct { + // Page is the page. + Page PageResponseV2 `json:"page,omitzero"` + // Projects is the projects. + Projects []OnboardingOnboardingProjectRow `json:"projects,omitempty"` +} + +// OnboardingGetStatisticsOptions contains parameters for the GetStatistics method. +type OnboardingGetStatisticsOptions struct { + // OrganizationKey is the organization key. + OrganizationKey string `json:"organizationKey,omitempty"` + // StartDate is the start date. + StartDate string `json:"startDate,omitempty"` + // EndDate is the end date. + EndDate string `json:"endDate,omitempty"` +} + +// OnboardingTimelinePoint represents the TimelinePoint object of the SonarQube V2 API. +type OnboardingTimelinePoint struct { + // Date is the date. + Date string `json:"date,omitempty"` + // RepositoriesImported is the repositories imported. + RepositoriesImported int64 `json:"repositoriesImported,omitempty"` + // ProjectsScanned is the projects scanned. + ProjectsScanned int64 `json:"projectsScanned,omitempty"` +} + +// OnboardingPlatformShare represents the PlatformShare object of the SonarQube V2 API. +type OnboardingPlatformShare struct { + // Platform is the platform. + Platform string `json:"platform,omitempty"` + // Count is the count. + Count int64 `json:"count,omitempty"` + // Percentage is the percentage. + Percentage float64 `json:"percentage,omitempty"` +} + +// OnboardingDevopsPlatforms represents the DevopsPlatforms object of the SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type OnboardingDevopsPlatforms struct { + // Total is the total. + Total int64 `json:"total,omitempty"` + // Shares is the shares. + Shares []OnboardingPlatformShare `json:"shares,omitempty"` +} + +// OnboardingOnboardingStatisticsResponse represents the OnboardingStatisticsResponse object of the +// SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type OnboardingOnboardingStatisticsResponse struct { + // DiscoveredTotal is the discovered total. + DiscoveredTotal int64 `json:"discoveredTotal,omitempty"` + // Timeline is the timeline. + Timeline []OnboardingTimelinePoint `json:"timeline,omitempty"` + // DevopsPlatforms is the devops platforms. + DevopsPlatforms OnboardingDevopsPlatforms `json:"devopsPlatforms,omitzero"` +} + +// ----------------------------------------------------------------------------- +// Validation Functions +// ----------------------------------------------------------------------------- + +// ValidateGetProjectsOpt validates the options for the GetProjects method. +func (s *OnboardingService) ValidateGetProjectsOpt(opt *OnboardingGetProjectsOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := IsValueAuthorized(opt.ScanStatus, allowedOnboardingGetProjectsScanStatus, "ScanStatus") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.AnalysisMode, allowedOnboardingGetProjectsAnalysisMode, "AnalysisMode") + if err != nil { + return err + } + + if opt.PageIndex < 0 { + return NewValidationError("PageIndex", "must be greater than 0", ErrOutOfRange) + } + + if opt.PageSize < 0 || opt.PageSize > 500 { + return NewValidationError("PageSize", "must be between 0 and 500", ErrOutOfRange) + } + + return nil +} + +// ----------------------------------------------------------------------------- +// Service Methods +// ----------------------------------------------------------------------------- + +// GetOverview calls GET /api/v2/onboarding/overview. +// +// API endpoint: GET /api/v2/onboarding/overview. +// Enterprise Edition only. +func (s *OnboardingService) GetOverview(ctx context.Context, opt *OnboardingGetOverviewOptions) (*OnboardingOnboardingOverviewResponse, *http.Response, error) { + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "onboarding/overview", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(OnboardingOnboardingOverviewResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetProjects calls GET /api/v2/onboarding/projects. +// +// API endpoint: GET /api/v2/onboarding/projects. +// Enterprise Edition only. +func (s *OnboardingService) GetProjects(ctx context.Context, opt *OnboardingGetProjectsOptions) (*OnboardingOnboardingProjectsResponse, *http.Response, error) { + err := s.ValidateGetProjectsOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "onboarding/projects", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(OnboardingOnboardingProjectsResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetStatistics calls GET /api/v2/onboarding/statistics. +// +// API endpoint: GET /api/v2/onboarding/statistics. +// Enterprise Edition only. +func (s *OnboardingService) GetStatistics(ctx context.Context, opt *OnboardingGetStatisticsOptions) (*OnboardingOnboardingStatisticsResponse, *http.Response, error) { + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "onboarding/statistics", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(OnboardingOnboardingStatisticsResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} diff --git a/sonar/onboarding_v2_service_test.go b/sonar/onboarding_v2_service_test.go new file mode 100644 index 0000000..dcb6751 --- /dev/null +++ b/sonar/onboarding_v2_service_test.go @@ -0,0 +1,47 @@ +package sonar + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestOnboardingService_GetOverview(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/onboarding/overview", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Onboarding.GetOverview(context.Background(), &OnboardingGetOverviewOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestOnboardingService_GetProjects(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/onboarding/projects", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Onboarding.GetProjects(context.Background(), &OnboardingGetProjectsOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestOnboardingService_GetProjects_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Onboarding.GetProjects(context.Background(), nil) + require.Error(t, err) +} + +func TestOnboardingService_GetStatistics(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/onboarding/statistics", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Onboarding.GetStatistics(context.Background(), &OnboardingGetStatisticsOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} diff --git a/sonar/remediation_agent_v2_service.go b/sonar/remediation_agent_v2_service.go new file mode 100644 index 0000000..ce6f684 --- /dev/null +++ b/sonar/remediation_agent_v2_service.go @@ -0,0 +1,621 @@ +package sonar + +import ( + "context" + "fmt" + "net/http" + "net/url" +) + +// RemediationAgentService handles communication with the remediation agent related methods of the +// SonarQube V2 API. This service is only available in Enterprise Edition. +type RemediationAgentService struct { + // client is used to communicate with the SonarQube API. + client *Client +} + +//nolint:gochecknoglobals // constant sets of allowed values +var ( + // allowedRemediationAgentListJobsStatus is the set of allowed values for the corresponding option. + allowedRemediationAgentListJobsStatus = map[string]struct{}{ + "PENDING": {}, + "IN_PROGRESS": {}, + "COMPLETED": {}, + "FAILED": {}, + } + // allowedRemediationAgentCreateBacklogJobAgentTask is the set of allowed values for the corresponding option. + allowedRemediationAgentCreateBacklogJobAgentTask = map[string]struct{}{ + "SONAR_REPORT": {}, + "HUNTER_FIX": {}, + } +) + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// RemediationAgentJob represents the Job object of the SonarQube V2 API. +type RemediationAgentJob struct { + // Id is the id. + Id string `json:"id,omitempty"` + // ProjectId is the project id. + ProjectId string `json:"projectId,omitempty"` + // BranchId stable identifier of the branch the job ran on. + BranchId string `json:"branchId,omitempty"` + // Status is the status. Allowed values: QUEUED, IN_PROGRESS, COMPLETED, FAILED, SKIPPED. + Status string `json:"status,omitempty"` + // SubStatus present only when a finer-grained phase is known; status remains the source of + // truth. Allowed values: AWAITING_FINDINGS, CLONING, EXPLORING, ANALYZING, CONSOLIDATING, + // REPORTING, CLONE_FAILED, AGENT_FAILED, LLM_NOT_ACCESSIBLE, ANALYSIS_TIMEOUT, + // RUNTIME_UNAVAILABLE, RESULT_PUBLICATION_FAILED, INSUFFICIENT_CREDITS, NCLOC_UNAVAILABLE, + // UNSUPPORTED_SCM_PROVIDER, SCM_CREDENTIALS_REJECTED, SCM_BRANCH_NOT_FOUND, + // PRIOR_FINDINGS_UNUSABLE. + SubStatus string `json:"subStatus,omitempty"` + // AnalysisType is the analysis type. Allowed values: FULL, INCREMENTAL. + AnalysisType string `json:"analysisType,omitempty"` + // TriggerType is the trigger type. Allowed values: ON_DEMAND, SCHEDULED. + TriggerType string `json:"triggerType,omitempty"` + // StartTime epoch millis when the job started; null while PENDING. + StartTime int64 `json:"startTime,omitempty"` + // DurationSeconds is the duration seconds. + DurationSeconds int64 `json:"durationSeconds,omitempty"` + // ConsumedCredits credits submitted to Billing for this run. Zero when the terminal run was + // not chargeable; null while non-terminal, for legacy runs, or when billing publication + // failed. + ConsumedCredits int32 `json:"consumedCredits,omitempty"` + // RequestTime is the request time. + RequestTime int64 `json:"requestTime,omitempty"` + // UpdatedAt is the updated at. + UpdatedAt int64 `json:"updatedAt,omitempty"` +} + +// RemediationAgentListJobsOptions contains parameters for the ListJobs method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type RemediationAgentListJobsOptions struct { + // ProjectKey restrict to jobs of this project. This field is required. + ProjectKey string `json:"projectKey"` + // Status filter by status. Omit to return all statuses. Allowed values: PENDING, IN_PROGRESS, + // COMPLETED, FAILED. + Status string `json:"status,omitempty"` + // SourcePullRequestKey filter by the key of the original pull request being remediated. + SourcePullRequestKey string `json:"sourcePullRequestKey,omitempty"` + // PageIndex 1-based page index. + // Minimum value: 1. Default: 1. + PageIndex int32 `json:"pageIndex,omitempty"` + // PageSize page size. Set to 0 to retrieve only the total count without any items. + // Must be between 0 and 500. Default: 50. + PageSize int32 `json:"pageSize,omitempty"` + // Sort by a resource field. Prefix '+' for ascending, '-' for descending. Default: + // '-createdAt' (newest first). Allowed fields: 'createdAt', 'startedAt', 'finishedAt', + // 'status'. + // Default: -createdAt. + Sort string `json:"sort,omitempty"` +} + +// RemediationAgentJobSearchResponse represents the JobSearchResponse object of the SonarQube V2 +// API. +type RemediationAgentJobSearchResponse struct { + // Jobs is the jobs. + Jobs []RemediationAgentJob `json:"jobs,omitempty"` + // Page is the page. + Page PageResponseV2 `json:"page,omitzero"` +} + +// RemediationAgentScheduledAgentConfigResource represents the ScheduledAgentConfigResource object +// of the SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type RemediationAgentScheduledAgentConfigResource struct { + // Id is the id. + Id string `json:"id,omitempty"` + // ProjectKey is the project key. + ProjectKey string `json:"projectKey,omitempty"` + // Enabled is the enabled. + Enabled bool `json:"enabled,omitempty"` + // Frequency is the frequency. + Frequency string `json:"frequency,omitempty"` + // Hour is the hour. + Hour int32 `json:"hour,omitempty"` + // DayOfWeek is the day of week. + DayOfWeek string `json:"dayOfWeek,omitempty"` + // Timezone is the timezone. + Timezone string `json:"timezone,omitempty"` + // NextRunUtc is the next run utc. + NextRunUtc int64 `json:"nextRunUtc,omitempty"` + // MaxIssuesPerRun is the max issues per run. + MaxIssuesPerRun int32 `json:"maxIssuesPerRun,omitempty"` + // MaxOpenPRs is the max open p rs. + MaxOpenPRs int32 `json:"maxOpenPRs,omitempty"` + // ProjectSelectionMode is the project selection mode. + ProjectSelectionMode string `json:"projectSelectionMode,omitempty"` + // SelectedProjectKeys is the selected project keys. + SelectedProjectKeys []string `json:"selectedProjectKeys,omitempty"` +} + +// RemediationAgentScheduledAgentConfigSearchRestResponse represents the +// ScheduledAgentConfigSearchRestResponse object of the SonarQube V2 API. +type RemediationAgentScheduledAgentConfigSearchRestResponse struct { + // ScheduledAgentConfigs is the scheduled agent configs. + ScheduledAgentConfigs []RemediationAgentScheduledAgentConfigResource `json:"scheduledAgentConfigs,omitempty"` + // Page is the page. + Page PageResponseV2 `json:"page,omitzero"` +} + +// RemediationAgentScheduledAgentDefaultResource represents the ScheduledAgentDefaultResource object +// of the SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type RemediationAgentScheduledAgentDefaultResource struct { + // Enabled is the enabled. + Enabled bool `json:"enabled,omitempty"` + // Frequency is the frequency. + Frequency string `json:"frequency,omitempty"` + // Hour is the hour. + Hour int32 `json:"hour,omitempty"` + // DayOfWeek is the day of week. + DayOfWeek string `json:"dayOfWeek,omitempty"` + // Timezone is the timezone. + Timezone string `json:"timezone,omitempty"` + // MaxIssuesPerRun is the max issues per run. + MaxIssuesPerRun int32 `json:"maxIssuesPerRun,omitempty"` + // MaxOpenPRs is the max open p rs. + MaxOpenPRs int32 `json:"maxOpenPRs,omitempty"` +} + +// RemediationAgentScheduledAgentConfigEffectiveRestResponse represents the +// ScheduledAgentConfigEffectiveRestResponse object of the SonarQube V2 API. +type RemediationAgentScheduledAgentConfigEffectiveRestResponse struct { + // ProjectConfig is the project config. + ProjectConfig RemediationAgentScheduledAgentConfigResource `json:"projectConfig,omitzero"` + // OrganizationDefault is the organization default. + OrganizationDefault RemediationAgentScheduledAgentDefaultResource `json:"organizationDefault,omitzero"` +} + +// RemediationAgentCreateBacklogJobOptions contains the request body for the CreateBacklogJob +// method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type RemediationAgentCreateBacklogJobOptions struct { + // ProjectKey key of the SonarQube project to remediate. This field is required. + ProjectKey string `json:"projectKey"` + // RepositoryUrl clone URL of the repository to remediate. When omitted, it is resolved from + // the project's configuration. + RepositoryUrl string `json:"repositoryUrl,omitempty"` + // Branch to remediate. Defaults to the repository's default branch. Required (must be non- + // blank) when agentTask is HUNTER_FIX, since the finding's branch is not otherwise known. + Branch string `json:"branch,omitempty"` + // IssueKeys sonarQube issue keys selected by the caller for remediation. Every key must belong + // to projectKey, or the request is rejected. Forwarded to the orchestrator, which enriches + // them into full issue metadata at dispatch time. This field is required. + IssueKeys []string `json:"issueKeys"` + // AgentTask which agent subcommand to run for this job. SONAR_REPORT (the default, and the + // value assumed when the field is absent) is the existing multi-issue Sonar-issue flow. + // HUNTER_FIX runs the hunter-fix subcommand against a single Hunter Agent finding and requires + // exactly one entry in issueKeys and a non-blank branch. Allowed values: SONAR_REPORT, + // HUNTER_FIX. + AgentTask string `json:"agentTask,omitempty"` +} + +// RemediationAgentCreatePullRequestJobOptions contains the request body for the +// CreatePullRequestJob method. +type RemediationAgentCreatePullRequestJobOptions struct { + // ProjectKey key of the SonarQube project to remediate. This field is required. + ProjectKey string `json:"projectKey"` + // PullRequestKey key of the pull request to scope the remediation to. This field is required. + PullRequestKey string `json:"pullRequestKey"` + // RepositoryUrl clone URL of the repository to remediate. When omitted, it is resolved from + // the project's configuration. + RepositoryUrl string `json:"repositoryUrl,omitempty"` + // IssueKeys sonarQube issue keys (scoped to the pull request) selected by the caller for + // remediation. Every key must belong to projectKey, or the request is rejected. Forwarded to + // the orchestrator, which enriches them into full issue metadata at dispatch time. This field + // is required. + IssueKeys []string `json:"issueKeys"` +} + +// RemediationAgentCreateScheduledAgentConfigOptions contains the request body for the +// CreateScheduledAgentConfig method. +type RemediationAgentCreateScheduledAgentConfigOptions struct { + // ProjectKey is the project key. + ProjectKey string `json:"projectKey,omitempty"` + // Enabled is the enabled. + Enabled *bool `json:"enabled,omitempty"` + // Frequency is the frequency. + Frequency string `json:"frequency,omitempty"` + // Hour is the hour. + Hour *int32 `json:"hour,omitempty"` + // DayOfWeek is the day of week. + DayOfWeek string `json:"dayOfWeek,omitempty"` + // Timezone is the timezone. + Timezone string `json:"timezone,omitempty"` + // MaxIssuesPerRun is the max issues per run. + MaxIssuesPerRun *int32 `json:"maxIssuesPerRun,omitempty"` + // MaxOpenPRs is the max open p rs. + MaxOpenPRs *int32 `json:"maxOpenPRs,omitempty"` + // ProjectSelectionMode is the project selection mode. + ProjectSelectionMode string `json:"projectSelectionMode,omitempty"` + // SelectedProjectKeys is the selected project keys. + SelectedProjectKeys []string `json:"selectedProjectKeys,omitempty"` +} + +// RemediationAgentUpdateScheduledAgentConfigOptions contains the request body for the +// UpdateScheduledAgentConfig method. +type RemediationAgentUpdateScheduledAgentConfigOptions struct { + // Enabled is the enabled. + Enabled *bool `json:"enabled,omitempty"` + // Frequency is the frequency. + Frequency string `json:"frequency,omitempty"` + // Hour is the hour. + Hour *int32 `json:"hour,omitempty"` + // DayOfWeek is the day of week. + DayOfWeek string `json:"dayOfWeek,omitempty"` + // Timezone is the timezone. + Timezone string `json:"timezone,omitempty"` + // MaxIssuesPerRun is the max issues per run. + MaxIssuesPerRun *int32 `json:"maxIssuesPerRun,omitempty"` + // MaxOpenPRs is the max open p rs. + MaxOpenPRs *int32 `json:"maxOpenPRs,omitempty"` + // ProjectSelectionMode is the project selection mode. + ProjectSelectionMode string `json:"projectSelectionMode,omitempty"` + // SelectedProjectKeys is the selected project keys. + SelectedProjectKeys []string `json:"selectedProjectKeys,omitempty"` +} + +// ----------------------------------------------------------------------------- +// Validation Functions +// ----------------------------------------------------------------------------- + +// ValidateListJobsOpt validates the options for the ListJobs method. +func (s *RemediationAgentService) ValidateListJobsOpt(opt *RemediationAgentListJobsOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.ProjectKey, "ProjectKey") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Status, allowedRemediationAgentListJobsStatus, "Status") + if err != nil { + return err + } + + if opt.PageIndex < 0 { + return NewValidationError("PageIndex", "must be greater than 0", ErrOutOfRange) + } + + if opt.PageSize < 0 || opt.PageSize > 500 { + return NewValidationError("PageSize", "must be between 0 and 500", ErrOutOfRange) + } + + return nil +} + +// ValidateCreateBacklogJobOpt validates the options for the CreateBacklogJob method. +func (s *RemediationAgentService) ValidateCreateBacklogJobOpt(opt *RemediationAgentCreateBacklogJobOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.ProjectKey, "ProjectKey") + if err != nil { + return err + } + + if len(opt.IssueKeys) == 0 { + return NewValidationError("IssueKeys", "is required", ErrMissingRequired) + } + + err = IsValueAuthorized(opt.AgentTask, allowedRemediationAgentCreateBacklogJobAgentTask, "AgentTask") + if err != nil { + return err + } + + return nil +} + +// ValidateCreatePullRequestJobOpt validates the options for the CreatePullRequestJob method. +func (s *RemediationAgentService) ValidateCreatePullRequestJobOpt(opt *RemediationAgentCreatePullRequestJobOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.ProjectKey, "ProjectKey") + if err != nil { + return err + } + + err = ValidateRequired(opt.PullRequestKey, "PullRequestKey") + if err != nil { + return err + } + + if len(opt.IssueKeys) == 0 { + return NewValidationError("IssueKeys", "is required", ErrMissingRequired) + } + + return nil +} + +// ----------------------------------------------------------------------------- +// Service Methods +// ----------------------------------------------------------------------------- + +// DeleteScheduledAgentConfig calls DELETE /api/v2/remediation-agent/scheduled-agent-configs/{id}. +// +// API endpoint: DELETE /api/v2/remediation-agent/scheduled-agent-configs/{id}. +// Enterprise Edition only. +func (s *RemediationAgentService) DeleteScheduledAgentConfig(ctx context.Context, scheduledAgentConfigID string) (*http.Response, error) { + err := ValidateRequired(scheduledAgentConfigID, "scheduledAgentConfigID") + if err != nil { + return nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodDelete, "remediation-agent/scheduled-agent-configs/"+url.PathEscape(scheduledAgentConfigID), nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// GetBacklogJob gets a remediation backlog job. Retrieve a remediation backlog job, including its +// current status. +// +// API endpoint: GET /api/v2/remediation-agent/backlog-jobs/{jobId}. +// Enterprise Edition only. +func (s *RemediationAgentService) GetBacklogJob(ctx context.Context, jobID string) (*RemediationAgentJob, *http.Response, error) { + err := ValidateRequired(jobID, "jobID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "remediation-agent/backlog-jobs/"+url.PathEscape(jobID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(RemediationAgentJob) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// ListJobs lists remediation jobs. Returns a paginated list of remediation jobs for a project. +// Default sort is '-createdAt' (newest first). +// +// API endpoint: GET /api/v2/remediation-agent/jobs. +// Enterprise Edition only. +func (s *RemediationAgentService) ListJobs(ctx context.Context, opt *RemediationAgentListJobsOptions) (*RemediationAgentJobSearchResponse, *http.Response, error) { + err := s.ValidateListJobsOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "remediation-agent/jobs", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(RemediationAgentJobSearchResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetPullRequestJob gets a remediation pull-request job. Retrieve a remediation pull-request job, +// including its current status. +// +// API endpoint: GET /api/v2/remediation-agent/pull-request-jobs/{jobId}. +// Enterprise Edition only. +func (s *RemediationAgentService) GetPullRequestJob(ctx context.Context, jobID string) (*RemediationAgentJob, *http.Response, error) { + err := ValidateRequired(jobID, "jobID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "remediation-agent/pull-request-jobs/"+url.PathEscape(jobID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(RemediationAgentJob) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// SearchScheduledAgentConfigs calls GET /api/v2/remediation-agent/scheduled-agent-configs. +// +// API endpoint: GET /api/v2/remediation-agent/scheduled-agent-configs. +// Enterprise Edition only. +func (s *RemediationAgentService) SearchScheduledAgentConfigs(ctx context.Context) (*RemediationAgentScheduledAgentConfigSearchRestResponse, *http.Response, error) { + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "remediation-agent/scheduled-agent-configs", nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(RemediationAgentScheduledAgentConfigSearchRestResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetScheduledAgentConfig calls GET /api/v2/remediation-agent/scheduled-agent-configs/{id}. +// +// API endpoint: GET /api/v2/remediation-agent/scheduled-agent-configs/{id}. +// Enterprise Edition only. +func (s *RemediationAgentService) GetScheduledAgentConfig(ctx context.Context, scheduledAgentConfigID string) (*RemediationAgentScheduledAgentConfigResource, *http.Response, error) { + err := ValidateRequired(scheduledAgentConfigID, "scheduledAgentConfigID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "remediation-agent/scheduled-agent-configs/"+url.PathEscape(scheduledAgentConfigID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(RemediationAgentScheduledAgentConfigResource) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetEffectiveScheduledAgentConfig calls GET /api/v2/remediation-agent/scheduled-agent- +// configs/{projectKey}/effective. +// +// API endpoint: GET /api/v2/remediation-agent/scheduled-agent-configs/{projectKey}/effective. +// Enterprise Edition only. +func (s *RemediationAgentService) GetEffectiveScheduledAgentConfig(ctx context.Context, projectKey string) (*RemediationAgentScheduledAgentConfigEffectiveRestResponse, *http.Response, error) { + err := ValidateRequired(projectKey, "projectKey") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "remediation-agent/scheduled-agent-configs/"+url.PathEscape(projectKey)+"/effective", nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(RemediationAgentScheduledAgentConfigEffectiveRestResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// CreateBacklogJob creates a remediation backlog job. Triggers a new full-repository remediation +// job for a project. +// +// API endpoint: POST /api/v2/remediation-agent/backlog-jobs. +// Enterprise Edition only. +func (s *RemediationAgentService) CreateBacklogJob(ctx context.Context, opt *RemediationAgentCreateBacklogJobOptions) (*RemediationAgentJob, *http.Response, error) { + err := s.ValidateCreateBacklogJobOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "remediation-agent/backlog-jobs", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(RemediationAgentJob) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// CreatePullRequestJob creates a remediation pull-request job. Triggers a new pull-request-scoped +// remediation job for a project. +// +// API endpoint: POST /api/v2/remediation-agent/pull-request-jobs. +// Enterprise Edition only. +func (s *RemediationAgentService) CreatePullRequestJob(ctx context.Context, opt *RemediationAgentCreatePullRequestJobOptions) (*RemediationAgentJob, *http.Response, error) { + err := s.ValidateCreatePullRequestJobOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "remediation-agent/pull-request-jobs", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(RemediationAgentJob) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// CreateScheduledAgentConfig calls POST /api/v2/remediation-agent/scheduled-agent-configs. +// +// API endpoint: POST /api/v2/remediation-agent/scheduled-agent-configs. +// Enterprise Edition only. +func (s *RemediationAgentService) CreateScheduledAgentConfig(ctx context.Context, opt *RemediationAgentCreateScheduledAgentConfigOptions) (*RemediationAgentScheduledAgentConfigResource, *http.Response, error) { + if opt == nil { + return nil, nil, NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPost, "remediation-agent/scheduled-agent-configs", nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(RemediationAgentScheduledAgentConfigResource) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// UpdateScheduledAgentConfig calls PUT /api/v2/remediation-agent/scheduled-agent-configs/{id}. +// +// API endpoint: PUT /api/v2/remediation-agent/scheduled-agent-configs/{id}. +// Enterprise Edition only. +func (s *RemediationAgentService) UpdateScheduledAgentConfig(ctx context.Context, scheduledAgentConfigID string, opt *RemediationAgentUpdateScheduledAgentConfigOptions) (*RemediationAgentScheduledAgentConfigResource, *http.Response, error) { + err := ValidateRequired(scheduledAgentConfigID, "scheduledAgentConfigID") + if err != nil { + return nil, nil, err + } + + if opt == nil { + return nil, nil, NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodPut, "remediation-agent/scheduled-agent-configs/"+url.PathEscape(scheduledAgentConfigID), nil, opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(RemediationAgentScheduledAgentConfigResource) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} diff --git a/sonar/remediation_agent_v2_service_test.go b/sonar/remediation_agent_v2_service_test.go new file mode 100644 index 0000000..75df16e --- /dev/null +++ b/sonar/remediation_agent_v2_service_test.go @@ -0,0 +1,189 @@ +package sonar + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRemediationAgentService_DeleteScheduledAgentConfig(t *testing.T) { + server := newTestServer(t, mockEmptyHandler(t, http.MethodDelete, "/v2/remediation-agent/scheduled-agent-configs/test-id", http.StatusOK)) + client := newTestClient(t, server.url()) + + resp, err := client.V2.RemediationAgent.DeleteScheduledAgentConfig(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) +} + +func TestRemediationAgentService_DeleteScheduledAgentConfig_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, err := client.V2.RemediationAgent.DeleteScheduledAgentConfig(context.Background(), "") + require.Error(t, err) +} + +func TestRemediationAgentService_GetBacklogJob(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/remediation-agent/backlog-jobs/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.RemediationAgent.GetBacklogJob(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestRemediationAgentService_GetBacklogJob_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.RemediationAgent.GetBacklogJob(context.Background(), "") + require.Error(t, err) +} + +func TestRemediationAgentService_ListJobs(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/remediation-agent/jobs", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.RemediationAgent.ListJobs(context.Background(), &RemediationAgentListJobsOptions{ProjectKey: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestRemediationAgentService_ListJobs_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.RemediationAgent.ListJobs(context.Background(), nil) + require.Error(t, err) +} + +func TestRemediationAgentService_GetPullRequestJob(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/remediation-agent/pull-request-jobs/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.RemediationAgent.GetPullRequestJob(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestRemediationAgentService_GetPullRequestJob_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.RemediationAgent.GetPullRequestJob(context.Background(), "") + require.Error(t, err) +} + +func TestRemediationAgentService_SearchScheduledAgentConfigs(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/remediation-agent/scheduled-agent-configs", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.RemediationAgent.SearchScheduledAgentConfigs(context.Background()) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestRemediationAgentService_GetScheduledAgentConfig(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/remediation-agent/scheduled-agent-configs/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.RemediationAgent.GetScheduledAgentConfig(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestRemediationAgentService_GetScheduledAgentConfig_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.RemediationAgent.GetScheduledAgentConfig(context.Background(), "") + require.Error(t, err) +} + +func TestRemediationAgentService_GetEffectiveScheduledAgentConfig(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/remediation-agent/scheduled-agent-configs/test-id/effective", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.RemediationAgent.GetEffectiveScheduledAgentConfig(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestRemediationAgentService_GetEffectiveScheduledAgentConfig_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.RemediationAgent.GetEffectiveScheduledAgentConfig(context.Background(), "") + require.Error(t, err) +} + +func TestRemediationAgentService_CreateBacklogJob(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/remediation-agent/backlog-jobs", http.StatusCreated, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.RemediationAgent.CreateBacklogJob(context.Background(), &RemediationAgentCreateBacklogJobOptions{ProjectKey: "test", IssueKeys: []string{"test"}}) + require.NoError(t, err) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestRemediationAgentService_CreateBacklogJob_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.RemediationAgent.CreateBacklogJob(context.Background(), nil) + require.Error(t, err) +} + +func TestRemediationAgentService_CreatePullRequestJob(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/remediation-agent/pull-request-jobs", http.StatusCreated, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.RemediationAgent.CreatePullRequestJob(context.Background(), &RemediationAgentCreatePullRequestJobOptions{ProjectKey: "test", PullRequestKey: "test", IssueKeys: []string{"test"}}) + require.NoError(t, err) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestRemediationAgentService_CreatePullRequestJob_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.RemediationAgent.CreatePullRequestJob(context.Background(), nil) + require.Error(t, err) +} + +func TestRemediationAgentService_CreateScheduledAgentConfig(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPost, "/v2/remediation-agent/scheduled-agent-configs", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.RemediationAgent.CreateScheduledAgentConfig(context.Background(), &RemediationAgentCreateScheduledAgentConfigOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestRemediationAgentService_CreateScheduledAgentConfig_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.RemediationAgent.CreateScheduledAgentConfig(context.Background(), nil) + require.Error(t, err) +} + +func TestRemediationAgentService_UpdateScheduledAgentConfig(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodPut, "/v2/remediation-agent/scheduled-agent-configs/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.RemediationAgent.UpdateScheduledAgentConfig(context.Background(), "test-id", &RemediationAgentUpdateScheduledAgentConfigOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestRemediationAgentService_UpdateScheduledAgentConfig_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.RemediationAgent.UpdateScheduledAgentConfig(context.Background(), "", nil) + require.Error(t, err) +} diff --git a/sonar/rules_service.go b/sonar/rules_service.go index a38aad6..dfb094f 100644 --- a/sonar/rules_service.go +++ b/sonar/rules_service.go @@ -9,6 +9,8 @@ import ( const ( // MaxRuleKeyLength is the maximum allowed length for rule keys and names. MaxRuleKeyLength = 200 + // MaxRuleContextKeyLength is the maximum allowed length for a rule description context key. + MaxRuleContextKeyLength = 50 // MinSearchQueryLength is the minimum required length for search queries. MinSearchQueryLength = 2 @@ -573,6 +575,10 @@ type RulesSearchOptions struct { type RulesShowOptions struct { // Key is the unique identifier of the rule to be retrieved (required). Key string `url:"key,omitempty"` + // ContextKey restricts the returned description sections to those matching the given rule-description + // context key, along with sections that have no context. When empty, all sections are returned. + // Maximum length: 50 characters. Since 2026.5. + ContextKey string `url:"contextKey,omitempty"` // Actives determines whether to include the list of quality profiles where the rule is active. Actives bool `url:"actives,omitempty"` } @@ -1085,6 +1091,11 @@ func (s *RulesService) ValidateShowOpt(opt *RulesShowOptions) error { return err } + err = ValidateMaxLength(opt.ContextKey, MaxRuleContextKeyLength, "ContextKey") + if err != nil { + return err + } + return nil } diff --git a/sonar/sca_v2_reachability_paths_service.go b/sonar/sca_v2_reachability_paths_service.go new file mode 100644 index 0000000..e6b373d --- /dev/null +++ b/sonar/sca_v2_reachability_paths_service.go @@ -0,0 +1,96 @@ +package sonar + +import ( + "context" + "fmt" + "net/http" +) + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// ScaListReachabilityPathsOptions contains parameters for the ListReachabilityPaths method. +type ScaListReachabilityPathsOptions struct { + // LanguageKey is the language key. This field is required. + LanguageKey string `json:"languageKey"` + // ProjectKey is the project key. This field is required. + ProjectKey string `json:"projectKey"` + // PageSize number of results per page. A value of 0 will only return the pagination + // information. + // Must be between 0 and 10000. Default: 10000. + PageSize int32 `json:"pageSize,omitempty"` + // PageIndex 1-based page index. + // Minimum value: 1. Default: 1. + PageIndex int32 `json:"pageIndex,omitempty"` +} + +// ScaReachabilityPathsRestResponse represents the ReachabilityPathsRestResponse object of the +// SonarQube V2 API. +type ScaReachabilityPathsRestResponse struct { + // Paths is the paths. + Paths []string `json:"paths,omitempty"` + // Page is the page. + Page PageResponseV2 `json:"page,omitzero"` +} + +// ----------------------------------------------------------------------------- +// Validation Functions +// ----------------------------------------------------------------------------- + +// ValidateListReachabilityPathsOpt validates the options for the ListReachabilityPaths method. +func (s *ScaService) ValidateListReachabilityPathsOpt(opt *ScaListReachabilityPathsOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.LanguageKey, "LanguageKey") + if err != nil { + return err + } + + err = ValidateRequired(opt.ProjectKey, "ProjectKey") + if err != nil { + return err + } + + if opt.PageSize < 0 || opt.PageSize > 10000 { + return NewValidationError("PageSize", "must be between 0 and 10000", ErrOutOfRange) + } + + if opt.PageIndex < 0 { + return NewValidationError("PageIndex", "must be greater than 0", ErrOutOfRange) + } + + return nil +} + +// ----------------------------------------------------------------------------- +// Service Methods +// ----------------------------------------------------------------------------- + +// ListReachabilityPaths gets reachability paths. Returns a page of distinct reachability paths. +// +// API endpoint: GET /api/v2/sca/reachability/list-paths. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *ScaService) ListReachabilityPaths(ctx context.Context, opt *ScaListReachabilityPathsOptions) (*ScaReachabilityPathsRestResponse, *http.Response, error) { + err := s.ValidateListReachabilityPathsOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "sca/reachability/list-paths", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(ScaReachabilityPathsRestResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} diff --git a/sonar/sca_v2_reachability_paths_service_test.go b/sonar/sca_v2_reachability_paths_service_test.go new file mode 100644 index 0000000..2604fd2 --- /dev/null +++ b/sonar/sca_v2_reachability_paths_service_test.go @@ -0,0 +1,27 @@ +package sonar + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestScaService_ListReachabilityPaths(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/sca/reachability/list-paths", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.Sca.ListReachabilityPaths(context.Background(), &ScaListReachabilityPathsOptions{LanguageKey: "test", ProjectKey: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestScaService_ListReachabilityPaths_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.Sca.ListReachabilityPaths(context.Background(), nil) + require.Error(t, err) +} diff --git a/sonar/sca_v2_service.go b/sonar/sca_v2_service.go index f66d143..102c6a1 100644 --- a/sonar/sca_v2_service.go +++ b/sonar/sca_v2_service.go @@ -2659,53 +2659,3 @@ func (s *ScaService) ParseDependencyFiles(ctx context.Context, opt *ScaParseDepe return result, resp, nil } - -// ScaReachabilityDefinitionsOptions contains parameters for the ListReachabilityDefinitions method. -type ScaReachabilityDefinitionsOptions struct { - // LanguageKey is the language to return reachability definitions for. This field is required. - LanguageKey string `json:"languageKey"` - // ProjectKey scopes the definitions to a project. Optional. - ProjectKey string `json:"projectKey,omitempty"` - // Organization scopes the definitions to an organization. Optional. - // Deprecated by SonarQube in favour of ProjectKey. - Organization string `json:"organization,omitempty"` -} - -// ValidateReachabilityDefinitionsOpt validates the options for the ListReachabilityDefinitions method. -func (s *ScaService) ValidateReachabilityDefinitionsOpt(opt *ScaReachabilityDefinitionsOptions) error { - if opt == nil { - return NewValidationError("opt", "option struct is required", ErrMissingRequired) - } - - return ValidateRequired(opt.LanguageKey, "LanguageKey") -} - -// ListReachabilityDefinitions returns the raw reachability-definition bundle for -// a language as an opaque byte stream (content type application/octet-stream); -// its internal format is not published, so the bytes are returned as-is. -// -// API endpoint: GET /api/v2/sca/reachability/list-definitions. -// Enterprise Edition only. Marked internal by SonarQube and subject to change -// without notice. -func (s *ScaService) ListReachabilityDefinitions(ctx context.Context, opt *ScaReachabilityDefinitionsOptions) ([]byte, *http.Response, error) { - err := s.ValidateReachabilityDefinitionsOpt(opt) - if err != nil { - return nil, nil, err - } - - req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "sca/reachability/list-definitions", opt, nil) - if err != nil { - return nil, nil, fmt.Errorf("failed to create request: %w", err) - } - - req.Header.Set("Accept", "application/octet-stream") - - var buf bytes.Buffer - - resp, err := s.client.Do(req, &buf) - if err != nil { - return nil, resp, err - } - - return buf.Bytes(), resp, nil -} diff --git a/sonar/sca_v2_service_test.go b/sonar/sca_v2_service_test.go index ccedd7f..3643119 100644 --- a/sonar/sca_v2_service_test.go +++ b/sonar/sca_v2_service_test.go @@ -1125,38 +1125,3 @@ func TestScaService_ParseDependencyFiles_ValidationError(t *testing.T) { }) assert.Error(t, err) } - -// ----------------------------------------------------------------------------- -// ListReachabilityDefinitions -// ----------------------------------------------------------------------------- - -func TestScaService_ListReachabilityDefinitions(t *testing.T) { - payload := []byte{0x01, 0x02, 0x03, 0x04} - - server := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodGet, r.Method) - assert.Equal(t, "/v2/sca/reachability/list-definitions", r.URL.Path) - assert.Equal(t, "java", r.URL.Query().Get("languageKey")) - assert.Equal(t, "application/octet-stream", r.Header.Get("Accept")) - - w.Header().Set("Content-Type", "application/octet-stream") - w.WriteHeader(http.StatusOK) - _, _ = w.Write(payload) - }) - client := newTestClient(t, server.URL) - - result, resp, err := client.V2.Sca.ListReachabilityDefinitions(context.Background(), &ScaReachabilityDefinitionsOptions{LanguageKey: "java"}) - require.NoError(t, err) - assert.Equal(t, http.StatusOK, resp.StatusCode) - assert.Equal(t, payload, result) -} - -func TestScaService_ListReachabilityDefinitions_ValidationError(t *testing.T) { - client := newLocalhostClient(t) - - _, _, err := client.V2.Sca.ListReachabilityDefinitions(context.Background(), nil) - assert.Error(t, err) - - _, _, err = client.V2.Sca.ListReachabilityDefinitions(context.Background(), &ScaReachabilityDefinitionsOptions{}) - assert.Error(t, err) -} diff --git a/sonar/security_alerts_v2_service.go b/sonar/security_alerts_v2_service.go new file mode 100644 index 0000000..1c58b6f --- /dev/null +++ b/sonar/security_alerts_v2_service.go @@ -0,0 +1,258 @@ +package sonar + +import ( + "context" + "fmt" + "net/http" + "net/url" +) + +// SecurityAlertsService handles communication with the security alerts related methods of the +// SonarQube V2 API. This service is only available in Enterprise Edition. +type SecurityAlertsService struct { + // client is used to communicate with the SonarQube API. + client *Client +} + +//nolint:gochecknoglobals // constant sets of allowed values +var ( + // allowedSecurityAlertsSearchSort is the set of allowed values for the corresponding option. + allowedSecurityAlertsSearchSort = map[string]struct{}{ + "FIRST_DETECTED_AT": {}, + "LAST_DETECTED_AT": {}, + } + // allowedSecurityAlertsSearchDirection is the set of allowed values for the corresponding option. + allowedSecurityAlertsSearchDirection = map[string]struct{}{ + "ASC": {}, + "DESC": {}, + } +) + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// SecurityAlertsSearchOptions contains parameters for the Search method. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type SecurityAlertsSearchOptions struct { + // AlertTypes filter by alert type, repeatable. Allowed values: DEPENDENCY_RISK. + AlertTypes []string `json:"alertTypes,omitempty"` + // Statuses filter by aggregate alert status, repeatable. Allowed values: OPEN, RESOLVED. + Statuses []string `json:"statuses,omitempty"` + // Sort field to sort by. Allowed values: FIRST_DETECTED_AT, LAST_DETECTED_AT. + // Default: LAST_DETECTED_AT. + Sort string `json:"sort,omitempty"` + // Direction sort direction. Allowed values: ASC, DESC. + // Default: DESC. + Direction string `json:"direction,omitempty"` + // PageSize number of results per page. A value of 0 will only return the pagination + // information. + // Must be between 0 and 500. Default: 50. + PageSize int32 `json:"pageSize,omitempty"` + // PageIndex 1-based page index. + // Minimum value: 1. Default: 1. + PageIndex int32 `json:"pageIndex,omitempty"` +} + +// SecurityAlertsCweInfo represents a SonarQube V2 API object. +// +// CWE entry with resolved name. +type SecurityAlertsCweInfo struct { + // Code CWE identifier (e.g. CWE-89). + Code string `json:"code,omitempty"` + // Name human-readable CWE name, or null if not found in the catalog. + Name string `json:"name,omitempty"` +} + +// SecurityAlertsScaIssue represents a SonarQube V2 API object. +// +// SCA issue linked to the security alert. +type SecurityAlertsScaIssue struct { + // ScaIssueUuid SCA issue UUID. + ScaIssueUuid string `json:"scaIssueUuid,omitempty"` + // VulnerabilityId CVE ID (e.g. CVE-2024-12345). + VulnerabilityId string `json:"vulnerabilityId,omitempty"` + // PackageUrl package URL (PURL). + PackageUrl string `json:"packageUrl,omitempty"` + // IssueType issue type (VULNERABILITY, MALWARE, PROHIBITED_LICENSE). + IssueType string `json:"issueType,omitempty"` + // RiskId representative SCA risk ID (highest severity among affected branches, tie-broken by + // uuid; null if no risk exists on any affected branch; may reference a non-OPEN e.g. FIXED + // risk). + RiskId string `json:"riskId,omitempty"` + // Cwes CWE codes with resolved names. + Cwes []SecurityAlertsCweInfo `json:"cwes,omitempty"` +} + +// SecurityAlertsScaRiskSummaryRestResponse represents the ScaRiskSummaryRestResponse object of the +// SonarQube V2 API. +type SecurityAlertsScaRiskSummaryRestResponse struct { + // RiskId is the risk id. + RiskId string `json:"riskId,omitempty"` + // PackageName is the package name. + PackageName string `json:"packageName,omitempty"` + // PackageVersion is the package version. + PackageVersion string `json:"packageVersion,omitempty"` + // PackageEcosystem is the package ecosystem. + PackageEcosystem string `json:"packageEcosystem,omitempty"` +} + +// SecurityAlertsAffectedBranchRestResponse represents the AffectedBranchRestResponse object of the +// SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type SecurityAlertsAffectedBranchRestResponse struct { + // ProjectId is the project id. + ProjectId string `json:"projectId,omitempty"` + // ProjectKey is the project key. + ProjectKey string `json:"projectKey,omitempty"` + // ProjectName is the project name. + ProjectName string `json:"projectName,omitempty"` + // BranchId is the branch id. + BranchId string `json:"branchId,omitempty"` + // BranchKey not set if the branch has been deleted. + BranchKey string `json:"branchKey,omitempty"` + // IsMain whether this is the project's primary branch. + IsMain bool `json:"isMain,omitempty"` + // Status is the status. Allowed values: OPEN, RESOLVED. + Status string `json:"status,omitempty"` + // FirstDetectedAt ISO-8601 UTC timestamp. + FirstDetectedAt string `json:"firstDetectedAt,omitempty"` + // LastDetectedAt ISO-8601 UTC timestamp. + LastDetectedAt string `json:"lastDetectedAt,omitempty"` + // ScaRisks SCA risk details per affected package/version. + ScaRisks []SecurityAlertsScaRiskSummaryRestResponse `json:"scaRisks,omitempty"` + // Severity SCA severity on this branch. Allowed values: BLOCKER, HIGH, MEDIUM, LOW, INFO. + Severity string `json:"severity,omitempty"` + // ReachabilityAnalyzed whether reachability has been analyzed. + ReachabilityAnalyzed bool `json:"reachabilityAnalyzed,omitempty"` +} + +// SecurityAlertsSecurityAlertRestResponse represents the SecurityAlertRestResponse object of the +// SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type SecurityAlertsSecurityAlertRestResponse struct { + // Id is the id. + Id string `json:"id,omitempty"` + // AlertType is the alert type. Allowed values: DEPENDENCY_RISK. + AlertType string `json:"alertType,omitempty"` + // Status aggregate status across all affected branches: OPEN if any affected branch is OPEN. + // Allowed values: OPEN, RESOLVED. + Status string `json:"status,omitempty"` + // Severity highest severity among the affected branches. Allowed values: BLOCKER, HIGH, + // MEDIUM, LOW, INFO. + Severity string `json:"severity,omitempty"` + // AffectedAuthorizedBranchesTotal total number of affected branches that the current user is + // authorized to view. + AffectedAuthorizedBranchesTotal int32 `json:"affectedAuthorizedBranchesTotal,omitempty"` + // FirstDetectedAt ISO-8601 UTC timestamp. + FirstDetectedAt string `json:"firstDetectedAt,omitempty"` + // LastDetectedAt ISO-8601 UTC timestamp. + LastDetectedAt string `json:"lastDetectedAt,omitempty"` + // ScaIssue linked SCA issue details (if any). + ScaIssue SecurityAlertsScaIssue `json:"scaIssue,omitzero"` + // AffectedBranches is the affected branches. + AffectedBranches []SecurityAlertsAffectedBranchRestResponse `json:"affectedBranches,omitempty"` +} + +// SecurityAlertsSecurityAlertSearchRestResponse represents the SecurityAlertSearchRestResponse +// object of the SonarQube V2 API. +type SecurityAlertsSecurityAlertSearchRestResponse struct { + // SecurityAlerts is the security alerts. + SecurityAlerts []SecurityAlertsSecurityAlertRestResponse `json:"securityAlerts,omitempty"` + // Page is the page. + Page PageResponseV2 `json:"page,omitzero"` +} + +// ----------------------------------------------------------------------------- +// Validation Functions +// ----------------------------------------------------------------------------- + +// ValidateSearchOpt validates the options for the Search method. +func (s *SecurityAlertsService) ValidateSearchOpt(opt *SecurityAlertsSearchOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := IsValueAuthorized(opt.Sort, allowedSecurityAlertsSearchSort, "Sort") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Direction, allowedSecurityAlertsSearchDirection, "Direction") + if err != nil { + return err + } + + if opt.PageSize < 0 || opt.PageSize > 500 { + return NewValidationError("PageSize", "must be between 0 and 500", ErrOutOfRange) + } + + if opt.PageIndex < 0 { + return NewValidationError("PageIndex", "must be greater than 0", ErrOutOfRange) + } + + return nil +} + +// ----------------------------------------------------------------------------- +// Service Methods +// ----------------------------------------------------------------------------- + +// Search searches security alerts. List security alerts, one row per alert, aggregating its +// affected branches. Requires 'Browse' permission on the affected projects; results are limited to +// alerts on projects visible to the current user. +// +// API endpoint: GET /api/v2/security-alerts/alerts. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *SecurityAlertsService) Search(ctx context.Context, opt *SecurityAlertsSearchOptions) (*SecurityAlertsSecurityAlertSearchRestResponse, *http.Response, error) { + err := s.ValidateSearchOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "security-alerts/alerts", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(SecurityAlertsSecurityAlertSearchRestResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// Get gets a security alert by ID. Returns a single security alert by its ID. Returns 404 if the +// alert does not exist or the current user does not have 'Browse' permission on any affected +// project. +// +// API endpoint: GET /api/v2/security-alerts/alerts/{id}. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *SecurityAlertsService) Get(ctx context.Context, securityAlertID string) (*SecurityAlertsSecurityAlertRestResponse, *http.Response, error) { + err := ValidateRequired(securityAlertID, "securityAlertID") + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "security-alerts/alerts/"+url.PathEscape(securityAlertID), nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(SecurityAlertsSecurityAlertRestResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} diff --git a/sonar/security_alerts_v2_service_test.go b/sonar/security_alerts_v2_service_test.go new file mode 100644 index 0000000..13428fa --- /dev/null +++ b/sonar/security_alerts_v2_service_test.go @@ -0,0 +1,44 @@ +package sonar + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSecurityAlertsService_Search(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/security-alerts/alerts", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.SecurityAlerts.Search(context.Background(), &SecurityAlertsSearchOptions{}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestSecurityAlertsService_Search_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.SecurityAlerts.Search(context.Background(), nil) + require.Error(t, err) +} + +func TestSecurityAlertsService_Get(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/security-alerts/alerts/test-id", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.SecurityAlerts.Get(context.Background(), "test-id") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestSecurityAlertsService_Get_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.SecurityAlerts.Get(context.Background(), "") + require.Error(t, err) +} diff --git a/sonar/security_reports_service.go b/sonar/security_reports_service.go index 19b967e..c5426ae 100644 --- a/sonar/security_reports_service.go +++ b/sonar/security_reports_service.go @@ -30,6 +30,8 @@ type SecurityReportCategory struct { // Version is the standard revision this category belongs to (e.g. "2017", "2021" // for owaspTop10, "3.2", "4.0" for pciDss). Empty for standards without revisions. Version string `json:"version,omitempty"` + // Level is the verification level of the category (owaspAsvs only, e.g. "1"). + Level string `json:"level,omitempty"` // Distribution is the CWE breakdown for this category. Only populated when // includeDistribution is requested (and non-empty for CWE-oriented standards). Distribution []SecurityReportCWEDistribution `json:"distribution,omitempty"` @@ -45,8 +47,12 @@ type SecurityReportCategory struct { ToReviewSecurityHotspots int `json:"toReviewSecurityHotspots,omitempty"` // ReviewedSecurityHotspots is the number of reviewed security hotspots. ReviewedSecurityHotspots int `json:"reviewedSecurityHotspots,omitempty"` + // DependencyRisks is the number of dependency risks in this category. + DependencyRisks int `json:"dependencyRisks,omitempty"` // HasMoreRules indicates whether more rules exist beyond TotalRules. HasMoreRules bool `json:"hasMoreRules,omitempty"` + // HasDependencyRisks indicates whether the category tracks dependency risks. + HasDependencyRisks bool `json:"hasDependencyRisks,omitempty"` } // SecurityReportCWEDistribution represents the per-CWE breakdown within a security @@ -54,6 +60,9 @@ type SecurityReportCategory struct { type SecurityReportCWEDistribution struct { // CWE is the CWE identifier (e.g. "89"). CWE string `json:"cwe,omitempty"` + // Category is the sub-category identifier, used instead of CWE by standards whose + // distribution is not CWE-oriented (e.g. "1.1" for cra). + Category string `json:"category,omitempty"` // Vulnerabilities is the number of vulnerabilities for this CWE. Vulnerabilities int `json:"vulnerabilities,omitempty"` // VulnerabilityRating is the vulnerability rating (1-5, A-E) for this CWE. @@ -68,8 +77,12 @@ type SecurityReportCWEDistribution struct { ToReviewSecurityHotspots int `json:"toReviewSecurityHotspots,omitempty"` // ReviewedSecurityHotspots is the number of reviewed security hotspots. ReviewedSecurityHotspots int `json:"reviewedSecurityHotspots,omitempty"` + // DependencyRisks is the number of dependency risks for this entry. + DependencyRisks int `json:"dependencyRisks,omitempty"` // HasMoreRules indicates whether more rules exist beyond TotalRules. HasMoreRules bool `json:"hasMoreRules,omitempty"` + // HasDependencyRisks indicates whether the entry tracks dependency risks. + HasDependencyRisks bool `json:"hasDependencyRisks,omitempty"` } // ----------------------------------------------------------------------------- diff --git a/sonar/software_quality_reports_v2_compliance_service.go b/sonar/software_quality_reports_v2_compliance_service.go new file mode 100644 index 0000000..6ba63c6 --- /dev/null +++ b/sonar/software_quality_reports_v2_compliance_service.go @@ -0,0 +1,135 @@ +package sonar + +import ( + "context" + "fmt" + "net/http" +) + +//nolint:gochecknoglobals // constant sets of allowed values +var ( + // allowedSoftwareQualityReportsGetComplianceReportStandard is the set of allowed values for the corresponding option. + allowedSoftwareQualityReportsGetComplianceReportStandard = map[string]struct{}{ + "misra": {}, + "wcag": {}, + } + // allowedSoftwareQualityReportsGetComplianceReportLevel is the set of allowed values for the corresponding option. + allowedSoftwareQualityReportsGetComplianceReportLevel = map[string]struct{}{ + "A": {}, + "AA": {}, + "AAA": {}, + } +) + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// SoftwareQualityReportsGetComplianceReportOptions contains parameters for the GetComplianceReport method. +type SoftwareQualityReportsGetComplianceReportOptions struct { + // BranchKey branch key. If not provided, the main branch is used for projects and + // applications. + BranchKey string `json:"branchKey,omitempty"` + // ComponentKey project, application, or portfolio key. This field is required. + ComponentKey string `json:"componentKey"` + // Standard compliance standard. This field is required. Allowed values: misra, wcag. + Standard string `json:"standard"` + // Version compliance standard version. This field is required. + Version string `json:"version"` + // Level optional WCAG conformance level. Valid values are A, AA, and AAA and they are + // inclusive. If omitted, all levels are included. Not supported for MISRA. Allowed values: A, + // AA, AAA. + Level string `json:"level,omitempty"` +} + +// SoftwareQualityReportsComplianceCategory represents the ComplianceCategory object of the +// SonarQube V2 API. +// +//nolint:govet // Field alignment less important than maintaining consistent field order for readability +type SoftwareQualityReportsComplianceCategory struct { + // ActiveRules is the active rules. + ActiveRules int32 `json:"activeRules,omitempty"` + // Issues is the issues. + Issues int32 `json:"issues,omitempty"` + // Name is the name. + Name string `json:"name,omitempty"` + // Categories is the list of sub-categories. The API spec omits this field, but the + // server returns it recursively (e.g. WCAG principle -> guideline -> success criterion). + Categories []SoftwareQualityReportsComplianceCategory `json:"categories,omitempty"` +} + +// SoftwareQualityReportsGetComplianceReportResponse represents the GetComplianceReportResponse +// object of the SonarQube V2 API. +type SoftwareQualityReportsGetComplianceReportResponse struct { + // Categories is the categories. + Categories []SoftwareQualityReportsComplianceCategory `json:"categories,omitempty"` +} + +// ----------------------------------------------------------------------------- +// Validation Functions +// ----------------------------------------------------------------------------- + +// ValidateGetComplianceReportOpt validates the options for the GetComplianceReport method. +func (s *SoftwareQualityReportsService) ValidateGetComplianceReportOpt(opt *SoftwareQualityReportsGetComplianceReportOptions) error { + if opt == nil { + return NewValidationError("opt", "option struct is required", ErrMissingRequired) + } + + err := ValidateRequired(opt.ComponentKey, "ComponentKey") + if err != nil { + return err + } + + err = ValidateRequired(opt.Standard, "Standard") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Standard, allowedSoftwareQualityReportsGetComplianceReportStandard, "Standard") + if err != nil { + return err + } + + err = ValidateRequired(opt.Version, "Version") + if err != nil { + return err + } + + err = IsValueAuthorized(opt.Level, allowedSoftwareQualityReportsGetComplianceReportLevel, "Level") + if err != nil { + return err + } + + return nil +} + +// ----------------------------------------------------------------------------- +// Service Methods +// ----------------------------------------------------------------------------- + +// GetComplianceReport gets compliance report. Get the compliance report for a project, application, +// or portfolio. +// +// API endpoint: GET /api/v2/software-quality-reports/compliance-reports. +// Enterprise Edition only. Marked internal by SonarQube and subject to change +// without notice. +func (s *SoftwareQualityReportsService) GetComplianceReport(ctx context.Context, opt *SoftwareQualityReportsGetComplianceReportOptions) (*SoftwareQualityReportsGetComplianceReportResponse, *http.Response, error) { + err := s.ValidateGetComplianceReportOpt(opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewSonarQubeV2APIRequest(ctx, http.MethodGet, "software-quality-reports/compliance-reports", opt, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + result := new(SoftwareQualityReportsGetComplianceReportResponse) + + resp, err := s.client.Do(req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} diff --git a/sonar/software_quality_reports_v2_compliance_service_test.go b/sonar/software_quality_reports_v2_compliance_service_test.go new file mode 100644 index 0000000..69d804d --- /dev/null +++ b/sonar/software_quality_reports_v2_compliance_service_test.go @@ -0,0 +1,27 @@ +package sonar + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSoftwareQualityReportsService_GetComplianceReport(t *testing.T) { + server := newTestServer(t, mockHandler(t, http.MethodGet, "/v2/software-quality-reports/compliance-reports", http.StatusOK, map[string]any{})) + client := newTestClient(t, server.url()) + + result, resp, err := client.V2.SoftwareQualityReports.GetComplianceReport(context.Background(), &SoftwareQualityReportsGetComplianceReportOptions{ComponentKey: "test", Standard: "misra", Version: "test"}) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, result) +} + +func TestSoftwareQualityReportsService_GetComplianceReport_ValidationError(t *testing.T) { + client := newLocalhostClient(t) + + _, _, err := client.V2.SoftwareQualityReports.GetComplianceReport(context.Background(), nil) + require.Error(t, err) +} diff --git a/sonar/system_service.go b/sonar/system_service.go index 9d975fe..e9cec82 100644 --- a/sonar/system_service.go +++ b/sonar/system_service.go @@ -116,6 +116,16 @@ type SystemHealthNode struct { Type string `json:"type,omitempty"` } +// SystemInfoAgentStatus represents the status of an agentic component in the system info. +// +//nolint:tagliatelle // JSON tags match SonarQube API +type SystemInfoAgentStatus struct { + // Enabled indicates whether the agent is enabled. + Enabled bool `json:"Enabled,omitempty"` + // Healthy indicates whether the agent is healthy. + Healthy bool `json:"Healthy,omitempty"` +} + // SystemInfo represents the response from getting detailed system information. // This contains extensive information about the SonarQube configuration and state. // @@ -123,6 +133,18 @@ type SystemHealthNode struct { type SystemInfo struct { // ALMs contains ALM integration configuration. ALMs SystemInfoALMs `json:"ALMs,omitzero"` + // AgentOrchestrator contains the agent orchestrator status. + AgentOrchestrator SystemInfoAgentStatus `json:"Agent Orchestrator,omitzero"` + // AgenticAnalysis contains the agentic analysis status. + AgenticAnalysis SystemInfoAgentStatus `json:"Agentic Analysis,omitzero"` + // HunterAgent contains the hunter agent status. + HunterAgent SystemInfoAgentStatus `json:"Hunter Agent,omitzero"` + // RemediationAgent contains the remediation agent status. + RemediationAgent SystemInfoAgentStatus `json:"Remediation Agent,omitzero"` + // Onboarding contains onboarding statistics (project counts by binding and analysis state). + Onboarding map[string]any `json:"Onboarding,omitempty"` + // DevOpsPlatformPersonalAccessTokens contains statistics about stored DevOps Platform personal access tokens. + DevOpsPlatformPersonalAccessTokens map[string]any `json:"DevOps Platform Personal Access Tokens,omitempty"` // Bundled contains versions of bundled plugins. Bundled SystemInfoBundled `json:"Bundled,omitzero"` // ComputeEngineDatabaseConnection contains CE database connection pool info. @@ -245,6 +267,18 @@ type SystemInfoBundled struct { Rpg string `json:"rpg,omitempty"` // Ruby is the Ruby plugin version. Ruby string `json:"ruby,omitempty"` + // Commona3scontextcollector is the common A3S context collector plugin version. + Commona3scontextcollector string `json:"commona3scontextcollector,omitempty"` + // Cvereachability is the CVE reachability analysis plugin version. + Cvereachability string `json:"cvereachability,omitempty"` + // Dreapex is the Apex deterministic rule engine plugin version. + Dreapex string `json:"dreapex,omitempty"` + // Dreruby is the Ruby deterministic rule engine plugin version. + Dreruby string `json:"dreruby,omitempty"` + // Javaa3scontextcollector is the Java A3S context collector plugin version. + Javaa3scontextcollector string `json:"javaa3scontextcollector,omitempty"` + // Rustenterprise is the Rust enterprise plugin version. + Rustenterprise string `json:"rustenterprise,omitempty"` // Rust is the Rust plugin version. Rust string `json:"rust,omitempty"` // Security is the security plugin version. @@ -599,6 +633,8 @@ type SystemUpgrade struct { Product string `json:"product,omitempty"` // Plugins contains plugin compatibility information. Plugins SystemUpgradePlugins `json:"plugins,omitzero"` + // Lta indicates whether the upgrade is a Long-Term Active version. + Lta bool `json:"lta,omitempty"` // ReleaseDate is the release date of the upgrade. ReleaseDate string `json:"releaseDate,omitempty"` // Version is the version of the upgrade. diff --git a/sonar/user_tokens_service.go b/sonar/user_tokens_service.go index ef1ce65..511d20c 100644 --- a/sonar/user_tokens_service.go +++ b/sonar/user_tokens_service.go @@ -54,6 +54,8 @@ type UserToken struct { ExpirationDate string `json:"expirationDate,omitempty"` // IsExpired indicates whether the token has expired. IsExpired bool `json:"isExpired,omitempty"` + // LastConnectionDate is the date the token was last used to authenticate. Absent if never used. + LastConnectionDate string `json:"lastConnectionDate,omitempty"` // Name is the name of the token. Name string `json:"name,omitempty"` // Project is the project associated with a PROJECT_ANALYSIS_TOKEN. diff --git a/sonar/users_service.go b/sonar/users_service.go index 34dd8d8..461818e 100644 --- a/sonar/users_service.go +++ b/sonar/users_service.go @@ -50,6 +50,8 @@ const ( NoticeTypeOverviewZeroNewIssuesSimplification = "overviewZeroNewIssuesSimplification" // NoticeTypeShowDesignAndArchitectureTour represents the show design and architecture tour notice type. NoticeTypeShowDesignAndArchitectureTour = "showDesignAndArchitectureTour" + // NoticeTypeShowProjectCoverageTour represents the show project coverage tour notice type. + NoticeTypeShowProjectCoverageTour = "showProjectCoverageTour" // NoticeTypeShowEnableSca represents the show enable SCA notice type. NoticeTypeShowEnableSca = "showEnableSca" ) @@ -87,6 +89,7 @@ var ( NoticeTypeOverviewZeroNewIssuesSimplification: {}, NoticeTypeShowDesignAndArchitectureTour: {}, NoticeTypeShowEnableSca: {}, + NoticeTypeShowProjectCoverageTour: {}, } ) @@ -226,6 +229,8 @@ type UsersDismissedNotices struct { ShowDesignAndArchitectureOptInBanner bool `json:"showDesignAndArchitectureOptInBanner,omitempty"` // ShowDesignAndArchitectureTour indicates whether the design and architecture tour notice was dismissed. ShowDesignAndArchitectureTour bool `json:"showDesignAndArchitectureTour,omitempty"` + // ShowProjectCoverageTour indicates whether the project coverage tour notice was dismissed. + ShowProjectCoverageTour bool `json:"showProjectCoverageTour,omitempty"` // ShowEnableSca indicates whether the enable SCA notice was dismissed. ShowEnableSca bool `json:"showEnableSca,omitempty"` // ShowNewModesBanner indicates whether the new modes banner notice was dismissed. @@ -389,7 +394,7 @@ type UsersDismissNoticeOptions struct { // Allowed values: educationPrinciples, sonarlintAd, showDesignAndArchitectureBanner, // showNewModesBanner, showSandboxedIssuesIntro, issueCleanCodeGuide, // issueNewIssueStatusAndTransitionGuide, showDesignAndArchitectureOptInBanner, - // overviewZeroNewIssuesSimplification, showDesignAndArchitectureTour, showEnableSca. + // overviewZeroNewIssuesSimplification, showDesignAndArchitectureTour, showEnableSca, showProjectCoverageTour. Notice string `url:"notice"` } diff --git a/sonar/views_service.go b/sonar/views_service.go index 6262c12..e39872a 100644 --- a/sonar/views_service.go +++ b/sonar/views_service.go @@ -61,6 +61,18 @@ type ViewDetails struct { SelectionMode string `json:"selectionMode,omitempty"` // SubViews is the list of sub-portfolios. SubViews []View `json:"subViews,omitempty"` + // SelectedProjects is the list of manually selected projects (selectionMode MANUAL). + SelectedProjects []ViewSelectedProject `json:"selectedProjects,omitempty"` + // ReferencedBy is the list of portfolios that reference this portfolio as a sub-portfolio. + ReferencedBy []ViewDetails `json:"referencedBy,omitempty"` +} + +// ViewSelectedProject is a project manually selected in a portfolio. +type ViewSelectedProject struct { + // ProjectKey is the key of the selected project. + ProjectKey string `json:"projectKey,omitempty"` + // SelectedBranches is the list of explicitly selected branches, if any. + SelectedBranches []string `json:"selectedBranches,omitempty"` } // ViewProject represents a project entry in a portfolio.