Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a1f59ee
feat: Add separate write paths for configuring `version_control` inte…
Animesh-Ghosh Sep 8, 2025
253321f
fix: Make Bitbucket workspace dropdown switcher work
Animesh-Ghosh Sep 14, 2025
15e09a0
feat: Add separate write paths for configuring `ci_cd` integrations
Animesh-Ghosh Sep 17, 2025
73ac1b9
Add separate write paths for configuring `build_channel` integrations
Animesh-Ghosh Sep 19, 2025
379adce
fix: Pre-select persisted integration config value
Animesh-Ghosh Sep 22, 2025
a5d2f57
chore: Fix integration card pre-opening UX
Animesh-Ghosh Sep 22, 2025
04abfa0
feat: Add separate write paths for configuring `monitoring` integrations
Animesh-Ghosh Sep 22, 2025
ce9d696
feat: Add separate write paths for configuring `project_management` i…
Animesh-Ghosh Sep 26, 2025
d0534ad
feat: Copy `AppConfig` validations to the respective `Integration`s
Animesh-Ghosh Oct 2, 2025
1ba11ce
feat: Move `AppConfig#ready?` to `Integration`
Animesh-Ghosh Oct 5, 2025
a96cd2b
feat: Move `AppConfig#code_repository_name` to `vcs_provider` Integra…
Animesh-Ghosh Oct 5, 2025
cf8de84
feat: Move `AppConfig#code_repository_url` to `vcs_provider` Integrat…
Animesh-Ghosh Oct 5, 2025
397e9d7
feat: Move `AppConfig#code_repo_namespace` to `vcs_provider` Integrat…
Animesh-Ghosh Oct 5, 2025
08adced
feat: Move `AppConfig#code_repo_name_only` to `vcs_provider` Integrat…
Animesh-Ghosh Oct 5, 2025
9d9d8dd
feat: Move `AppConfig#bitrise_project` to `BitriseIntegration`
Animesh-Ghosh Oct 5, 2025
5ba3372
chore: Fix failing specs for `GitlabIntegration`
Animesh-Ghosh Oct 5, 2025
7d32eb4
chore: Attempt to fix the remaining failing specs
Animesh-Ghosh Oct 5, 2025
d019bd2
feat: Remove migrated `bugsnag_*` public methods from `AppConfig`
Animesh-Ghosh Oct 5, 2025
2b0628d
chore: Annotate mystery methods
Animesh-Ghosh Oct 5, 2025
69df925
feat: Move `AppConfig#firebase_app` to `GoogleFirebaseIntegration`
Animesh-Ghosh Oct 5, 2025
dbff8e3
feat: Remove remaining public methods from `AppConfig`
Animesh-Ghosh Oct 8, 2025
11eb9df
chore: Fix Bitrise provider readiness check
Animesh-Ghosh Oct 8, 2025
a40b6b3
fix: Allow build servers to be configurable
Animesh-Ghosh Oct 8, 2025
43f8262
fix: Make the app config wizard work again
Animesh-Ghosh Oct 8, 2025
7ebdd8a
fix: Address AI review comments
Animesh-Ghosh Oct 9, 2025
d4c2e5b
feat: Remove safe-nav operator
Animesh-Ghosh Oct 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 31 additions & 15 deletions app/components/integration_card_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,35 @@
<% if connected? %>
<div class="flex justify-between w-full items-baseline">
<%= render BadgeComponent.new(text: "Connected", status: :success) %>
<% if disconnectable_categories? %>
<%= render ButtonComponent.new(
scheme: :danger,
options: app_integration_path(@app, integration),
type: :button,
size: :xxs,
turbo: false,
disabled: !disconnectable?,
html_options: {method: :delete, data: {turbo_method: :delete, turbo_confirm: "Are you sure you want disconnect the integration?"}}
) do |b|
b.with_icon("trash.svg", size: :sm)
end %>
<% end %>
<div class="flex gap-2">
<% if further_setup? %>
<%= render ModalComponent.new(title: category_title, open: pre_open_category?) do |modal| %>
<% button = modal.with_button(scheme: :light, type: :action, size: :xxs) %>
<% button.with_icon("cog.svg", size: :sm) %>
<% modal.with_body do %>
<%= tag.turbo_frame id: category_config_turbo_frame_id,
src: edit_config_path,
loading: :lazy,
class: "with-turbo-frame-loader" do %>
<%= render LoadingIndicatorComponent.new(skeleton_only: true, turbo_frame: true) %>
<% end %>
<% end %>
<% end %>
<% end %>

