<%= opt[:title] %>
<%= opt[:subtitle] %>
diff --git a/app/controllers/integrations_controller.rb b/app/controllers/integrations_controller.rb
index 00ab04bf2..4138e354b 100644
--- a/app/controllers/integrations_controller.rb
+++ b/app/controllers/integrations_controller.rb
@@ -62,11 +62,11 @@ def destroy
private
def initiate_integration(existing_integration)
- @app.integrations.find_or_initialize_by(
+ @app.integrations.build(
category: @integration.category,
status: Integration.statuses[:connected],
metadata: existing_integration.metadata,
- providable: existing_integration.providable
+ providable: existing_integration.providable.dup
)
end
@@ -79,7 +79,7 @@ def set_integration
end
def set_existing_integration
- @existing_integration = Integration.find_by(id: params[:id])
+ @existing_integration = Integration.find_by(id: params[:integration][:existing_integration_id])
end
def set_providable
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index a4fa6ac9f..3e4c16ef6 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -61,14 +61,6 @@ def version_in_progress(version)
version.to_semverish.to_s(patch_glob: true)
end
- def text_field_classes(is_disabled:)
- if is_disabled
- "form-input w-full disabled:border-slate-200 disabled:bg-slate-100 disabled:text-slate-600 disabled:cursor-not-allowed"
- else
- "form-input w-full"
- end
- end
-
def ago_in_words(time, prefix: nil, suffix: "ago")
return "N/A" unless time
builder = ""
@@ -127,10 +119,6 @@ def time_format(timestamp, with_year: false, with_time: true, only_time: false,
timestamp.strftime("%b #{timestamp.day.ordinalize}#{", %Y" if with_year}#{" at %-l:%M %P" if with_time}")
end
- def subtitle(text)
- content_tag(:span, text, class: "text-sm text-slate-400")
- end
-
def short_sha(sha)
sha[0, 7]
end
diff --git a/app/models/bitbucket_integration.rb b/app/models/bitbucket_integration.rb
index f46b8bd44..8de404cfa 100644
--- a/app/models/bitbucket_integration.rb
+++ b/app/models/bitbucket_integration.rb
@@ -40,6 +40,7 @@ def install_path
end
def complete_access
+ return if oauth_access_token.present? && oauth_refresh_token.present?
set_tokens(Installations::Bitbucket::Api.oauth_access_token(code, redirect_uri))
end
diff --git a/app/models/gitlab_integration.rb b/app/models/gitlab_integration.rb
index 2f8d66ee8..7888df12e 100644
--- a/app/models/gitlab_integration.rb
+++ b/app/models/gitlab_integration.rb
@@ -115,6 +115,7 @@ def install_path
end
def complete_access
+ return if oauth_access_token.present? && oauth_refresh_token.present?
set_tokens(Installations::Gitlab::Api.oauth_access_token(code, redirect_uri))
end
diff --git a/app/models/google_play_store_integration.rb b/app/models/google_play_store_integration.rb
index 2245a08b3..d1e6d090f 100644
--- a/app/models/google_play_store_integration.rb
+++ b/app/models/google_play_store_integration.rb
@@ -23,7 +23,7 @@ class GooglePlayStoreIntegration < ApplicationRecord
attr_accessor :json_key_file
- after_create :draft_check
+ after_create_commit :draft_check
after_create_commit :refresh_external_app
PROD_CHANNEL = {id: :production, name: "Production", is_production: true}.freeze
diff --git a/app/models/integration.rb b/app/models/integration.rb
index e6bf6bf04..afcf3b232 100644
--- a/app/models/integration.rb
+++ b/app/models/integration.rb
@@ -197,8 +197,10 @@ def firebase_build_channel_provider
kept.build_channel.find(&:google_firebase_integration?)&.providable
end
- def existing_integration(app, providable_type)
- app.integrations.connected.find_by(providable_type: providable_type)
+ def existing_integrations_across_apps(app, providable_type)
+ Integration.connected
+ .where(integrable_id: app.organization.apps, providable_type: providable_type)
+ .select("DISTINCT ON (metadata) *")
end
def build_channels_for_platform(platform)
diff --git a/app/models/slack_integration.rb b/app/models/slack_integration.rb
index f9771bb11..f5bf2618d 100644
--- a/app/models/slack_integration.rb
+++ b/app/models/slack_integration.rb
@@ -75,6 +75,7 @@ def install_path
end
def complete_access
+ return if oauth_access_token.present?
self.oauth_access_token = Installations::Slack::Api.oauth_access_token(code)
end
diff --git a/app/views/apps/_form.html.erb b/app/views/apps/_form.html.erb
index 5233426e1..182058043 100644
--- a/app/views/apps/_form.html.erb
+++ b/app/views/apps/_form.html.erb
@@ -1,13 +1,13 @@
-<%= form_with(model: [app], builder: EnhancedFormHelper::AuthzForm) do |form| %>
+<%= render FormComponent.new(model: [app], free_form: true) do |form| %>
-
<%= form.labeled_text_field :name, "Name" %>
-
<%= form.labeled_text_field :bundle_identifier, "Bundle Identifier" %>
+
<%= form.F.labeled_text_field :name, "Name" %>
+
<%= form.F.labeled_text_field :bundle_identifier, "Bundle Identifier" %>
- <%= form.labeled_number_field :build_number,
+ <%= form.F.labeled_number_field :build_number,
"Build Number",
{ data: { domain__build_number_help_target: "input",
action: "domain--build-number-help#increment" } } %>
@@ -26,10 +26,12 @@
-
<%= form.labeled_select :platform, "Mobile Platform", options_for_select(App.allowed_platforms, "Android") %>
-
<%= form.labeled_tz_select :timezone, "Timezone", default_timezones, { model: ActiveSupport::TimeZone } %>
-
<%= form.labeled_textarea :description, "Description" %>
+
<%= form.F.labeled_select :platform, "Mobile Platform", options_for_select(App.allowed_platforms, "Android") %>
+
<%= form.F.labeled_tz_select :timezone, "Timezone", default_timezones, { model: ActiveSupport::TimeZone } %>
+
<%= form.F.labeled_textarea :description, "Description" %>
- <%= form.authz_submit "Add an app", "plus.svg" %>
+ <% form.with_action do %>
+ <%= form.F.authz_submit "Add an app", "plus.svg" %>
+ <% end %>
<% end %>
diff --git a/app/views/integrations/_app_reuseable.erb b/app/views/integrations/_app_reuseable.erb
new file mode 100644
index 000000000..3dbc26d9f
--- /dev/null
+++ b/app/views/integrations/_app_reuseable.erb
@@ -0,0 +1,21 @@
+<%= render FormComponent.new(model: [app, integration], url: url, method: :post, free_form: true) do |form| %>
+
+ <% integrations_options = existing_integrations.each_with_index.map { |integration, i|
+ {
+ title: integration.app.name,
+ subtitle: integration.providable.connection_data,
+ icon: "integrations/logo_#{provider}.png",
+ opt_name: :existing_integration_id,
+ opt_value: integration.id,
+ options: {checked: i == 0}
+ }
+ } %>
+ <%= render OptionCardsComponent.new(form: form.F, options: integrations_options) %>
+ <%= form.F.hidden_field :category, value: category %>
+ <%= form.F.hidden_field "providable[type]", value: type %>
+
+
+ <% form.with_action do %>
+ <%= form.F.authz_submit "Save", "archive.svg", size: :sm %>
+ <% end %>
+<% end %>
diff --git a/app/views/integrations/_reusable.html.erb b/app/views/integrations/_reusable.html.erb
index f1b7520c9..ba1af29fb 100644
--- a/app/views/integrations/_reusable.html.erb
+++ b/app/views/integrations/_reusable.html.erb
@@ -1,7 +1,6 @@
<%= form_with(model: [app, integration], method: :post, builder: EnhancedFormHelper::AuthzForm, url:, data: { turbo: false }) do |form| %>
<%= form.hidden_field :category, value: category %>
- <%= form.fields_for :providable do |subform| %>
- <%= subform.hidden_field :type, value: type %>
- <% end %>
+ <%= form.hidden_field :existing_integration_id, value: existing_integration.id %>
+ <%= form.hidden_field "providable[type]", value: type %>
<%= form.authz_submit "Reuse existing integration", "repeat.svg", scheme: :supporting, size: :xxs %>
<% end %>
diff --git a/app/views/trains/_versioning_strategy.html.erb b/app/views/trains/_versioning_strategy.html.erb
deleted file mode 100644
index 6edc5a01d..000000000
--- a/app/views/trains/_versioning_strategy.html.erb
+++ /dev/null
@@ -1,10 +0,0 @@
-
- <%= form.label "Strategy", class: "block text-sm font-medium mb-1" %>
- <%= form.select :versioning_strategy,
- options_for_select(Train.versioning_strategies.transform_values(&:titleize).invert, train.versioning_strategy),
- { required: true, },
- { class: text_field_classes(is_disabled: train.persisted?),
- disabled: train.persisted? } %>
-
diff --git a/config/routes.rb b/config/routes.rb
index e48dd5aea..5c99af197 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -127,10 +127,8 @@
end
resources :integrations, only: %i[index create destroy] do
- member do
- post :reuse
- end
collection do
+ post :reuse
get :connect, to: "integrations#connect", as: :connect
resource :google_play_store, only: [:create],
diff --git a/spec/controllers/integrations_controller_spec.rb b/spec/controllers/integrations_controller_spec.rb
index eb88cd802..217208ecf 100644
--- a/spec/controllers/integrations_controller_spec.rb
+++ b/spec/controllers/integrations_controller_spec.rb
@@ -5,18 +5,18 @@
let(:organization) { app.organization }
let(:user) { create(:user, :with_email_authentication, :as_developer, member_organization: organization) }
let(:existing_integration) { create(:integration, status: "connected", category: "version_control", providable: create(:github_integration), integrable: app, metadata: {id: Faker::Number.number(digits: 8)}) }
- let(:integration) { create(:integration, category: "ci_cd", providable: create(:github_integration), integrable: app) }
+ let(:integration) { build(:integration, category: "ci_cd", providable: create(:github_integration), integrable: app) }
before do
sign_in user.email_authentication
allow_any_instance_of(described_class).to receive(:current_user).and_return(user)
- allow_any_instance_of(described_class).to receive(:set_integration)
end
describe "POST #reuse" do
context "when the existing integration is not connected or does not exist" do
it "redirects to the integrations path with an error message" do
- post :reuse, params: {id: Faker::Internet.uuid, app_id: app.id}
+ existing_integration.update(status: "disconnected")
+ post :reuse, params: {integration: {existing_integration_id: existing_integration.id}, app_id: app.id}
expect(response).to redirect_to(app_integrations_path(app))
expect(flash[:alert]).to eq("Integration not found or not connected.")
@@ -24,15 +24,11 @@
end
context "when the existing integration is connected" do
- before do
- allow(Integration).to receive(:find_by).with(id: existing_integration.id.to_s).and_return(existing_integration)
- end
-
it "reuses the integration and redirects to the integrations path with a success message" do
new_integration = instance_double(Integration, save: true)
allow(controller).to receive(:initiate_integration).and_return(new_integration)
- post :reuse, params: {id: existing_integration.id, app_id: app.id}
+ post :reuse, params: {integration: {existing_integration_id: existing_integration.id}, app_id: app.id}
expect(response).to redirect_to(app_integrations_path(app))
expect(flash[:notice]).to eq("#{existing_integration.providable_type} integration reused successfully.")
@@ -42,7 +38,7 @@
new_integration = instance_double(Integration, save: false, errors: instance_double(ActiveModel::Errors, full_messages: ["Save failed"]))
allow(controller).to receive(:initiate_integration).and_return(new_integration)
- post :reuse, params: {id: existing_integration.id, app_id: app.id}
+ post :reuse, params: {integration: {existing_integration_id: existing_integration.id}, app_id: app.id}
expect(response).to redirect_to(app_integrations_path(app))
expect(flash[:error]).to eq("Save failed")