Skip to content

Conversation

Animesh-Ghosh
Copy link
Contributor

@Animesh-Ghosh Animesh-Ghosh commented Sep 8, 2025

Why

Currently, AppConfig holds a lot of integration-specific configuration, which feels like a data-modelling smell. This modelling also makes it harder to develop certain code paths for AppVariants.
Moving the integration-specific configurations to the apt integration providable should simplify our data modelling and allow further features to be built on top of these changes.

This addresses

Enter the details of what all this covers, include screenshots, demos, thought processes etc.

Making Integration providables own their integration-specific configuration by adding schema migrations.

We also enable dedicated write paths for each Integration's configuration which closely models AppConfig's write path, while also modifying existing read paths to use persisted configuration from the Integrations' providables, rather than the global AppConfig. This involved the following UI changes on the app's Integrations tab:

Before After
Screenshot 2025-10-09 at 10 50 57 PM (the configure button was at the integration list level) Screenshot 2025-10-09 at 10 50 23 PM (the configure button is now on the integration card level)

We also try to ensure the app setup wizard behaves as it did when the configuration values were being read from AppConfig.

Scenario Screenshot
When required integrations are not connected yet Screenshot 2025-10-09 at 10 56 23 PM
When connected integrations are not configured yet Apps-Ueno-Android-Tramline-10-09-2025_11_13_PM
App.Finishing.Up.Integration.Configuration.mp4

Due to the fact that Integration providables can now own their own config, an edge case had to be handled where if a CI/CD integration is attempted to be connected, rather than re-used, the repository config would not be present on the integration, and vice-versa for version control integrations. This would disallow train creation. Thus, the following CI/CD integrations were made to be further configurable:

  • BitbucketIntegration
  • GithubIntegration
  • GitlabIntegration

This edge-case should not surface if the integration is re-used instead.

CI/CD Integration New Configuration pop-up
Bitrise No Screenshot 2025-10-10 at 12 11 32 AM
Bitbucket Yes Screenshot 2025-10-10 at 12 12 52 AM
Github Yes Screenshot 2025-10-10 at 12 13 14 AM
Gitlab Yes Screenshot 2025-10-10 at 12 13 47 AM

Scenarios tested

  • Configuring version control integrations persists the repository config at the integration providable-level
  • Configuring CI/CD integration persists the project config at the integration providable-level for Bitrise integration
  • Configuring build channel integration persists the platform-specific config at the integration providable-level for the Google Firebase integration
  • Configuring monitoring integration persists the platform-specific config at the integration providable-level for the Bugsnag integration
  • Configuring project management integration persists the project config at the integration providable-level for Linear integration
  • Configuring project management integration persists the project config at the integration providable-level for Jira integration
  • Bitbucket, Github, Gitlab integrations CI/CD integrations can be connected and configured separately from the version control integrations
  • Bitbucket, Github, Gitlab integrations CI/CD integrations can be reused and copy over the repository config from the version control integration and vice-versa for version control integrations
  • App configuration wizard nudges to connect minimum required integrations
  • App configuration wizard nudges to configure the connected integrations and opens the pop-up on the Integrations tab on clicking the 'Finish up' link
  • Setup train link shows up only when all connected integrations are configured properly
  • Train can be created once all the integrations are configured
  • Train's Submission settings loads the workflows based on which CI/CD integration has been connected and configured
  • A fully configured Train sends out a Release successfully
  • Existing app's configs are migrated to integration-providables properly post data migration
  • Existing app's releases continue to run properly post data migration
  • App variants can be created
  • App variants work properly post data migration

Summary by CodeRabbit

  • New Features
    • In-app configuration UIs for Version Control, CI/CD, Build Channel, Monitoring, and Project Management with Turbo Frame edit workflows and selector forms; new update flows and success messages.
  • Improvements
    • Integration cards now offer an optional “further setup” (cog) modal alongside connect/disconnect; streamlined disconnect confirmation and updated layout.
    • App readiness and integration status reflect per-category configuration.
  • Bug Fixes
    • Release tag links now point to the correct repository URL.

@Animesh-Ghosh Animesh-Ghosh self-assigned this Sep 8, 2025
Copy link

coderabbitai bot commented Sep 8, 2025

Walkthrough

Adds JSONB-backed per-integration configuration, new namespaced config controllers and Turbo Frame edit views, integration-level accessors/validations, routes and migrations, component/UI updates to surface per-category setup, and updates App readiness logic to use integration-based readiness.

Changes