<% if disconnectable_categories? %>
<%= render ButtonComponent.new(
scheme: :danger,
options: app_integration_path(@app, integration),
type: :button,
size: :xxs,
disabled: !disconnectable?,
html_options: {method: :delete, data: {turbo_confirm: "Are you sure you want to disconnect this integration?"}}
) do |b|
b.with_icon("trash.svg", size: :sm)
end %>
<% end %>
</div>
</div>
<% end %>

Expand All @@ -45,8 +61,8 @@

<% if creatable? %>
<%= render ModalComponent.new(title: creatable_modal_title) do |modal| %>
<% modal.with_button(label: "Connect", scheme: :light, type: :action, size: :xxs, arrow: :none)
.with_icon("plus.svg", size: :md) %>
<% button = modal.with_button(label: "Connect", scheme: :light, type: :action, size: :xxs, arrow: :none) %>
<% button.with_icon("plus.svg", size: :md) %>
<% modal.with_body do %>
<%= creatable_form_partial %>
<% end %>
Expand Down
80 changes: 78 additions & 2 deletions app/components/integration_card_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ class IntegrationCardComponent < BaseComponent
bitrise: "Access Token"
}

def initialize(app, integration, category)
def initialize(app, integration, category, pre_open_category = nil)
@app = app
@integration = integration
@category = category
@pre_open_category = pre_open_category
end
Comment on lines +13 to 18
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.


attr_reader :integration
Expand All @@ -22,7 +23,8 @@ def initialize(app, integration, category)
:providable,
:connection_data,
:providable_type,
:disconnectable_categories?, to: :integration, allow_nil: true
:disconnectable_categories?,
:further_setup?, to: :integration, allow_nil: true
delegate :creatable?, :connectable?, to: :provider
alias_method :provider, :providable

Expand Down Expand Up @@ -77,4 +79,78 @@ def reusable_integrations_form_partial(existing_integrations)
def disconnectable?
integration.disconnectable? && disconnectable_categories?
end

def category_title
"Configure #{Integration.human_enum_name(:category, @category)}"
end

def pre_open_category?
@pre_open_category == @category
end

def category_config_turbo_frame_id
"#{@category}_config"
end

def edit_config_path
case integration.category
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

private

def edit_app_version_control_config_path
case integration.providable_type
when "GithubIntegration" then edit_app_version_control_github_config_path(@app)
when "GitlabIntegration" then edit_app_version_control_gitlab_config_path(@app)
when "BitbucketIntegration" then edit_app_version_control_bitbucket_config_path(@app)
else unsupported_integration_type
end
end

def edit_app_ci_cd_config_path
case integration.providable_type
when "GithubIntegration" then edit_app_ci_cd_github_config_path(@app)
when "GitlabIntegration" then edit_app_ci_cd_gitlab_config_path(@app)
when "BitbucketIntegration" then edit_app_ci_cd_bitbucket_config_path(@app)
when "BitriseIntegration" then edit_app_ci_cd_bitrise_config_path(@app)
else unsupported_integration_type
end
end

def edit_app_build_channel_config_path
case integration.providable_type
when "GoogleFirebaseIntegration" then edit_app_build_channel_google_firebase_config_path(@app)
else unsupported_integration_type
end
end

def edit_app_monitoring_config_path
case integration.providable_type
when "BugsnagIntegration" then edit_app_monitoring_bugsnag_config_path(@app)
else unsupported_integration_type
end
end

def edit_app_project_management_config_path
case integration.providable_type
when "JiraIntegration" then edit_app_project_management_jira_config_path(@app)
when "LinearIntegration" then edit_app_project_management_linear_config_path(@app)
else unsupported_integration_type
end
end

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

def unsupported_integration_type
raise TypeError, "Unsupported integration type: #{integration.providable_type}"
end
end
18 changes: 1 addition & 17 deletions app/components/integration_list_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,10 @@
<div id="<%= category %>">
<div class="grid grid-cols-10 gap-6 mt-4">
<% integrations.each do |integration| %>
<%= render IntegrationCardComponent.new(@app, integration, category) %>
<%= render IntegrationCardComponent.new(@app, integration, category, @pre_open_category) %>
<% end %>
</div>
</div>
<% if connected_integrations?(integrations) %>
<% sc.with_sidenote do %>
<%= render ModalComponent.new(title: title(category), open: pre_open?(category)) do |modal| %>
<% modal.with_button(label: "Configure", scheme: :light, type: :action, size: :xxs, arrow: :none)
.with_icon("cog.svg", size: :md) %>
<% modal.with_body do %>
<%= tag.turbo_frame id: "#{category}_config",
src: edit_app_app_config_path(@app, integration_category: category),
loading: :lazy,
class: "with-turbo-frame-loader" do %>
<%= render LoadingIndicatorComponent.new(skeleton_only: true, turbo_frame: true) %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<%= render SectionComponent.new(style: :titled, title: "Coming Soon") do %>
Expand Down
4 changes: 0 additions & 4 deletions app/components/integration_list_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,4 @@ def title(category)
def pre_open?(category)
@pre_open_category == category
end

def connected_integrations?(integrations)
integrations.any? { |i| i.connected? && i.further_setup? }
end
end
2 changes: 1 addition & 1 deletion app/components/live_release/finalize_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def subtitle
end

def tag_link
link = release.tag_url || release.app.config&.code_repo_url
link = release.tag_url || release.app.vcs_provider&.code_repo_url
return NOT_AVAILABLE if link.blank?
link_to_external train.vcs_provider.display, link, class: "underline"
end
Expand Down
7 changes: 5 additions & 2 deletions app/controllers/apps_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ def show
redirect_to app_train_releases_path(@app, selected_train)
end

@train_in_creation = @app.train_in_creation
@app_setup_instructions = @app.app_setup_instructions
if @app.ready?
@train_in_creation = @app.train_in_creation
else
@app_setup_instructions = @app.app_setup_instructions
end
end

def edit
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
class BuildChannel::GoogleFirebaseConfigsController < SignedInApplicationController
using RefinedString

before_action :require_write_access!
before_action :set_app
before_action :set_google_firebase_integration
around_action :set_time_zone

def edit
set_firebase_apps

respond_to do |format|
format.html do |variant|
variant.turbo_frame { render :edit }
end
end
end

def update
if @google_firebase_integration.update(parsed_google_firebase_config_params)
redirect_to app_path(@app), notice: t(".success")
else
redirect_back fallback_location: app_integrations_path(@app),
flash: {error: @google_firebase_integration.errors.full_messages.to_sentence}
end
end

private

def set_app
@app = current_organization.apps.friendly.find(params[:app_id])
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."}
end
end
Comment on lines +34 to +39
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.


def set_firebase_apps
config = @google_firebase_integration.setup
@firebase_android_apps, @firebase_ios_apps = config[:android], config[:ios]
end

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
Comment on lines +46 to +53
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.

end
55 changes: 55 additions & 0 deletions app/controllers/ci_cd/bitbucket_configs_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
class CiCd::BitbucketConfigsController < SignedInApplicationController
using RefinedString

before_action :require_write_access!
before_action :set_app
before_action :set_bitbucket_integration
around_action :set_time_zone

def edit
set_code_repositories

respond_to do |format|
format.html do |variant|
variant.turbo_frame { render :edit }
end
format.turbo_stream { render :edit }
end
end

def update
if @bitbucket_integration.update(parsed_bitbucket_config_params)
redirect_to app_path(@app), notice: t(".success")
else
redirect_back fallback_location: app_integrations_path(@app),
flash: {error: @bitbucket_integration.errors.full_messages.to_sentence}
end
end

private

def set_app
@app = current_organization.apps.friendly.find(params[:app_id])
end

def set_bitbucket_integration
@bitbucket_integration = @app.ci_cd_provider
unless @bitbucket_integration.is_a?(BitbucketIntegration)
redirect_to app_integrations_path(@app), flash: {error: "CI/CD integration not found."}
end
end

def set_code_repositories
@workspaces = @bitbucket_integration.workspaces || []
workspace = params[:workspace] || @workspaces.first
@code_repositories = @bitbucket_integration.repos(workspace)
end

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
Comment on lines +48 to +54
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.

end
53 changes: 53 additions & 0 deletions app/controllers/ci_cd/bitrise_configs_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
class CiCd::BitriseConfigsController < SignedInApplicationController
using RefinedString

before_action :require_write_access!
before_action :set_app
before_action :set_bitrise_integration
around_action :set_time_zone

def edit
set_ci_cd_projects

respond_to do |format|
format.html do |variant|
variant.turbo_frame { render :edit }
end
end
end

def update
if @bitrise_integration.update(parsed_bitrise_config_params)
redirect_to app_path(@app), notice: t(".success")
else
redirect_back fallback_location: app_integrations_path(@app),
flash: {error: @bitrise_integration.errors.full_messages.to_sentence}
end
end

private

def set_app
@app = current_organization.apps.friendly.find(params[:app_id])
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: "CI/CD integration not found."}
end
end
Comment on lines +34 to +39
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.


def set_ci_cd_projects
@ci_cd_apps = @bitrise_integration.setup
@project_config = @bitrise_integration.project_config
end
Comment on lines +41 to +44
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.


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
Comment on lines +46 to +52
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).

end
Loading
Loading