Cohort / File(s) Summary
Components – Cards & Lists
app/components/integration_card_component.html.erb, app/components/integration_card_component.rb, app/components/integration_list_component.html.erb, app/components/integration_list_component.rb
Adds optional configure modal (Turbo Frame) and pre_open_category support; exposes edit_config_path helpers; shows configure + disconnect side-by-side; removes connected_integrations? and updates constructor usage.
Controllers – Config editors
app/controllers/version_control/*_configs_controller.rb, app/controllers/ci_cd/*_configs_controller.rb, app/controllers/build_channel/google_firebase_configs_controller.rb, app/controllers/monitoring/bugsnag_configs_controller.rb, app/controllers/project_management/*_configs_controller.rb
New namespaced controllers with edit/update: load app+integration, preload repos/projects/workspaces, parse nested JSON params (safe_json_parse), persist integration-level JSON (repository_config, project_config, android_config/ios_config), handle Turbo Frame/Stream responses and redirects.
Views – Config forms & frames
app/views/version_control/*, app/views/ci_cd/*, app/views/build_channel/*, app/views/monitoring/*, app/views/project_management/*, app/views/jira_integration/*, app/views/linear_integration/*
Add EnhancedTurboFrame-backed edit templates and partials for selecting repos/projects/teams/apps; switch nested form scope from *_config to project_config for Jira/Linear; add partials for Firebase, Bitbucket, Bitrise, Bugsnag, etc.
Models – Integrations & readiness
app/models/integration.rb, app/models/github_integration.rb, app/models/gitlab_integration.rb, app/models/bitbucket_integration.rb, app/models/bitrise_integration.rb, app/models/bugsnag_integration.rb, app/models/google_firebase_integration.rb, app/models/jira_integration.rb, app/models/linear_integration.rb
Add repository_config / project_config / platform configs and accessors; migrate many accessors from app_config to integration-level JSON; add validations (release_filters), add readiness helpers and further_setup_by_category, change further_setup? semantics for several providers.
Models – App & AppConfig
app/models/app.rb, app/models/app_config.rb, app/models/concerns/app_configurable.rb
App.ready? and setup instructions now use integration-based readiness; AppConfig removes many legacy helpers and disconnect logic; small comments added to concern.
Libs – Webhooks / Coordinators
app/libs/coordinators/webhooks/pull_request.rb, app/libs/coordinators/webhooks/push.rb
Repo/branch validation now reads repository metadata via app.vcs_provider.* instead of app.config.*.
Controllers – Apps
app/controllers/apps_controller.rb
show action now conditionally sets view variables based on @app.ready? (either @train_in_creation or @app_setup_instructions).
Routes & Locales
config/routes.rb, config/locales/en.yml
Add singleton config routes under apps for version_control, ci_cd, build_channel, monitoring, project_management; add update.success locale keys.
DB – Migrations & Schema
db/migrate/*, db/schema.rb
Add JSONB columns: repository_config (VCS), project_config (Bitrise/Jira/Linear), android_config/ios_config (Firebase/Bugsnag); add workspace to bitbucket_integrations; schema version bump.
Specs & Factories
spec/factories/*, spec/libs/*, spec/models/*
Update factories to include repository_config/project_config; tests updated to read repo/config data from integrations; add validations tests for release_filters.
Components – Live Release
app/components/live_release/finalize_component.rb
Tag link now sources repo URL from release.app.vcs_provider.code_repo_url instead of app.config.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant Page as Integrations Page
  participant Card as IntegrationCardComponent
  participant Modal as ModalComponent
  participant TF as Turbo Frame
  participant C as ConfigsController
  participant I as Integration(Model)
  participant App as App

  U->>Page: open app integrations
  Page->>Card: render for each integration
  Card->>I: query further_setup? / pre_open_category?
  alt configure available
    U->>Card: click Configure (cog)
    Card->>Modal: open modal (light action)
    Modal->>TF: load frame (category_config_turbo_frame_id)
    TF->>C: GET edit (app + integration category)
    C->>I: load config data (repos/projects/apps)
    C-->>TF: render form partial
    U->>TF: submit Update (PATCH)
    TF->>C: update (parsed JSON params)
    C->>I: persist config JSON
    alt success
      C-->>U: redirect to app_path with success notice
    else failure
      C-->>U: redirect back with error flash
    end
  else only disconnect
    U->>Card: click Disconnect
    Card->>I: send DELETE (Turbo default)
  end
Loading
sequenceDiagram
  autonumber
  participant AppsCtrl as AppsController
  participant App as App
  participant Integrations as Integration

  AppsCtrl->>App: call ready?
  App->>Integrations: Integration.configured?
  Integrations->>Integrations: further_setup_by_category (aggregate readiness)
  alt ready
    AppsCtrl-->>View: set @train_in_creation
  else not ready
    AppsCtrl-->>View: set @app_setup_instructions
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • kitallis

Poem

A rabbit taps the JSON drum, thump-thump, config tune!
Frames pop open, repos bloom beneath a crescent moon.
Jira queues and Linear teams hop into neat arrays,
Firebase and Bugsnag hum through platform-configured days.
Click update, little button — hop along the build highway! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ⚠️ Warning The pull request description is missing the required “Closes:” line linking to an issue and still contains the placeholder text under the “## This addresses” section instead of being fully replaced with the actual details, so it does not conform to the repository’s description template. Please add the “Closes: https://github.com/tramlinehq/site/issues/” line at the top of the description and remove or replace the placeholder text under “## This addresses” with the actual explanation and details of what the PR covers.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly refers to the primary change of refactoring the AppConfig component and captures the core intent of the PR without extraneous details.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/app-config-refactor

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

❤️ Share

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

@Animesh-Ghosh Animesh-Ghosh force-pushed the chore/app-config-refactor branch 2 times, most recently from 2453349 to e0e39e8 Compare September 10, 2025 03:38
@Animesh-Ghosh Animesh-Ghosh force-pushed the chore/app-config-refactor branch 4 times, most recently from c7d159b to 2f007eb Compare September 17, 2025 03:56
…grations

Update the read path on `AppConfig#further_setup_by_category?` for `code_repository`.
This set of changes has a few TODO items that should be resolved once the remaining
configurable integrations are brought under the fold of the refactor.
Bitbucket version control configuration requires the user to select
a workspace to select the code repository.
The workspace dropdown is supposed work by re-rendering the configuration
form via Turbo stream. However, the partial for the TURBO_STREAM format
was missing, which would break the interaction, along with raising a
server-side error.
These changes refactor the HTML partial to render the form in both the
HTML and the TURBO_STREAM formats, to make the drop-down UX work.
Update read path on `AppConfig#further_setup_by_category?` for `ci_cd`.
Fix config update translations.
@Animesh-Ghosh Animesh-Ghosh force-pushed the chore/app-config-refactor branch 2 times, most recently from e5f05d8 to be7cdaa Compare September 19, 2025 02:55
Update read path on `AppConfig#further_setup_by_category?` for `build_channel`.
@Animesh-Ghosh Animesh-Ghosh force-pushed the chore/app-config-refactor branch from be7cdaa to 73ac1b9 Compare September 19, 2025 03:00
The persisted config value, when serialized, could generate a JSON string
that could be different from the one generated by the
view helper for the drop-down options' values, which would make it so that
the persisted config value is not pre-selected by default.
Thus, we explicitly find the correct config and use that as the default,
rather than using the persisted value.
Simplify some config read paths in `AppConfig`.
Update read paths for `monitoring` config in `AppConfig` for:
- `#further_setup_by_category?`
- bugsnag related attributes
…ntegrations

Update read path for `project_management` config in `AppConfig#further_setup_by_category?`.
Remove unnecessary code and markup from `IntegrationCard` and `IntegrationList` components.
Remove unused strong params method in `Monitoring::BugsnagConfigsController` controller.
Removing model validations from `AppConfig` has to be done as part of
deprecating the model.
@Animesh-Ghosh Animesh-Ghosh force-pushed the chore/app-config-refactor branch from d62d187 to a96cd2b Compare October 5, 2025 07:25
@Animesh-Ghosh Animesh-Ghosh force-pushed the chore/app-config-refactor branch from ca1259e to 397e9d7 Compare October 5, 2025 07:34
@Animesh-Ghosh Animesh-Ghosh force-pushed the chore/app-config-refactor branch from 260b54d to 7d32eb4 Compare October 5, 2025 13:47
@Animesh-Ghosh Animesh-Ghosh force-pushed the chore/app-config-refactor branch from 700cd34 to d019bd2 Compare October 5, 2025 17:57
Since factoring out the `code_repository` to individual integration
providers, creating trains would fail since CI/CD integrations wouldn't
have the necessary `repository_config` present.
This was observed only for Github, Gitlab and Bitbucket integrations.
Thus, we now allow CI/CD integrations to now be configurable.
Update the new integration config controllers to redirect to the app's show page.
Separate integration connection-readiness and integration config-readiness checks
so that the wizard isn't stuck at just showing the "Complete your app setup" section.
Update CI/CD integration readiness check.
@Animesh-Ghosh Animesh-Ghosh marked this pull request as ready for review October 8, 2025 18:19
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 20

Caution

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

⚠️ Outside diff range comments (5)
app/views/linear_integration/_release_filter_form.html.erb (1)

4-10: Add nested attributes configuration to LinearIntegration model
The LinearIntegration model is missing accepts_nested_attributes_for :release_filters, allow_destroy: true, so the _destroy hidden field in the form won’t work as intended.

app/models/jira_integration.rb (1)

98-102: Fix undefined variable in complete_access (resource is nil).

resource is not defined in this branch, leading to a NameError. Use resources.first.

-    if resources.length == 1
-      self.cloud_id = resources.first["id"]
-      self.organization_url = resource["url"]
-      self.organization_name = resource["name"]
-      true
+    if resources.length == 1
+      resource = resources.first
+      self.cloud_id = resource["id"]
+      self.organization_url = resource["url"]
+      self.organization_name = resource["name"]
+      true
app/models/integration.rb (1)

177-215: Use connected. for provider lookups to avoid stale/disconnected picks*

Provider helpers should align with readiness checks and only consider connected integrations.

- def vcs_provider
-   kept.version_control.first&.providable
- end
+ def vcs_provider
+   connected.version_control.first&.providable
+ end

- def ci_cd_provider
-   kept.ci_cd.first&.providable
- end
+ def ci_cd_provider
+   connected.ci_cd.first&.providable
+ end

- def bitrise_ci_cd_provider
-   kept.ci_cd.find(&:bitrise_integration?)&.providable
- end
+ def bitrise_ci_cd_provider
+   connected.ci_cd.find(&:bitrise_integration?)&.providable
+ end

- def monitoring_provider
-   kept.monitoring.first&.providable
- end
+ def monitoring_provider
+   connected.monitoring.first&.providable
+ end

- def android_store_provider
-   kept.build_channel.find(&:google_play_store_integration?)&.providable
- end
+ def android_store_provider
+   connected.build_channel.find(&:google_play_store_integration?)&.providable
+ end

- def ios_store_provider
-   kept.build_channel.find(&:app_store_integration?)&.providable
- end
+ def ios_store_provider
+   connected.build_channel.find(&:app_store_integration?)&.providable
+ end

- def slack_build_channel_provider
-   kept.build_channel.find(&:slack_integration?)&.providable
- end
+ def slack_build_channel_provider
+   connected.build_channel.find(&:slack_integration?)&.providable
+ end

- def firebase_build_channel_provider
-   kept.build_channel.find(&:google_firebase_integration?)&.providable
- end
+ def firebase_build_channel_provider
+   connected.build_channel.find(&:google_firebase_integration?)&.providable
+ end

- def project_management_provider
-   kept.project_management.first&.providable
- end
+ def project_management_provider
+   connected.project_management.first&.providable
+ end
app/models/bitbucket_integration.rb (1)

218-220: Fix URL encoding: CGI.escapeURIComponent does not exist in Ruby

This will raise at runtime. Use ERB::Util.url_encode (or CGI.escape) instead.

Apply this diff:

-  def compare_url(to_branch, from_branch)
-    "https://bitbucket.org/#{code_repository_name}/branch/#{from_branch}?dest=#{CGI.escapeURIComponent(to_branch)}"
-  end
+  def compare_url(to_branch, from_branch)
+    "https://bitbucket.org/#{code_repository_name}/branch/#{from_branch}?dest=#{ERB::Util.url_encode(to_branch)}"
+  end
app/models/app_config.rb (1)

76-94: Project management readiness short-circuits when both Jira and Linear exist

If Jira is present but not ready, Linear readiness isn’t checked. Either rely on the app’s project_management_provider or OR-combine readiness.

-  if jira
-    return jira.project_config.present? &&
-        jira.project_config["selected_projects"].present? &&
-        jira.project_config["selected_projects"].any? &&
-        jira.project_config["project_configs"].present?
-  end
-
-  if linear
-    return linear.project_config.present? &&
-        linear.project_config["selected_teams"].present? &&
-        linear.project_config["selected_teams"].any? &&
-        linear.project_config["team_configs"].present?
-  end
+  if jira || linear
+    jira_ready = jira && jira.project_config.present? &&
+      jira.project_config["selected_projects"].present? &&
+      jira.project_config["selected_projects"].any? &&
+      jira.project_config["project_configs"].present?
+
+    linear_ready = linear && linear.project_config.present? &&
+      linear.project_config["selected_teams"].present? &&
+      linear.project_config["selected_teams"].any? &&
+      linear.project_config["team_configs"].present?
+
+    return jira_ready || linear_ready
+  end
♻️ Duplicate comments (1)
app/views/version_control/gitlab_configs/edit.html+turbo_frame.erb (1)

8-15: Duplicate repository selection logic.

This view duplicates the same complex repository selection logic from app/views/ci_cd/github_configs/edit.html+turbo_frame.erb (lines 9-16).

See the helper extraction suggestion in the GitHub config view review. The same helper method would simplify this view as well:

-        <%= section.F.labeled_select :repository_config,
-              "Code Repository",
-              options_for_select(
-                display_channels(@code_repositories) { |repo| repo[:full_name] },
-                @code_repositories.find { |repo| repo[:id] == @gitlab_integration.repository_config&.dig("id") }&.to_json
-              ) %>
+        <%= section.F.labeled_select :repository_config,
+              "Code Repository",
+              repository_selection_options(@code_repositories, @gitlab_integration.repository_config) %>
🧹 Nitpick comments (34)
app/components/integration_card_component.html.erb (1)

64-65: LGTM; consider i18n for the button label.

Looks good. Optionally wrap "Connect" with I18n.

app/components/integration_card_component.rb (4)

83-90: Minor: consider i18n for the "Configure" prefix.

Use a translation key so UI is localizable.


91-104: Base routing switch on @category for consistency.

Avoid relying on integration.category if @category is the source of truth for this card:

-def edit_config_path
-  case integration.category
+def edit_config_path
+  case @category.to_s
     when "version_control" then edit_app_version_control_config_path
     when "ci_cd" then edit_app_ci_cd_config_path
     when "build_channel" then edit_app_build_channel_config_path
     when "monitoring" then edit_app_monitoring_config_path
     when "project_management" then edit_app_project_management_config_path
     else unsupported_integration_category
   end
 end

Ensure @category always matches integration.category in callers.


108-147: Route mapping via large case trees is brittle; push to integration/providable.

Define an API on integration/providable (e.g., integration.edit_config_path_for(@category, app: @app)) and delegate here. This removes per-type branching from the component and centralizes ownership.

I can draft the interface and a stepwise refactor if helpful.


149-155: Use ArgumentError for unsupported inputs (optional).

Raising ArgumentError better communicates invalid input than TypeError here:

-def unsupported_integration_category
-  raise TypeError, "Unsupported integration category: #{integration.category}"
+def unsupported_integration_category
+  raise ArgumentError, "Unsupported integration category: #{integration.category}"
 end

-def unsupported_integration_type
-  raise TypeError, "Unsupported integration type: #{integration.providable_type}"
+def unsupported_integration_type
+  raise ArgumentError, "Unsupported integration type: #{integration.providable_type}"
 end
app/models/concerns/app_configurable.rb (1)

4-5: Acknowledge the noted refactoring opportunity.

The comments correctly identify that firebase_app could be moved to GoogleFirebaseIntegration to further align with the integration-specific configuration pattern established in this PR.

Would you like me to help refactor this method into GoogleFirebaseIntegration, or should this be tracked as a follow-up task?

app/views/ci_cd/github_configs/edit.html+turbo_frame.erb (1)

9-16: Consider extracting repository selection logic into a helper.

The repository selection logic (finding and preselecting the repository) is complex and will be duplicated across similar views (GitLab, Bitbucket).

Consider extracting this into a view helper method:

# app/helpers/integration_configs_helper.rb
def repository_selection_options(repositories, selected_repository_config)
  options = display_channels(repositories) { |repo| repo[:full_name] }
  selected = repositories.find { |repo| repo[:id] == selected_repository_config&.dig("id") }&.to_json
  options_for_select(options, selected)
end

Then simplify the view to:

-        <%= section.F.labeled_select :repository_config,
-              "Code Repository",
-              options_for_select(
-                display_channels(@code_repositories) { |repo| repo[:full_name] },
-                @code_repositories.find { |repo| repo[:id] == @github_integration.repository_config&.dig("id") }&.to_json
-              ) %>
+        <%= section.F.labeled_select :repository_config,
+              "Code Repository",
+              repository_selection_options(@code_repositories, @github_integration.repository_config) %>
app/controllers/ci_cd/bitrise_configs_controller.rb (1)

1-53: Consider extracting the common controller pattern.

This controller follows the same structure as other CI/CD config controllers (GitHub, GitLab, Bitbucket). Consider extracting the common pattern into a concern or base class to reduce duplication while allowing integration-specific behavior.

For example, a concern could provide:

  • Standard set_app and redirect error handling
  • Parameter parsing pattern with safe_json_parse
  • Standard update flow with redirect paths

Each controller would then only need to define integration-specific methods like set_ci_cd_projects.

spec/factories/apps.rb (1)

9-11: Track the TODO for removing app_config.

The comment indicates this line can be removed, suggesting it's technical debt left during the gradual migration from AppConfig to per-integration configs.

Would you like me to open a new issue to track the removal of app.config from the factory once all integration-specific configurations have been fully migrated?

app/views/project_management/linear_configs/edit.html+turbo_frame.erb (1)

2-2: Prefer safe navigation for nil checks.

The conditional @linear_data && @linear_data[:teams].present? works but could be more idiomatic.

Consider using safe navigation:

-  <% if @linear_data && @linear_data[:teams].present? %>
+  <% if @linear_data&.dig(:teams).present? %>
app/models/jira_integration.rb (1)

65-65: Release filters validation: solid, consider reuse with Linear.

Validation logic is correct. Given identical logic in LinearIntegration, consider extracting a small concern/helper to DRY it up while keeping per‑integration VALID_FILTER_TYPES.

Also applies to: 268-274

app/models/bitrise_integration.rb (1)

64-68: Good: read project from project_config; add nil guards at call sites.

Accessor is fine. When project_config is unset, project is nil; downstream calls like workflows, trigger_workflow_run!, and artifact_url may break.

  • Add early returns (e.g., return [] if project.blank?) in read paths and clear errors/raise in write/trigger paths when not configured.
  • Verify current API methods safely handle nil app id.
app/views/project_management/jira_configs/edit.html+turbo_frame.erb (1)

13-14: Name consistency: current_jira_config vs project_config.

The partial now binds to project_config; consider renaming current_jira_config to current_project_config for clarity.

app/models/linear_integration.rb (1)

63-63: Same release_filters validator duplication as Jira.

Works as intended; consider extracting to a small shared concern to avoid drift.

Also applies to: 246-252

app/views/monitoring/_bugsnag_project_form.html.erb (2)

10-10: Escape JSON for data attribute

Embed JSON safely to avoid breaking the attribute and to prevent injection. Use json_escape (or j) on to_json.

-       data-nested-select-options-value="<%= projects.to_json %>"
+       data-nested-select-options-value="<%= json_escape(projects.to_json) %>"

14-21: *Avoid using full JSON as option value for _project_id

The select name suggests it expects an ID, but the option values look like JSON blobs. Prefer using the project id as the option value and keep the JSON in data-* attributes (or via the nested-select controller state). This simplifies params handling and reduces payload size.

config/locales/en.yml (1)

780-821: Reduce duplication via shared i18n key

These per-provider update.success messages are identical. Consider referencing a shared message (e.g., config.update.success) to reduce duplication and future drift.

db/schema.rb (1)

382-390: Legacy app_id column still present

Model ignores google_firebase_integrations.app_id. Consider a follow-up migration to drop this column to reduce confusion and risk of stale data.

app/models/github_integration.rb (2)

138-141: Wrap external call in retries for resiliency

Align with other providers (e.g., GitLab/Bitbucket) and wrap list_repos with with_api_retries.

-def repos(_ = nil)
-  installation.list_repos(REPOS_TRANSFORMATIONS)
-end
+def repos(_ = nil)
+  with_api_retries { installation.list_repos(REPOS_TRANSFORMATIONS) }
+end

382-384: Handle missing namespace in namespaced_branch

Avoid generating ":branch" when namespace is absent.

-def namespaced_branch(branch_name)
-  [code_repo_namespace, ":", branch_name].join
-end
+def namespaced_branch(branch_name)
+  return branch_name if code_repo_namespace.blank?
+  "#{code_repo_namespace}:#{branch_name}"
+end
app/controllers/version_control/github_configs_controller.rb (1)

45-51: Make repository_config parsing type-safe

Handle both String and Hash inputs to avoid NoMethodError on Hash.

- github_config_params = params.require(:github_integration)
-   .permit(:repository_config)
- github_config_params.merge(
-   repository_config: github_config_params[:repository_config]&.safe_json_parse
- )
+ github_config_params = params.require(:github_integration).permit(:repository_config)
+ repo_cfg = github_config_params[:repository_config]
+ repo_cfg = repo_cfg.is_a?(String) ? repo_cfg.safe_json_parse : repo_cfg
+ github_config_params.merge(repository_config: repo_cfg)
app/models/integration.rb (1)

164-171: Tighten configured? to connected scope

Use connected.exists? and avoid relying on Relation#none? semantics.

- def configured?
-   return false if none? # need at least one integration
-
-   further_setup_by_category
-     .values
-     .pluck(:ready)
-     .all?
- end
+ def configured?
+   return false unless connected.exists?
+   further_setup_by_category.values.pluck(:ready).all?
+ end
app/controllers/ci_cd/gitlab_configs_controller.rb (1)

45-51: Type-safe parsing for repository_config

Accept both String and Hash inputs.

- gitlab_config_params = params.require(:gitlab_integration)
-   .permit(:repository_config)
- gitlab_config_params.merge(
-   repository_config: gitlab_config_params[:repository_config]&.safe_json_parse
- )
+ gitlab_config_params = params.require(:gitlab_integration).permit(:repository_config)
+ repo_cfg = gitlab_config_params[:repository_config]
+ repo_cfg = repo_cfg.is_a?(String) ? repo_cfg.safe_json_parse : repo_cfg
+ gitlab_config_params.merge(repository_config: repo_cfg)
app/controllers/ci_cd/github_configs_controller.rb (1)

12-16: Consider adding turbo_stream response for consistency

Other CI/CD config controllers support turbo_stream. Add if desired for uniform UX.

Apply this diff:

     respond_to do |format|
       format.html do |variant|
         variant.turbo_frame { render :edit }
       end
+      format.turbo_stream { render :edit }
     end
app/controllers/project_management/linear_configs_controller.rb (1)

64-65: Side effects on GET

Persisting defaults in edit (save! if changed?) writes on GET. Consider deferring to update or a background init.

app/controllers/ci_cd/bitbucket_configs_controller.rb (1)

42-46: Handle empty workspace list gracefully

If there are no workspaces, repos(nil) may fail. Default to empty repos.

Apply this diff:

   def set_code_repositories
     @workspaces = @bitbucket_integration.workspaces || []
-    workspace = params[:workspace] || @workspaces.first
-    @code_repositories = @bitbucket_integration.repos(workspace)
+    workspace = params[:workspace] || @workspaces.first
+    @code_repositories = workspace.present? ? @bitbucket_integration.repos(workspace) : []
   end
app/models/app_config.rb (4)

60-62: Prefer dig over fetch for project_config id

Simpler and avoids potential KeyError semantics.

-  app.ci_cd_provider&.project_config&.fetch("id", nil).present?
+  app.ci_cd_provider&.project_config&.dig("id").present?

64-68: Add safe-nav to Bugsnag readiness; keep provider access consistent

Minor safety/consistency improvement.

-  monitoring_provider = app.monitoring_provider
-  configs_ready?(monitoring_provider.ios_config, monitoring_provider.android_config)
+  monitoring_provider = app.monitoring_provider
+  configs_ready?(monitoring_provider&.ios_config, monitoring_provider&.android_config)

70-74: Deduplicate configs_ready? with Integration-level helper

This duplicates Integration.configs_ready? with a different signature, inviting drift. Centralize and delegate to the canonical helper.

Example:

def configs_ready?(ios, android)
  app.integrations.configs_ready?(app, android, ios)
end

31-40: Move remaining validations to integration models and remove from AppConfig

You already left a TODO. Recommend migrating these to JiraIntegration/LinearIntegration and deleting the AppConfig counterparts to avoid split validation logic.

app/controllers/project_management/jira_configs_controller.rb (4)

61-65: Heuristic for “done” statuses may be too strict

Matching name == "Done" likely misses custom “Done” categories. Prefer statusCategory key (e.g., status['statusCategory']['key'] == 'done') if available.


68-69: Avoid persisting defaults during GET edit

Saving in set_jira_projects causes side effects on GET. Prefer computing defaults in-memory and persisting only on update.

Possible approaches:

  • Move the default-initialization into update if config blank.
  • Or wrap save! in a non-GET guard.

2-2: Unused refinement import

RefinedString isn’t used here. Consider removing to reduce surface area.


90-91: Compact blanks in selected_projects

Prevents empty strings sneaking in.

-      selected_projects: Array(config_params[:selected_projects]),
+      selected_projects: Array(config_params[:selected_projects]).compact_blank,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91c8a1b and 43f8262.

📒 Files selected for processing (70)
  • app/components/integration_card_component.html.erb (2 hunks)
  • app/components/integration_card_component.rb (3 hunks)
  • app/components/integration_list_component.html.erb (1 hunks)
  • app/components/integration_list_component.rb (0 hunks)
  • app/components/live_release/finalize_component.rb (1 hunks)
  • app/controllers/apps_controller.rb (1 hunks)
  • app/controllers/build_channel/google_firebase_configs_controller.rb (1 hunks)
  • app/controllers/ci_cd/bitbucket_configs_controller.rb (1 hunks)
  • app/controllers/ci_cd/bitrise_configs_controller.rb (1 hunks)
  • app/controllers/ci_cd/github_configs_controller.rb (1 hunks)
  • app/controllers/ci_cd/gitlab_configs_controller.rb (1 hunks)
  • app/controllers/monitoring/bugsnag_configs_controller.rb (1 hunks)
  • app/controllers/project_management/jira_configs_controller.rb (1 hunks)
  • app/controllers/project_management/linear_configs_controller.rb (1 hunks)
  • app/controllers/version_control/bitbucket_configs_controller.rb (1 hunks)
  • app/controllers/version_control/github_configs_controller.rb (1 hunks)
  • app/controllers/version_control/gitlab_configs_controller.rb (1 hunks)
  • app/libs/coordinators/webhooks/pull_request.rb (1 hunks)
  • app/libs/coordinators/webhooks/push.rb (1 hunks)
  • app/models/app.rb (2 hunks)
  • app/models/app_config.rb (3 hunks)
  • app/models/bitbucket_integration.rb (3 hunks)
  • app/models/bitrise_integration.rb (2 hunks)
  • app/models/bugsnag_integration.rb (4 hunks)
  • app/models/concerns/app_configurable.rb (1 hunks)
  • app/models/github_integration.rb (4 hunks)
  • app/models/gitlab_integration.rb (5 hunks)
  • app/models/google_firebase_integration.rb (3 hunks)
  • app/models/integration.rb (3 hunks)
  • app/models/jira_integration.rb (4 hunks)
  • app/models/linear_integration.rb (4 hunks)
  • app/views/build_channel/_firebase_integration_form.html.erb (1 hunks)
  • app/views/build_channel/google_firebase_configs/edit.html+turbo_frame.erb (1 hunks)
  • app/views/ci_cd/bitbucket_configs/_form.html.erb (1 hunks)
  • app/views/ci_cd/bitbucket_configs/edit.html+turbo_frame.erb (1 hunks)
  • app/views/ci_cd/bitbucket_configs/edit.turbo_stream.erb (1 hunks)
  • app/views/ci_cd/bitrise_configs/edit.html+turbo_frame.erb (1 hunks)
  • app/views/ci_cd/github_configs/edit.html+turbo_frame.erb (1 hunks)
  • app/views/ci_cd/gitlab_configs/edit.html+turbo_frame.erb (1 hunks)
  • app/views/jira_integration/_project_selection.html.erb (2 hunks)
  • app/views/jira_integration/_release_filter_form.html.erb (1 hunks)
  • app/views/linear_integration/_release_filter_form.html.erb (1 hunks)
  • app/views/linear_integration/_team_selection.html.erb (2 hunks)
  • app/views/monitoring/_bugsnag_project_form.html.erb (1 hunks)
  • app/views/monitoring/bugsnag_configs/edit.html+turbo_frame.erb (1 hunks)
  • app/views/project_management/jira_configs/edit.html+turbo_frame.erb (1 hunks)
  • app/views/project_management/linear_configs/edit.html+turbo_frame.erb (1 hunks)
  • app/views/version_control/bitbucket_configs/_form.html.erb (1 hunks)
  • app/views/version_control/bitbucket_configs/edit.html+turbo_frame.erb (1 hunks)
  • app/views/version_control/bitbucket_configs/edit.turbo_stream.erb (1 hunks)
  • app/views/version_control/github_configs/edit.html+turbo_frame.erb (1 hunks)
  • app/views/version_control/gitlab_configs/edit.html+turbo_frame.erb (1 hunks)
  • config/locales/en.yml (1 hunks)
  • config/routes.rb (1 hunks)
  • db/migrate/20250908020424_add_repository_config_to_vcs_integrations.rb (1 hunks)
  • db/migrate/20250909193500_add_project_config_to_bitrise_integration.rb (1 hunks)
  • db/migrate/20250918025642_add_config_to_google_firebase_integration.rb (1 hunks)
  • db/migrate/20250919124803_add_config_to_bugsnag_integration.rb (1 hunks)
  • db/migrate/20250924035033_add_config_to_project_management_integrations.rb (1 hunks)
  • db/schema.rb (6 hunks)
  • spec/factories/apps.rb (1 hunks)
  • spec/factories/bitbucket_integrations.rb (1 hunks)
  • spec/factories/github_integrations.rb (1 hunks)
  • spec/factories/gitlab_integrations.rb (1 hunks)
  • spec/factories/integrations.rb (1 hunks)
  • spec/factories/jira_integrations.rb (1 hunks)
  • spec/libs/triggers/pull_request_spec.rb (5 hunks)
  • spec/models/gitlab_integration_spec.rb (7 hunks)
  • spec/models/jira_integration_spec.rb (3 hunks)
  • spec/models/linear_integration_spec.rb (1 hunks)
💤 Files with no reviewable changes (1)
  • app/components/integration_list_component.rb
🧰 Additional context used
🧬 Code graph analysis (31)
spec/models/jira_integration_spec.rb (2)
app/libs/installations/jira/api.rb (1)
  • include (2-150)
app/models/jira_integration.rb (1)
  • fetch_tickets_for_release (160-176)
app/libs/coordinators/webhooks/push.rb (6)
app/models/integration.rb (1)
  • vcs_provider (177-179)
app/models/bitbucket_integration.rb (1)
  • code_repository_name (33-35)
app/models/github_integration.rb (1)
  • code_repository_name (113-115)
app/models/gitlab_integration.rb (1)
  • code_repository_name (159-161)
app/libs/coordinators/webhooks/bitbucket/pull_request.rb (1)
  • repository_name (25-27)
app/libs/coordinators/webhooks/bitbucket/push.rb (2)
  • repository_name (35-37)
  • branch_name (31-33)
app/models/app.rb (1)
app/models/integration.rb (3)
  • ready? (160-162)
  • configured? (164-171)
  • further_setup_by_category (227-267)
spec/models/gitlab_integration_spec.rb (1)
app/models/gitlab_integration.rb (2)
  • installation (334-336)
  • code_repository_name (159-161)
app/controllers/monitoring/bugsnag_configs_controller.rb (4)
app/controllers/application_controller.rb (1)
  • current_organization (30-42)
app/models/integration.rb (1)
  • monitoring_provider (189-191)
app/models/bugsnag_integration.rb (1)
  • setup (83-85)
app/refinements/refined_string.rb (1)
  • safe_json_parse (42-46)
app/models/github_integration.rb (2)
app/models/bitbucket_integration.rb (5)
  • code_repository_name (33-35)
  • code_repo_url (37-39)
  • code_repo_namespace (41-41)
  • code_repo_name_only (43-43)
  • repos (165-167)
app/models/gitlab_integration.rb (5)
  • code_repository_name (159-161)
  • code_repo_url (163-165)
  • code_repo_namespace (167-167)
  • code_repo_name_only (169-169)
  • repos (198-200)
app/components/integration_card_component.rb (2)
app/components/integration_list_component.rb (2)
  • initialize (1-15)
  • initialize (2-6)
app/models/application_record.rb (1)
  • human_enum_name (21-24)
app/controllers/ci_cd/github_configs_controller.rb (5)
app/controllers/ci_cd/bitbucket_configs_controller.rb (5)
  • using (1-55)
  • edit (9-18)
  • set_code_repositories (42-46)
  • update (20-27)
  • set_app (31-33)
app/controllers/ci_cd/gitlab_configs_controller.rb (5)
  • using (1-52)
  • edit (9-17)
  • set_code_repositories (41-43)
  • update (19-26)
  • set_app (30-32)
app/models/integration.rb (1)
  • ci_cd_provider (181-183)
app/models/github_integration.rb (1)
  • repos (138-140)
app/refinements/refined_string.rb (1)
  • safe_json_parse (42-46)
spec/factories/apps.rb (1)
app/models/app_variant.rb (1)
  • config (34-34)
app/components/live_release/finalize_component.rb (5)
app/models/bitbucket_integration.rb (2)
  • tag_url (214-216)
  • code_repo_url (37-39)
app/models/github_integration.rb (2)
  • tag_url (189-191)
  • code_repo_url (117-119)
app/models/gitlab_integration.rb (2)
  • tag_url (322-324)
  • code_repo_url (163-165)
app/models/release.rb (1)
  • tag_url (433-435)
app/models/integration.rb (1)
  • vcs_provider (177-179)
app/controllers/build_channel/google_firebase_configs_controller.rb (4)
app/controllers/application_controller.rb (1)
  • current_organization (30-42)
app/models/integration.rb (1)
  • firebase_build_channel_provider (209-211)
app/models/google_firebase_integration.rb (1)
  • setup (106-117)
app/refinements/refined_string.rb (1)
  • safe_json_parse (42-46)
app/models/linear_integration.rb (1)
app/models/jira_integration.rb (1)
  • release_filters_are_valid (268-274)
app/models/jira_integration.rb (1)
app/models/linear_integration.rb (1)
  • release_filters_are_valid (246-252)
app/controllers/ci_cd/bitrise_configs_controller.rb (4)
app/controllers/application_controller.rb (1)
  • current_organization (30-42)
app/models/integration.rb (1)
  • ci_cd_provider (181-183)
app/models/bitrise_integration.rb (1)
  • setup (93-95)
app/refinements/refined_string.rb (1)
  • safe_json_parse (42-46)
app/controllers/version_control/bitbucket_configs_controller.rb (4)
app/controllers/version_control/github_configs_controller.rb (5)
  • using (1-52)
  • edit (9-17)
  • set_code_repositories (41-43)
  • update (19-26)
  • set_app (30-32)
app/models/integration.rb (1)
  • vcs_provider (177-179)
app/models/bitbucket_integration.rb (2)
  • workspaces (96-98)
  • repos (165-167)
app/refinements/refined_string.rb (1)
  • safe_json_parse (42-46)
app/controllers/version_control/github_configs_controller.rb (5)
app/controllers/version_control/bitbucket_configs_controller.rb (5)
  • using (1-55)
  • edit (9-18)
  • set_code_repositories (42-46)
  • update (20-27)
  • set_app (31-33)
app/controllers/version_control/gitlab_configs_controller.rb (5)
  • using (1-52)
  • edit (9-17)
  • set_code_repositories (41-43)
  • update (19-26)
  • set_app (30-32)
app/models/integration.rb (1)
  • vcs_provider (177-179)
app/models/github_integration.rb (1)
  • repos (138-140)
app/refinements/refined_string.rb (1)
  • safe_json_parse (42-46)
app/models/google_firebase_integration.rb (1)
app/models/concerns/app_configurable.rb (1)
  • firebase_app (6-13)
app/controllers/project_management/linear_configs_controller.rb (4)
app/controllers/project_management/jira_configs_controller.rb (4)
  • using (1-109)
  • edit (9-17)
  • update (19-26)
  • set_app (30-32)
app/models/integration.rb (1)
  • project_management_provider (213-215)
app/models/linear_integration.rb (1)
  • setup (116-132)
app/libs/installations/linear/api.rb (1)
  • workflow_states (95-103)
spec/libs/triggers/pull_request_spec.rb (5)
app/models/integration.rb (1)
  • vcs_provider (177-179)
app/models/bitbucket_integration.rb (3)
  • code_repository_name (33-35)
  • code_repo_namespace (41-41)
  • code_repo_name_only (43-43)
app/models/github_integration.rb (3)
  • code_repository_name (113-115)
  • code_repo_namespace (121-123)
  • code_repo_name_only (125-127)
app/models/gitlab_integration.rb (3)
  • code_repository_name (159-161)
  • code_repo_namespace (167-167)
  • code_repo_name_only (169-169)
app/libs/triggers/pull_request.rb (1)
  • repo_integration (129-131)
app/controllers/ci_cd/bitbucket_configs_controller.rb (5)
app/controllers/ci_cd/github_configs_controller.rb (5)
  • using (1-52)
  • edit (9-17)
  • set_code_repositories (41-43)
  • update (19-26)
  • set_app (30-32)
app/controllers/ci_cd/gitlab_configs_controller.rb (5)
  • using (1-52)
  • edit (9-17)
  • set_code_repositories (41-43)
  • update (19-26)
  • set_app (30-32)
app/models/integration.rb (1)
  • ci_cd_provider (181-183)
app/models/bitbucket_integration.rb (2)
  • workspaces (96-98)
  • repos (165-167)
app/refinements/refined_string.rb (1)
  • safe_json_parse (42-46)
app/controllers/project_management/jira_configs_controller.rb (3)
app/controllers/project_management/linear_configs_controller.rb (4)
  • using (1-100)
  • edit (9-17)
  • update (19-26)
  • set_app (30-32)
app/models/integration.rb (1)
  • project_management_provider (213-215)
app/models/jira_integration.rb (1)
  • setup (129-146)
app/controllers/ci_cd/gitlab_configs_controller.rb (7)
app/controllers/ci_cd/bitbucket_configs_controller.rb (5)
  • using (1-55)
  • edit (9-18)
  • set_code_repositories (42-46)
  • update (20-27)
  • set_app (31-33)
app/controllers/ci_cd/github_configs_controller.rb (5)
  • using (1-52)
  • edit (9-17)
  • set_code_repositories (41-43)
  • update (19-26)
  • set_app (30-32)
app/models/integration.rb (1)
  • ci_cd_provider (181-183)
app/models/bitbucket_integration.rb (1)
  • repos (165-167)
app/models/github_integration.rb (1)
  • repos (138-140)
app/models/gitlab_integration.rb (1)
  • repos (198-200)
app/refinements/refined_string.rb (1)
  • safe_json_parse (42-46)
app/models/integration.rb (4)
app/models/app_config.rb (5)
  • firebase_ready? (53-57)
  • bugsnag_ready? (64-68)
  • project_management_ready? (76-97)
  • bitrise_ready? (59-62)
  • configs_ready? (70-74)
app/models/app.rb (2)
  • bitrise_connected? (109-111)
  • bugsnag_connected? (113-115)
app/models/bitrise_integration.rb (1)
  • bitrise_project (64-66)
app/models/concerns/integrable.rb (1)
  • firebase_connected? (14-16)
app/models/gitlab_integration.rb (3)
app/models/bitbucket_integration.rb (4)
  • code_repository_name (33-35)
  • code_repo_url (37-39)
  • with_api_retries (369-385)
  • installation (60-62)
app/libs/installations/github/api.rb (1)
  • get_commit (287-289)
app/libs/installations/gitlab/api.rb (1)
  • get_commit (111-115)
app/libs/coordinators/webhooks/pull_request.rb (6)
app/models/integration.rb (1)
  • vcs_provider (177-179)
app/models/bitbucket_integration.rb (1)
  • code_repository_name (33-35)
app/models/github_integration.rb (1)
  • code_repository_name (113-115)
app/models/gitlab_integration.rb (1)
  • code_repository_name (159-161)
app/libs/coordinators/webhooks/bitbucket/pull_request.rb (1)
  • repository_name (25-27)
app/libs/coordinators/webhooks/bitbucket/push.rb (1)
  • repository_name (35-37)
app/models/bitbucket_integration.rb (2)
app/models/github_integration.rb (4)
  • code_repository_name (113-115)
  • code_repo_url (117-119)
  • code_repo_namespace (121-123)
  • code_repo_name_only (125-127)
app/models/gitlab_integration.rb (4)
  • code_repository_name (159-161)
  • code_repo_url (163-165)
  • code_repo_namespace (167-167)
  • code_repo_name_only (169-169)
app/controllers/apps_controller.rb (2)
app/models/app.rb (3)
  • ready? (129-131)
  • train_in_creation (137-139)
  • app_setup_instructions (171-203)
app/models/integration.rb (1)
  • ready? (160-162)
app/controllers/version_control/gitlab_configs_controller.rb (6)
app/controllers/version_control/bitbucket_configs_controller.rb (5)
  • using (1-55)
  • edit (9-18)
  • set_code_repositories (42-46)
  • update (20-27)
  • set_app (31-33)
app/controllers/version_control/github_configs_controller.rb (5)
  • using (1-52)
  • edit (9-17)
  • set_code_repositories (41-43)
  • update (19-26)
  • set_app (30-32)
app/controllers/application_controller.rb (1)
  • current_organization (30-42)
app/models/integration.rb (1)
  • vcs_provider (177-179)
app/models/gitlab_integration.rb (1)
  • repos (198-200)
app/refinements/refined_string.rb (1)
  • safe_json_parse (42-46)
app/models/app_config.rb (3)
app/models/integration.rb (7)
  • monitoring_provider (189-191)
  • firebase_ready? (307-313)
  • firebase_build_channel_provider (209-211)
  • configs_ready? (346-350)
  • bitrise_ready? (296-301)
  • ci_cd_provider (181-183)
  • bugsnag_ready? (315-321)
app/models/concerns/integrable.rb (1)
  • firebase_connected? (14-16)
app/models/app.rb (2)
  • bitrise_connected? (109-111)
  • bugsnag_connected? (113-115)
app/models/bitrise_integration.rb (1)
app/models/integration.rb (1)
  • bitrise_project (303-305)
app/models/bugsnag_integration.rb (1)
app/models/app_config.rb (1)
  • set_bugsnag_config (45-51)
🪛 ast-grep (0.39.5)
spec/models/gitlab_integration_spec.rb

[warning] 16-16: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: expect(installation).to have_received(:create_release!).with(gitlab_integration.code_repository_name, "v1.0.0", "main", "Release notes")
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 16-16: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: have_received(:create_release!).with(gitlab_integration.code_repository_name, "v1.0.0", "main", "Release notes")
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 64-64: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: expect(installation).to have_received(:update_file!).with(gitlab_integration.code_repository_name, "main", "path/to/file.txt", "new content", "commit message", author_name: "Test User", author_email: "test@example.com")
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 64-64: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: have_received(:update_file!).with(gitlab_integration.code_repository_name, "main", "path/to/file.txt", "new content", "commit message", author_name: "Test User", author_email: "test@example.com")
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 91-91: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: expect(installation).to have_received(:run_pipeline_with_job!).with(gitlab_integration.code_repository_name, "main", {key: "value"}, "ci_cd_channel", "abc123", GitlabIntegration::WORKFLOW_RUN_TRANSFORMATIONS)
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 91-91: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: have_received(:run_pipeline_with_job!).with(gitlab_integration.code_repository_name, "main", {key: "value"}, "ci_cd_channel", "abc123", GitlabIntegration::WORKFLOW_RUN_TRANSFORMATIONS)
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 115-115: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: expect(installation).to have_received(:retry_job!).with(gitlab_integration.code_repository_name, "123", GitlabIntegration::JOB_RUN_TRANSFORMATIONS)
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 115-115: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: have_received(:retry_job!).with(gitlab_integration.code_repository_name, "123", GitlabIntegration::JOB_RUN_TRANSFORMATIONS)
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 123-123: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: expect(installation).to have_received(:get_job).with(gitlab_integration.code_repository_name, "123")
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 123-123: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: have_received(:get_job).with(gitlab_integration.code_repository_name, "123")
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 131-131: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: expect(installation).to have_received(:create_tag!).with(gitlab_integration.code_repository_name, "v1.0.0", "abcdef")
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 131-131: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: have_received(:create_tag!).with(gitlab_integration.code_repository_name, "v1.0.0", "abcdef")
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 161-161: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: expect(installation).to have_received(:cherry_pick_pr).with(gitlab_integration.code_repository_name, "main", "abcdef", "patch-branch", "PR Title", "Custom description", GitlabIntegration::PR_TRANSFORMATIONS)
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 161-161: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: have_received(:cherry_pick_pr).with(gitlab_integration.code_repository_name, "main", "abcdef", "patch-branch", "PR Title", "Custom description", GitlabIntegration::PR_TRANSFORMATIONS)
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)

🪛 GitHub Actions: Lint
db/schema.rb

[warning] 1-1: Schema linting warnings: db/schema.rb changed in commits without an update in lib/tasks/anonymize.rake afterward.


[warning] 1-1: Schema linting warnings: db/schema.rb changed in commits without an update in lib/tasks/anonymize.rake afterward.


[warning] 1-1: Schema linting warnings: db/schema.rb changed in commits without an update in lib/tasks/anonymize.rake afterward.


[warning] 1-1: Schema linting warnings: db/schema.rb changed in commits without an update in lib/tasks/anonymize.rake afterward.


[warning] 1-1: Schema linting warnings: db/schema.rb changed in commits without an update in lib/tasks/anonymize.rake afterward.

Comment on lines +13 to 18
def initialize(app, integration, category, pre_open_category = nil)
@app = app
@integration = integration
@category = category
@pre_open_category = pre_open_category
end
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Use a keyword for pre_open_category to avoid positional arg mistakes.

Switch to a keyword to align with IntegrationListComponent and improve readability:

-def initialize(app, integration, category, pre_open_category = nil)
+def initialize(app, integration, category, pre_open_category: nil)
   @app = app
   @integration = integration
   @category = category
-  @pre_open_category = pre_open_category
+  @pre_open_category = pre_open_category
 end

Update callers accordingly.

Run to find/adjust all call sites:


🏁 Script executed:

#!/bin/bash
rg -nP --type=ruby 'IntegrationCardComponent\.new\(' -C2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search all files for direct instantiation
rg -nP -C2 'IntegrationCardComponent\.new\('
# Search for render calls in views
rg -nP -C2 'render\s+IntegrationCardComponent'

Length of output: 993


Convert initializer to keyword argument and update its callers

-def initialize(app, integration, category, pre_open_category = nil)
+def initialize(app, integration, category, pre_open_category: nil)

In app/components/integration_list_component.html.erb (line 8):

-<%= render IntegrationCardComponent.new(@app, integration, category, @pre_open_category) %>
+<%= render IntegrationCardComponent.new(@app, integration, category, pre_open_category: @pre_open_category) %>
🤖 Prompt for AI Agents
In app/components/integration_card_component.rb around lines 13 to 18, the
initializer currently uses positional arguments; change it to keyword arguments
(e.g., app:, integration:, category:, pre_open_category: nil) and update every
caller to pass keyword names (notably
app/components/integration_list_component.html.erb at line 8) so parameters map
correctly; run the test suite and search/replace other instantiations to ensure
no positional calls remain and update any docs or specs accordingly.

Comment on lines +46 to +53
def parsed_google_firebase_config_params
google_firebase_config_params = params.require(:google_firebase_integration)
.permit(:android_config, :ios_config)
google_firebase_config_params.merge(
android_config: google_firebase_config_params[:android_config]&.safe_json_parse,
ios_config: google_firebase_config_params[:ios_config]&.safe_json_parse
)
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Prevent unintended config wipe when params are omitted

Merging unconditionally injects nil and can clear jsonb columns if a field isn’t submitted. Parse only when the key is present.

Apply this diff:

 def parsed_google_firebase_config_params
-    google_firebase_config_params = params.require(:google_firebase_integration)
-      .permit(:android_config, :ios_config)
-    google_firebase_config_params.merge(
-      android_config: google_firebase_config_params[:android_config]&.safe_json_parse,
-      ios_config: google_firebase_config_params[:ios_config]&.safe_json_parse
-    )
+    p = params.require(:google_firebase_integration)
+      .permit(:android_config, :ios_config)
+    p[:android_config] = p[:android_config]&.safe_json_parse if p.key?(:android_config)
+    p[:ios_config]     = p[:ios_config]&.safe_json_parse     if p.key?(:ios_config)
+    p
 end
🤖 Prompt for AI Agents
In app/controllers/build_channel/google_firebase_configs_controller.rb around
lines 46 to 53, the current merge unconditionally sets android_config and
ios_config to nil when keys are omitted, which can wipe jsonb columns; change
the method to only parse and include a key when it is present in params (e.g.
build a result hash from google_firebase_config_params and conditionally assign
result[:android_config] = parsed value only if
google_firebase_config_params.key?(:android_config), similarly for :ios_config),
then return that result so absent keys are not merged and stored as nil.

Comment on lines +48 to +54
def parsed_bitbucket_config_params
bitbucket_config_params = params.require(:bitbucket_integration)
.permit(:repository_config, :workspace)
bitbucket_config_params.merge(
repository_config: bitbucket_config_params[:repository_config]&.safe_json_parse
)
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Guard against clearing repository_config on partial updates

Avoid merging a nil repository_config when the field isn’t submitted.

Apply this diff:

 def parsed_bitbucket_config_params
-    bitbucket_config_params = params.require(:bitbucket_integration)
-      .permit(:repository_config, :workspace)
-    bitbucket_config_params.merge(
-      repository_config: bitbucket_config_params[:repository_config]&.safe_json_parse
-    )
+    p = params.require(:bitbucket_integration).permit(:repository_config, :workspace)
+    if p.key?(:repository_config)
+      p[:repository_config] = p[:repository_config]&.safe_json_parse
+    end
+    p
 end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def parsed_bitbucket_config_params
bitbucket_config_params = params.require(:bitbucket_integration)
.permit(:repository_config, :workspace)
bitbucket_config_params.merge(
repository_config: bitbucket_config_params[:repository_config]&.safe_json_parse
)
end
def parsed_bitbucket_config_params
p = params.require(:bitbucket_integration).permit(:repository_config, :workspace)
if p.key?(:repository_config)
p[:repository_config] = p[:repository_config]&.safe_json_parse
end
p
end
🤖 Prompt for AI Agents
In app/controllers/ci_cd/bitbucket_configs_controller.rb around lines 48 to 54,
the current merge unconditionally sets repository_config to nil when the param
is omitted; change it to only merge repository_config when the incoming params
include that key and it is not nil/blank. Specifically, check
params[:bitbucket_integration].key?(:repository_config) (or use presence) before
calling safe_json_parse and merging, leaving repository_config untouched for
partial updates when the key is absent.

Comment on lines +45 to +51
def parsed_github_config_params
github_config_params = params.require(:github_integration)
.permit(:repository_config)
github_config_params.merge(
repository_config: github_config_params[:repository_config]&.safe_json_parse
)
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Do not clear repository_config when the param is absent

Unconditional merge sets repository_config to nil if the field isn’t submitted. Guard by key presence.

Apply this diff:

 def parsed_github_config_params
-    github_config_params = params.require(:github_integration)
-      .permit(:repository_config)
-    github_config_params.merge(
-      repository_config: github_config_params[:repository_config]&.safe_json_parse
-    )
+    p = params.require(:github_integration).permit(:repository_config)
+    if p.key?(:repository_config)
+      p[:repository_config] = p[:repository_config]&.safe_json_parse
+    end
+    p
 end
🤖 Prompt for AI Agents
In app/controllers/ci_cd/github_configs_controller.rb around lines 45 to 51, the
current parsed_github_config_params unconditionally merges repository_config
which sets it to nil when the param is absent; change the logic to only merge
repository_config when params[:github_integration] includes the
repository_config key (or github_config_params.key?(:repository_config)), and
otherwise return github_config_params unchanged so existing repository_config is
not cleared.

Comment on lines 129 to 131
def ready?
integrations.ready? and config&.ready?
integrations.ready? && integrations.configured?
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

❓ Verification inconclusive

Verify the readiness logic change is tested and intentional.

The change from a single config.ready? check to integrations.ready? && integrations.configured? introduces a stricter requirement: both the base integration categories (MINIMUM_REQUIRED_SET) and their configurations must be complete. This represents a potential behavior change from the previous implementation.

Verify that test coverage exists for this updated readiness logic:


🏁 Script executed:

#!/bin/bash
# Search for tests covering app.ready? with different integration states
rg -n -B5 -A10 'app\.ready\?' spec/models/app_spec.rb spec/requests/ spec/system/ --type=rb

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Search for tests covering App#ready? with different integration states
rg -n -B5 -A10 'ready\?' -g '*.rb' spec/

Length of output: 0


Add tests for App#ready? covering new readiness logic

No specs currently exercise App#ready?. Introduce tests in spec/models/app_spec.rb for:

  • integrations.ready? false → ready? false
  • integrations.ready? true & integrations.configured? false → false
  • both true → true
🤖 Prompt for AI Agents
In app/models/app.rb around lines 129–131, there are no specs covering the new
App#ready? logic; add tests in spec/models/app_spec.rb that create an App (or
build a minimal instance), stub or mock the app.integrations to return the three
combinations described, and assert ready? returns false when integrations.ready?
is false, false when integrations.ready? is true but integrations.configured? is
false, and true when both are true; ensure each example isolates the
integrations double and sets expectations on the boolean return values.

Comment on lines +116 to +124
def project(platform)
case platform
when "android" then android_config&.dig("project_id")
when "ios" then ios_config&.dig("project_id")
else
raise ArgumentError, "Invalid platform: #{platform}"
end
end

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Fix platform project getters: current shape mismatch will raise at runtime

project(platform) returns a String ID, but project_url/project_id expect a Hash and call fetch(), causing NoMethodError and nil IDs.

Proposed fix: read directly from android_config/ios_config and avoid fetch() on non-Hash.

- def project(platform)
-   case platform
-   when "android" then android_config&.dig("project_id")
-   when "ios" then ios_config&.dig("project_id")
-   else
-     raise ArgumentError, "Invalid platform: #{platform}"
-   end
- end
+ def project(platform)
+   # Kept for compatibility but returns the project_id String
+   project_id(platform)
+ end

- def project_url(platform)
-   project(platform)&.fetch("url", nil)
- end
+ def project_url(platform)
+   case platform
+   when "android" then android_config&.dig("project_url")
+   when "ios"    then ios_config&.dig("project_url")
+   else
+     raise ArgumentError, "Invalid platform: #{platform}"
+   end
+ end

- def project_id(platform)
-   project(platform)&.fetch("id", nil)
- end
+ def project_id(platform)
+   case platform
+   when "android" then android_config&.dig("project_id")
+   when "ios"    then ios_config&.dig("project_id")
+   else
+     raise ArgumentError, "Invalid platform: #{platform}"
+   end
+ end

Optional: normalize platform with platform = platform.to_s to accept symbols.

Also applies to: 143-149

🤖 Prompt for AI Agents
In app/models/bugsnag_integration.rb around lines 116–124, project(platform)
currently returns a String ID while downstream methods expect a Hash and call
fetch(), causing NoMethodError; change project(platform) to return the config
Hash (e.g., android_config or ios_config) instead of digging the project_id so
callers can safely call fetch("project_id"), and normalize platform with
platform = platform.to_s to accept symbols; apply the same change to the similar
logic at lines 143–149 so both code paths return the config Hash rather than a
raw String ID.

Comment on lines 361 to 363
def get_commit(sha)
with_api_retries { installation.get_commit(app_config.code_repository["id"], sha, COMMITS_TRANSFORMATIONS) }
with_api_retries { installation.get_commit(repository_config["id"], sha, COMMITS_TRANSFORMATIONS) }
end
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Guard against missing repository_config in get_commit

Calling ["id"] on nil will raise; add a guard for clearer behavior.

- def get_commit(sha)
-   with_api_retries { installation.get_commit(repository_config["id"], sha, COMMITS_TRANSFORMATIONS) }
- end
+ def get_commit(sha)
+   repo_id = repository_config&.fetch("id", nil)
+   return nil if repo_id.blank?
+   with_api_retries { installation.get_commit(repo_id, sha, COMMITS_TRANSFORMATIONS) }
+ end

Alternatively, raise a domain error if you prefer explicit failure.


🏁 Script executed:

#!/bin/bash
set -e
# Locate the repository_config accessor or method definition
rg -n "def repository_config" -C2 --glob "app/models/gitlab_integration.rb"

# Find where repository_config is assigned or fetched
rg -n "repository_config" -C3 --glob "app/models/gitlab_integration.rb"

# Inspect get_commit context
sed -n '350,380p' app/models/gitlab_integration.rb

Length of output: 0


🏁 Script executed:

#!/bin/bash
set -e
# Find the path of the file
find . -type f -name "gitlab_integration.rb"

# Inspect the file around the get_commit definition if found
sed -n '350,380p' app/models/gitlab_integration.rb 2>/dev/null || true

# Search globally for get_commit definitions
rg -n "def get_commit" -C2

# Search globally for repository_config usages
rg -n "repository_config" -C2

Length of output: 19492


Guard against missing repository_config["id"] in get_commit

Replace direct indexing with a safe fetch and early return:

 def get_commit(sha)
-  with_api_retries { installation.get_commit(repository_config["id"], sha, COMMITS_TRANSFORMATIONS) }
+  repo_id = repository_config&.fetch("id", nil)
+  return nil if repo_id.blank?
+  with_api_retries { installation.get_commit(repo_id, sha, COMMITS_TRANSFORMATIONS) }
 end
🤖 Prompt for AI Agents
In app/models/gitlab_integration.rb around lines 361 to 363, get_commit
currently directly indexes repository_config["id"] which can be nil and raise;
change to safely fetch the repository id (e.g., repo_id =
repository_config&.fetch("id", nil) or repository_config["id"] rescue nil) and
return nil or appropriate early value if repo_id is blank before calling
with_api_retries; this prevents NoMethodError/KeyError and ensures get_commit
exits early when the repository id is missing.

Comment on lines +59 to +68
INVALID_PLATFORM_ERROR = "platform must be valid"

def firebase_app(platform)
case platform
when "android" then android_config["app_id"]
when "ios" then ios_config["app_id"]
else
raise ArgumentError, INVALID_PLATFORM_ERROR
end
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Guard against nil configs in firebase_app (avoid NoMethodError)

android_config/ios_config can be nil (allow_blank: true). Indexing ["app_id"] will raise. Normalize platform, nil-safe access, and raise a clear error when app_id missing.

-  def firebase_app(platform)
-    case platform
-    when "android" then android_config["app_id"]
-    when "ios" then ios_config["app_id"]
-    else
-      raise ArgumentError, INVALID_PLATFORM_ERROR
-    end
-  end
+  def firebase_app(platform)
+    platform = platform.to_s
+    app_id =
+      case platform
+      when "android" then android_config&.fetch("app_id", nil)
+      when "ios"     then ios_config&.fetch("app_id", nil)
+      else
+        raise ArgumentError, INVALID_PLATFORM_ERROR
+      end
+    raise ArgumentError, "app_id missing for #{platform}" if app_id.blank?
+    app_id
+  end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
INVALID_PLATFORM_ERROR = "platform must be valid"
def firebase_app(platform)
case platform
when "android" then android_config["app_id"]
when "ios" then ios_config["app_id"]
else
raise ArgumentError, INVALID_PLATFORM_ERROR
end
end
INVALID_PLATFORM_ERROR = "platform must be valid"
def firebase_app(platform)
platform = platform.to_s
app_id =
case platform
when "android" then android_config&.fetch("app_id", nil)
when "ios" then ios_config&.fetch("app_id", nil)
else
raise ArgumentError, INVALID_PLATFORM_ERROR
end
raise ArgumentError, "app_id missing for #{platform}" if app_id.blank?
app_id
end
🤖 Prompt for AI Agents
In app/models/google_firebase_integration.rb around lines 59 to 68, firebase_app
currently indexes android_config/ios_config directly which can be nil; normalize
the incoming platform (e.g., to_s.downcase), select the correct config with safe
navigation (or fetch via dig) to avoid NoMethodError, retrieve the "app_id" in a
nil-safe way, and raise ArgumentError with a clear message (e.g., missing app_id
for platform) if the config or app_id is absent.

Comment on lines +28 to +31
<% f.with_action do %>
<%= f.F.authz_submit "Update", "plus.svg", size: :xs %>
<% end %>
<% end %>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hide submit when not connected.

Submit renders even if Bugsnag isn’t connected (no fields shown). Gate it behind the same connectivity check.

-      <% f.with_action do %>
-        <%= f.F.authz_submit "Update", "plus.svg", size: :xs %>
-      <% end %>
+      <% if @app.bugsnag_connected? %>
+        <% f.with_action do %>
+          <%= f.F.authz_submit "Update", "plus.svg", size: :xs %>
+        <% end %>
+      <% end %>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<% f.with_action do %>
<%= f.F.authz_submit "Update", "plus.svg", size: :xs %>
<% end %>
<% end %>
<% if @app.bugsnag_connected? %>
<% f.with_action do %>
<%= f.F.authz_submit "Update", "plus.svg", size: :xs %>
<% end %>
<% end %>
<% end %>
🤖 Prompt for AI Agents
In app/views/monitoring/bugsnag_configs/edit.html+turbo_frame.erb around lines
28 to 31, the "Update" submit button is rendered even when Bugsnag is not
connected; wrap the f.with_action block that renders f.F.authz_submit in the
same conditional used earlier to display the integration fields (i.e., only
render the submit when the connectivity check is true), so the submit button is
hidden when there is no Bugsnag connection.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

♻️ Duplicate comments (7)
app/components/integration_card_component.html.erb (1)

25-27: Add an accessible label to the icon-only config trigger.

This button renders only an icon, so assistive tech users still get no name. Please pass visible text or an aria-label via html_options. Example fix:

-                  <% button = modal.with_button(scheme: :light, type: :action, size: :xxs) %>
+                  <% button = modal.with_button(
+                        scheme: :light,
+                        type: :action,
+                        size: :xxs,
+                        html_options: { aria: { label: category_title } }
+                      ) %>
app/controllers/ci_cd/bitbucket_configs_controller.rb (1)

48-54: Guard against clearing repository_config on partial updates.

The previous review comment correctly identified that this merge will unconditionally set repository_config to nil when the param is not submitted, which can clear existing configuration on partial updates.

Apply this diff to only merge repository_config when explicitly provided:

 def parsed_bitbucket_config_params
-    bitbucket_config_params = params.require(:bitbucket_integration)
-      .permit(:repository_config, :workspace)
-    bitbucket_config_params.merge(
-      repository_config: bitbucket_config_params[:repository_config]&.safe_json_parse
-    )
+    p = params.require(:bitbucket_integration).permit(:repository_config, :workspace)
+    if p.key?(:repository_config)
+      p[:repository_config] = p[:repository_config]&.safe_json_parse
+    end
+    p
 end
app/controllers/version_control/gitlab_configs_controller.rb (1)

34-39: Type verification implemented.

The type check using .is_a?(GitlabIntegration) correctly addresses the concern raised in the previous review about verifying the VCS provider type. The nil case is also handled properly since nil.is_a?(GitlabIntegration) returns false.

app/controllers/version_control/bitbucket_configs_controller.rb (1)

42-46: This issue was previously flagged and remains unaddressed.

The past review identified that calling repos(nil) can error when no workspaces exist, and the code does not prefer the persisted workspace to maintain user context. The suggested fix from the previous review should be applied:

 def set_code_repositories
   @workspaces = @bitbucket_integration.workspaces || []
-  workspace = params[:workspace] || @workspaces.first
-  @code_repositories = @bitbucket_integration.repos(workspace)
+  workspace = params[:workspace] || @bitbucket_integration.workspace || @workspaces.first
+  @code_repositories = workspace.present? ? @bitbucket_integration.repos(workspace) : []
 end
app/controllers/build_channel/google_firebase_configs_controller.rb (1)

46-53: Prevent unintended config wipe when params are omitted.

This issue was previously flagged: merging unconditionally injects nil for keys not present in params, which clears JSONB columns if a field isn't submitted (e.g., editing only android_config would wipe ios_config). Parse only when the key is present.

Apply this diff:

 def parsed_google_firebase_config_params
-    google_firebase_config_params = params.require(:google_firebase_integration)
-      .permit(:android_config, :ios_config)
-    google_firebase_config_params.merge(
-      android_config: google_firebase_config_params[:android_config]&.safe_json_parse,
-      ios_config: google_firebase_config_params[:ios_config]&.safe_json_parse
-    )
+    p = params.require(:google_firebase_integration)
+      .permit(:android_config, :ios_config)
+    p[:android_config] = p[:android_config]&.safe_json_parse if p.key?(:android_config)
+    p[:ios_config]     = p[:ios_config]&.safe_json_parse     if p.key?(:ios_config)
+    p
 end
app/models/app_config.rb (1)

48-52: Nil-guard monitoring_provider to avoid NPE.

This issue was already flagged in a previous review. The monitoring_provider can be nil if the app has no monitoring integration, and calling methods on nil will raise NoMethodError.

app/controllers/monitoring/bugsnag_configs_controller.rb (1)

45-54: Avoid mass-assigning unknown attributes; return only ios_config/android_config.

This issue was previously flagged: merging scalar params (:ios_project_id, :ios_release_stage, etc.) into the update hash can raise UnknownAttributeError if the model doesn't have those attributes. The model expects only the nested :ios_config and :android_config keys.

Apply this diff to return only the nested config:

 def parsed_bugsnag_config_params
-    bugsnag_config_params = params.require(:bugsnag_integration)
-      .permit(
-        :ios_project_id,
-        :ios_release_stage,
-        :android_project_id,
-        :android_release_stage
-      )
-    bugsnag_config_params.merge(bugsnag_config(bugsnag_config_params))
+    p = params.require(:bugsnag_integration).permit(
+      :ios_project_id, :ios_release_stage, :android_project_id, :android_release_stage
+    )
+    bugsnag_config(p)
 end
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43f8262 and 7ebdd8a.

📒 Files selected for processing (14)
  • app/components/integration_card_component.html.erb (2 hunks)
  • app/controllers/build_channel/google_firebase_configs_controller.rb (1 hunks)
  • app/controllers/ci_cd/bitbucket_configs_controller.rb (1 hunks)
  • app/controllers/ci_cd/bitrise_configs_controller.rb (1 hunks)
  • app/controllers/ci_cd/github_configs_controller.rb (1 hunks)
  • app/controllers/ci_cd/gitlab_configs_controller.rb (1 hunks)
  • app/controllers/monitoring/bugsnag_configs_controller.rb (1 hunks)
  • app/controllers/project_management/jira_configs_controller.rb (1 hunks)
  • app/controllers/version_control/bitbucket_configs_controller.rb (1 hunks)
  • app/controllers/version_control/github_configs_controller.rb (1 hunks)
  • app/controllers/version_control/gitlab_configs_controller.rb (1 hunks)
  • app/models/app_config.rb (3 hunks)
  • app/models/concerns/app_configurable.rb (1 hunks)
  • app/models/jira_integration.rb (5 hunks)
✅ Files skipped from review due to trivial changes (1)
  • app/models/concerns/app_configurable.rb
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/controllers/version_control/github_configs_controller.rb
  • app/controllers/project_management/jira_configs_controller.rb
  • app/controllers/ci_cd/gitlab_configs_controller.rb
  • app/controllers/ci_cd/github_configs_controller.rb
🧰 Additional context used
🧬 Code graph analysis (8)
app/controllers/version_control/bitbucket_configs_controller.rb (5)
app/controllers/ci_cd/bitbucket_configs_controller.rb (7)
  • using (1-55)
  • edit (9-18)
  • set_code_repositories (42-46)
  • update (20-27)
  • parsed_bitbucket_config_params (48-54)
  • set_app (31-33)
  • set_bitbucket_integration (35-40)
app/controllers/version_control/github_configs_controller.rb (5)
  • using (1-52)
  • edit (9-17)
  • set_code_repositories (41-43)
  • update (19-26)
  • set_app (30-32)
app/models/integration.rb (1)
  • vcs_provider (177-179)
app/models/bitbucket_integration.rb (2)
  • workspaces (96-98)
  • repos (165-167)
app/refinements/refined_string.rb (1)
  • safe_json_parse (42-46)
app/controllers/version_control/gitlab_configs_controller.rb (5)
app/controllers/version_control/bitbucket_configs_controller.rb (5)
  • using (1-55)
  • edit (9-18)
  • set_code_repositories (42-46)
  • update (20-27)
  • set_app (31-33)
app/controllers/version_control/github_configs_controller.rb (5)
  • using (1-52)
  • edit (9-17)
  • set_code_repositories (41-43)
  • update (19-26)
  • set_app (30-32)
app/models/integration.rb (1)
  • vcs_provider (177-179)
app/models/gitlab_integration.rb (1)
  • repos (198-200)
app/refinements/refined_string.rb (1)
  • safe_json_parse (42-46)
app/models/jira_integration.rb (1)
app/models/linear_integration.rb (1)
  • release_filters_are_valid (246-252)
app/controllers/monitoring/bugsnag_configs_controller.rb (4)
app/controllers/build_channel/google_firebase_configs_controller.rb (4)
  • using (1-54)
  • edit (9-17)
  • update (19-26)
  • set_app (30-32)
app/models/integration.rb (1)
  • monitoring_provider (189-191)
app/models/bugsnag_integration.rb (1)
  • setup (83-85)
app/refinements/refined_string.rb (1)
  • safe_json_parse (42-46)
app/models/app_config.rb (4)
app/models/bugsnag_integration.rb (1)
  • set_bugsnag_config (136-141)
app/models/integration.rb (7)
  • monitoring_provider (189-191)
  • firebase_ready? (307-313)
  • firebase_build_channel_provider (209-211)
  • configs_ready? (346-350)
  • bitrise_ready? (296-301)
  • ci_cd_provider (181-183)
  • bugsnag_ready? (315-321)
app/models/concerns/integrable.rb (1)
  • firebase_connected? (14-16)
app/models/app.rb (2)
  • bitrise_connected? (109-111)
  • bugsnag_connected? (113-115)
app/controllers/ci_cd/bitrise_configs_controller.rb (5)
app/controllers/ci_cd/bitbucket_configs_controller.rb (4)
  • using (1-55)
  • edit (9-18)
  • update (20-27)
  • set_app (31-33)
app/controllers/ci_cd/github_configs_controller.rb (4)
  • using (1-52)
  • edit (9-17)
  • update (19-26)
  • set_app (30-32)
app/models/integration.rb (1)
  • ci_cd_provider (181-183)
app/models/bitrise_integration.rb (1)
  • setup (93-95)
app/refinements/refined_string.rb (1)
  • safe_json_parse (42-46)
app/controllers/build_channel/google_firebase_configs_controller.rb (4)
app/controllers/application_controller.rb (1)
  • current_organization (30-42)
app/models/integration.rb (1)
  • firebase_build_channel_provider (209-211)
app/models/google_firebase_integration.rb (1)
  • setup (106-117)
app/refinements/refined_string.rb (1)
  • safe_json_parse (42-46)
app/controllers/ci_cd/bitbucket_configs_controller.rb (5)
app/controllers/version_control/bitbucket_configs_controller.rb (7)
  • using (1-55)
  • edit (9-18)
  • set_code_repositories (42-46)
  • update (20-27)
  • parsed_bitbucket_config_params (48-54)
  • set_app (31-33)
  • set_bitbucket_integration (35-40)
app/controllers/application_controller.rb (1)
  • current_organization (30-42)
app/models/integration.rb (1)
  • ci_cd_provider (181-183)
app/models/bitbucket_integration.rb (2)
  • workspaces (96-98)
  • repos (165-167)
app/refinements/refined_string.rb (1)
  • safe_json_parse (42-46)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint
  • GitHub Check: test
🔇 Additional comments (18)
app/controllers/version_control/gitlab_configs_controller.rb (2)

1-26: LGTM!

The controller structure, actions, and filters are implemented correctly and consistently with the GitHub and Bitbucket config controllers. The edit and update flows properly handle repository configuration with Turbo Frame support, parameter parsing, and error handling.


30-32: LGTM!

The helper methods are correctly implemented:

  • set_app uses the standard friendly finder pattern
  • set_code_repositories calls the integration's repos method (which includes retry logic)
  • parsed_gitlab_config_params safely parses JSON with proper nil-handling via the safe navigation operator and safe_json_parse

Also applies to: 41-51

app/controllers/version_control/bitbucket_configs_controller.rb (5)

1-7: LGTM: Controller setup follows Rails conventions.

The class setup with authentication guards, refinements, and time zone handling is well-structured.


9-18: LGTM: Edit action properly implements Turbo Frame pattern.

The action correctly handles both turbo_frame and turbo_stream formats for editing Bitbucket configuration. Note that this action depends on set_code_repositories, which has unresolved issues flagged in previous reviews.


20-27: LGTM: Update action handles success and failure appropriately.

The update flow with proper redirects and user-friendly error messages follows Rails best practices.


31-40: LGTM: Helper methods properly scope and validate the integration.

The private methods correctly load the app scoped to the current organization and validate that the integration is a BitbucketIntegration with appropriate error handling.


48-54: LGTM: Parameter parsing safely handles JSON and nil cases.

The strong parameters with safe_json_parse and safe navigation operator properly handle edge cases, returning an empty hash for invalid JSON.

app/controllers/build_channel/google_firebase_configs_controller.rb (5)

1-7: LGTM!

The controller setup follows the established pattern seen in other config controllers (CiCd, VersionControl). Access control, integration loading, and timezone handling are correctly configured.


9-17: LGTM!

The edit action follows the standard Turbo Frame pattern used across other config controllers in this PR.


19-26: LGTM!

The update action correctly handles success and failure cases with appropriate redirects and flash messages.


30-32: LGTM!

Standard app loading pattern consistent with other controllers.


41-44: LGTM!

The Firebase apps setup extraction is straightforward. Assuming the redirect fix in set_google_firebase_integration, this will execute safely.

app/models/jira_integration.rb (3)

100-101: Fix looks correct.

The change to use resources.first instead of resource (singular) correctly fixes what appears to be an undefined variable bug. Since this code is in the branch where cloud_id is NOT present and resources.length == 1, using resources.first is appropriate.

Note: The AI summary incorrectly states this fix is "when cloud_id is already present." The fix is actually for the case when cloud_id is blank and there's exactly one available resource (lines 98-102), not for the earlier branch at lines 90-96 where cloud_id is already present.


161-164: Data source migration looks correct.

The changes successfully migrate from the global app.config.jira_config to the per-integration project_config field. The early return guard (line 161) and safe navigation operators are properly placed.


10-10: Migration backfills existing project_config The AddConfigToProjectManagementIntegrations migration sets default: {} and null: false, so existing records are initialized correctly.

app/controllers/monitoring/bugsnag_configs_controller.rb (3)

1-7: LGTM! Standard Rails controller setup.

The controller follows established patterns with appropriate access control filters and time zone handling.


9-26: LGTM! Standard CRUD actions.

Both edit and update actions follow Rails conventions with proper Turbo Frame support and error handling.


30-43: LGTM! Well-structured helper methods.

The private helper methods properly load and validate the required objects with appropriate error handling.

Comment on lines +34 to +39
def set_google_firebase_integration
@google_firebase_integration = @app.integrations.firebase_build_channel_provider
unless @google_firebase_integration.is_a?(GoogleFirebaseIntegration)
redirect_to app_integrations_path(@app), flash: {error: "Firebase build channel integration not found."}
end
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add explicit return after redirect to prevent continued execution.

Rails redirects do not halt execution. If firebase_build_channel_provider returns nil or an unexpected type, the redirect executes but the before_action chain continues, leading to a NoMethodError when set_firebase_apps calls .setup on nil (line 42).

Apply this diff:

   def set_google_firebase_integration
     @google_firebase_integration = @app.integrations.firebase_build_channel_provider
     unless @google_firebase_integration.is_a?(GoogleFirebaseIntegration)
-      redirect_to app_integrations_path(@app), flash: {error: "Firebase build channel integration not found."}
+      redirect_to app_integrations_path(@app), flash: {error: "Firebase build channel integration not found."} and return
     end
   end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def set_google_firebase_integration
@google_firebase_integration = @app.integrations.firebase_build_channel_provider
unless @google_firebase_integration.is_a?(GoogleFirebaseIntegration)
redirect_to app_integrations_path(@app), flash: {error: "Firebase build channel integration not found."}
end
end
def set_google_firebase_integration
@google_firebase_integration = @app.integrations.firebase_build_channel_provider
unless @google_firebase_integration.is_a?(GoogleFirebaseIntegration)
redirect_to app_integrations_path(@app), flash: {error: "Firebase build channel integration not found."} and return
end
end
🤖 Prompt for AI Agents
In app/controllers/build_channel/google_firebase_configs_controller.rb around
lines 34 to 39, the before_action uses redirect_to when the integration is
missing but does not halt execution, allowing subsequent callbacks to run and
cause NoMethodError; after calling redirect_to with the error flash, immediately
return (e.g., use `redirect_to ... and return` or `redirect_to(...); return`) so
execution stops and downstream methods like set_firebase_apps do not run on a
nil integration.

Comment on lines +34 to +39
def set_bitrise_integration
@bitrise_integration = @app.ci_cd_provider
unless @bitrise_integration.is_a?(BitriseIntegration)
redirect_to app_integrations_path(@app), flash: {error: "CI/CD integration not found."}
end
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Type verification implemented, consider improving error message.

The is_a?(BitriseIntegration) check now properly verifies the provider type, addressing the previous review concern. However, the error message "CI/CD integration not found." is generic and doesn't clearly indicate that a Bitrise-specific integration is required.

While this message is consistent with similar controllers (BitbucketConfigsController, GithubConfigsController), consider making it more specific to improve the user experience:

 def set_bitrise_integration
   @bitrise_integration = @app.ci_cd_provider
   unless @bitrise_integration.is_a?(BitriseIntegration)
-    redirect_to app_integrations_path(@app), flash: {error: "CI/CD integration not found."}
+    redirect_to app_integrations_path(@app), flash: {error: "Bitrise integration not found or incorrect CI/CD provider configured."}
   end
 end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def set_bitrise_integration
@bitrise_integration = @app.ci_cd_provider
unless @bitrise_integration.is_a?(BitriseIntegration)
redirect_to app_integrations_path(@app), flash: {error: "CI/CD integration not found."}
end
end
def set_bitrise_integration
@bitrise_integration = @app.ci_cd_provider
unless @bitrise_integration.is_a?(BitriseIntegration)
redirect_to app_integrations_path(@app), flash: {error: "Bitrise integration not found or incorrect CI/CD provider configured."}
end
end
🤖 Prompt for AI Agents
In app/controllers/ci_cd/bitrise_configs_controller.rb around lines 34 to 39,
the type check for BitriseIntegration is fine but the flash error is too
generic; change the flash message to explicitly mention Bitrise (e.g., "Bitrise
CI/CD integration not found." or the appropriate i18n key) so the user knows a
Bitrise-specific integration is required, keeping the redirect and behavior
unchanged and matching the style used in other controllers.

Comment on lines +41 to +44
def set_ci_cd_projects
@ci_cd_apps = @bitrise_integration.setup
@project_config = @bitrise_integration.project_config
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify error handling for setup call.

The setup method is called without error handling. If setup or the underlying list_apps method fails or returns nil, @ci_cd_apps could be unset or invalid, potentially causing issues in the view.


🏁 Script executed:

#!/bin/bash
# Verify if BitriseIntegration#setup can fail or return nil
# and check how @ci_cd_apps is used in the view

# Find the setup/list_apps implementation
ast-grep --pattern 'def setup
  $$$
end'

# Find the view that uses @ci_cd_apps
fd -e erb bitrise_configs | xargs rg -n '@ci_cd_apps' -A 3 -B 1

Length of output: 6108


Inspect BitriseIntegration#list_apps implementation for error handling and return value:


🏁 Script executed:

#!/bin/bash
rg -n 'def list_apps' -n app/models/bitrise_integration.rb -A10 -B5

Length of output: 482


Additional context from BitriseIntegration#list_apps:


🏁 Script executed:

#!/bin/bash
rg -n 'class BitriseIntegration' -A5 -B0 app/models/bitrise_integration.rb
rg -n 'def installation' -A5 -B5 app/models/bitrise_integration.rb

Length of output: 362


Add error handling around BitriseIntegration#setup. The call to @bitrise_integration.setup (which invokes installation.list_apps) has no rescue and can raise, causing a 500. Wrap it in a rescue Installations::Error and default @ci_cd_apps to an empty array to prevent uncaught exceptions. [app/controllers/ci_cd/bitrise_configs_controller.rb:42]

🤖 Prompt for AI Agents
In app/controllers/ci_cd/bitrise_configs_controller.rb around lines 41 to 44,
the call to @bitrise_integration.setup can raise from installation.list_apps and
currently has no rescue; wrap the setup call in a begin/rescue block that
rescues Installations::Error, sets @ci_cd_apps = [] on error (so the controller
doesn't 500), and optionally log or notify the error (e.g. Rails.logger.warn
with the exception message); preserve the existing assignment of @project_config
after handling the potential exception.

Comment on lines +46 to +52
def parsed_bitrise_config_params
bitrise_config_params = params.require(:bitrise_integration)
.permit(:project_config)
bitrise_config_params.merge(
project_config: bitrise_config_params[:project_config]&.safe_json_parse
)
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

JSON parsing silently fails on invalid input.

The safe_json_parse method returns an empty hash {} when JSON parsing fails. If a user submits invalid JSON for project_config, it will be silently replaced with an empty hash, potentially overwriting existing configuration without clear feedback.

Consider adding validation to detect and report JSON parsing failures, or at minimum, logging when parsing fails to aid debugging.

Note: This pattern is consistent with similar controllers (BitbucketConfigsController, GithubConfigsController), suggesting this might be intentional behavior across the codebase.

🤖 Prompt for AI Agents
In app/controllers/ci_cd/bitrise_configs_controller.rb around lines 46 to 52,
the call to safe_json_parse silently returns {} on invalid JSON which can
overwrite existing project_config without feedback; update
parsed_bitrise_config_params to detect parse failures by checking whether
project_config was valid JSON (e.g., have safe_json_parse return nil or raise,
or check raw string vs parsed result), and on failure either add a validation
error to the params/record (so the controller responds with a 4xx and an error
message) or at minimum log the parsing error with context (user/id/params)
before returning; ensure the controller path used by similar controllers is
followed for consistent behavior (reject invalid JSON rather than silently
replacing it).

Comment on lines +56 to +74
def bugsnag_config(config_params)
config = {}

if config_params[:ios_release_stage].present?
config[:ios_config] = {
project_id: config_params[:ios_project_id]&.safe_json_parse,
release_stage: config_params[:ios_release_stage]
}
end

if config_params[:android_release_stage].present?
config[:android_config] = {
project_id: config_params[:android_project_id]&.safe_json_parse,
release_stage: config_params[:android_release_stage]
}
end

config
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify error handling for invalid JSON in project IDs.

Lines 61 and 68 use safe_json_parse, which returns an empty hash ({}) on parse errors. If project_id contains invalid JSON, it will silently be set to {} rather than raising an error or storing the raw value. This might not be the desired behavior.

Ensure the BugsnagIntegration model has appropriate validations to reject empty or invalid project_id values, or consider explicitly handling JSON parse errors in the controller to provide user feedback.

Run this script to check for model validations:


🏁 Script executed:

#!/bin/bash
# Check BugsnagIntegration model for validations on config fields

ast-grep --pattern $'class BugsnagIntegration
  $$$
  validate $$$
  $$$
'

# Also check for any custom validation methods
rg -nP --type=ruby -A 5 'def (validate|valid_project_id|valid_config)' app/models/bugsnag_integration.rb

Length of output: 0



---

🏁 Script executed:

```shell
#!/bin/bash
# Locate the BugsnagIntegration class definition
model_file=$(rg -l 'class BugsnagIntegration' -g '*.rb')
echo "Found model file: $model_file"

# Search for validation macros or custom validation methods
rg -n 'validates?' "$model_file"
rg -n 'def validate' "$model_file"

Length of output: 136


Add validation for parsed project_id in BugsnagIntegration. The model (app/models/bugsnag_integration.rb) currently only validates access_token and correct_key, so {} from safe_json_parse can be saved silently. Add validates or a custom validator for ios_config[:project_id] and android_config[:project_id], or surface JSON parse errors in the controller.

🤖 Prompt for AI Agents
In app/controllers/monitoring/bugsnag_configs_controller.rb around lines 56–74,
the controller currently assigns project_id using safe_json_parse which can
return {} and be saved silently; add a check after parsing to ensure the parsed
project_id is present and of the expected type (or not an empty hash), and if it
is invalid respond with a parameter error (or add to config.errors) so the bad
payload is rejected. Alternatively (or additionally), add validations in
app/models/bugsnag_integration.rb that require ios_config["project_id"] when
ios_release_stage is present and android_config["project_id"] when
android_release_stage is present (presence and format/type checks or a custom
validator to detect empty-hash from failed JSON parse), so invalid or empty
parsed project_id values fail model validation instead of being saved.

Comment on lines +57 to +58
firebase_build_channel = app.integrations.firebase_build_channel_provider
configs_ready?(firebase_build_channel.ios_config, firebase_build_channel.android_config)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Nil-guard firebase_build_channel to avoid NPE.

The firebase_build_channel_provider can return nil even when firebase_connected? is true (e.g., if the integration exists but has no providable, or in edge cases during deletion). Calling .ios_config on nil will raise NoMethodError.

Apply this diff to add a nil guard:

   def firebase_ready?
     return true unless app.firebase_connected?
     firebase_build_channel = app.integrations.firebase_build_channel_provider
+    return false unless firebase_build_channel
     configs_ready?(firebase_build_channel.ios_config, firebase_build_channel.android_config)
   end

Alternatively, use safe navigation:

   def firebase_ready?
     return true unless app.firebase_connected?
     firebase_build_channel = app.integrations.firebase_build_channel_provider
-    configs_ready?(firebase_build_channel.ios_config, firebase_build_channel.android_config)
+    configs_ready?(firebase_build_channel&.ios_config, firebase_build_channel&.android_config)
   end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
firebase_build_channel = app.integrations.firebase_build_channel_provider
configs_ready?(firebase_build_channel.ios_config, firebase_build_channel.android_config)
def firebase_ready?
return true unless app.firebase_connected?
firebase_build_channel = app.integrations.firebase_build_channel_provider
return false unless firebase_build_channel
configs_ready?(firebase_build_channel.ios_config, firebase_build_channel.android_config)
end
🤖 Prompt for AI Agents
In app/models/app_config.rb around lines 57 to 58, the code calls
firebase_build_channel.ios_config without guarding against
firebase_build_channel being nil; change the code to nil-guard the provider
(e.g., return false early if firebase_build_channel is nil) or use safe
navigation when passing configs (e.g., firebase_build_channel&.ios_config,
firebase_build_channel&.android_config) so NoMethodError is avoided and
configs_ready? receives nils safely.

Comment on lines +68 to +69
monitoring_provider = app.monitoring_provider
configs_ready?(monitoring_provider.ios_config, monitoring_provider.android_config)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Nil-guard monitoring_provider to avoid NPE.

The monitoring_provider can return nil even when bugsnag_connected? is true (e.g., if the integration exists but has no providable, or in edge cases during deletion). Calling .ios_config on nil will raise NoMethodError.

Apply this diff to add a nil guard:

   def bugsnag_ready?
     return true unless app.bugsnag_connected?
     monitoring_provider = app.monitoring_provider
+    return false unless monitoring_provider
     configs_ready?(monitoring_provider.ios_config, monitoring_provider.android_config)
   end

Alternatively, use safe navigation:

   def bugsnag_ready?
     return true unless app.bugsnag_connected?
     monitoring_provider = app.monitoring_provider
-    configs_ready?(monitoring_provider.ios_config, monitoring_provider.android_config)
+    configs_ready?(monitoring_provider&.ios_config, monitoring_provider&.android_config)
   end
🤖 Prompt for AI Agents
In app/models/app_config.rb around lines 68-69, calling
monitoring_provider.ios_config (and .android_config) can raise NoMethodError
when monitoring_provider is nil; guard against nil by checking
monitoring_provider before accessing its configs or use safe navigation (e.g.,
only call .ios_config and .android_config if monitoring_provider.present? or use
monitoring_provider&.ios_config, monitoring_provider&.android_config) so
configs_ready? receives nils instead of raising.

delegate :app, to: :integration
delegate :cache, to: Rails
validates :cloud_id, presence: true
validate :release_filters_are_valid, if: -> { project_config&.dig("release_filters").present? }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add array type check to validation condition.

The validation condition checks for presence but not type. If project_config["release_filters"] is set to a non-array value (e.g., a string or hash), the condition evaluates to true, triggering the validation method which calls .each on line 269, resulting in a NoMethodError.

Apply this diff to add an array type check:

-  validate :release_filters_are_valid, if: -> { project_config&.dig("release_filters").present? }
+  validate :release_filters_are_valid, if: -> { project_config&.dig("release_filters").is_a?(Array) }

Note: is_a?(Array) returns false for nil and non-array values, so it's both a presence and type check.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
validate :release_filters_are_valid, if: -> { project_config&.dig("release_filters").present? }
validate :release_filters_are_valid, if: -> { project_config&.dig("release_filters").is_a?(Array) }
🤖 Prompt for AI Agents
In app/models/jira_integration.rb around line 65, the validation guard uses
presence only and can be true for non-array values causing the
release_filters_are_valid method to call `.each` on a non-array and raise
NoMethodError; update the condition to both check presence and type (e.g.,
ensure project_config&.dig("release_filters").is_a?(Array)) so the validation
runs only when release_filters is an Array.

Comment on lines +268 to +274
def release_filters_are_valid
project_config["release_filters"].each do |filter|
unless filter.is_a?(Hash) && VALID_FILTER_TYPES.include?(filter["type"]) && filter["value"].present?
errors.add(:project_config, "release filters must contain valid type and value")
end
end
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Guard against non-array release_filters.

The method calls .each on project_config["release_filters"] without verifying it's an array. If release_filters is nil or a non-array type, this raises a NoMethodError. While the validation condition on line 65 checks for presence, it doesn't enforce array type, allowing this failure scenario.

Apply this diff to add a type guard:

 def release_filters_are_valid
+  return unless project_config["release_filters"].is_a?(Array)
+
   project_config["release_filters"].each do |filter|
     unless filter.is_a?(Hash) && VALID_FILTER_TYPES.include?(filter["type"]) && filter["value"].present?
       errors.add(:project_config, "release filters must contain valid type and value")
     end
   end
 end

Consider extracting duplicate validation logic.

This validation method is duplicated in LinearIntegration (see app/models/linear_integration.rb lines 245-251). Consider extracting this logic into a shared concern or module to follow the DRY principle and simplify maintenance.

For example, create a concern:

# app/models/concerns/release_filter_validatable.rb
module ReleaseFilterValidatable
  extend ActiveSupport::Concern

  VALID_FILTER_TYPES = %w[label fix_version].freeze

  included do
    validate :release_filters_are_valid, if: -> { project_config&.dig("release_filters").is_a?(Array) }
  end

  private

  def release_filters_are_valid
    project_config["release_filters"].each do |filter|
      unless filter.is_a?(Hash) && VALID_FILTER_TYPES.include?(filter["type"]) && filter["value"].present?
        errors.add(:project_config, "release filters must contain valid type and value")
      end
    end
  end
end

Then include it in both JiraIntegration and LinearIntegration:

class JiraIntegration < ApplicationRecord
  include ReleaseFilterValidatable
  # ...
end

Based on learnings.

Safe-nav felt unnecessary since Integration depends on App.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ebdd8a and d4c2e5b.

📒 Files selected for processing (1)
  • app/models/integration.rb (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/models/integration.rb (5)
app/models/app_config.rb (5)
  • firebase_ready? (55-59)
  • bugsnag_ready? (66-70)
  • project_management_ready? (78-99)
  • bitrise_ready? (61-64)
  • configs_ready? (72-76)
app/models/app.rb (2)
  • bitrise_connected? (109-111)
  • bugsnag_connected? (113-115)
app/models/bitrise_integration.rb (1)
  • bitrise_project (64-66)
app/models/concerns/integrable.rb (1)
  • firebase_connected? (14-16)
app/models/external_app.rb (2)
  • ios? (33-33)
  • android? (35-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint
  • GitHub Check: test
🔇 Additional comments (1)
app/models/integration.rb (1)

182-184: Connected vs kept scope change is safe. Since disconnect sets discarded_at, the kept scope (non-discarded) will never include disconnected records. No action needed.

Comment on lines 145 to 147
def category_ready?(category)
# Does the following need to change to `ready.first.integrable` since app seems like an optional `belongs_to` association?
app = ready.first&.app
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Address the existing concern about optional app association.

The comment on line 146 correctly identifies a potential issue. Since app is an optional association (line 25: belongs_to :app, optional: true), but integrable is required (via delegated_type), using integrable would be more robust.

However, integrable can be either an App or AppVariant. If this method needs specifically an App, you'll need to handle the case where integrable is an AppVariant:

-# Does the following need to change to `ready.first.integrable` since app seems like an optional `belongs_to` association?
-app = ready.first&.app
+app = ready.first&.integrable
+app = app.app if app.is_a?(AppVariant) # Get parent app if integrable is AppVariant
+return false unless app

Or if the logic should work at the integrable level rather than app level, adjust the subsequent checks accordingly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def category_ready?(category)
# Does the following need to change to `ready.first.integrable` since app seems like an optional `belongs_to` association?
app = ready.first&.app
def category_ready?(category)
app = ready.first&.integrable
app = app.app if app.is_a?(AppVariant) # Get parent app if integrable is AppVariant
return false unless app
# rest of method unchanged
🤖 Prompt for AI Agents
In app/models/integration.rb around lines 145 to 147, replace the fragile
ready.first&.app access with ready.first&.integrable because app is an optional
belongs_to while integrable is required; then handle the two possible integrable
types: if you specifically need an App, detect when integrable is an AppVariant
and obtain its parent App (or return false if no parent), otherwise proceed
using the integrable directly if your logic applies to either App or
AppVariant—alternatively, if the method should operate at the integrable level,
update the subsequent checks to reference integrable instead of app.

Comment on lines +165 to +172
def configured?
return false if none? # need at least one integration

further_setup_by_category
.values
.pluck(:ready)
.all?
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: configured? doesn't verify minimum required categories are present.

The method only checks that present categories are ready, not that the minimum required set (:version_control, :ci_cd, :build_channel) are actually connected. If a required category is missing, it won't appear in further_setup_by_category and configured? will incorrectly return true.

Compare with the ready? method (lines 161-163) which explicitly checks MINIMUM_REQUIRED_SET.

Apply this diff to ensure minimum categories are present:

 def configured?
   return false if none? # need at least one integration
+  return false unless ready? # ensure minimum required categories are connected

   further_setup_by_category
     .values
     .pluck(:ready)
     .all?
 end
🤖 Prompt for AI Agents
In app/models/integration.rb around lines 165 to 172, configured? currently only
checks that existing categories are ready but doesn't ensure the minimum
required categories (:version_control, :ci_cd, :build_channel) are present;
update configured? to first verify that MINIMUM_REQUIRED_SET is a subset of
further_setup_by_category.keys (or that all required category keys exist), then
ensure all those categories (or all values) report ready. Concretely: if none?
return false, compute the category map, return false if any required category is
missing, then check the ready flags (pluck(:ready).all?) and return that result.

Comment on lines +246 to +251
if connected_integrations.build_channel.present?
categories[:build_channel] = {
further_setup: connected_integrations.build_channel.map(&:providable).any?(&:further_setup?),
ready: firebase_ready?
}
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Major: Provider-specific readiness check applied to entire category.

The build_channel category calls firebase_ready? for all build channel providers, but this category can include AppStoreIntegration, GooglePlayStoreIntegration, GoogleFirebaseIntegration, and SlackIntegration. Only Firebase requires the config checked by firebase_ready?.

If the build channel is AppStore or GooglePlayStore without Firebase, this incorrectly returns ready: true when those providers might need their own configuration checks.

Consider refactoring to check provider-specific readiness:

 if connected_integrations.build_channel.present?
+  build_providers = connected_integrations.build_channel.map(&:providable)
   categories[:build_channel] = {
-    further_setup: connected_integrations.build_channel.map(&:providable).any?(&:further_setup?),
-    ready: firebase_ready?
+    further_setup: build_providers.any?(&:further_setup?),
+    ready: build_providers.all? { |p| provider_ready?(p) }
   }
 end

Where provider_ready? dispatches to the appropriate readiness check per provider type.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +253 to +258
if connected_integrations.monitoring.present?
categories[:monitoring] = {
further_setup: connected_integrations.monitoring.any?(&:further_setup?),
ready: bugsnag_ready?
}
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Major: Provider-specific readiness check applied to entire category.

Similar to the build_channel issue, monitoring calls bugsnag_ready? for all monitoring providers, but this category can include both BugsnagIntegration and CrashlyticsIntegration. Only Bugsnag requires the config checked by bugsnag_ready?.

Apply the same provider-specific readiness pattern suggested for build_channel.

🤖 Prompt for AI Agents
In app/models/integration.rb around lines 253 to 258, the monitoring category
currently uses a single bugsnag_ready? check for all providers; change this to a
provider-specific readiness computation like in build_channel: inspect
connected_integrations.monitoring to detect provider classes (e.g.,
BugsnagIntegration, CrashlyticsIntegration), and set
categories[:monitoring][:ready] based on the appropriate readiness check for
each detected provider (call bugsnag_ready? only when BugsnagIntegration
instances exist, call the Crashlytics readiness helper when
CrashlyticsIntegration exists, or compute a combined boolean that aggregates
per-provider readiness).

Comment on lines +297 to +306
def bitrise_ready?
app = first.integrable
return true unless app.bitrise_connected?

bitrise_project.present?
end

def bitrise_project
bitrise_ci_cd_provider&.bitrise_project
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Potential NoMethodError and confusing logic.

Two issues:

  1. Line 298: first.integrable will raise NoMethodError if the collection is empty. Add a guard:

    app = first&.integrable
    return false unless app
  2. Line 299: The check return true unless app.bitrise_connected? is confusing in this context. bitrise_ready? is only called from ci_cd_ready? when ci_cd_provider is a BitriseIntegration, which implies Bitrise is already connected. Returning true when it's not connected seems contradictory.

If the intent is "Bitrise doesn't need configuration if not connected," this logic should be in ci_cd_ready?, not here.

Apply this diff to fix the nil safety issue:

 def bitrise_ready?
-  app = first.integrable
+  app = first&.integrable
+  return false unless app
   return true unless app.bitrise_connected?

   bitrise_project.present?
 end
🤖 Prompt for AI Agents
app/models/integration.rb around lines 297 to 306: fix nil-safety and clarify
the connectivity check by making app retrieval safe (use first&.integrable) and
return false if app is nil, and change the confusing early-true branch to
require the app to be connected (replace `return true unless
app.bitrise_connected?` with `return false unless app.bitrise_connected?`),
leaving the final result as `bitrise_project.present?`.

Comment on lines +308 to +322
def firebase_ready?
app = first.integrable
return true unless app.firebase_connected?

firebase_build_channel = firebase_build_channel_provider
configs_ready?(app, firebase_build_channel&.android_config, firebase_build_channel&.ios_config)
end

def bugsnag_ready?
app = first.integrable
return true unless app.bugsnag_connected?

monitoring = monitoring_provider
configs_ready?(app, monitoring&.android_config, monitoring&.ios_config)
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Potential NoMethodError in both methods.

Both firebase_ready? (line 309) and bugsnag_ready? (line 317) call first.integrable without nil checks, which will raise NoMethodError if the collection is empty.

Apply this diff to add nil guards:

 def firebase_ready?
-  app = first.integrable
+  app = first&.integrable
+  return false unless app
   return true unless app.firebase_connected?

   firebase_build_channel = firebase_build_channel_provider
   configs_ready?(app, firebase_build_channel&.android_config, firebase_build_channel&.ios_config)
 end

 def bugsnag_ready?
-  app = first.integrable
+  app = first&.integrable
+  return false unless app
   return true unless app.bugsnag_connected?

   monitoring = monitoring_provider
   configs_ready?(app, monitoring&.android_config, monitoring&.ios_config)
 end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def firebase_ready?
app = first.integrable
return true unless app.firebase_connected?
firebase_build_channel = firebase_build_channel_provider
configs_ready?(app, firebase_build_channel&.android_config, firebase_build_channel&.ios_config)
end
def bugsnag_ready?
app = first.integrable
return true unless app.bugsnag_connected?
monitoring = monitoring_provider
configs_ready?(app, monitoring&.android_config, monitoring&.ios_config)
end
def firebase_ready?
app = first&.integrable
return false unless app
return true unless app.firebase_connected?
firebase_build_channel = firebase_build_channel_provider
configs_ready?(app, firebase_build_channel&.android_config, firebase_build_channel&.ios_config)
end
def bugsnag_ready?
app = first&.integrable
return false unless app
return true unless app.bugsnag_connected?
monitoring = monitoring_provider
configs_ready?(app, monitoring&.android_config, monitoring&.ios_config)
end
🤖 Prompt for AI Agents
In app/models/integration.rb around lines 308 to 322, both firebase_ready? and
bugsnag_ready? call first.integrable without guarding for an empty collection
which can raise NoMethodError; fix by capturing the first record (e.g.
first_record = first), return true if first_record is nil, then get app =
first_record.integrable (or use safe navigation first&.integrable) and guard if
app is nil before calling app.firebase_connected? / app.bugsnag_connected? so
the methods return safely when no first record or integrable exists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant