diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 1607d607b..d3422fa90 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -17,20 +17,12 @@ Rails/I18nLocaleTexts: - 'app/controllers/integrations_controller.rb' - 'app/controllers/releases_controller.rb' - 'app/controllers/signed_in_application_controller.rb' - - 'app/controllers/steps_controller.rb' - 'app/controllers/trains_controller.rb' - 'app/controllers/release_platforms_controller.rb' - - 'app/controllers/commits_controller.rb' - 'app/controllers/build_queues_controller.rb' - 'app/controllers/accounts/organizations_controller.rb' - 'app/mailers/test_mailer.rb' - - 'app/models/deployment.rb' - - 'app/models/step.rb' - 'app/models/train.rb' - - 'app/controllers/deployments_controller.rb' - - 'app/controllers/step_runs_controller.rb' - - 'app/controllers/staged_rollouts_controller.rb' - - 'app/controllers/deployment_runs_controller.rb' - 'app/controllers/release_metadata_controller.rb' - 'app/controllers/integrations/slack_controller.rb' - 'app/controllers/integrations/google_firebase_controller.rb' diff --git a/.v2_todo.yml b/.v2_todo.yml deleted file mode 100644 index 79536a727..000000000 --- a/.v2_todo.yml +++ /dev/null @@ -1,19 +0,0 @@ -models_to_delete: - - app/models/step.rb - - app/models/step_run.rb - - app/models/deployment.rb - - app/models/deployment_run.rb - - app/models/staged_rollout.rb -jobs_to_delete: - - deployments/* - - releases/cancel_step_run.rb - - releases/cancel_workflow_run_job.rb - - releases/find_build_job.rb - - releases/find_workflow_run.rb - - releases/trigger_workflow_run_job.rb - - releases/upload_artifact.rb - - releases/fetch_health_metrics_job.rb -poros_to_migrate: - libs/charts/devops_report.rb: queries/devops.rb - libs/triggers/pre_release: coordinators/pre_release - libs/triggers/post_release: coordinators/post_release diff --git a/app/assets/images/workflow.svg b/app/assets/images/workflow.svg index 164272c0d..b83cd3f6d 100644 --- a/app/assets/images/workflow.svg +++ b/app/assets/images/workflow.svg @@ -1 +1,6 @@ - + + + + + diff --git a/app/components/all_builds_table_component.html.erb b/app/components/all_builds_table_component.html.erb index 4f298e38f..3a1e9f6a3 100644 --- a/app/components/all_builds_table_component.html.erb +++ b/app/components/all_builds_table_component.html.erb @@ -48,7 +48,7 @@
- <%= Deployment.display.pluralize %> + Submissions
@@ -118,13 +118,13 @@ <% if build.ci_link.present? %> <%= link_to_external build.ci_link, title: "link to CI" do %> - <%= inline_svg('workflow.svg', classname: "inline-flex w-5 mr-2") %> + <%= render V2::IconComponent.new("workflow.svg", size: :lg) %> <% end %> <% end %> <% if build.download_url.present? %> <%= link_to_external build.download_url, title: "download build" do %> - <%= inline_svg('download.svg', classname: "inline-flex w-5") %> + <%= render V2::IconComponent.new("download.svg", size: :lg) %> <% end %> <% end %> diff --git a/app/components/all_builds_table_component.rb b/app/components/all_builds_table_component.rb index 26caea36d..4c446039b 100644 --- a/app/components/all_builds_table_component.rb +++ b/app/components/all_builds_table_component.rb @@ -2,9 +2,6 @@ class AllBuildsTableComponent < ViewComponent::Base include Pagy::Frontend include ApplicationHelper include LinkHelper - include AssetsHelper - include ReleasesHelper - include DeploymentsHelper def initialize(builds:, paginator:, query_params:) @builds = builds @@ -35,7 +32,8 @@ def sort_indicator end def release_status(build) - release_status_badge(build.release_status) + ReleasePresenter::RELEASE_STATUS.fetch(build.release_status.to_sym) => {text:, status:} + status_badge(text, status) end def release_platform(build) diff --git a/app/components/build_health_component.html.erb b/app/components/build_health_component.html.erb deleted file mode 100644 index b5224f680..000000000 --- a/app/components/build_health_component.html.erb +++ /dev/null @@ -1,14 +0,0 @@ -<% if health_data.present? %> -
- <% if show_title %> -
<%= step.name %>
- <% end %> -
- <% chartable_metadata.each do |metadata| %> - <% if chartable?(metadata) %> - <%= render ChartComponent.new(chart_data(metadata)) %> - <% end %> - <% end %> -
-
-<% end %> diff --git a/app/components/build_health_component.rb b/app/components/build_health_component.rb deleted file mode 100644 index 544a5f8d9..000000000 --- a/app/components/build_health_component.rb +++ /dev/null @@ -1,86 +0,0 @@ -class BuildHealthComponent < ViewComponent::Base - attr_reader :release_platform_run, :step, :show_title - delegate :current_user, to: :helpers - - def initialize(step:, release_platform_run:, show_title: true) - @step = step - @release_platform_run = release_platform_run - @show_title = show_title - end - - def step_runs - @step_runs ||= release_platform_run.step_runs_for(step).not_failed.sequential - end - - def chartable_metadata - default_metadata = ["app_size"] - external_metadata = release_platform_run - .external_builds - .last - .normalized_metadata - .filter(&:numerical?) - .map(&:identifier) - (default_metadata + external_metadata).uniq - end - - def app_size_data - return nil unless step_runs.any?(&:build_size) - - { - identifier: "app_size", - name: "App Size", - description: "", - type: "number", - unit: "MB", - data: step_runs.filter_map { |srun| [srun.build_number, {"MB" => srun.build_size}] if srun.build_size }.to_h - } - end - - def health_data - return unless step_runs.size > 1 - - initial_health_data = app_size_data - if release_platform_run.external_builds.any? { |em| em.normalized_metadata.any? { |m| m.identifier == "app_size" } } - initial_health_data = {} - end - - @health_data ||= step_runs.each_with_object(initial_health_data) do |step_run, acc| - metadata = step_run.external_build&.normalized_metadata - next unless metadata - - metadata.each do |data| - next unless data.numerical? - - acc[data.identifier] ||= { - identifier: data.identifier, - name: data.name, - description: data.description, - type: data.type, - unit: data.unit, - data: {} - } - - acc[data.identifier][:data][step_run.build_number] = {data.unit => data.value} - end - end - end - - def chartable?(metadata_id) - health_data[metadata_id].present? - end - - def chart_data(metadata_id) - return if health_data[metadata_id].blank? - { - data: health_data[metadata_id][:data].sort.to_h, - type: "line", - value_format: health_data[metadata_id][:type], - name: metadata_id, - title: health_data[metadata_id][:name], - scope: "Across all the builds", - help_text: health_data[metadata_id][:description], - show_x_axis: false, - show_y_axis: true - } - end -end diff --git a/app/components/build_metadata_component.html.erb b/app/components/build_metadata_component.html.erb deleted file mode 100644 index 1f78ea18b..000000000 --- a/app/components/build_metadata_component.html.erb +++ /dev/null @@ -1,15 +0,0 @@ -
- <%= render MetricCardComponent.new(name: "Build Metadata", values: metrics) %> - <%= render TableComponent.new(columns: %w[name value]) do |table| %> - <% text_values.each do |metadata| %> - <% table.with_row do |row| %> - <% row.with_cell do %> - <%= metadata.name %> - <% end %> - <% row.with_cell do %> - <%= metadata.value %> - <% end %> - <% end %> - <% end %> - <% end %> -
diff --git a/app/components/build_metadata_component.rb b/app/components/build_metadata_component.rb deleted file mode 100644 index 5a07cde80..000000000 --- a/app/components/build_metadata_component.rb +++ /dev/null @@ -1,34 +0,0 @@ -class BuildMetadataComponent < ViewComponent::Base - attr_reader :step_run - - UNIT_MAPPING = { - "seconds" => "secs", - "milliseconds" => "ms", - "percentage" => "%" - } - - def initialize(step_run:) - @step_run = step_run - end - - def external_build - @external_build ||= step_run.external_build - end - - def metrics - values = {"App Size" => {value: step_run.build_size, unit: "MB"}} - values.merge number_values.to_h { |metadata| - [metadata.name, {value: metadata.value, unit: UNIT_MAPPING.fetch(metadata.unit, metadata.unit)}] - } - end - - def number_values - return [] unless external_build - external_build.normalized_metadata.filter(&:numerical?) - end - - def text_values - return [] unless external_build - external_build.normalized_metadata.filter(&:textual?) - end -end diff --git a/app/components/chart_component.rb b/app/components/chart_component.rb index 44cdee89d..297a0bd90 100644 --- a/app/components/chart_component.rb +++ b/app/components/chart_component.rb @@ -1,5 +1,4 @@ class ChartComponent < V2::BaseComponent - include AssetsHelper using RefinedHash CHART_TYPES = %w[area line stacked-bar polar-area] InvalidChartType = Class.new(StandardError) diff --git a/app/components/event_timeline/event_message_component.html.erb b/app/components/event_timeline/event_message_component.html.erb index 7dbb7e486..56c280701 100644 --- a/app/components/event_timeline/event_message_component.html.erb +++ b/app/components/event_timeline/event_message_component.html.erb @@ -1,7 +1,7 @@ <%= author_avatar(passport.author_full_name) %>
- <%= inline_svg("#{passport_icon(passport)}.svg", classname: "inline-flex antialiased w-5") %> + <%= render V2::IconComponent.new("#{passport_icon(passport)}.svg", size: :lg, classes: "antialiased") %>
<%= raw(passport.message) %>
diff --git a/app/components/event_timeline/event_message_component.rb b/app/components/event_timeline/event_message_component.rb index e031f69eb..f57be7602 100644 --- a/app/components/event_timeline/event_message_component.rb +++ b/app/components/event_timeline/event_message_component.rb @@ -1,13 +1,15 @@ class EventTimeline::EventMessageComponent < ViewComponent::Base - include AssetsHelper include ApplicationHelper STAMPABLE_ICONS = { - DeploymentRun => "truck_delivery", + StoreSubmission => "truck_delivery", Commit => "git_commit", - StepRun => "box", + PreProdRelease => "box", + ProductionRelease => "box", ReleasePlatformRun => "bolt", - Release => "bolt" + Release => "bolt", + WorkflowRun => "workflow", + Build => "drill" } BADGE = { diff --git a/app/components/event_timeline_component.html.erb b/app/components/event_timeline_component.html.erb index caf24c637..1d4293982 100644 --- a/app/components/event_timeline_component.html.erb +++ b/app/components/event_timeline_component.html.erb @@ -5,7 +5,7 @@ <% end %>
- <%= inline_svg('calendar_time.svg', classname: "inline-flex w-10") %> + <%= render V2::IconComponent.new("calendar_time.svg", size: :xl_3) %> <%= toggle_for(hide_timeline?(index)) %>
diff --git a/app/components/event_timeline_component.rb b/app/components/event_timeline_component.rb index d76778d7c..2864d33f4 100644 --- a/app/components/event_timeline_component.rb +++ b/app/components/event_timeline_component.rb @@ -1,16 +1,12 @@ class EventTimelineComponent < ViewComponent::Base include ApplicationHelper - include AssetsHelper def initialize(app:, events:) @app = app @events = events end - EXCLUSIONS = { - "DeploymentRun" => ["created"], - "StepRun" => %w[build_available finished] - } + EXCLUSIONS = {} def events_by_days @events diff --git a/app/components/final_summary_component.html.erb b/app/components/final_summary_component.html.erb deleted file mode 100644 index 29a39760f..000000000 --- a/app/components/final_summary_component.html.erb +++ /dev/null @@ -1,213 +0,0 @@ -
-
- <%= render partial: "shared/live_release/section_title", locals: { heading: "Summary", subheading: "#{time_ago_in_words(release.completed_at)} ago" } %> - <% if loaded? %> -
- Great work team! This release is complete! -
- <% end %> -
- - <% if loaded? %> -
šŸŽ‰ -
- <% end %> -
-
- <% if loaded? %> - <%= render TabGroupComponent.new(groups: tab_groups) do |component| %> - <% component.with_tab do %> -
-
-
-
-
<%= overall.version %>
-
Final version
-
- - <% if overall.tag.present? %> -
-
<%= link_to_external overall.tag, overall.tag_url, class: "underline" %>
-
Release tag
-
- <% end %> - - <% if overall.hotfixes.present? %> -
-
<%= hotfixes %>
-
Hotfixes
-
- <% end %> - -
-
<%= overall.commits_count %>
-
Commits in release
-
- - <% if hotfix? %> -
-
<%= overall.hotfixed_from %>
-
Hotfix released from
-
- <% end %> - - <% if backmerges? %> -
-
<%= overall.backmerge_pr_count %>
-
Backmerge PRs
-
- -
-
<%= overall.backmerge_failure_count %>
-
Automatic Backmerge failures
-
- <% end %> -
- -
-
-
<%= duration_in_words overall.duration %>
-
Release duration
-
- -
-
<%= time_format overall.kickoff_at %>
-
Kicked Off
-
- -
-
<%= time_format overall.finished_at %>
-
Finished
-
- - <% if reldex? %> -
-
- <%= render V2::Reldex::StatusComponent.new(release:, reldex_score: reldex) %> -
-
Reldex
-
- <% end %> -
-
-
- <% end %> - - <% if store_versions? %> - <% component.with_tab do %> - - <% end %> - <% end %> - - <% component.with_tab do %> - - <% end %> - - <% if pull_requests? %> - <% component.with_tab do %> - - <% end %> - <% end %> - - <% if teams_present? %> - <% component.with_tab do %> - - <% end %> - <% end %> - <% end %> - <% else %> -
-
- We're preparing a release summary for you, please wait... -
-
- <% end %> -
diff --git a/app/components/final_summary_component.rb b/app/components/final_summary_component.rb deleted file mode 100644 index 2d33a329a..000000000 --- a/app/components/final_summary_component.rb +++ /dev/null @@ -1,130 +0,0 @@ -class FinalSummaryComponent < ViewComponent::Base - include ApplicationHelper - include LinkHelper - attr_reader :release - - delegate :current_organization, :current_user, to: :helpers - delegate :team_colors, to: :current_organization - - def initialize(release:) - @release = release - end - - def summary - @summary ||= Queries::ReleaseSummary.all(release.id) - end - - def duration_in_words(interval_in_seconds) - return unless interval_in_seconds - - distance_of_time_in_words(Time.current, Time.current + interval_in_seconds.seconds, include_seconds: true) - end - - def overall - summary[:overall] - end - - def pull_requests - summary[:pull_requests] - end - - def team_stability_commits - summary[:team_stability_commits] - end - - def team_release_commits - summary[:team_release_commits] - end - - def store_versions_by_platform - summary[:store_versions].all.sort_by(&:platform).group_by(&:platform) - end - - def step_summary_by_platform - summary[:steps_summary].all.sort_by(&:platform).group_by(&:platform) - end - - def reldex - summary[:reldex] - end - - def reldex? - reldex.present? - end - - def staged_rollouts(store_version) - store_version.staged_rollouts.each do |sr| - yield(sr[:rollout_percentage], sr[:timestamp]) - end - end - - def tab_groups - [ - "Overall", - store_versions? ? "Store versions" : nil, - "Step summary", - pull_requests? ? "Pull requests" : nil, - teams_present? ? "Team analysis" : nil - ].compact - end - - def store_versions? - summary[:store_versions].all.present? - end - - def pull_requests? - pull_requests.present? - end - - def teams_present? - team_stability_commits.present? || team_release_commits.present? - end - - def loaded? - summary.present? - end - - def backmerges? - release.continuous_backmerge? - end - - def hotfix? - overall.is_hotfix - end - - def hotfixes - content_tag(:div, class: "inline-flex") do - overall.hotfixes.each_with_index do |(release_version, release_link), i| - concat link_to_external release_version, release_link, class: "underline ml-2" - end - end - end - - def staged_rollouts?(store_version) - store_version.staged_rollouts.present? - end - - def team_stability_chart - { - data: team_stability_commits&.reject { |_, value| value.zero? }, - colors: team_colors, - type: "polar-area", - value_format: "number", - name: "release_summary.stability_contributors", - show_x_axis: false, - show_y_axis: false - } - end - - def team_release_chart - { - data: team_release_commits&.reject { |_, value| value.zero? }, - colors: team_colors, - type: "polar-area", - value_format: "number", - name: "release_summary.release_contributors", - show_x_axis: false, - show_y_axis: false - } - end -end diff --git a/app/components/live_release/commits/detail_component.html.erb b/app/components/live_release/commits/detail_component.html.erb deleted file mode 100644 index 15e4aab33..000000000 --- a/app/components/live_release/commits/detail_component.html.erb +++ /dev/null @@ -1,105 +0,0 @@ -
-
- <%= commit_link %> - <%= commit_number %> -
<%= commit_info %>
-
- -
- <%= render V2::TeamPillComponent.new(team) if current_organization.teams_supported? %> - <%= render partial: "shared/live_release/commit_backmerge_status", locals: { commit: commit } %> - <%= details_toggle %> -
- -
> - <% if stale? %> -
This commit has been replaced by a newer commit.
- <% end %> - -
- <% platform_runs.each do |run| %> - <% if not_triggered?(run) %> -
<%= actionable_commit(run) %>
- <% else %> -
- <% commit.step_runs_for(run).each do |step_run| %> -
- -
"> -
- <%= step_run.step.name %> - <%= dev_show { step_run.id.strip } %> -
- - -
- -
- <% if step_run.ci_link.present? %> - <%= link_to_external "CI workflow run ā†—", step_run.ci_link, class: "underline" %> - <% else %> - CI workflow running - <% end %> - - <%= build_status_badge(step_run) %> - - <% if step_run.fetching_build? %> - <%= status_badge("Waiting for build", %w[bg-green-500 text-white], pulse: true) %> - <% end %> -
- - - <% if step_run.build_artifact_available? %> -
- <%= link_to_external "Download build ā†—", step_run.download_url, class: "underline" %> - -
- <%= render partial: "shared/build_details", locals: { step_run:, with_artifact: true } %> -
-
- <% end %> - - - <% if step_run.deployment_runs.exists? %> -
- <%= Deployment.display.pluralize %> -
- <% step_run.deployment_runs.each do |deployment_run| %> - <% deployment = deployment_run.deployment %> - -
-
- <%= render partial: "shared/deployment", locals: { deployment: deployment } %> -
- - <%= deployment_run_status_badge(deployment_run) %> - - <% if deployment_run.released? && deployment_run.rollout_percentage %> -
- - Released to <%= number_to_percentage deployment_run.rollout_percentage %> of the users - - -
-
- -
-
-
- <% end %> - - <%= render partial: "shared/live_release/external_release", locals: { deployment_run: } %> -
- <% end %> -
-
- <% end %> -
-
- <% end %> -
- <% end %> - <% end %> -
-
-
diff --git a/app/components/live_release/commits/detail_component.rb b/app/components/live_release/commits/detail_component.rb deleted file mode 100644 index de9e9e519..000000000 --- a/app/components/live_release/commits/detail_component.rb +++ /dev/null @@ -1,79 +0,0 @@ -class LiveRelease::Commits::DetailComponent < ViewComponent::Base - include ApplicationHelper - include ButtonHelper - include ReleasesHelper - include AssetsHelper - include LinkHelper - - def initialize(commit, number) - @commit = commit - @release = commit.release - @number = number - end - - attr_reader :commit, :release, :number - delegate :writer?, :current_organization, to: :helpers - delegate :stale?, :team, to: :commit - - def number_style - if stale? - "bg-slate-100 text-slate-400" - else - "bg-slate-200 text-slate-600" - end - end - - def link_style - if stale? - "text-slate-400" - else - "font-medium" - end - end - - def commit_link - link_to_external commit.message.truncate(80), commit.url, class: "underline #{link_style}" - end - - def commit_number - "##{number}" - end - - def commit_info - formatted_commit_info(commit) - end - - def apply_commit_button(platform_run) - form_with(model: commit, url: apply_release_commit_path(release, commit.id), method: :post, builder: ButtonHelper::AuthzForms) do |form| - concat form.hidden_field :release_platform_run_id, value: platform_run.id - concat form.authz_submit :blue, "Apply commit to #{platform_run.display_attr(:platform)}" - end - end - - def locked_notice - render "shared/note_box", message: "This release was completed and is now locked." - end - - def not_triggered?(platform_run) - commit != platform_run.last_commit && !stale? - end - - def actionable_commit(run) - if run.on_track? - apply_commit_button(run) - else - locked_notice - end - end - - def platform_runs - release.release_platform_runs - end - - def details_toggle - toggle_for(stale?) do - content_tag(:span, "Details", - class: "text-slate-500 font-medium underline mr-2 group-hover:text-slate-800") - end - end -end diff --git a/app/components/live_release/commits_component.html.erb b/app/components/live_release/commits_component.html.erb deleted file mode 100644 index b1b85ec09..000000000 --- a/app/components/live_release/commits_component.html.erb +++ /dev/null @@ -1,11 +0,0 @@ -
-
- <%= commits_toggle %> -
- -
- <% commits.each_with_index do |commit, index| %> - <%= render LiveRelease::Commits::DetailComponent.new(commit, commit_number(index)) %> - <% end %> -
-
diff --git a/app/components/live_release/commits_component.rb b/app/components/live_release/commits_component.rb deleted file mode 100644 index f9887f678..000000000 --- a/app/components/live_release/commits_component.rb +++ /dev/null @@ -1,26 +0,0 @@ -class LiveRelease::CommitsComponent < ViewComponent::Base - include ApplicationHelper - include AssetsHelper - - def initialize(commits) - @commits = commits - end - - def commit_count - commits.size - end - - def commit_number(index) - commit_count - index - end - - def commits_toggle - toggle_for(false, full_width: true) do - content_tag(:span, - "commits (#{commit_count})", - class: "text-xs font-semibold uppercase text-slate-500") - end - end - - attr_reader :commits, :release -end diff --git a/app/components/meta_table/meta_description_component.html.erb b/app/components/meta_table/meta_description_component.html.erb deleted file mode 100644 index 00d745944..000000000 --- a/app/components/meta_table/meta_description_component.html.erb +++ /dev/null @@ -1,2 +0,0 @@ -
<%= term %>
-
<%= content %>
diff --git a/app/components/meta_table/meta_description_component.rb b/app/components/meta_table/meta_description_component.rb deleted file mode 100644 index e322c2003..000000000 --- a/app/components/meta_table/meta_description_component.rb +++ /dev/null @@ -1,7 +0,0 @@ -class MetaTable::MetaDescriptionComponent < ViewComponent::Base - def initialize(term) - @term = term - end - - attr_reader :term -end diff --git a/app/components/meta_table_component.html.erb b/app/components/meta_table_component.html.erb deleted file mode 100644 index f70340e35..000000000 --- a/app/components/meta_table_component.html.erb +++ /dev/null @@ -1,7 +0,0 @@ -
- <% descriptions.each_with_index do |desc, idx| %> -
- <%= desc %> -
- <% end %> -
diff --git a/app/components/meta_table_component.rb b/app/components/meta_table_component.rb deleted file mode 100644 index 3dea1fa6f..000000000 --- a/app/components/meta_table_component.rb +++ /dev/null @@ -1,7 +0,0 @@ -class MetaTableComponent < ViewComponent::Base - renders_many :descriptions, MetaTable::MetaDescriptionComponent - - def bg_color(idx) - idx.odd? ? "bg-white" : "bg-slate-50" - end -end diff --git a/app/components/notification_settings_component.rb b/app/components/notification_settings_component.rb index 35ade1cfb..e0b54425f 100644 --- a/app/components/notification_settings_component.rb +++ b/app/components/notification_settings_component.rb @@ -1,28 +1,12 @@ class NotificationSettingsComponent < ViewComponent::Base include ApplicationHelper - include ButtonHelper - include AssetsHelper InvalidNotificationSettings = Class.new(StandardError) NOTIFICATIONS = { release_scheduled: {icon: "v2/clock.svg", description: "Your scheduled release will run in a few hours"}, release_started: {icon: "v2/zap.svg", description: "A new release was started for the release train"}, - step_started: {icon: "v2/play.svg", description: "A step was started for the release train"}, - build_available: {icon: "v2/drill.svg", description: "A new build is available for a direct download"}, - step_failed: {icon: "v2/alert_circle.svg", description: "A step failed to run fully for the release train"}, backmerge_failed: {icon: "v2/alert_circle.svg", description: "Tramline failed to create a backmerge PR for the commit in the release"}, - submit_for_review: {icon: "v2/clipboard_list.svg", description: "A build was submitted for review to store for the release"}, - review_approved: {icon: "v2/clipboard_check.svg", description: "A production build review was approved by the store"}, - review_failed: {icon: "v2/alert_circle.svg", description: "A production build review was rejected by the store"}, - staged_rollout_updated: {icon: "v2/arrow_big_up_dash.svg", description: "The staged rollout was increased for the production build in the store"}, - staged_rollout_paused: {icon: "v2/pause.svg", description: "The staged rollout was paused for the production build in the store"}, - staged_rollout_resumed: {icon: "v2/play.svg", description: "The staged rollout was resumed for the production build in the store"}, - staged_rollout_halted: {icon: "v2/stop_circle.svg", description: "The staged rollout was halted for the production build in the store"}, - staged_rollout_completed: {icon: "v2/sparkles.svg", description: "The staged rollout was completed for the production build in the store"}, - staged_rollout_fully_released: {icon: "v2/fast_forward.svg", description: "The staged rollout was fully released to 100% for the production build in the store"}, - deployment_finished: {icon: "v2/truck.svg", description: "The distribution was successful to a channel"}, - deployment_failed: {icon: "v2/alert_circle.svg", description: "The distribution to a channel failed"}, release_ended: {icon: "v2/sparkles.svg", description: "The release finished successfully"}, release_stopped: {icon: "v2/stop_circle.svg", description: "The release was stopped before it finished"}, release_health_events: {icon: "v2/heart_pulse.svg", description: "A health event has occurred for the release"}, diff --git a/app/components/release_monitoring_component.html.erb b/app/components/release_monitoring_component.html.erb deleted file mode 100644 index 30bece8ec..000000000 --- a/app/components/release_monitoring_component.html.erb +++ /dev/null @@ -1,98 +0,0 @@ -
- <% if show_version_info %> -
<%= build_identifier %>
- <% end %> - -
- <% if :staged_rollout.in? metrics %> - <% if staged_rollout.nil? %> - <%= render StatCardComponent.new("Staged Rollout", - type: :empty, - empty_stat_help_text: "This data loads up when the staged/phased rollout begins.") %> - <% else %> - <%= render ProgressCardComponent.new(name: "Staged Rollout", - current: staged_rollout_percentage, - subtitle: staged_rollout_text, - provider: store_provider, - size:, - external_url: external_link) %> - <% end %> - <% end %> - - <% if :adoption_rate.in? metrics %> - <% if empty_component? %> - <%= render StatCardComponent.new("Adoption Rate", - type: :empty, - empty_stat_help_text: "This data gets pulled from your monitoring integration.") %> - <% else %> - <%= render ProgressCardComponent.new(name: "Adoption Rate", - current: adoption_rate, - subtitle: "Last 24 hours", - provider: monitoring_provider, - size:, - external_url: external_link) %> - <% end %> - <% end %> - - <% if :stability.in? metrics %> - <% if empty_component? %> - <%= render StatCardComponent.new("Stability", - type: :empty, - empty_stat_help_text: "This data gets pulled from your monitoring integration.") %> - <% else %> - <%= render MetricCardComponent.new(name: "Stability", - values: { "users" => user_stability, "sessions" => session_stability }, - provider: monitoring_provider, - size:, - external_url: monitoring_provider_url) %> - <% end %> - <% end %> - - <% if :errors.in? metrics %> - <% if empty_component? %> - <%= render StatCardComponent.new("Errors", - type: :empty, - empty_stat_help_text: "This data gets pulled from your monitoring integration.") %> - <% else %> - <%= render MetricCardComponent.new(name: "Errors", - values: { "total" => errors_count, "new" => new_errors_count }, - provider: monitoring_provider, - size:, - external_url: monitoring_provider_url) %> - <% end %> - <% end %> - - <% if show_release_health? %> -
-
-
- Overall Release Health: - <%= release_health %> - <%= health_status_duration %> -
- <%= render V2::ButtonComponent.new( - scheme: :naked_icon, - options: rules_app_train_path(app, train), - size: :none, - authz: true, - type: :link_external) do |b| - b.with_icon("v2/arrow_right.svg", size: :sm) - b.with_tooltip("Go to rules configuration", placement: "top") - end %> -
- - <% if events.present? %> -
- <%= render TimelineComponent.new(events:) %> -
- <% end %> -
- <% end %> - - <% if :adoption_chart.in? metrics %> - <% if adoption_chart_data.present? %> -
<%= render ChartComponent.new(adoption_chart_data) %>
- <% end %> - <% end %> -
-
diff --git a/app/components/release_monitoring_component.rb b/app/components/release_monitoring_component.rb deleted file mode 100644 index c820f3ed0..000000000 --- a/app/components/release_monitoring_component.rb +++ /dev/null @@ -1,169 +0,0 @@ -class ReleaseMonitoringComponent < V2::BaseComponent - METRICS = [:staged_rollout, :adoption_rate, :adoption_chart, :errors, :stability] - SIZES = { - compact: {cols: 2}, - default: {cols: 3} - } - - def initialize(deployment_run:, metrics: METRICS, show_version_info: true, size: :default, num_events: 3) - raise ArgumentError, "metrics must be one of #{METRICS}" unless (metrics - METRICS).empty? - - @deployment_run = deployment_run - @metrics = metrics - @show_version_info = show_version_info - @size = size - @cols = SIZES[@size][:cols] - @num_events = num_events - end - - delegate :adoption_rate, to: :release_data, allow_nil: true - delegate :app, :train, :release_health_rules, :platform, :external_link, :show_health?, to: :deployment_run - delegate :monitoring_provider, to: :app - delegate :current_user, to: :helpers - - attr_reader :deployment_run, :metrics, :show_version_info, :size - - def show_release_health? - release_health_rules.present? && show_health? - end - - def empty_component? - release_data.blank? - end - - def build_identifier - "#{deployment_run.build_version} (#{deployment_run.build_number})" - end - - def monitoring_provider_url - monitoring_provider.dashboard_url(platform:, release_id: release_data&.external_release_id) - end - - def store_provider - deployment_run.integration.providable - end - - def release_data - @release_data ||= deployment_run&.latest_health_data - end - - def staged_rollout - @staged_rollout ||= deployment_run&.staged_rollout - end - - def events - deployment_run.release_health_events.reorder("event_timestamp DESC").first(@num_events).map do |event| - type = event.healthy? ? :success : :error - rule_health = event.healthy? ? "healthy" : "unhealthy" - { - timestamp: time_format(event.event_timestamp, with_year: false), - title: "#{event.release_health_rule.display_name} is #{rule_health}", - description: event_description(event), - type: - } - end - end - - def event_description(event) - return if event.healthy? - metric = event.release_health_metric - triggers = event.release_health_rule.triggers - triggers.filter_map do |expr| - value = metric.evaluate(expr.metric) - expr.evaluation(value) => { is_healthy:, expression: } - expression unless is_healthy - end.join(", ") - end - - def release_healthy? - @is_healthy ||= deployment_run.healthy? - end - - def release_health - return "Not Available" if release_data.blank? - return "Healthy" if release_healthy? - "Unhealthy" - end - - def health_status_duration - last_event = deployment_run.release_health_events.reorder("event_timestamp DESC").first - return unless last_event - ago_in_words(last_event.event_timestamp, prefix: "since", suffix: nil) - end - - def release_health_class - return "text-main-600" if release_data.blank? - return "text-green-800" if release_healthy? - "text-red-800" - end - - def staged_rollout_percentage - return Deployment::FULL_ROLLOUT_VALUE unless staged_rollout - staged_rollout.last_rollout_percentage || 0 - end - - def staged_rollout_text - return "Fully Released" unless staged_rollout - return "Fully Released" if staged_rollout.fully_released? - "Stage #{staged_rollout.display_current_stage} of #{staged_rollout.config.size}" - end - - def user_stability - value = release_data.user_stability.blank? ? "-" : "#{release_data.user_stability}%" - metric_data("user_stability", value) - end - - def session_stability - value = release_data.session_stability.blank? ? "-" : "#{release_data.session_stability}%" - metric_data("session_stability", value) - end - - def errors_count - metric_data("errors_count", release_data.errors_count) - end - - def new_errors_count - metric_data("new_errors_count", release_data.new_errors_count) - end - - def adoption_chart_data - return unless release_data - range_end = release_data.fetched_at - range_start = deployment_run.created_at - @chart_data ||= deployment_run - .release_health_metrics - .group_by_day(:fetched_at, range: range_start..range_end) - .maximum("round(CAST(sessions_in_last_day::float * 100 / total_sessions_in_last_day::float as numeric), 2)") - .compact - .map { |k, v| [k.strftime("%d %b"), {adoption_rate: v, rollout_percentage: deployment_run.rollout_percentage_at(k)}] } - .last(10) - .to_h - - return unless @chart_data.keys.size >= 2 - - { - data: @chart_data, - type: "line", - value_format: "number", - name: "release_health.adoption_rate" - } - end - - def grid_cols - "grid-cols-#{@cols}" - end - - def full_span - "col-span-#{@cols}" - end - - private - - def metric_data(metric_name, value) - { - value:, - is_healthy: release_data.metric_healthy?(metric_name), - rules: release_data.rules_for_metric(metric_name) - } - end -end diff --git a/app/components/search_bar_component.rb b/app/components/search_bar_component.rb index da00027d8..f75099cff 100644 --- a/app/components/search_bar_component.rb +++ b/app/components/search_bar_component.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class SearchBarComponent < ViewComponent::Base - include AssetsHelper - def initialize(path:, placeholder:, value:, turbo_frame: nil) @path = path @placeholder = placeholder diff --git a/app/components/staged_rollout/config_component.html.erb b/app/components/staged_rollout/config_component.html.erb deleted file mode 100644 index 62aebcb69..000000000 --- a/app/components/staged_rollout/config_component.html.erb +++ /dev/null @@ -1,18 +0,0 @@ -
- <% config.each_with_index do |stage, idx| %> -
-
Stage <%= idx + 1 %>
-
-
> -
-
-
- - <% if until_current?(idx) %> -
- <%= inline_svg("progress_check.svg", classname: "w-5 fill-current text-green-600") %> -
- <% end %> -
- <% end %> -
diff --git a/app/components/staged_rollout/config_component.rb b/app/components/staged_rollout/config_component.rb deleted file mode 100644 index 31eaa38a0..000000000 --- a/app/components/staged_rollout/config_component.rb +++ /dev/null @@ -1,27 +0,0 @@ -class StagedRollout::ConfigComponent < ViewComponent::Base - include AssetsHelper - - def initialize(config:, current_stage: nil, disabled: false) - @config = config - @current_stage = current_stage - @disabled = disabled - end - - attr_reader :config, :current_stage, :disabled - - def stage_perc(stage) - return "0%" if stage.nil? - "#{stage}%" - end - - def until_current?(stage) - return false if current_stage.nil? - current_stage >= stage - end - - def wrapper_class - base_class = "w-48" - base_class += " opacity-50" if disabled - base_class - end -end diff --git a/app/components/staged_rollout_component.html.erb b/app/components/staged_rollout_component.html.erb deleted file mode 100644 index d40d01477..000000000 --- a/app/components/staged_rollout_component.html.erb +++ /dev/null @@ -1,27 +0,0 @@ -
-
-
-
-
<%= current_stage_perc %>
-
of users
-
-
<%= badge %>
-
<%= stage_help %>
-
- -
-
- <% actions.each do |action| %> - <%= form_with(model: [release, step_run, deployment_run, staged_rollout], url: action[:form_url], - data: { turbo_confirm: action[:confirm] }, builder: ButtonHelper::AuthzForms) do |form| %> - <% form.authz_submit action[:type], action[:name], class: "btn-xs" %> - <% end %> - <% end %> -
-
-
- -
- <%= render StagedRollout::ConfigComponent.new(config:, current_stage:, disabled: fully_released?) %> -
-
diff --git a/app/components/staged_rollout_component.rb b/app/components/staged_rollout_component.rb deleted file mode 100644 index bc4771891..000000000 --- a/app/components/staged_rollout_component.rb +++ /dev/null @@ -1,125 +0,0 @@ -class StagedRolloutComponent < ViewComponent::Base - include ApplicationHelper - include ButtonHelper - include AssetsHelper - - HALT_CONFIRM = "You are about to halt the rollout of this build to production.\n\nAre you sure?" - START_RELEASE_CONFIRM = "You are about to release this build to the first stage in production.\n\nAre you sure?" - RELEASE_CONFIRM = "You are about to release this build to the next stage in production.\n\nAre you sure?" - FULLY_RELEASE_CONFIRM = "You are about to release this build to all users in production.\n\nAre you sure?" - PAUSE_RELEASE_CONFIRM = "You are about to pause the scheduled phased release in production.\n\nAre you sure?" - RESUME_RELEASE_CONFIRM = "You are about to resume the scheduled phased release in production.\n\nAre you sure?" - RESUME_HALTED_CONFIRM = "You are about to resume the halted rollout of this build in production.\n\nAre you sure?" - - def initialize(staged_rollout) - @staged_rollout = staged_rollout - @deployment_run = @staged_rollout.deployment_run - @step_run = @deployment_run.step_run - @release = @step_run.release - end - - delegate :started?, - :failed?, - :stopped?, - :completed?, - :paused?, - :fully_released?, - :config, - :started?, - :created?, - :current_stage, - :last_rollout_percentage, - to: :staged_rollout - delegate :controllable_rollout?, :rolloutable?, :automatic_rollout?, to: :deployment_run - - def actions - actions = [] - - if controllable_rollout? - actions << {form_url: increase_release_path, confirm: START_RELEASE_CONFIRM, type: :blue, name: "Start Rollout"} if created? - actions << {form_url: increase_release_path, confirm: RELEASE_CONFIRM, type: :blue, name: "Increase Rollout"} if started? - actions << {form_url: increase_release_path, confirm: RELEASE_CONFIRM, type: :blue, name: "Retry"} if failed? - actions << {form_url: resume_release_path, confirm: RESUME_HALTED_CONFIRM, type: :blue, name: "Resume Rollout"} if stopped? - end - - if automatic_rollout? - actions << {form_url: resume_release_path, confirm: RESUME_RELEASE_CONFIRM, type: :blue, name: "Resume Phased Release"} if paused? - actions << {form_url: pause_release_path, confirm: PAUSE_RELEASE_CONFIRM, type: :amber, name: "Pause Phased Release"} if started? && last_rollout_percentage - end - - if last_rollout_percentage - actions << {form_url: halt_release_path, confirm: HALT_CONFIRM, type: :red, name: "Halt Release"} if started? || paused? - actions << {form_url: full_release_path, confirm: FULLY_RELEASE_CONFIRM, type: :blue, name: "Release to 100%"} if started? - end - - actions - end - - def current_stage_perc - return "0%" if last_rollout_percentage.nil? - "#{last_rollout_percentage}%" - end - - def stage_help - return if completed? || fully_released? - return "Halted at the #{current_stage.succ.ordinalize} stage of rollout" if stopped? - return "Paused at the #{current_stage.succ.ordinalize} stage of rollout" if paused? - - if started? - "In the #{current_stage.succ.ordinalize} stage of rollout" - else - "Rollout has not kicked-off yet" - end - end - - private - - delegate :writer?, to: :helpers - attr_reader :release, :step_run, :deployment_run, :staged_rollout - - def increase_release_path - increase_deployment_run_staged_rollout_path(deployment_run) - end - - def halt_release_path - halt_deployment_run_staged_rollout_path(deployment_run) - end - - def pause_release_path - pause_deployment_run_staged_rollout_path(deployment_run) - end - - def resume_release_path - resume_deployment_run_staged_rollout_path(deployment_run) - end - - def full_release_path - fully_release_deployment_run_staged_rollout_path(deployment_run) - end - - def badge - status = staged_rollout.status.to_sym - - status, styles = - case status - when :created - ["Ready", :routine] - when :started - ["Active", :ongoing] - when :failed - ["Failed", :failure] - when :completed - ["Completed", :success] - when :stopped - ["Halted", :inert] - when :fully_released - ["Released to all users", :success] - when :paused - ["Paused phased release", :ongoing] - else - ["Unknown", :neutral] - end - - status_badge(status, styles) - end -end diff --git a/app/components/tab_group_component.html.erb b/app/components/tab_group_component.html.erb deleted file mode 100644 index bdf3ba65b..000000000 --- a/app/components/tab_group_component.html.erb +++ /dev/null @@ -1,22 +0,0 @@ -
- - -
- <% tabs.each do |tab| %> - <%= tab %> - <% end %> -
-
diff --git a/app/components/tab_group_component.rb b/app/components/tab_group_component.rb deleted file mode 100644 index 75447cbe6..000000000 --- a/app/components/tab_group_component.rb +++ /dev/null @@ -1,17 +0,0 @@ -class TabGroupComponent < ViewComponent::Base - renders_one :tab_heading - renders_many :tabs - - def initialize(groups:) - @groups = groups - end - - def tab_headings - @groups.each_with_index.map do |group, idx| - { - selected: idx == 0, - name: group - } - end - end -end diff --git a/app/components/table_component.html.erb b/app/components/table_component.html.erb deleted file mode 100644 index 277c604ae..000000000 --- a/app/components/table_component.html.erb +++ /dev/null @@ -1,27 +0,0 @@ -
- <% if heading %> -
- <%= heading %> -
- <% end %> - <% if rows.present? %> -
- - - - <% columns.each do |column| %> - - <% end %> - - - - <% rows.each do |row| %> - <%= row %> - <% end %> - -
-
<%= column %>
-
-
- <% end %> -
diff --git a/app/components/table_component.rb b/app/components/table_component.rb deleted file mode 100644 index 634216280..000000000 --- a/app/components/table_component.rb +++ /dev/null @@ -1,10 +0,0 @@ -class TableComponent < ViewComponent::Base - renders_one :heading - renders_many :rows, TableComponent::RowComponent - - def initialize(columns:) - @columns = columns - end - - attr_reader :columns -end diff --git a/app/components/table_component/row_component.html.erb b/app/components/table_component/row_component.html.erb deleted file mode 100644 index 32fd1623f..000000000 --- a/app/components/table_component/row_component.html.erb +++ /dev/null @@ -1,5 +0,0 @@ - - <% cells.each do |cell| %> - <%= cell %> - <% end %> - diff --git a/app/components/table_component/row_component.rb b/app/components/table_component/row_component.rb deleted file mode 100644 index 158136ecf..000000000 --- a/app/components/table_component/row_component.rb +++ /dev/null @@ -1,9 +0,0 @@ -class TableComponent::RowComponent < ViewComponent::Base - renders_many :cells, "CellComponent" - - class CellComponent < ViewComponent::Base - def call - content_tag :td, content, {class: "p-2 whitespace-nowrap"} - end - end -end diff --git a/app/components/v2/base_release_component.rb b/app/components/v2/base_release_component.rb deleted file mode 100644 index 249606f9f..000000000 --- a/app/components/v2/base_release_component.rb +++ /dev/null @@ -1,143 +0,0 @@ -# This is a base component for other release-related components -# Note: this doesn't actually render anything -class V2::BaseReleaseComponent < V2::BaseComponent - include Memery - using RefinedHash - using RefinedInteger - - RELEASE_STATUS = { - finished: ["Completed", :success], - stopped: ["Stopped", :failure], - created: ["Running", :ongoing], - on_track: ["Running", :ongoing], - upcoming: ["Upcoming", :inert], - post_release: ["Finalizing", :neutral], - post_release_started: ["Finalizing", :neutral], - post_release_failed: ["Finalizing", :neutral], - partially_finished: ["Partially Finished", :ongoing], - stopped_after_partial_finish: ["Stopped & Partially Finished", :failure] - } - - def initialize(release) - @release = release - end - - delegate :release_branch, :tag_name, to: :@release - - memoize def release_pilot_name - @release.release_pilot&.full_name || "Tramline" - end - - def release_pilot_avatar - user_avatar(release_pilot_name, size: 22) - end - - def stop_release_warning - message = "" - message += "You have finished release to one of the platforms. " if @release.partially_finished? - message += "You have unmerged commits in this release branch. " if @release.all_commits.size > 1 - message + "Are you sure you want to stop the release?" - end - - memoize def status - return RELEASE_STATUS.fetch(:upcoming) if @release.upcoming? - RELEASE_STATUS.fetch(@release.status.to_sym) - end - - def human_slug - @release.slug - end - - def platform_runs - @platform_runs ||= - @release.release_platform_runs.includes(step_runs: {deployment_runs: [:staged_rollout]}) - end - - def cross_platform? - @release.app.cross_platform? - end - - memoize def hotfixed_from - @release.hotfixed_from - end - - def hotfix_badge - if @release.hotfix? - badge = V2::BadgeComponent.new(kind: :badge) - badge.with_icon("band_aid.svg") - badge.with_link("Hotfixed from #{hotfixed_from.release_version}", hotfixed_from.live_release_link) - badge - end - end - - def scheduled_badge - if @release.is_automatic? - badge = V2::BadgeComponent.new(text: "Automatic", kind: :badge) - badge.with_icon("v2/robot.svg") - else - badge = V2::BadgeComponent.new(text: "Manual", kind: :badge) - badge.with_icon("v2/person_standing.svg") - end - badge - end - - def automatic? - @release.train.automatic? - end - - def backmerges? - @release.continuous_backmerge? - end - - def step_summary(platform) - @step_summary ||= Queries::ReleaseSummary::StepsSummary.from_release(@release).all - platform_steps = @step_summary.select { |step| step.platform_raw == platform } - - initial_data = {started_at: nil, ended_at: nil, builds_created_count: 0, duration: "--"} - initial_phase_data = {review: initial_data.dup, release: initial_data.dup} - - result = platform_steps.each_with_object(initial_phase_data) do |step, acc| - acc[step.phase.to_sym][:started_at] = [step.started_at, acc[step.phase.to_sym][:started_at]].compact.min - acc[step.phase.to_sym][:ended_at] = [step.ended_at, acc[step.phase.to_sym][:ended_at]].compact.max - acc[step.phase.to_sym][:builds_created_count] += step.builds_created_count || 0 - end - - [:review, :release].each do |phase| - next unless result[phase][:started_at] - duration = distance_of_time_in_words(result[phase][:started_at], result[phase][:ended_at] || Time.current) - result[phase][:duration] = duration - end - - result - end - - memoize def release_summary - Queries::ReleaseSummary.all(@release.id) - end - - def commit_count - [@release.applied_commits.size, 1].max - 1 - end - - memoize def release_version - @release.release_version - end - - def interval - return start_time unless @release.end_time - "#{start_time} ā€” #{end_time}" - end - - def start_time - time_format @release.scheduled_at, with_time: false, with_year: true, dash_empty: true - end - - def end_time - time_format @release.end_time, with_time: false, with_year: true, dash_empty: true - end - - def duration - return distance_of_time_in_words(@release.scheduled_at, @release.end_time) if @release.end_time - distance_of_time_in_words(@release.scheduled_at, Time.current) - end -end diff --git a/app/components/v2/build_info_component.html.erb b/app/components/v2/build_info_component.html.erb deleted file mode 100644 index 6bf57dd41..000000000 --- a/app/components/v2/build_info_component.html.erb +++ /dev/null @@ -1,45 +0,0 @@ -<% if @deployment_run %> -
-
- <%= render V2::BadgeComponent.new(kind: :badge) do |badge| %> - <% badge.with_icon(deployment_logo) %> - <% badge.with_link(build_info, external_link) %> - <% end %> - - <% if show_ci_info %> - <%= inline_svg("v2/connect_line.svg", classname: "w-5 h-5 inline-flex") %> - - <%= render V2::BadgeComponent.new(kind: :badge) do |badge| %> - <% badge.with_icon(ci_cd_provider_logo) %> - <% badge.with_link(ci_info, ci_link) %> - <% end %> - <% end %> - - <%= render V2::BadgeComponent.new(**status) %> - - <% if previous_release.present? %> - <%= render V2::ModalComponent.new(title: "Changes since last submission", - subtitle: diff_between, - size: :xl_3, - authz: false) do |modal| %> - <% button = modal.with_button(scheme: :naked_icon, type: :action, size: :none) %> - <% button.with_icon("v2/diff.svg", size: :lg) %> - <% button.with_tooltip("Changes since last submission", placement: "top") %> - <% modal.with_body do %> - <%= render partial: "shared/divide_collection", - locals: { collection: render(V2::CommitComponent.with_collection(commits_since_last_release)) } %> - <% end %> - <% end %> - <% end %> -
- - <%= render V2::TooltipComponent.new("Originally started on #{build_deployed_at}", placement: "top") do |tooltip| %> - <% tooltip.with_body do %> -
- <%= inline_svg("v2/dotted_line.svg", classname: "w-4 h-4 inline-flex mx-2 text-secondary") %> - <%= last_activity_at %> -
- <% end %> - <% end %> -
-<% end %> diff --git a/app/components/v2/build_info_component.rb b/app/components/v2/build_info_component.rb deleted file mode 100644 index c89cb953c..000000000 --- a/app/components/v2/build_info_component.rb +++ /dev/null @@ -1,105 +0,0 @@ -class V2::BuildInfoComponent < V2::BaseComponent - include Memery - - STATUS = { - created: {text: "About to start", status: :inert}, - started: {text: "Running", status: :ongoing}, - preparing_release: {text: "Preparing store version", status: :ongoing}, - prepared_release: {text: "Ready for review", status: :ongoing}, - failed_prepare_release: {text: "Failed to start release", status: :inert}, - submitted_for_review: {text: "Submitted for review", status: :inert}, - review_failed: {text: "Review rejected", status: :failure}, - ready_to_release: {text: "Review approved", status: :ongoing}, - uploading: {text: "Uploading", status: :neutral}, - uploaded: {text: "Uploaded", status: :ongoing}, - rollout_started: {text: "Rollout started", status: :ongoing}, - released: {text: "Released", status: :success}, - failed: {text: "Failed", status: :failure}, - failed_with_action_required: {text: "Needs manual submission", status: :failure} - } - - def initialize(deployment_run, index:, all_releases:, show_ci_info: true) - @deployment_run = deployment_run - @staged_rollout = deployment_run.staged_rollout - @step_run = deployment_run.step_run - @index = index - @all_releases = all_releases - @show_ci_info = show_ci_info - end - - attr_reader :show_ci_info - delegate :step, :release_platform_run, to: :@step_run - delegate :deployment, :external_link, to: :@deployment_run - - def status - return staged_rollout_status if @staged_rollout.present? - STATUS[@deployment_run.status.to_sym] || {text: "Unknown", status: :neutral} - end - - def staged_rollout_status - percentage = "" - - if @staged_rollout.last_rollout_percentage.present? - formatter = (@staged_rollout.last_rollout_percentage % 1 == 0) ? "%.0f" : "%.02f" - percentage = formatter % @staged_rollout.last_rollout_percentage - end - - case @staged_rollout.status.to_sym - when :created - {text: "Rollout started", status: :ongoing} - when :started - {text: "Rolled out to #{percentage}%", status: :ongoing} - when :failed - {text: "Failed to rollout out after #{percentage}%", status: :failure} - when :completed - {text: "Released", status: :success} - when :stopped - {text: "Rollout halted at #{percentage}%", status: :inert} - when :fully_released - {text: "Released", status: :success} - when :paused - {text: "Rollout paused at #{percentage}%", status: :neutral} - else - {text: "Unknown", status: :neutral} - end - end - - def build_info - "#{@step_run.build_version} (#{@step_run.build_number})" - end - - def ci_info - @step_run.commit.short_sha - end - - def ci_link - @step_run.ci_link - end - - def build_deployed_at - time_format @deployment_run.updated_at - end - - def last_activity_at - ago_in_words(@staged_rollout&.updated_at || @deployment_run.updated_at) - end - - def deployment_logo - "integrations/logo_#{deployment.integration_type}.png" - end - - memoize def previous_release - return if @all_releases.size == 1 - return if @index == @all_releases.size - @all_releases.fetch(@index + 1, nil) - end - - def commits_since_last_release - return unless previous_release - release_platform_run.commits_between(previous_release.step_run, @step_run) - end - - def diff_between - "#{previous_release.build_display_name} ā†’ #{@deployment_run.build_display_name}" - end -end diff --git a/app/components/v2/commit_component.rb b/app/components/v2/commit_component.rb index b19b61075..cf53bb839 100644 --- a/app/components/v2/commit_component.rb +++ b/app/components/v2/commit_component.rb @@ -1,5 +1,4 @@ class V2::CommitComponent < V2::BaseComponent - include ReleasesHelper OUTER_CLASSES = "py-1.5 px-2 hover:bg-main-100 hover:border-main-100 hover:first:rounded-sm hover:last:rounded-sm" def initialize(commit:, avatar: true, detailed: true) diff --git a/app/components/v2/external_app_component.rb b/app/components/v2/external_app_component.rb index 52c409926..1bcb3f9ec 100644 --- a/app/components/v2/external_app_component.rb +++ b/app/components/v2/external_app_component.rb @@ -1,7 +1,5 @@ class V2::ExternalAppComponent < V2::BaseComponent include ApplicationHelper - include ButtonHelper - include AssetsHelper LOGOS = { android: "integrations/logo_google_play_store.png", diff --git a/app/components/v2/header_component.html.erb b/app/components/v2/header_component.html.erb index 2fad95f18..c429a9597 100644 --- a/app/components/v2/header_component.html.erb +++ b/app/components/v2/header_component.html.erb @@ -47,7 +47,7 @@ <% end %> <% if default_app %> - <%= inline_svg("layer_separator.svg", classname: "w-3 h-3 text-secondary dark:text-secondary-50") %> + <%= render V2::IconComponent.new("layer_separator.svg", size: :sm, classes: "text-secondary dark:text-secondary-50") %> <%= render V2::DropdownComponent.new(authz: false) do |dropdown| %> <% button = dropdown.with_button(size: :xs) %> diff --git a/app/components/v2/live_release/changeset_tracking_component.rb b/app/components/v2/live_release/changeset_tracking_component.rb index 41553be4c..991870a76 100644 --- a/app/components/v2/live_release/changeset_tracking_component.rb +++ b/app/components/v2/live_release/changeset_tracking_component.rb @@ -4,7 +4,7 @@ class V2::LiveRelease::ChangesetTrackingComponent < V2::BaseComponent def initialize(release) @release = release @build_queue = release.active_build_queue - @applied_commits = release.applied_commits.sequential.includes(step_runs: :step) + @applied_commits = release.applied_commits.sequential @mid_release_prs = release.mid_release_prs.open @open_backmerge_prs = release.pull_requests.ongoing.open @change_queue_commits = @build_queue&.commits&.sequential diff --git a/app/components/v2/live_release/container_component.html.erb b/app/components/v2/live_release/container_component.html.erb index 4acc9727a..4a9fba81c 100644 --- a/app/components/v2/live_release/container_component.html.erb +++ b/app/components/v2/live_release/container_component.html.erb @@ -72,7 +72,7 @@ <%= render(scheduled_badge) if automatic? %> - <%# TODO: [V2] use the new rollout domain object / use radial charts %> + <%# TODO: use the new rollout domain object / use radial charts %> <%# platform_runs.each do |platform_run| %> <%# if staged_rollout_status(platform_run) %> <%#= render V2::BadgeComponent.new(**staged_rollout_status(platform_run)) do |status| %> diff --git a/app/components/v2/live_release/metadata_component.rb b/app/components/v2/live_release/metadata_component.rb index 18891b572..08931eeb3 100644 --- a/app/components/v2/live_release/metadata_component.rb +++ b/app/components/v2/live_release/metadata_component.rb @@ -25,6 +25,6 @@ def no_locale_set end def editable? - @release.release_platform_runs.any?(&:metadata_editable_v2?) + @release.release_platform_runs.any?(&:metadata_editable?) end end diff --git a/app/components/v2/live_release/prod_release/previous_rollout_component.html.erb b/app/components/v2/live_release/prod_release/previous_rollout_component.html.erb index 0209e8ba8..bc31e4ab9 100644 --- a/app/components/v2/live_release/prod_release/previous_rollout_component.html.erb +++ b/app/components/v2/live_release/prod_release/previous_rollout_component.html.erb @@ -14,7 +14,7 @@
- <%= inline_svg("v2/dotted_line.svg", classname: "w-4 h-4 inline-flex mx-2 text-secondary") %> + <%= render V2::IconComponent.new("v2/dotted_line.svg", size: :md, classes: "mx-2 text-secondary") %> <%= ago_in_words(store_rollout.updated_at) %>
diff --git a/app/components/v2/live_release/prod_release/previous_submission_component.html.erb b/app/components/v2/live_release/prod_release/previous_submission_component.html.erb index 4d8285dba..ba3962e25 100644 --- a/app/components/v2/live_release/prod_release/previous_submission_component.html.erb +++ b/app/components/v2/live_release/prod_release/previous_submission_component.html.erb @@ -14,7 +14,7 @@
- <%= inline_svg("v2/dotted_line.svg", classname: "w-4 h-4 inline-flex mx-2 text-secondary") %> + <%= render V2::IconComponent.new("v2/dotted_line.svg", size: :md, classes: "mx-2 text-secondary") %> <%= ago_in_words(submission.updated_at) %>
diff --git a/app/components/v2/live_release/prod_release/rollout_component.rb b/app/components/v2/live_release/prod_release/rollout_component.rb index e342f9ec1..e644b75e6 100644 --- a/app/components/v2/live_release/prod_release/rollout_component.rb +++ b/app/components/v2/live_release/prod_release/rollout_component.rb @@ -56,7 +56,7 @@ def events latest_events.map do |event| { timestamp: time_format(event.event_timestamp, with_year: false), - title: I18n.t("passport.store_rollout.reasons.#{event.reason}"), + title: I18n.t("passport.store_rollout.reasons.#{event.reason}") + " ā€“ " + event.author_full_name, description: event.message, type: event.kind.to_sym } diff --git a/app/components/v2/live_release/prod_release/submission_component.html.erb b/app/components/v2/live_release/prod_release/submission_component.html.erb index 02b1104be..3479f79e7 100644 --- a/app/components/v2/live_release/prod_release/submission_component.html.erb +++ b/app/components/v2/live_release/prod_release/submission_component.html.erb @@ -39,7 +39,7 @@ <% end %> <% end %> - <% if release_platform_run.metadata_editable_v2? %> + <% if release_platform_run.metadata_editable? %>
<%= render V2::AlertComponent.new(type: :info, title: "You can edit the release notes until you prepare for review. Once prepared, you won't be able to change the release notes.", full_screen: false) %>
diff --git a/app/components/v2/live_release/submission_config_component.html.erb b/app/components/v2/live_release/submission_config_component.html.erb index 6e6ced0a6..a49e28c49 100644 --- a/app/components/v2/live_release/submission_config_component.html.erb +++ b/app/components/v2/live_release/submission_config_component.html.erb @@ -13,7 +13,7 @@
<%= render V2::CardComponent.new(title: "Configuration summary", separator: false) do %>
- +

ā€¢ The largest build artifact from the workflow is picked up.

ā€¢ Submissions will happen automatically when the build is available.

diff --git a/app/components/v2/platform_overview_component.html.erb b/app/components/v2/platform_overview_component.html.erb deleted file mode 100644 index 962c4792a..000000000 --- a/app/components/v2/platform_overview_component.html.erb +++ /dev/null @@ -1,36 +0,0 @@ -<%= render V2::PlatformViewComponent.new(release, occupy:, detail: false) do |platform_component| %> - <% platform_component.platform_runs.each do |run| %> -
- <% store_releases = run.store_submitted_releases %> - <% store_releases.each_with_index do |store_release, index| %> - <%= render V2::BuildInfoComponent.new(store_release, index:, all_releases: store_releases, show_ci_info:) %> - <% end %> -
- <% end %> - - <% platform_component.platform_runs.each do |run| %> -
- <% summary = step_summary(run.platform) %> - <%= render V2::HorizontalDataSetComponent.new do |component| %> - <% component.with_data_set(title: "Time in review phase").with_content(summary[:review][:duration]) %> - <% component.with_data_set(title: "Review builds").with_content(summary[:review][:builds_created_count]) %> - <% component.with_data_set(title: "Time in release phase").with_content(summary[:release][:duration]) %> - <% end %> -
- <% end %> - - <% if show_monitoring %> - <% platform_component.platform_runs.each do |run| %> -
- <% if run.store_submitted_releases.first.present? %> - <%= render ReleaseMonitoringComponent.new( - deployment_run: run.store_submitted_releases.first, - metrics: [:stability, :adoption_rate, :staged_rollout], - show_version_info: false, - size: monitoring_size, - num_events: 1) %> - <% end %> -
- <% end %> - <% end %> -<% end %> diff --git a/app/components/v2/platform_overview_component.rb b/app/components/v2/platform_overview_component.rb deleted file mode 100644 index a8f2ce374..000000000 --- a/app/components/v2/platform_overview_component.rb +++ /dev/null @@ -1,22 +0,0 @@ -class V2::PlatformOverviewComponent < V2::BaseReleaseComponent - SIZES = %i[default compact].freeze - - def initialize(release, size: :default, occupy: true, show_monitoring: true) - raise ArgumentError, "Invalid size: #{size}" unless SIZES.include?(size) - @release = release - @size = size - @occupy = occupy - @show_monitoring = show_monitoring - super(@release) - end - - attr_reader :release, :occupy, :size, :show_monitoring - - def show_ci_info - @size != :compact - end - - def monitoring_size - cross_platform? ? size : :default - end -end diff --git a/app/components/v2/reldex/form_component.html.erb b/app/components/v2/reldex/form_component.html.erb index d05344305..412370f50 100644 --- a/app/components/v2/reldex/form_component.html.erb +++ b/app/components/v2/reldex/form_component.html.erb @@ -48,7 +48,7 @@
<% end %> -
+
Component
@@ -56,7 +56,7 @@
Acceptable Range
<% components.each do |component| %> -
+
<% section.F.fields_for :release_index_components, component do |component_fields| %>
@@ -73,7 +73,7 @@ <% end %> <% end %>
- @@ -82,9 +82,9 @@ compact: true, step: 1, in: 0..100, - data: { domain__release_index_target: "weight", - domain__release_index_component_target: "weight", - action: "domain--release-index#computeTotal domain--release-index-component#markActionable" } %> + data: { domain__reldex_target: "weight", + domain__reldex_component_target: "weight", + action: "domain--reldex#computeTotal domain--reldex-component#markActionable" } %>
@@ -98,10 +98,10 @@
Total
-
+
-
+
diff --git a/app/components/v2/release_list_component.rb b/app/components/v2/release_list_component.rb index e367ac546..5c2934aa7 100644 --- a/app/components/v2/release_list_component.rb +++ b/app/components/v2/release_list_component.rb @@ -40,7 +40,7 @@ def release_startable? end def release_options - return [] unless train.manually_startable? + return [] if train.inactive? upcoming_release_startable = train.upcoming_release_startable? return [] if train.ongoing_release && !upcoming_release_startable @@ -117,13 +117,8 @@ def no_release_empty_state end else platform = train.release_platforms.first.platform - text = - if train.product_v2? - "You can now start creating new releases. We have added some default submissions settings for you. This involves picking the right workflows and configuring the right channels for build distribution. Please review these before starting a release." - else - "You can now start creating new releases. Please review the release steps and submissions settings before starting a release." - end - button_link = train.product_v2? ? edit_app_train_platform_submission_config_path(app, train, platform) : steps_app_train_path(app, train) + text = "You can now start creating new releases. We have added some default submissions settings for you. This involves picking the right workflows and configuring the right channels for build distribution. Please review these before starting a release." + button_link = edit_app_train_platform_submission_config_path(app, train, platform) { title: "Create your very first release", text:, diff --git a/app/components/v2/release_overview_component.html.erb b/app/components/v2/release_overview_component.html.erb index 59e474220..8cd1a4d7a 100644 --- a/app/components/v2/release_overview_component.html.erb +++ b/app/components/v2/release_overview_component.html.erb @@ -87,10 +87,6 @@
- <% if release.is_v2? %> - <%= render V2::PlatformOverviewV2Component.new(release, occupy: false) %> - <% else %> - <%= render V2::PlatformOverviewComponent.new(release, occupy: false) %> - <% end %> + <%= render V2::PlatformOverviewV2Component.new(release, occupy: false) %> <% end %> <% end %> diff --git a/app/controllers/api/v1/apps_controller.rb b/app/controllers/api/v1/apps_controller.rb index 8ebec6a63..b8cb88249 100644 --- a/app/controllers/api/v1/apps_controller.rb +++ b/app/controllers/api/v1/apps_controller.rb @@ -10,10 +10,6 @@ def app end def latest_store_version - if app.production_store_rollouts.none? - return app.latest_store_step_runs.map(&:release_info).group_by { _1[:platform] } - end - app.production_store_rollouts .group_by(&:platform) .transform_values { |rollouts| rollouts.max_by(&:updated_at) } diff --git a/app/controllers/api/v1/builds_controller.rb b/app/controllers/api/v1/builds_controller.rb index 853a031f9..01510ffca 100644 --- a/app/controllers/api/v1/builds_controller.rb +++ b/app/controllers/api/v1/builds_controller.rb @@ -2,17 +2,11 @@ class Api::V1::BuildsController < ApiController def external_metadata render json: {error: "No metadata provided"}, status: :unprocessable_entity and return if build_params[:external_metadata].blank? - step_run = app.step_runs.where(build_number: build_params[:version_code], build_version: build_params[:version_name]).first build = app.builds.where(build_number: build_params[:version_code], version_name: build_params[:version_name]).first - raise ActiveRecord::RecordNotFound if step_run.blank? && build.blank? + raise ActiveRecord::RecordNotFound if build.blank? - new_metadata = - if step_run.present? - ExternalBuild.find_or_initialize_by(step_run:).update_or_insert!(build_params[:external_metadata].map(&:to_h)) - else - ExternalBuild.find_or_initialize_by(build:).update_or_insert!(build_params[:external_metadata].map(&:to_h)) - end + new_metadata = ExternalBuild.find_or_initialize_by(build:).update_or_insert!(build_params[:external_metadata].map(&:to_h)) if new_metadata.errors.present? render json: {error: new_metadata.errors}, status: :unprocessable_entity diff --git a/app/controllers/api/v1/releases_controller.rb b/app/controllers/api/v1/releases_controller.rb index bdc8ed25b..504032e2e 100644 --- a/app/controllers/api/v1/releases_controller.rb +++ b/app/controllers/api/v1/releases_controller.rb @@ -7,20 +7,9 @@ def show private def all_versions - store_releases = releases.flat_map do |release| - if release.is_v2? - release - .production_store_rollouts - .reorder(:updated_at) - .flat_map(&:release_info) - else - release - .all_store_step_runs - &.map(&:release_info) - end - end - - store_releases.group_by { |sr| sr[:platform] } + releases + .flat_map { |release| release.production_store_rollouts.reorder(:updated_at).flat_map(&:release_info) } + .then { |store_releases| store_releases.group_by { _1[:platform] } } end def releases diff --git a/app/controllers/app_configs_controller.rb b/app/controllers/app_configs_controller.rb index 4660968c2..e2bc46990 100644 --- a/app/controllers/app_configs_controller.rb +++ b/app/controllers/app_configs_controller.rb @@ -39,7 +39,6 @@ def pick_category when Integration.categories[:version_control] then configure_version_control when Integration.categories[:ci_cd] then configure_ci_cd when Integration.categories[:monitoring] then configure_monitoring - when Integration.categories[:notification] then configure_notification_channel when Integration.categories[:build_channel] then configure_build_channel else raise "Invalid integration category." end @@ -53,10 +52,6 @@ def configure_version_control set_code_repositories if further_setup_by_category?.dig(:version_control, :further_setup) end - def configure_notification_channel - set_notification_channels if @app.notifications_set_up? - end - def configure_ci_cd set_ci_cd_projects if further_setup_by_category?.dig(:ci_cd, :further_setup) end @@ -78,7 +73,6 @@ def app_config_params .require(:app_config) .permit( :code_repository, - :notification_channel, :bitrise_project_id, :firebase_android_config, :firebase_ios_config, @@ -93,7 +87,6 @@ def app_config_params def parsed_app_config_params app_config_params .merge(code_repository: app_config_params[:code_repository]&.safe_json_parse) - .merge(notification_channel: app_config_params[:notification_channel]&.safe_json_parse) .merge(bitrise_project_id: app_config_params[:bitrise_project_id]&.safe_json_parse) .merge(bugsnag_config(app_config_params.slice(*BUGSNAG_CONFIG_PARAMS))) .merge(firebase_ios_config: app_config_params[:firebase_ios_config]&.safe_json_parse) @@ -121,10 +114,6 @@ def set_code_repositories @code_repositories = @app.vcs_provider.repos(workspace) end - def set_notification_channels - @notification_channels = @app.notification_provider.channels if @app.notifications_set_up? - end - def set_integration_category if Integration.categories.key?(params[:integration_category]) @integration_category = params[:integration_category] diff --git a/app/controllers/build_queues_controller.rb b/app/controllers/build_queues_controller.rb index 2e57f1743..b9ab0ec47 100644 --- a/app/controllers/build_queues_controller.rb +++ b/app/controllers/build_queues_controller.rb @@ -5,20 +5,10 @@ class BuildQueuesController < SignedInApplicationController before_action :set_build_queue, only: %i[apply] def apply - if @release.is_v2? - if (result = Action.apply_build_queue!(@build_queue)).ok? - redirect_to changeset_tracking_release_path(@release), notice: "Build queue has been applied and emptied." - else - redirect_to current_release_path, flash: {error: t(".failure", errors: result.error.message)} - end + if (result = Action.apply_build_queue!(@build_queue)).ok? + redirect_to changeset_tracking_release_path(@release), notice: "Build queue has been applied and emptied." else - @release.with_lock do - locked_release_error and return unless @release.committable? - already_triggered_error and return unless @build_queue.is_active? - @build_queue.apply! - end - - redirect_to current_release_path, notice: "Build queue has been applied and emptied." + redirect_to current_release_path, flash: {error: t(".failure", errors: result.error.message)} end end diff --git a/app/controllers/commits_controller.rb b/app/controllers/commits_controller.rb deleted file mode 100644 index ffcf8f9a7..000000000 --- a/app/controllers/commits_controller.rb +++ /dev/null @@ -1,56 +0,0 @@ -class CommitsController < SignedInApplicationController - around_action :set_time_zone - before_action :require_write_access!, only: %i[apply] - before_action :set_release, only: %i[apply] - before_action :set_commit, only: %i[apply] - before_action :set_release_platform_run, only: %i[apply] - before_action :ensure_release_platform_run, only: %i[apply] - - def apply - @release_platform_run.with_lock do - locked_release_error and return unless @release_platform_run.on_track? - already_triggered_error and return if @release_platform_run.commit_applied?(@commit) - @commit.trigger_step_runs_for(@release_platform_run, force: true) - end - - redirect_to current_release_path, notice: "Steps have been triggered for the commit." - end - - private - - def already_triggered_error - redirect_to current_release_path, flash: {error: "Cannot re-apply a commit to a release!"} - end - - def locked_release_error - redirect_to current_release_path, flash: {error: "Cannot apply a commit to a locked release."} - end - - def ensure_release_platform_run - if @release_platform_run.blank? - redirect_to current_release_path, flash: {error: "Could not find the release!"} - end - end - - def set_release_platform_run - @release_platform_run = @release.release_platform_runs.find_by(id: commit_params[:release_platform_run_id]) - end - - def set_commit - @commit = Commit.find(params[:id]) - end - - def set_release - @release = Release.friendly.find(params[:release_id]) - end - - def commit_params - params.require(:commit).permit( - :release_platform_run_id - ) - end - - def current_release_path - release_path(@release) - end -end diff --git a/app/controllers/concerns/tabbable.rb b/app/controllers/concerns/tabbable.rb index 17a5ff062..0c5c826e6 100644 --- a/app/controllers/concerns/tabbable.rb +++ b/app/controllers/concerns/tabbable.rb @@ -15,10 +15,9 @@ def set_app_config_tabs def set_train_config_tabs @tab_configuration = [ [1, "Release Settings", edit_app_train_path(@app, @train), "v2/cog.svg"], - ([2, "Steps", steps_app_train_path(@app, @train), "v2/route.svg"] unless v2?), - ([2, "Android Flow Settings", edit_app_train_platform_submission_config_path(@app, @train, :android), "v2/logo_google_play_store_bw.svg"] if @app.cross_platform? && v2?), - ([3, "iOS Flow Settings", edit_app_train_platform_submission_config_path(@app, @train, :ios), "v2/logo_app_store_bw.svg"] if @app.cross_platform? && v2?), - ([2, "Submission Settings", edit_app_train_platform_submission_config_path(@app, @train, @app.platform), "v2/sliders.svg"] if v2? && !@app.cross_platform? && v2?), + ([2, "Android Flow Settings", edit_app_train_platform_submission_config_path(@app, @train, :android), "v2/logo_google_play_store_bw.svg"] if @app.cross_platform?), + ([3, "iOS Flow Settings", edit_app_train_platform_submission_config_path(@app, @train, :ios), "v2/logo_app_store_bw.svg"] if @app.cross_platform?), + ([2, "Submission Settings", edit_app_train_platform_submission_config_path(@app, @train, @app.platform), "v2/sliders.svg"] unless @app.cross_platform?), [4, "Notification Settings", app_train_notification_settings_path(@app, @train), "bell.svg"], [5, "Release Health Rules", rules_app_train_path(@app, @train), "v2/heart_pulse.svg"], [6, "Reldex Settings", edit_app_train_release_index_path(@app, @train), "v2/ruler.svg"] diff --git a/app/controllers/deployment_runs_controller.rb b/app/controllers/deployment_runs_controller.rb deleted file mode 100644 index 11b6c93a5..000000000 --- a/app/controllers/deployment_runs_controller.rb +++ /dev/null @@ -1,63 +0,0 @@ -class DeploymentRunsController < SignedInApplicationController - before_action :set_deployment_run - before_action :ensure_reviewable, only: [:submit_for_review] - before_action :ensure_releasable, only: [:start_release] - before_action :ensure_preparable, only: [:prepare_release] - - def submit_for_review - Deployments::AppStoreConnect::Release.submit_for_review!(@deployment_run) - - if @deployment_run.failed? - redirect_back fallback_location: root_path, flash: {error: "Failed to submit for review due to #{@deployment_run.display_attr(:failure_reason)}"} - else - redirect_back fallback_location: root_path, notice: "Submitted for review!" - end - end - - def start_release - @deployment_run.start_release! - - if @deployment_run.failed? - redirect_back fallback_location: root_path, flash: {error: "Failed to start the release due to #{@deployment_run.display_attr(:failure_reason)}"} - else - redirect_back fallback_location: root_path, notice: "The release has kicked-off!" - end - end - - def prepare_release - @deployment_run.start_prepare_release!(force: deployment_run_params[:force]) - - redirect_back fallback_location: root_path, notice: "The new release has begun preparing." - end - - private - - def deployment_run_params - params.require(:deployment_run).permit(:force) - end - - def ensure_reviewable - unless @deployment_run.reviewable? - redirect_back fallback_location: root_path, - flash: {error: "Cannot perform this operation. This deployment cannot be submitted for a review."} - end - end - - def ensure_releasable - unless @deployment_run.releasable? - redirect_back fallback_location: root_path, - flash: {error: "Cannot perform this operation. This deployment cannot be released."} - end - end - - def ensure_preparable - unless @deployment_run.may_start_prepare_release? - redirect_back fallback_location: root_path, - flash: {error: "Cannot perform this operation. This deployment cannot be prepared for release."} - end - end - - def set_deployment_run - @deployment_run = DeploymentRun.find_by(id: params[:id]) - end -end diff --git a/app/controllers/deployments_controller.rb b/app/controllers/deployments_controller.rb deleted file mode 100644 index 2515be007..000000000 --- a/app/controllers/deployments_controller.rb +++ /dev/null @@ -1,29 +0,0 @@ -class DeploymentsController < SignedInApplicationController - before_action :require_write_access!, only: %i[start] - before_action :set_release - before_action :set_step_run - before_action :set_deployment - - def start - Triggers::Deployment.call(step_run: @step_run, deployment: @deployment) - redirect_back fallback_location: root_path, notice: "Deployment successfully started!" - end - - private - - def set_release - @release = - ReleasePlatformRun - .joins(release_platform: :app) - .where(apps: {organization: current_organization}) - .find_by(id: params[:release_id]) - end - - def set_step_run - @step_run = @release.step_runs.find_by(id: params[:step_run_id]) - end - - def set_deployment - @deployment = @step_run.deployments.find_by(id: params[:id]) - end -end diff --git a/app/controllers/integration_listeners/github_controller.rb b/app/controllers/integration_listeners/github_controller.rb index 1af982f6f..92f0fe407 100644 --- a/app/controllers/integration_listeners/github_controller.rb +++ b/app/controllers/integration_listeners/github_controller.rb @@ -26,27 +26,15 @@ def handle_ping end def handle_push - response = - if train.product_v2? - result = Action.process_push_webhook(train, push_params) - result.ok? ? result.value! : Response.new(:unprocessable_entity, "Error processing push") - else - WebhookHandlers::Push.process(train, push_params) - end - + result = Action.process_push_webhook(train, push_params) + response = result.ok? ? result.value! : Response.new(:unprocessable_entity, "Error processing push") Rails.logger.debug { response.body } head response.status end def handle_pull_request - response = - if train.product_v2? - result = Action.process_pull_request_webhook(train, pull_request_params) - result.ok? ? result.value! : Response.new(:unprocessable_entity, "Error processing pull request") - else - WebhookHandlers::PullRequest.process(train, pull_request_params) - end - + result = Action.process_pull_request_webhook(train, pull_request_params) + response = result.ok? ? result.value! : Response.new(:unprocessable_entity, "Error processing pull request") Rails.logger.debug { response.body } head response.status end diff --git a/app/controllers/integration_listeners/gitlab_controller.rb b/app/controllers/integration_listeners/gitlab_controller.rb index f8a1f6e28..7a10beb38 100644 --- a/app/controllers/integration_listeners/gitlab_controller.rb +++ b/app/controllers/integration_listeners/gitlab_controller.rb @@ -26,27 +26,15 @@ def handle_ping end def handle_push - response = - if train.product_v2? - result = Action.process_push_webhook(train, push_params) - result.ok? ? result.value! : Response.new(:unprocessable_entity, "Error processing push") - else - WebhookHandlers::Push.process(train, push_params) - end - + result = Action.process_push_webhook(train, push_params) + response = result.ok? ? result.value! : Response.new(:unprocessable_entity, "Error processing push") Rails.logger.debug { response.body } head response.status end def handle_pull_request - response = - if train.product_v2? - result = Action.process_pull_request_webhook(train, pull_request_params) - result.ok? ? result.value! : Response.new(:unprocessable_entity, "Error processing pull request") - else - WebhookHandlers::PullRequest.process(train, pull_request_params) - end - + result = Action.process_pull_request_webhook(train, pull_request_params) + response = result.ok? ? result.value! : Response.new(:unprocessable_entity, "Error processing pull request") Rails.logger.debug { response.body } head response.status end diff --git a/app/controllers/notification_settings_controller.rb b/app/controllers/notification_settings_controller.rb index 9bcc2319a..14afe29df 100644 --- a/app/controllers/notification_settings_controller.rb +++ b/app/controllers/notification_settings_controller.rb @@ -10,11 +10,7 @@ class NotificationSettingsController < SignedInApplicationController def index if @train.send_notifications? - @notification_settings = if @train.product_v2? - @train.notification_settings.where(kind: NotificationSetting.kinds.values - NotificationSetting::DEPRECATED_KINDS.values) - else - @train.notification_settings.where(kind: NotificationSetting.kinds.values - NotificationSetting::V2_KINDS.values) - end + @notification_settings = @train.notification_settings.where(kind: NotificationSetting.kinds.values) end set_train_config_tabs diff --git a/app/controllers/release_metadata_controller.rb b/app/controllers/release_metadata_controller.rb index f5e86a40d..6bd5b5ef3 100644 --- a/app/controllers/release_metadata_controller.rb +++ b/app/controllers/release_metadata_controller.rb @@ -1,13 +1,9 @@ class ReleaseMetadataController < SignedInApplicationController include Tabbable - before_action :require_write_access!, only: %i[edit update] - before_action :set_release, only: %i[edit update index update_all] - before_action :set_release_platform, only: %i[edit update] - before_action :set_release_platform_run, only: %i[edit update] - before_action :set_train, only: %i[edit update index update_all] - before_action :set_app_from_train, only: %i[edit update index update_all] - before_action :ensure_editable, only: %i[edit update] + before_action :set_release, only: %i[index update_all] + before_action :set_train, only: %i[index update_all] + before_action :set_app_from_train, only: %i[index update_all] def index set_metadata @@ -18,20 +14,6 @@ def index end end - def edit - @release_metadatum = @release_platform_run.release_metadatum - end - - def update - @release_metadatum = ReleaseMetadata.find(params[:id]) - - if @release_metadatum.update(release_metadata_params) - redirect_to release_path(@release), notice: t(".success") - else - render :edit, status: :unprocessable_entity - end - end - def update_all language = params.require(:language) rm_params = params.require(:release_metadata) @@ -80,22 +62,10 @@ def set_metadata @android_metadata = @release.android_release_platform_run&.metadata_for(@language, :android) end - def release_metadata_params - params.require(:release_metadata).permit(:release_notes, :promo_text) - end - def set_release @release = Release.friendly.find(params[:release_id]) end - def set_release_platform - @release_platform = @release.release_platforms.friendly.find(params[:release_platform_id]) - end - - def set_release_platform_run - @release_platform_run = @release.release_platform_runs.find_by(release_platform: @release_platform) - end - def set_train @train = @release.train end @@ -103,11 +73,4 @@ def set_train def set_app_from_train @app = @train.app end - - def ensure_editable - unless @release_platform_run.metadata_editable? - redirect_back fallback_location: release_path(@release), - flash: {error: t(".metadata_not_editable")} - end - end end diff --git a/app/controllers/releases_controller.rb b/app/controllers/releases_controller.rb index 0579992f7..058ea10a9 100644 --- a/app/controllers/releases_controller.rb +++ b/app/controllers/releases_controller.rb @@ -12,14 +12,7 @@ def index end def show - if @release.is_v2? - redirect_to live_release_active_tab - return - end - - set_commits - set_pull_requests - render :show + redirect_to live_release_active_tab end def create @@ -207,10 +200,6 @@ def set_pull_requests @mid_release_prs = @release.mid_release_prs end - def set_commits - @commits = @release.applied_commits.sequential.includes(step_runs: :step) - end - def current_release_path(current_release) release_path(current_release) end diff --git a/app/controllers/signed_in_application_controller.rb b/app/controllers/signed_in_application_controller.rb index c6bb510f2..60036bc59 100644 --- a/app/controllers/signed_in_application_controller.rb +++ b/app/controllers/signed_in_application_controller.rb @@ -189,10 +189,6 @@ def turbo_frame_request_variant request.variant = :turbo_frame if turbo_frame_request? end - def v2? - @train&.product_v2? - end - def stream_flash turbo_stream.update("flash_stream", V2::FlashComponent.new(flash)) end diff --git a/app/controllers/staged_rollouts_controller.rb b/app/controllers/staged_rollouts_controller.rb deleted file mode 100644 index d1bed2a00..000000000 --- a/app/controllers/staged_rollouts_controller.rb +++ /dev/null @@ -1,101 +0,0 @@ -class StagedRolloutsController < SignedInApplicationController - before_action :require_write_access!, only: %i[increase halt] - before_action :set_deployment_run - before_action :set_staged_rollout - before_action :ensure_controlled_rolloutable, only: [:increase, :halt] - before_action :ensure_rolloutable, only: [:fully_release, :resume] - before_action :ensure_auto_rolloutable, only: [:pause] - - def increase - @staged_rollout.move_to_next_stage! - - if @staged_rollout.failed? - redirect_back fallback_location: root_path, flash: {error: "Failed to increase the rollout. Please retry!"} - else - redirect_back fallback_location: root_path, notice: "Increased the rollout!" - end - end - - def pause - unless @staged_rollout.may_pause? - redirect_back fallback_location: root_path, flash: {error: "Rollout cannot be paused at this stage."} - return - end - - @staged_rollout.pause_release! - - if @staged_rollout.paused? - redirect_back fallback_location: root_path, notice: "Paused the rollout!" - else - redirect_back fallback_location: root_path, flash: {error: "Failed to pause the rollout. Please retry!"} - end - end - - def resume - unless @staged_rollout.may_resume? - redirect_back fallback_location: root_path, flash: {error: "Rollout cannot be resumed at this stage."} - return - end - - @staged_rollout.resume_release! - - if @staged_rollout.started? - redirect_back fallback_location: root_path, notice: "Resumed the rollout!" - else - redirect_back fallback_location: root_path, flash: {error: "Failed to resume the rollout. Please retry!"} - end - end - - def halt - unless @staged_rollout.may_halt? - redirect_back fallback_location: root_path, flash: {error: "Rollout cannot be halted at this stage."} - return - end - - @staged_rollout.halt_release! - - if @staged_rollout.stopped? - redirect_back fallback_location: root_path, notice: "Halted the rollout!" - else - redirect_back fallback_location: root_path, flash: {error: "Failed to halt the rollout. Please retry!"} - end - end - - def fully_release - @staged_rollout.fully_release! - - if @staged_rollout.fully_released? - redirect_back fallback_location: root_path, notice: "Released to all users!" - else - redirect_back fallback_location: root_path, flash: {error: "Failed to release to all users due to #{@deployment_run.display_attr(:failure_reason)}"} - end - end - - private - - def ensure_rolloutable - unless @deployment_run.rolloutable? - redirect_back fallback_location: root_path, flash: {error: "Cannot perform this operation. The deployment is not in rollout stage."} - end - end - - def ensure_controlled_rolloutable - unless @deployment_run.controllable_rollout? - redirect_back fallback_location: root_path, flash: {error: "Cannot perform this operation. The deployment is not in rollout stage."} - end - end - - def ensure_auto_rolloutable - unless @deployment_run.automatic_rollout? - redirect_back fallback_location: root_path, flash: {error: "Cannot perform this operation. The deployment is not in rollout stage."} - end - end - - def set_staged_rollout - @staged_rollout = @deployment_run.staged_rollout - end - - def set_deployment_run - @deployment_run = DeploymentRun.find_by(id: params[:deployment_run_id]) - end -end diff --git a/app/controllers/step_runs_controller.rb b/app/controllers/step_runs_controller.rb deleted file mode 100644 index 61776feb1..000000000 --- a/app/controllers/step_runs_controller.rb +++ /dev/null @@ -1,71 +0,0 @@ -class StepRunsController < SignedInApplicationController - before_action :require_write_access!, only: %i[start retry_ci_workflow] - before_action :set_release - before_action :set_step, only: %i[start] - before_action :set_step_run, only: %i[retry_ci_workflow sync_store_status] - before_action :ensure_startable, only: %i[start] - before_action :ensure_syncable, only: %i[sync_store_status] - - # FIXME: This action incorrectly consumes a step_id and not a step_run_id as the route suggests - def start - Triggers::StepRun.call(@step, @release.last_commit, @release) - redirect_back fallback_location: root_path, notice: "Step successfully started" - end - - def retry_ci_workflow - @step_run.retry_ci! - redirect_back fallback_location: root_path, notice: "CI workflow retried!" - rescue - error = "Failed to retry the CI workflow! Contact support if the issue persists." - redirect_back fallback_location: root_path, flash: {error:} - end - - def sync_store_status - @step_run.sync_store_status! - - if @step_run.deployment_restarted? - redirect_back fallback_location: root_path, notice: "Status resolved on the console UI, the release train will move forward." - else - redirect_back fallback_location: root_path, flash: {error: "Status remains unresolved on the console UI. Please make sure to submit the changes for review in a public track."} - end - rescue - error = "Failed to sync the store status! Contact support if the issue persists." - redirect_back fallback_location: root_path, flash: {error:} - end - - private - - def set_step_run - @step_run = @release.step_runs.find(params[:id]) - end - - def ensure_startable - unless @release.manually_startable_step?(@step) - redirect_back fallback_location: root_path, - flash: {error: "Cannot perform this operation. This step cannot be started."} - end - end - - def ensure_syncable - unless @step_run.failed_with_action_required? - redirect_back fallback_location: root_path, - flash: {error: "Cannot perform this operation. This step does not require a manual submission."} - end - end - - def set_release - @release = - ReleasePlatformRun - .joins(release_platform: :app) - .where(apps: {organization: current_organization}) - .find(params[:release_id]) - end - - def set_step - @step = @release.release_platform.steps.friendly.find(params[:id]) - end - - def deployment_attributes - params.require(:step_runs).permit(deployment_attributes: [:integration_id, :build_artifact_channel]) - end -end diff --git a/app/controllers/steps_controller.rb b/app/controllers/steps_controller.rb deleted file mode 100644 index 96e3db044..000000000 --- a/app/controllers/steps_controller.rb +++ /dev/null @@ -1,151 +0,0 @@ -class StepsController < SignedInApplicationController - using RefinedString - using RefinedInteger - - before_action :require_write_access!, only: %i[new create update] - before_action :set_train, only: %i[new create update] - before_action :set_release_platform, only: %i[new create update] - before_action :set_ci_actions, only: %i[new create] - before_action :integrations_are_ready?, only: %i[new create] - around_action :set_time_zone - - def new - kind = params.extract!(:kind).require(:kind) - - head :forbidden and return if @train.active_runs.exists? - head :forbidden and return if kind.blank? - - @step = @release_platform.steps.new(kind:) - - if @step.release? && @release_platform.has_release_step? - redirect_back fallback_location: app_train_releases_path(@app, @train), flash: {error: "You can only have one release step in a train!"} - end - - set_build_channels - end - - def create - head :forbidden and return if @train.active_runs.exists? - @step = @release_platform.steps.new(parsed_step_params) - - respond_to do |format| - if @step.save - format.html { new_step_redirect } - else - set_build_channels - format.html { render :new, status: :unprocessable_entity } - end - end - end - - def update - @step = - Step - .joins(release_platform: :app) - .where(release_platforms: {apps: {organization: current_organization}}) - .friendly - .find(params[:id]) - head :forbidden and return if @train.active_runs.exists? - - if @step.update(parsed_step_params) - redirect_to steps_app_train_path(@app, @train), notice: "Step was successfully updated." - else - @ci_actions = @step.ci_cd_provider.workflows - redirect_to steps_app_train_path(@app, @train), flash: {error: @step.errors.full_messages.to_sentence} - end - end - - private - - def new_step_redirect - if @app.guided_train_setup? - redirect_to app_path(@app), notice: "Step was successfully created." - else - redirect_to app_train_releases_path(@app, @train), notice: "Step was successfully created." - end - end - - def set_step - @step = @train.steps.friendly.find(params[:id]) - end - - def set_train - @train = @app.trains.friendly.find(params[:train_id]) - end - - def set_release_platform - @release_platform = @train.release_platforms.friendly.find(params[:platform_id]) - end - - def step_params - params.require(:step).permit( - :name, - :description, - :ci_cd_channel, - :release_suffix, - :kind, - :auto_deploy, - :build_artifact_name_pattern, - :app_variant_id - ) - end - - def parsed_step_params - step_params - .merge(parsed_deployments_params) - .merge(ci_cd_channel: step_params[:ci_cd_channel]&.safe_json_parse) - end - - def integrations_are_ready? - unless @train.ready? - redirect_to app_train_releases_path(@app, @train), alert: "Cannot create steps before notifiers are complete." - end - end - - def set_ci_actions - @ci_actions = @train.ci_cd_provider.workflows - end - - def set_build_channels - @build_channel_integrations = set_build_channel_integrations - @selected_integration = @build_channel_integrations.first # TODO: what is first even? - @selected_build_channels = - Integration.find_build_channels(@selected_integration.last, with_production: @step.release?) - end - - def deployments_params - params - .require(:step) - .permit(deployments_attributes: [ - :integration_id, - :build_artifact_channel, - :deployment_number, - :is_staged_rollout, - :notes, - :staged_rollout_config - ]) - end - - def parsed_deployments_params - deployments_params.merge(deployments_attributes: parsed_deployments_attributes) - end - - def parsed_deployments_attributes - deployments_params[:deployments_attributes].to_h.to_h do |number, attributes| - [ - number, - attributes.merge( - staged_rollout_config: attributes[:staged_rollout_config]&.safe_csv_parse, - build_artifact_channel: attributes[:build_artifact_channel]&.safe_json_parse - ) - ] - end - end - - def set_build_channel_integrations - @release_platform - .build_channel_integrations - .map { |bc| [bc.providable.display, bc.id] } - .push(Integration::EXTERNAL_BUILD_INTEGRATION[:build_integration]) - end -end diff --git a/app/controllers/trains_controller.rb b/app/controllers/trains_controller.rb index c3e354ce1..da22d5a6d 100644 --- a/app/controllers/trains_controller.rb +++ b/app/controllers/trains_controller.rb @@ -6,8 +6,8 @@ class TrainsController < SignedInApplicationController before_action :require_write_access!, only: %i[new create edit update destroy activate deactivate] around_action :set_time_zone - before_action :set_train, only: %i[edit update destroy activate deactivate steps rules] - before_action :set_train_config_tabs, only: %i[edit update steps rules destroy activate deactivate] + before_action :set_train, only: %i[edit update destroy activate deactivate rules] + before_action :set_train_config_tabs, only: %i[edit update rules destroy activate deactivate] before_action :validate_integration_status, only: %i[new create] before_action :set_notification_channels, only: %i[new create edit update] @@ -19,10 +19,6 @@ def edit @edit_not_allowed = @train.active_runs.exists? end - def steps - @edit_not_allowed = @train.active_runs.exists? - end - def rules @unconfigured = @train.monitoring_provider.nil? end @@ -81,11 +77,9 @@ def deactivate def new_train_redirect if @app.trains.size == 1 redirect_to app_path(@app), notice: "Train was successfully created." - elsif v2? + else platform = @train.release_platforms.first.platform redirect_to edit_app_train_platform_submission_config_path(@app, @train, platform), notice: "Train was successfully created." - else - redirect_to steps_app_train_path(@app, @train), notice: "Train was successfully created." end end @@ -123,7 +117,6 @@ def train_params :release_schedule_enabled, :stop_automatic_releases_on_failure, :continuous_backmerge_enabled, - :manual_release, :compact_build_notes, :tag_all_store_releases, :tag_platform_releases, @@ -162,7 +155,6 @@ def train_update_params :release_schedule_enabled, :stop_automatic_releases_on_failure, :continuous_backmerge_enabled, - :manual_release, :compact_build_notes, :tag_all_store_releases, :tag_platform_releases, @@ -196,7 +188,7 @@ def train_path def set_notification_channels @notification_channels = @app.notification_provider.channels if @app.notifications_set_up? - @current_notification_channel = @train&.notification_channel || @app.config.notification_channel + @current_notification_channel = @train&.notification_channel end def build_queue_config_params diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 93cf830df..2df11a80c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -32,6 +32,10 @@ module ApplicationHelper inert: "bg-main-400 dark:bg-main-200" } + def setup_instruction_color(is_completed) + is_completed ? "bg-green-400" : "bg-blue-200" + end + def status_picker(picker, status) picker[status.to_sym] || {text: status.humanize, status: :neutral} end @@ -44,23 +48,6 @@ def resolve_color(color) end end - def write_only(&block) - return concat(content_tag(:div, capture(&block), class: "hidden")) unless writer? - yield(block) - end - - def sidebar_active_path(path, style) - if current_page?(path) - style - end - end - - def sidebar_active_resource(resource, style) - if resource.eql?(controller_name) - style - end - end - def toggle_for(hide, full_width: false, &block) render( partial: "shared/toggle_button", @@ -70,18 +57,6 @@ def toggle_for(hide, full_width: false, &block) ) end - def dynamic_header_color - if Rails.env.development? - "bg-rose-100" - else - "bg-white" - end - end - - def step_color(step_kind) - (step_kind == "release") ? "amber" : "slate" - end - def version_in_progress(version) version.to_semverish.to_s(patch_glob: true) end @@ -114,15 +89,6 @@ def current_deploy } end - NOTE_BOX_COLORS = { - info: "text-amber-500", - error: "text-red-500" - }.freeze - - def note_box_color(type) - NOTE_BOX_COLORS[type] - end - def status_badge(status, custom = [], fixed = nil, pulse: false) return if status.blank? @@ -146,10 +112,6 @@ def status_badge(status, custom = [], fixed = nil, pulse: false) content_tag(:span, status, class: classes) end - def dev_show(&blk) - yield blk if Rails.env.development? - end - def display_channels(channels, with_none: false) channels .map { |chan| [yield(chan), chan.to_json] } @@ -177,10 +139,6 @@ def user_avatar(name, **) Initials.svg(name, **) end - def safe_simple_format(text) - simple_format(h(text)) - end - def comment end diff --git a/app/helpers/apps_helper.rb b/app/helpers/apps_helper.rb deleted file mode 100644 index 7c3034c13..000000000 --- a/app/helpers/apps_helper.rb +++ /dev/null @@ -1,10 +0,0 @@ -module AppsHelper - def setup_instruction_color(is_completed) - is_completed ? "bg-green-400" : "bg-blue-200" - end - - def store_logo(app) - return "integrations/logo_app_store.png" if app.ios? - "integrations/logo_google_play_store.png" if app.android? - end -end diff --git a/app/helpers/assets_helper.rb b/app/helpers/assets_helper.rb index d35fa4e37..eddeb43fb 100644 --- a/app/helpers/assets_helper.rb +++ b/app/helpers/assets_helper.rb @@ -1,6 +1,4 @@ module AssetsHelper - include SvgHelper - def inline_svg(asset_name, classname: "svg-container") return if asset_name.blank? content_tag(:div, safe_svg(inline_file(asset_name)), class: classname) @@ -16,4 +14,8 @@ def inline_file(asset_name) Rails.root.join("public/assets/#{asset_path}").read end end + + def safe_svg(body) + sanitize(body, tags: Loofah::HTML5::WhiteList::SVG_ELEMENTS, attributes: Loofah::HTML5::WhiteList::SVG_ATTRIBUTES) + end end diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb deleted file mode 100644 index 308ba42ef..000000000 --- a/app/helpers/button_helper.rb +++ /dev/null @@ -1,120 +0,0 @@ -module ButtonHelper - using RefinedString - BASE_OPTS = "btn group" - - BUTTON_OPTIONS = { - green: { - class: "#{BASE_OPTS} bg-emerald-500 hover:bg-emerald-600 text-white" - }, - blue: { - class: "#{BASE_OPTS} bg-indigo-500 hover:bg-indigo-600 text-white" - }, - red: { - class: "#{BASE_OPTS} bg-rose-500 hover:bg-rose-600 text-white" - }, - neutral: { - class: "#{BASE_OPTS} border-slate-300 hover:border-slate-400 text-slate-600" - }, - slate: { - class: "#{BASE_OPTS} border-slate-400 hover:border-slate-600 text-slate-600" - }, - amber: { - class: "#{BASE_OPTS} bg-amber-500 hover:bg-amber-600 text-white" - }, - disabled: - { - class: "#{BASE_OPTS} opacity-30 disabled cursor-not-allowed bg-transparent border-slate-300 hover:border-slate-300", - disabled: true - } - } - - def apply_button_loader(value) - concat content_tag(:span, value, class: "group-disabled:hidden") - concat content_tag(:span, "Processing...", class: "hidden group-disabled:inline group-disabled:opacity-60") - end - - def apply_button_options(options, new_options) - options ||= {} - options[:class] ||= "" - options[:class] << " #{new_options[:class]}" - options[:class].squish - options.merge(new_options.except(:class)) - end - - def apply_button_styles(style, options, html_options, block) - new_opts = BUTTON_OPTIONS[style] - - if block - options = apply_button_options(options, new_opts) - else - html_options = apply_button_options(html_options, new_opts) - end - - [options, html_options] - end - - # link that looks like a styled button - def decorated_link_to(style, name = nil, options = nil, html_options = nil, &block) - options, html_options = apply_button_styles(style, options, html_options, block) - name = name&.better_titleize - link_to(name, options, html_options, &block) - end - - # styled button with path - def decorated_button_to(style, name = nil, options = nil, html_options = nil, &block) - options, html_options = apply_button_styles(style, options, html_options, block) - name = name&.better_titleize - - # if there is no block, the button loader is auto-applied on clicks - # when block is supplied, the user is expected to attach the button loader inside the block - if block || style.eql?(:disabled) - button_to(name, options, html_options, &block) - else - button_to(options, html_options) { apply_button_loader(name) } - end - end - - # styled button tag - def decorated_button_tag(style, options = nil, html_options = nil, &block) - options, html_options = apply_button_styles(style, options, html_options, block) - button_tag(options, html_options, &block) - end - - # auth-aware link that looks like a styled button - def authz_link_to(style, name = nil, options = nil, html_options = nil, &block) - style = :disabled unless writer? - - if style == :disabled - if block - name = "javascript:void(0);" - else - options = "javascript:void(0);" - end - end - - decorated_link_to(style, name, options, html_options, &block) - end - - # auth-aware styled button with path - def authz_button_to(style, name = nil, options = nil, html_options = nil, &) - style = :disabled unless writer? - decorated_button_to(style, name, options, html_options, &) - end - - class AuthzForms < ActionView::Helpers::FormBuilder - def decorated_submit(style, value, options, &block) - _options, html_options = @template.apply_button_styles(style, {}, options, nil) - - if block || style.eql?(:disabled) - button(value, html_options, &block) - else - button(html_options) { @template.apply_button_loader(value) } - end - end - - def authz_submit(style, value = nil, options = nil, &) - style = :disabled unless @template.writer? - decorated_submit(style, value, options, &) - end - end -end diff --git a/app/helpers/deployments_helper.rb b/app/helpers/deployments_helper.rb deleted file mode 100644 index e55e9333c..000000000 --- a/app/helpers/deployments_helper.rb +++ /dev/null @@ -1,11 +0,0 @@ -module DeploymentsHelper - def deployment_channel_name(chan) - chan["is_internal"] ? chan["name"] + " (Internal)" : chan["name"] - end - - def show_deployment(deployment) - display = deployment.display_attr(:integration_type) - display += " ā€¢ #{deployment_channel_name(deployment.build_artifact_channel)}" if deployment.display_channel? - display - end -end diff --git a/app/helpers/enhanced_form_helper.rb b/app/helpers/enhanced_form_helper.rb index db2ccd178..fa8557ad3 100644 --- a/app/helpers/enhanced_form_helper.rb +++ b/app/helpers/enhanced_form_helper.rb @@ -1,6 +1,4 @@ module EnhancedFormHelper - include ButtonHelper - class BetterForm < ActionView::Helpers::FormBuilder def mandatory_label(method, *args) options = args.extract_options! diff --git a/app/helpers/external_release_helper.rb b/app/helpers/external_release_helper.rb deleted file mode 100644 index c4979663c..000000000 --- a/app/helpers/external_release_helper.rb +++ /dev/null @@ -1,11 +0,0 @@ -module ExternalReleaseHelper - def external_release_status_timestamp(external_release) - if external_release.released_at - "Released #{ago_in_words(external_release.released_at)}" - elsif external_release.reviewed_at - "Reviewed #{ago_in_words(external_release.reviewed_at)}" - else - "Changed #{ago_in_words(external_release.updated_at)}" - end - end -end diff --git a/app/helpers/releases_helper.rb b/app/helpers/releases_helper.rb deleted file mode 100644 index 5031a353a..000000000 --- a/app/helpers/releases_helper.rb +++ /dev/null @@ -1,164 +0,0 @@ -module ReleasesHelper - include ApplicationHelper - include Memery - - SHOW_RELEASE_STATUS = { - finished: ["Completed", :success], - stopped: ["Stopped", :inert], - created: ["Running", :ongoing], - on_track: ["Running", :ongoing], - upcoming: ["Upcoming", :inert], - post_release: ["Finalizing", :neutral], - post_release_started: ["Finalizing", :neutral], - post_release_failed: ["Finalizing", :neutral], - partially_finished: ["Partially Finished", :ongoing], - stopped_after_partial_finish: ["Stopped & Partially Finished", :inert] - } - - def release_status_badge(status) - status, styles = SHOW_RELEASE_STATUS.fetch(status.to_sym) - status_badge(status, styles) - end - - def build_status_badge(step_run) - status, styles = - case step_run.status.to_sym - when :ci_workflow_triggered, :on_track - ["Waiting for CI", :routine] - when :ci_workflow_started - ["In progress", :ongoing] - when :build_ready - ["Looking for build to deploy", :ongoing] - when :deployment_started, :deployment_restarted - ["Deployments in progress", :ongoing] - when :build_found_in_store - ["Build found in store", :routine] - when :build_not_found_in_store - ["Build not found in store", :failure] - when :success - ["Success", :success] - when :ci_workflow_failed - ["CI workflow failure", :failure] - when :ci_workflow_unavailable - ["CI workflow not found", :failure] - when :ci_workflow_halted - ["CI workflow cancelled", :inert] - when :build_unavailable - ["Build unavailable", :failure] - when :deployment_failed - ["Deployment failed", :failure] - when :failed_with_action_required - ["Needs manual submission", :failure] - when :cancelling - ["Cancelling", :inert] - when :cancelled - ["Cancelled", :inert] - when :cancelled_before_start - ["Overwritten", :neutral] - else - ["Unknown", :neutral] - end - - status_badge(status, styles) - end - - def deployment_run_status_badge(deployment_run) - status, styles = - case deployment_run.status.to_sym - when :created - ["About to start", :inert] - when :started - ["Running", :ongoing] - when :preparing_release - ["Preparing store version", :ongoing] - when :prepared_release - ["Ready for review", :ongoing] - when :failed_prepare_release - ["Failed to start release", :inert] - when :submitted_for_review - ["Submitted for review", :ongoing] - when :review_failed - ["Review rejected", :failure] - when :ready_to_release - ["Review approved", :ongoing] - when :uploading - ["Uploading", :routine] - when :uploaded - ["Uploaded", :routine] - when :rollout_started - ["Release in progress", :routine] - when :released - ["Released", :success] - when :failed - ["Failed", :failure] - when :failed_with_action_required - ["Needs manual submission", :failure] - else - ["Unknown", :neutral] - end - - status_badge(status, styles) - end - - def pull_request_status(pull_request) - case pull_request.state.to_sym - when :open - :success - when :closed - :ongoing - else - :neutral - end - end - - def pull_request_badge(pull_request) - status_badge(pull_request.state, pull_request_status(pull_request)) - end - - def stop_release_warning(release) - message = "" - message += "You have finished release to one of the platforms. " if release.partially_finished? - message += "You have unmerged commits in this release branch. " if release.all_commits.size > 1 - message + "Are you sure you want to stop the release?" - end - - def formatted_commit_info(commit) - name = commit.author_name || commit.author_login - author_link = commit.author_url || "mailto:#{commit.author_email}" - author_url = link_to_external(name, author_link, class: "underline") - builder = content_tag(:code, commit.short_sha) - builder += " ā€¢ " - builder += author_url + " committed " + ago_in_words(commit.timestamp) - builder += " ā€¢ applied " + ago_in_words(commit.applied_at) if commit.applied_at.present? - builder - end - - def blocked_step_release_link(release) - release_url = if release.ongoing? - hotfix_release_app_train_releases_path(release.train.app, release.train) - else - ongoing_release_app_train_releases_path(release.train.app, release.train) - end - link_text = release.ongoing? ? "current hotfix release" : "current ongoing release" - link_to link_text, release_url, class: "underline" - end - - def release_title(release) - if release.hotfix? - concat content_tag :span, release.release_version.to_s, class: "pr-2" - concat inline_svg("band_aid.svg", classname: "w-6 align-middle inline-flex") - content_tag :span, "hotfix release", class: "ml-2 text-sm bg-amber-50 px-2 py-1" - else - release.release_version - end - end - - def hotfixed_from(release) - hotfixed_from = release.hotfixed_from - content_tag(:div, class: "inline-flex") do - concat content_tag(:span, "(hotfixed from ".html_safe) - concat link_to content_tag(:code, hotfixed_from.release_version.to_s), hotfixed_from.live_release_link, class: "underline" - concat content_tag(:span, ")") - end - end -end diff --git a/app/helpers/scheduled_releases_helper.rb b/app/helpers/scheduled_releases_helper.rb deleted file mode 100644 index 2b69720d0..000000000 --- a/app/helpers/scheduled_releases_helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ScheduledReleasesHelper - def scheduled_release_badge(scheduled_release) - if scheduled_release.is_success? - status_badge("success", :success) - elsif !scheduled_release.pending? - status_badge("skipped", :neutral) - else - status_badge("scheduled", :ongoing) - end - end - - def scheduled_release_text(scheduled_release) - time_format(scheduled_release.scheduled_at) - end -end diff --git a/app/helpers/steps_helper.rb b/app/helpers/steps_helper.rb deleted file mode 100644 index 9003dfc75..000000000 --- a/app/helpers/steps_helper.rb +++ /dev/null @@ -1,26 +0,0 @@ -module StepsHelper - def show_ci_cd(step) - "#{step.ci_cd_provider.display} ā€¢ #{step.ci_cd_channel["name"]}" - end - - def platform_subtitle(app, step) - "For the #{step.release_platform.display_attr(:platform)} platform" if app.cross_platform? - end - - def auto_deploy_status_badge(step) - step.auto_deploy? ? "" : "Manual Distribution" - end - - def auto_deploy_status(step) - step.auto_deploy? ? "ON" : "OFF" - end - - def build_artifact_pattern_text(step) - return unless step.has_uploadables? - if step.build_artifact_name_pattern.present? - "picks up build with the pattern #{step.build_artifact_name_pattern} from the above workflow" - else - "picks up the largest build from the above workflow" - end - end -end diff --git a/app/helpers/svg_helper.rb b/app/helpers/svg_helper.rb deleted file mode 100644 index cd0a53b2c..000000000 --- a/app/helpers/svg_helper.rb +++ /dev/null @@ -1,5 +0,0 @@ -module SvgHelper - def safe_svg(body) - sanitize(body, tags: Loofah::HTML5::WhiteList::SVG_ELEMENTS, attributes: Loofah::HTML5::WhiteList::SVG_ATTRIBUTES) - end -end diff --git a/app/helpers/trains_helper.rb b/app/helpers/trains_helper.rb index bd12c6aa0..3750c4a4b 100644 --- a/app/helpers/trains_helper.rb +++ b/app/helpers/trains_helper.rb @@ -1,23 +1,4 @@ module TrainsHelper - def steps_heading(release_platform) - return release_platform.display_attr(:platform) + " Steps" if release_platform.app.cross_platform? - "Steps" - end - - def start_release_text(train, major: false) - text = train.automatic? ? "Manually start " : "Start " - text += major ? "major " : "minor " - text += "release " - text + train.next_version(major_only: major) - end - - def start_upcoming_release_text(ongoing_release, major: false) - text = "Prepare next " - text += major ? "major " : "minor " - text += "release " - text + ongoing_release.next_version(major_only: major) - end - def release_schedule(train) if train.automatic? date = time_format(train.kickoff_at, with_year: true, with_time: false) @@ -28,20 +9,4 @@ def release_schedule(train) "No release schedule" end end - - def build_queue_config(train) - if train.build_queue_enabled? - "Applied every #{train.build_queue_wait_time.inspect} OR #{train.build_queue_size} commits" - else - "Applied with every new commit" - end - end - - def backmerge_text(train) - if train.continuous_backmerge? - "Changes on the release branch will be merged continuously to the working branch." - else - "Changes on the release branch will be merged to the working branch at the end of the release." - end - end end diff --git a/app/javascript/controllers/branching_selector_controller.js b/app/javascript/controllers/domain/branching_selector_controller.js similarity index 100% rename from app/javascript/controllers/branching_selector_controller.js rename to app/javascript/controllers/domain/branching_selector_controller.js diff --git a/app/javascript/controllers/domain/release_index_component_controller.js b/app/javascript/controllers/domain/reldex_component_controller.js similarity index 100% rename from app/javascript/controllers/domain/release_index_component_controller.js rename to app/javascript/controllers/domain/reldex_component_controller.js diff --git a/app/javascript/controllers/domain/release_index_controller.js b/app/javascript/controllers/domain/reldex_controller.js similarity index 100% rename from app/javascript/controllers/domain/release_index_controller.js rename to app/javascript/controllers/domain/reldex_controller.js diff --git a/app/javascript/controllers/dropdown_stream_controller.js b/app/javascript/controllers/dropdown_stream_controller.js deleted file mode 100644 index 9529047fa..000000000 --- a/app/javascript/controllers/dropdown_stream_controller.js +++ /dev/null @@ -1,78 +0,0 @@ -import {Controller} from "@hotwired/stimulus" -import {useMutation} from "stimulus-use" -import {get} from "@rails/request.js" - -export default class extends Controller { - static values = { - dynamicSelectUrl: String, - dynamicSelectKey: String, - showElementIf: String, - } - static targets = ["dynamicSelect", "showElement", "hideElement"] - - connect() { - useMutation(this, {childList: true, subtree: true, element: this.dynamicSelectTarget}) - this.showElementOnDynamicSelectChange() - } - - mutate() { - this.showElementOnDynamicSelectChange() - } - - fetchDynamicSelect(event) { - let url = new URL(this.dynamicSelectUrlValue) - url.searchParams.set(this.dynamicSelectKeyValue, event.target.selectedOptions[0].value); - url.searchParams.set('target', this.dynamicSelectTarget.id); - - get(url, {responseKind: "turbo-stream"}) - } - - showElementOnDynamicSelectChange() { - const selectedShowElementValue = this.dynamicSelectTarget.selectedOptions[0].value - - if (this.showingElementAllowed()) { - const parsedSelected = this.__safeJSONParse(selectedShowElementValue) - const parsedMatchers = this.__safeJSONParse(this.showElementIfValue) - - if (parsedSelected && parsedMatchers) { - this.showElementTarget.hidden = !this.__is_any(parsedSelected, parsedMatchers) - } else { - this.showElementTarget.hidden = selectedShowElementValue !== this.showElementIfValue - } - - if (this.hasHideElementTarget) this.hideElementTarget.hidden = !this.showElementTarget.hidden - } - } - - showingElementAllowed() { - return this.hasShowElementIfValue && this.hasShowElementTarget - } - - __is_any(obj, matcher) { - let flag = false; - - for (const [key, value] of Object.entries(matcher)) { - if (this.__hasKeySetTo(obj, key, value)) { - flag = true - } - } - - return flag; - } - - __hasKeySetTo(obj, k, v) { - return obj.hasOwnProperty(k) && obj[k] === v; - } - - __safeJSONParse(str) { - let parsedJSON = null; - - try { - parsedJSON = JSON.parse(str); - } catch (e) { - return false; - } - - return parsedJSON; - } -} diff --git a/app/jobs/build_queue_application_job.rb b/app/jobs/build_queue_application_job.rb index d028ac569..c61328794 100644 --- a/app/jobs/build_queue_application_job.rb +++ b/app/jobs/build_queue_application_job.rb @@ -8,14 +8,6 @@ def perform(build_queue_id) return unless build_queue.release.committable? return unless build_queue.is_active? - if build_queue.release.is_v2? - Signal.build_queue_can_be_applied!(build_queue) - else - build_queue.release.with_lock do - return unless build_queue.release.committable? - return unless build_queue.is_active? - build_queue.apply! - end - end + Signal.build_queue_can_be_applied!(build_queue) end end diff --git a/app/jobs/deployments/app_store_connect/find_live_release_job.rb b/app/jobs/deployments/app_store_connect/find_live_release_job.rb deleted file mode 100644 index b91976bf6..000000000 --- a/app/jobs/deployments/app_store_connect/find_live_release_job.rb +++ /dev/null @@ -1,21 +0,0 @@ -class Deployments::AppStoreConnect::FindLiveReleaseJob - include Sidekiq::Job - extend Loggable - extend Backoffable - - queue_as :high - sidekiq_options retry: 6000 - - sidekiq_retry_in do |_count, ex| - if ex.is_a?(Deployments::AppStoreConnect::Release::ReleaseNotFullyLive) - 5.minutes.to_i - else - elog(ex) - :kill - end - end - - def perform(deployment_run_id) - Deployments::AppStoreConnect::Release.track_live_release_status(DeploymentRun.find(deployment_run_id)) - end -end diff --git a/app/jobs/deployments/app_store_connect/prepare_for_release_job.rb b/app/jobs/deployments/app_store_connect/prepare_for_release_job.rb deleted file mode 100644 index 6c9c15f48..000000000 --- a/app/jobs/deployments/app_store_connect/prepare_for_release_job.rb +++ /dev/null @@ -1,25 +0,0 @@ -class Deployments::AppStoreConnect::PrepareForReleaseJob - include Sidekiq::Job - extend Backoffable - - queue_as :high - sidekiq_options retry: 3 - - sidekiq_retry_in do |count, ex| - if ex.is_a?(Deployments::AppStoreConnect::Release::PreparedVersionNotFoundError) - backoff_in(attempt: count, period: :minutes, type: :static, factor: 1).to_i - else - :kill - end - end - - sidekiq_retries_exhausted do |msg, ex| - run = DeploymentRun.find(msg["args"].first) - run.fail_with_error(ex) - end - - def perform(deployment_run_id, force = false) - run = DeploymentRun.find(deployment_run_id) - Deployments::AppStoreConnect::Release.prepare_for_release!(run, force:) - end -end diff --git a/app/jobs/deployments/app_store_connect/test_flight_release_job.rb b/app/jobs/deployments/app_store_connect/test_flight_release_job.rb deleted file mode 100644 index 72c999bd3..000000000 --- a/app/jobs/deployments/app_store_connect/test_flight_release_job.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Deployments::AppStoreConnect::TestFlightReleaseJob < ApplicationJob - queue_as :high - - def perform(deployment_run_id) - Deployments::AppStoreConnect::Release.to_test_flight!(DeploymentRun.find(deployment_run_id)) - end -end diff --git a/app/jobs/deployments/app_store_connect/update_build_notes_job.rb b/app/jobs/deployments/app_store_connect/update_build_notes_job.rb deleted file mode 100644 index 216642671..000000000 --- a/app/jobs/deployments/app_store_connect/update_build_notes_job.rb +++ /dev/null @@ -1,11 +0,0 @@ -class Deployments::AppStoreConnect::UpdateBuildNotesJob < ApplicationJob - queue_as :high - - def perform(deployment_run_id) - run = DeploymentRun.find(deployment_run_id) - return unless run.send_notes? - return unless run.test_flight_release? - - Deployments::AppStoreConnect::Release.update_build_notes!(run) - end -end diff --git a/app/jobs/deployments/app_store_connect/update_external_release_job.rb b/app/jobs/deployments/app_store_connect/update_external_release_job.rb deleted file mode 100644 index e4a92b72d..000000000 --- a/app/jobs/deployments/app_store_connect/update_external_release_job.rb +++ /dev/null @@ -1,21 +0,0 @@ -class Deployments::AppStoreConnect::UpdateExternalReleaseJob - include Sidekiq::Job - extend Loggable - extend Backoffable - - queue_as :high - sidekiq_options retry: 2000 - - sidekiq_retry_in do |count, ex| - if ex.is_a?(Deployments::AppStoreConnect::Release::ExternalReleaseNotInTerminalState) - backoff_in(attempt: count, period: :minutes, type: :static, factor: 5).to_i - else - elog(ex) - :kill - end - end - - def perform(deployment_run_id) - Deployments::AppStoreConnect::Release.update_external_release(DeploymentRun.find(deployment_run_id)) - end -end diff --git a/app/jobs/deployments/google_firebase/update_build_notes_job.rb b/app/jobs/deployments/google_firebase/update_build_notes_job.rb deleted file mode 100644 index 0d11f7ba3..000000000 --- a/app/jobs/deployments/google_firebase/update_build_notes_job.rb +++ /dev/null @@ -1,11 +0,0 @@ -class Deployments::GoogleFirebase::UpdateBuildNotesJob < ApplicationJob - queue_as :high - - def perform(deployment_run_id, release_name) - run = DeploymentRun.find(deployment_run_id) - return unless run.send_notes? - return unless run.google_firebase_integration? - - Deployments::GoogleFirebase::Release.update_build_notes!(run, release_name) - end -end diff --git a/app/jobs/deployments/google_firebase/update_upload_status_job.rb b/app/jobs/deployments/google_firebase/update_upload_status_job.rb deleted file mode 100644 index 7074b21e9..000000000 --- a/app/jobs/deployments/google_firebase/update_upload_status_job.rb +++ /dev/null @@ -1,24 +0,0 @@ -class Deployments::GoogleFirebase::UpdateUploadStatusJob - include Sidekiq::Job - extend Loggable - extend Backoffable - - queue_as :high - sidekiq_options retry: 5 - - sidekiq_retry_in do |count, ex| - if ex.is_a?(Deployments::GoogleFirebase::Release::UploadNotComplete) - backoff_in(attempt: count, period: :minutes, type: :static, factor: 2).to_i - else - elog(ex) - :kill - end - end - - def perform(deployment_run_id, op_name) - run = DeploymentRun.find(deployment_run_id) - return unless run.google_firebase_integration? - - Deployments::GoogleFirebase::Release.update_upload_status!(run, op_name) - end -end diff --git a/app/jobs/deployments/google_firebase/upload_job.rb b/app/jobs/deployments/google_firebase/upload_job.rb deleted file mode 100644 index f5ee76143..000000000 --- a/app/jobs/deployments/google_firebase/upload_job.rb +++ /dev/null @@ -1,10 +0,0 @@ -class Deployments::GoogleFirebase::UploadJob < ApplicationJob - queue_as :high - - def perform(deployment_run_id) - run = DeploymentRun.find(deployment_run_id) - return unless run.google_firebase_integration? - - Deployments::GoogleFirebase::Release.upload!(run) - end -end diff --git a/app/jobs/deployments/google_play_store/upload.rb b/app/jobs/deployments/google_play_store/upload.rb deleted file mode 100644 index 6a309a601..000000000 --- a/app/jobs/deployments/google_play_store/upload.rb +++ /dev/null @@ -1,10 +0,0 @@ -class Deployments::GooglePlayStore::Upload < ApplicationJob - queue_as :high - - def perform(deployment_run_id) - run = DeploymentRun.find(deployment_run_id) - return unless run.google_play_store_integration? - - Deployments::GooglePlayStore::Release.upload!(run) - end -end diff --git a/app/jobs/deployments/release_job.rb b/app/jobs/deployments/release_job.rb deleted file mode 100644 index b25308715..000000000 --- a/app/jobs/deployments/release_job.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Deployments::ReleaseJob < ApplicationJob - queue_as :high - - def perform(deployment_run_id) - DeploymentRun.find(deployment_run_id).start_release! - end -end diff --git a/app/jobs/deployments/slack_job.rb b/app/jobs/deployments/slack_job.rb deleted file mode 100644 index 6caa4e20c..000000000 --- a/app/jobs/deployments/slack_job.rb +++ /dev/null @@ -1,9 +0,0 @@ -class Deployments::SlackJob < ApplicationJob - queue_as :high - - def perform(deployment_run_id) - run = DeploymentRun.find(deployment_run_id) - return unless run.slack_integration? - run.push_to_slack! - end -end diff --git a/app/jobs/refresh_reldex_job.rb b/app/jobs/refresh_reldex_job.rb index 5d0690634..f2dc89d98 100644 --- a/app/jobs/refresh_reldex_job.rb +++ b/app/jobs/refresh_reldex_job.rb @@ -6,17 +6,9 @@ def perform(train_id) train = Train.find(train_id) train.releases.finished.each do |release| - if release.is_v2? - Queries::ReleaseBreakdown.warm(release.id, [:reldex]) - else - Queries::ReleaseSummary.warm(release.id) - end + Queries::ReleaseBreakdown.warm(release.id, [:reldex]) end - if train.product_v2? - Queries::DevopsReport.warm(train) - else - Charts::DevopsReport.warm(train) - end + Queries::DevopsReport.warm(train) end end diff --git a/app/jobs/refresh_reports_job.rb b/app/jobs/refresh_reports_job.rb index 9cdcea12a..2ef31e0f7 100644 --- a/app/jobs/refresh_reports_job.rb +++ b/app/jobs/refresh_reports_job.rb @@ -5,17 +5,7 @@ class RefreshReportsJob < ApplicationJob def perform(release_id) release = Release.find(release_id) train = release.train - - if release.is_v2? - Queries::ReleaseBreakdown.warm(release.id) - else - Queries::ReleaseSummary.warm(release_id) - end - - if train.product_v2? - Queries::DevopsReport.warm(train) - else - Charts::DevopsReport.warm(train) - end + Queries::ReleaseBreakdown.warm(release.id) + Queries::DevopsReport.warm(train) end end diff --git a/app/jobs/releases/cancel_step_run.rb b/app/jobs/releases/cancel_step_run.rb deleted file mode 100644 index 5ded69fed..000000000 --- a/app/jobs/releases/cancel_step_run.rb +++ /dev/null @@ -1,13 +0,0 @@ -class Releases::CancelStepRun < ApplicationJob - include Loggable - - queue_as :high - - def perform(step_run_id) - step_run = StepRun.find(step_run_id) - return unless step_run.active? - - Rails.logger.debug { "Cancelling step run - #{step_run_id}" } - step_run.cancel! - end -end diff --git a/app/jobs/releases/cancel_workflow_run_job.rb b/app/jobs/releases/cancel_workflow_run_job.rb deleted file mode 100644 index 6c3496815..000000000 --- a/app/jobs/releases/cancel_workflow_run_job.rb +++ /dev/null @@ -1,17 +0,0 @@ -class Releases::CancelWorkflowRunJob < ApplicationJob - extend Backoffable - WorkflowRunNotFound = Class.new(StandardError) - - queue_as :high - retry_on WorkflowRunNotFound, wait: ->(c) { backoff_in(attempt: c, period: :seconds, type: :linear) }, attempts: 500 - - def perform(step_run_id) - step_run = StepRun.find(step_run_id) - return unless step_run.cancelling? - raise WorkflowRunNotFound unless step_run.workflow_found? - - Rails.logger.debug { "Cancelling workflow for step run - #{step_run_id}" } - step_run.cancel_ci_workflow! - step_run.cancel! - end -end diff --git a/app/jobs/releases/fetch_health_metrics_job.rb b/app/jobs/releases/fetch_health_metrics_job.rb deleted file mode 100644 index 0affaae3d..000000000 --- a/app/jobs/releases/fetch_health_metrics_job.rb +++ /dev/null @@ -1,17 +0,0 @@ -class Releases::FetchHealthMetricsJob < ApplicationJob - queue_as :high - - RELEASE_MONITORING_PERIOD_IN_DAYS = 15 - - def perform(deployment_run_id, frequency) - run = DeploymentRun.find(deployment_run_id) - return if run.release.stopped? - return if run.release.finished? && run.release.completed_at < RELEASE_MONITORING_PERIOD_IN_DAYS.days.ago - - begin - run.fetch_health_data! - ensure - Releases::FetchHealthMetricsJob.set(wait: frequency).perform_later(deployment_run_id, frequency) - end - end -end diff --git a/app/jobs/releases/find_build_job.rb b/app/jobs/releases/find_build_job.rb deleted file mode 100644 index d170e2b68..000000000 --- a/app/jobs/releases/find_build_job.rb +++ /dev/null @@ -1,34 +0,0 @@ -class Releases::FindBuildJob - include Sidekiq::Job - extend Loggable - extend Backoffable - - queue_as :high - sidekiq_options retry: 8 - - sidekiq_retry_in do |count, ex| - if ex.is_a?(Installations::Error) && ex.reason == :build_not_found - backoff_in(attempt: count, period: :minutes).to_i - else - elog(ex) - :kill - end - end - - sidekiq_retries_exhausted do |msg, ex| - if ex.is_a?(Installations::Error) && ex.reason == :build_not_found - run = StepRun.find(msg["args"].first) - run.build_not_found! - run.event_stamp!(reason: :build_not_found_in_store, kind: :error, data: {version: run.build_version}) - end - end - - def perform(step_run_id) - run = StepRun.find(step_run_id) - return unless run.active? - - run.find_build.value! - run.build_found! - run.event_stamp!(reason: :build_found_in_store, kind: :notice, data: {version: run.build_version}) - end -end diff --git a/app/jobs/releases/find_workflow_run.rb b/app/jobs/releases/find_workflow_run.rb deleted file mode 100644 index f25db4b97..000000000 --- a/app/jobs/releases/find_workflow_run.rb +++ /dev/null @@ -1,32 +0,0 @@ -class Releases::FindWorkflowRun - include Sidekiq::Job - include Loggable - - queue_as :high - sidekiq_options retry: 25 - - sidekiq_retry_in do |count, exception| - if exception.is_a?(Installations::Error) && exception.reason == :workflow_run_not_found - 10 * (count + 1) - else - :kill - end - end - - sidekiq_retries_exhausted do |msg, ex| - if ex.is_a?(Installations::Error) && ex.reason == :workflow_run_not_found - run = StepRun.find(msg["args"].first) - run.ci_unavailable! if run.may_ci_unavailable? - run.event_stamp!(reason: :ci_workflow_unavailable, kind: :error, data: {}) - end - end - - def perform(step_run_id) - step_run = StepRun.find(step_run_id) - step_run.find_and_update_workflow_run - step_run.ci_start! if step_run.may_ci_start? - rescue => e - elog(e) - raise # TODO: remove this and elog in the retry-kill case - end -end diff --git a/app/jobs/releases/pre_release_job.rb b/app/jobs/releases/pre_release_job.rb index a3cf3d603..8772b9ae4 100644 --- a/app/jobs/releases/pre_release_job.rb +++ b/app/jobs/releases/pre_release_job.rb @@ -6,8 +6,7 @@ def perform(release_id) if release.retrigger_for_hotfix? latest_commit = release.latest_commit_hash(sha_only: false) - return WebhookProcessors::Push.process(release, latest_commit, []) unless release.is_v2? - return Signal.commits_have_landed!(release, latest_commit, []) if release.is_v2? + return Signal.commits_have_landed!(release, latest_commit, []) end Triggers::PreRelease.call(release) diff --git a/app/jobs/releases/trigger_workflow_run_job.rb b/app/jobs/releases/trigger_workflow_run_job.rb deleted file mode 100644 index 85b1470fc..000000000 --- a/app/jobs/releases/trigger_workflow_run_job.rb +++ /dev/null @@ -1,13 +0,0 @@ -class Releases::TriggerWorkflowRunJob < ApplicationJob - include Loggable - - queue_as :high - - def perform(step_run_id) - step_run = StepRun.find(step_run_id) - return unless step_run.active? - return step_run.cancel! unless step_run.commit.applicable? - - step_run.trigger_ci_worfklow_run! - end -end diff --git a/app/jobs/releases/upload_artifact.rb b/app/jobs/releases/upload_artifact.rb deleted file mode 100644 index c03bed538..000000000 --- a/app/jobs/releases/upload_artifact.rb +++ /dev/null @@ -1,31 +0,0 @@ -class Releases::UploadArtifact - include Sidekiq::Job - extend Loggable - queue_as :high - - sidekiq_options retry: 3 - - sidekiq_retry_in do |count, exception| - if exception.is_a?(Installations::Error) && exception && exception.reason == :artifact_not_found - 10 * (count + 1) - else - :kill - end - end - - sidekiq_retries_exhausted do |msg, ex| - elog(ex) - run = StepRun.find(msg["args"].first) - run.build_upload_failed! - run.event_stamp!(reason: :build_unavailable, kind: :error, data: {version: run.build_version}) - end - - def perform(step_run_id, artifacts_url) - run = StepRun.find(step_run_id) - return unless run.active? - - run.artifacts_url = artifacts_url - run.upload_artifact! - run.event_stamp!(reason: :build_available, kind: :notice, data: {version: run.build_version}) - end -end diff --git a/app/jobs/webhook_processors/close_pull_request_job.rb b/app/jobs/webhook_processors/close_pull_request_job.rb deleted file mode 100644 index 2789067d7..000000000 --- a/app/jobs/webhook_processors/close_pull_request_job.rb +++ /dev/null @@ -1,13 +0,0 @@ -class WebhookProcessors::ClosePullRequestJob < ApplicationJob - queue_as :high - - def perform(train_id, pr_attributes) - head_ref = pr_attributes[:head_ref] - number = pr_attributes[:number] - - Train.find(train_id).open_active_prs_for(head_ref).where(number:).find_each do |pr| - pr.update_or_insert!(pr_attributes) - pr.release.event_stamp!(reason: :pr_merged, kind: :success, data: {url: pr.url, number: pr.number, base_branch: pr.base_ref}) - end - end -end diff --git a/app/jobs/webhook_processors/open_pull_request_job.rb b/app/jobs/webhook_processors/open_pull_request_job.rb deleted file mode 100644 index 11173fbed..000000000 --- a/app/jobs/webhook_processors/open_pull_request_job.rb +++ /dev/null @@ -1,17 +0,0 @@ -class WebhookProcessors::OpenPullRequestJob < ApplicationJob - queue_as :high - - def perform(train_id, pr_attributes) - Rails.logger.debug { "Create/update PR with attributes: #{pr_attributes}" } - - base_ref = pr_attributes[:base_ref] - - train = Train.find(train_id) - release = train.active_runs.where(branch_name: base_ref).sole - - return unless release - - pr = release.pull_requests.mid_release.open.build - pr.update_or_insert!(pr_attributes) - end -end diff --git a/app/jobs/webhook_processors/push_job.rb b/app/jobs/webhook_processors/push_job.rb deleted file mode 100644 index c6b927764..000000000 --- a/app/jobs/webhook_processors/push_job.rb +++ /dev/null @@ -1,9 +0,0 @@ -class WebhookProcessors::PushJob < ApplicationJob - queue_as :high - - def perform(train_run_id, head_commit, rest_commits) - run = Release.find(train_run_id) - return unless run.committable? - WebhookProcessors::Push.process(run, head_commit, rest_commits) - end -end diff --git a/app/jobs/workflow_processors/workflow_run_job.rb b/app/jobs/workflow_processors/workflow_run_job.rb deleted file mode 100644 index def1c8bcc..000000000 --- a/app/jobs/workflow_processors/workflow_run_job.rb +++ /dev/null @@ -1,11 +0,0 @@ -class WorkflowProcessors::WorkflowRunJob < ApplicationJob - queue_as :high - - def perform(step_run_id) - run = StepRun.find(step_run_id) - return unless run.active? - return unless run.ci_workflow_started? - - WorkflowProcessors::WorkflowRun.process(run) - end -end diff --git a/app/libs/charts/devops_report.rb b/app/libs/charts/devops_report.rb deleted file mode 100644 index 9b7749616..000000000 --- a/app/libs/charts/devops_report.rb +++ /dev/null @@ -1,260 +0,0 @@ -class Charts::DevopsReport - include Memery - include Loggable - using RefinedString - - def self.warm(train) = new(train).warm - - def self.all(train) = new(train).all - - def initialize(train) - @train = train - @organization = train.organization - @team_colors = organization.team_colors - end - - def warm - cache.write(cache_key, report) - rescue => e - elog(e) - end - - def all - cache.fetch(cache_key) - end - - def report - { - refreshed_at: Time.current, - mobile_devops: { - duration: { - data: duration, - type: "area", - value_format: "time", - name: "devops.duration" - }, - frequency: { - data: frequency, - type: "area", - value_format: "number", - name: "devops.frequency" - }, - time_in_review: { - data: time_in_review, - type: "area", - value_format: "time", - name: "devops.time_in_review" - }, - hotfixes: { - data: hotfixes, - type: "area", - value_format: "number", - name: "devops.hotfixes" - }, - time_in_phases: { - data: time_in_phases, - stacked: true, - type: "stacked-bar", - value_format: "time", - name: "devops.time_in_phases", - height: "250" - }, - reldex_scores: { - data: reldex_scores, - type: "line", - value_format: "number", - name: "devops.reldex", - height: "250", - show_y_axis: true, - y_annotations: [ - {y: 0..train.release_index.tolerable_range.min, text: "Mediocre", color: "mediocre"}, - {y: train.release_index.tolerable_range.max, text: "Excellent", color: "excellent"} - ] - } - }, - operational_efficiency: { - stability_contributors: { - data: release_stability_contributors, - type: "line", - value_format: "number", - name: "operational_efficiency.stability_contributors", - show_y_axis: true - }, - contributors: { - data: contributors, - type: "line", - value_format: "number", - name: "operational_efficiency.contributors", - show_y_axis: true - }, - team_stability_contributors: { - data: team_stability_contributors, - stacked: true, - type: "stacked-bar", - value_format: "number", - name: "operational_efficiency.team_stability_contributors", - colors: team_colors, - show_y_axis: true, - height: "250" - }, - team_contributors: { - data: team_contributors, - stacked: true, - type: "stacked-bar", - value_format: "number", - name: "operational_efficiency.team_contributors", - colors: team_colors, - show_y_axis: true, - height: "250" - } - } - } - end - - LAST_RELEASES = 6 - LAST_TIME_PERIOD = 6 - - memoize def duration(last: LAST_RELEASES) - finished_releases(last) - .group_by(&:release_version) - .sort_by { |v, _| v.to_semverish }.to_h - .transform_values { {duration: _1.first.duration.seconds} } - end - - memoize def frequency(period = :month, format = "%b", last: LAST_TIME_PERIOD) - finished_releases(last, hotfix: true) - .reorder("") - .group_by_period(period, :completed_at, last: last, current: true, format:) - .count - .transform_values { {releases: _1} } - end - - memoize def reldex_scores(last: 10) - return if train.release_index.blank? - - finished_releases(last) - .group_by(&:release_version) - .sort_by { |v, _| v.to_semverish }.to_h - .transform_values { {reldex: _1.first.index_score&.value} } - end - - memoize def release_stability_contributors(last: LAST_RELEASES) - finished_releases(last) - .group_by(&:release_version) - .sort_by { |v, _| v.to_semverish }.to_h - .transform_values { _1.flat_map(&:all_commits).flat_map(&:author_email) } - .transform_values { {contributors: _1.uniq.size} } - end - - memoize def contributors(last: LAST_RELEASES) - finished_releases(last) - .group_by(&:release_version) - .sort_by { |v, _| v.to_semverish }.to_h - .transform_values { {contributors: _1.flat_map(&:release_changelog).compact.flat_map(&:unique_authors).size} } - end - - memoize def team_stability_contributors(last: LAST_RELEASES) - finished_releases(last) - .group_by(&:release_version) - .sort_by { |v, _| v.to_semverish }.to_h - .transform_values { |releases| release_summary(releases[0]).team_stability_commits } - .compact_blank - end - - memoize def team_contributors(last: LAST_RELEASES) - finished_releases(last) - .group_by(&:release_version) - .sort_by { |v, _| v.to_semverish }.to_h - .transform_values { |releases| release_summary(releases[0]).team_release_commits } - .compact_blank - end - - memoize def time_in_review(last: LAST_RELEASES) - train - .external_releases - .includes(deployment_run: [:deployment, {step_run: {release_platform_run: [:release]}}]) - .where.not(reviewed_at: nil) - .filter { _1.deployment_run.production_release_happened? } - .last(last) - .group_by(&:build_version) - .sort_by { |v, _| v.to_semverish }.to_h - .transform_values { _1.flat_map(&:review_time) } - .transform_values { {time: _1.sum(&:seconds) / _1.size.to_f} } - end - - memoize def hotfixes(last: LAST_RELEASES) - by_version = - finished_releases(last) - .flat_map(&:step_runs) - .flat_map(&:deployment_runs) - .filter { _1.production_release_happened? } - .group_by { _1.release.release_version } - .sort_by { |v, _| v.to_semverish }.to_h - - by_version.transform_values do |runs| - runs.group_by(&:platform).transform_values { |d| d.size.pred } - end - end - - def recovery_time - # group by rel - # recovery time: time between last rollout and next hotfix (line graph, across platforms) - raise NotImplementedError - end - - memoize def time_in_phases(last: LAST_RELEASES) - by_version = - finished_releases(last) - .flat_map(&:step_runs) - .group_by(&:release_version) - .sort_by { |v, _| v.to_semverish }.to_h - - by_version.transform_values do |runs| - runs.group_by(&:platform).transform_values do |by_platform| - by_platform.group_by(&:name).transform_values do - _1.pluck(:updated_at).max - _1.pluck(:scheduled_at).min - end - end - end - end - - # ci workflow time (per step, area graph) - def ci_workflow_time - raise NotImplementedError - end - - def automations_run - raise NotImplementedError - end - - attr_reader :train, :organization, :team_colors - - private - - delegate :cache, to: Rails - - memoize def finished_releases(n, hotfix: false) - releases = - train - .releases - .limit(n) - .finished - .reorder("completed_at DESC") - .includes(:release_changelog, {release_platform_runs: [:release_platform]}, :all_commits, step_runs: [:deployment_runs, :step]) - - return releases if hotfix - releases.release - end - - memoize def release_summary(release) - Queries::ReleaseSummary.new(release.id) - end - - def cache_key - "train/#{train.id}/devops_report" - end - - def thaw - cache.delete(cache_key) - end -end diff --git a/app/libs/computations/release/reldex_parameters.rb b/app/libs/computations/release/reldex_parameters.rb deleted file mode 100644 index 810f9c643..000000000 --- a/app/libs/computations/release/reldex_parameters.rb +++ /dev/null @@ -1,47 +0,0 @@ -class Computations::Release::ReldexParameters - def self.call(release) - new(release).call - end - - def initialize(release) - @release = release - end - - delegate :deployment_runs, :completed_at, :scheduled_at, :all_commits, :previous_release, :stability_commits, :duration, :all_hotfixes, to: :@release - - def call - rollout_duration = 0 - stability_duration = 0 - rollout_fixes = 0 - rollout_changes = 0 - days_since_last_release = 0 - - submitted_at = deployment_runs.filter_map(&:submitted_at).min - rollout_started_at = deployment_runs.filter_map(&:release_started_at).min - platform_store_versions = deployment_runs.reached_production.group_by(&:platform) - max_store_versions = platform_store_versions.transform_values(&:size).values.max - first_store_version = platform_store_versions.values.flatten.min_by(&:created_at) - - rollout_fixes = max_store_versions - 1 if max_store_versions.present? - rollout_duration = ActiveSupport::Duration.build(completed_at - rollout_started_at).in_days if rollout_started_at.present? - stability_duration = ActiveSupport::Duration.build(submitted_at - scheduled_at).in_days if submitted_at.present? - days_since_last_release = ActiveSupport::Duration.build(completed_at - previous_release&.completed_at).in_days if previous_release.present? - - if rollout_fixes > 0 - base_commit = first_store_version.step_run.commit - head_commit = all_commits.reorder(timestamp: :desc).first - rollout_changes = all_commits.between_commits(base_commit, head_commit).size - end - - { - hotfixes: all_hotfixes.size, - rollout_fixes:, - rollout_duration:, - duration: duration.in_days, - stability_duration:, - stability_changes: stability_commits.count, - days_since_last_release:, - rollout_changes: - } - end -end diff --git a/app/libs/computations/release/step_statuses.rb b/app/libs/computations/release/step_statuses.rb index 92ac52803..b2605b4c0 100644 --- a/app/libs/computations/release/step_statuses.rb +++ b/app/libs/computations/release/step_statuses.rb @@ -60,7 +60,7 @@ def release_candidate_status end def notes_status - return STATUS[:unblocked] if any_platforms? { |rp| rp.metadata_editable_v2? } + return STATUS[:unblocked] if any_platforms? { |rp| rp.metadata_editable? } STATUS[:success] end diff --git a/app/libs/coordinators.rb b/app/libs/coordinators.rb index cc961c878..e1f667adb 100644 --- a/app/libs/coordinators.rb +++ b/app/libs/coordinators.rb @@ -39,10 +39,14 @@ # ā€¢ This does not replace internal state machines of other sub-models. # ā€¢ It currently does not have any state of its own. module Coordinators - # TODO: [V2] fixes: - # metadata - module Signals + def self.release_has_started!(release) + release.notify!("New release has commenced!", :release_started, release.notification_params) + Releases::PreReleaseJob.perform_later(release.id) + Releases::FetchCommitLogJob.perform_later(release.id) + RefreshReportsJob.perform_later(release.hotfixed_from.id) if release.hotfix? + end + def self.commits_have_landed!(release, head_commit, rest_commits) Coordinators::ProcessCommits.call(release, head_commit, rest_commits) end @@ -97,10 +101,6 @@ def self.apply_build_queue!(build_queue) Res.new { Coordinators::ApplyBuildQueue.call(build_queue) } end - def self.save_metadata!(release, metadata) - # TODO: [V2] save metadata - end - def self.start_workflow_run!(workflow_run) Res.new do raise "release is not actionable" unless workflow_run.triggering_release.actionable? diff --git a/app/libs/coordinators/finalize_release.rb b/app/libs/coordinators/finalize_release.rb index a2704fc8c..61cbdf3f7 100644 --- a/app/libs/coordinators/finalize_release.rb +++ b/app/libs/coordinators/finalize_release.rb @@ -46,8 +46,7 @@ def call def on_finish! release.update_train_version! release.event_stamp!(reason: :finished, kind: :success, data: {version: release_version}) - notify_data = release.notification_params.merge(release.finalize_phase_metadata) - release.notify!("Release has finished!", :release_ended, notify_data) + release.notify!("Release has finished!", :release_ended, release.notification_params) RefreshReportsJob.perform_later(release.id) end diff --git a/app/libs/coordinators/finish_platform_run.rb b/app/libs/coordinators/finish_platform_run.rb index 948c0b144..1bf96c3dc 100644 --- a/app/libs/coordinators/finish_platform_run.rb +++ b/app/libs/coordinators/finish_platform_run.rb @@ -19,7 +19,7 @@ def call end end - RefreshPlatformBreakdownJob.perform_later(release_platform_run.id) if release.is_v2? + RefreshPlatformBreakdownJob.perform_later(release_platform_run.id) ReleasePlatformRuns::CreateTagJob.perform_later(release_platform_run.id, last_commit.id) if train.tag_platform_at_release_end? release_platform_run.event_stamp!(reason: :finished, kind: :success, data: {version: release_platform_run.release_version}) app.refresh_external_app diff --git a/app/libs/coordinators/process_commits.rb b/app/libs/coordinators/process_commits.rb index 221421456..edb275c21 100644 --- a/app/libs/coordinators/process_commits.rb +++ b/app/libs/coordinators/process_commits.rb @@ -28,11 +28,6 @@ def call end attempt_backmerge!(created_head_commit, created_rest_commits) - - # TODO: [V2] move this to trigger release - if release.all_commits.size.eql?(1) - release.notify!("New release has commenced!", :release_started, release.notification_params) - end end private diff --git a/app/libs/coordinators/start_release.rb b/app/libs/coordinators/start_release.rb index 9bbfad62b..1cde48f7d 100644 --- a/app/libs/coordinators/start_release.rb +++ b/app/libs/coordinators/start_release.rb @@ -27,8 +27,6 @@ def call raise "Could not kickoff a hotfix because the source tag does not exist" if hotfix_from_new_branch? && !hotfix_tag_exists? raise "Could not kickoff a hotfix because the source release branch does not exist" if hotfix_from_previous_branch? && !hotfix_branch_exists? raise "Cannot start a train that is not active!" if train.inactive? - # TODO [V2]: Remove this method - raise "Cannot start a train that has no release step. Please add at least one release step to the train." unless train.startable? raise "No more releases can be started until the ongoing release is finished!" if train.ongoing_release.present? && automatic raise "No more releases can be started until the ongoing release is finished!" if train.upcoming_release.present? && !hotfix? raise "Upcoming releases are not allowed for your train." if train.ongoing_release.present? && !train.upcoming_release_startable? && !hotfix? @@ -36,7 +34,7 @@ def call raise "Hotfix platform - #{hotfix_platform} is not valid!" if invalid_hotfix_platform? kickoff - RefreshReportsJob.perform_later(release.hotfixed_from.id) if release.hotfix? + Coordinators::Signals.release_has_started!(release) release end @@ -69,7 +67,7 @@ def create_release hotfix_platform: (hotfix_platform if hotfix?), custom_version: custom_version, release_pilot_id: Current.user&.id, - is_v2: train.product_v2? + is_v2: true # TODO: remove this after full removal of v2 ) end diff --git a/app/libs/deployments/app_store_connect/release.rb b/app/libs/deployments/app_store_connect/release.rb deleted file mode 100644 index 2acec69d7..000000000 --- a/app/libs/deployments/app_store_connect/release.rb +++ /dev/null @@ -1,315 +0,0 @@ -module Deployments - module AppStoreConnect - class Release - include Loggable - - ExternalReleaseNotInTerminalState = Class.new(StandardError) - ReleaseNotFullyLive = Class.new(StandardError) - PreparedVersionNotFoundError = Class.new(StandardError) - - RETRYABLE_FAILURE_REASONS = [:attachment_upload_in_progress] - - def self.kickoff!(deployment_run) - new(deployment_run).kickoff! - end - - def self.update_build_notes!(deployment_run) - new(deployment_run).update_build_notes! - end - - def self.update_external_release(deployment_run) - new(deployment_run).update_external_release - end - - def self.to_test_flight!(deployment_run) - new(deployment_run).to_test_flight! - end - - def self.prepare_for_release!(deployment_run, force: false) - new(deployment_run).prepare_for_release!(force:) - end - - def self.submit_for_review!(deployment_run) - new(deployment_run).submit_for_review! - end - - def self.start_release!(deployment_run) - new(deployment_run).start_release! - end - - def self.track_live_release_status(deployment_run) - new(deployment_run).track_live_release_status - end - - def self.complete_phased_release!(deployment_run) - new(deployment_run).complete_phased_release! - end - - def self.pause_phased_release!(deployment_run) - new(deployment_run).pause_phased_release! - end - - def self.resume_phased_release!(deployment_run) - new(deployment_run).resume_phased_release! - end - - def self.halt_phased_release!(deployment_run) - new(deployment_run).halt_phased_release! - end - - def initialize(deployment_run) - @deployment_run = deployment_run - end - - attr_reader :deployment_run - alias_method :run, :deployment_run - delegate :provider, - :deployment_channel, - :build_number, - :release_version, - :staged_rollout?, - :app_store_release?, - :test_flight_release?, - :app_store_integration?, - :app_store?, - :staged_rollout_config, - :release_metadatum, - :internal_channel?, - :deployment_notes, - to: :run - - def kickoff! - return run.start_prepare_release! if app_store_release? - Deployments::AppStoreConnect::TestFlightReleaseJob.perform_later(run.id) if test_flight_release? - end - - # NOTE: likely moves to internal/beta step - def to_test_flight! - return unless test_flight_release? - - Deployments::AppStoreConnect::UpdateBuildNotesJob.perform_later(run.id) - - return internal_release! if internal_channel? - - result = provider.release_to_testflight(deployment_channel, build_number) - - unless result.ok? - run.fail_with_error(result.error) - return - end - - run.submit_for_review! - end - - # NOTE: likely moves to internal/beta step - def update_build_notes! - provider.update_release_notes(build_number, deployment_notes) - end - - # NOTE: moves to store submission - def prepare_for_release!(force: false) - return unless app_store_release? - - metadata = [{ - whats_new: release_metadatum.release_notes, - promotional_text: release_metadatum.promo_text, - locale: release_metadatum.locale - }] - - result = provider.prepare_release(build_number, release_version, staged_rollout?, metadata, force) - - unless result.ok? - case result.error.reason - when :release_not_found then raise PreparedVersionNotFoundError - when :release_already_exists then run.fail_prepare_release!(reason: result.error.reason) - else run.fail_with_error(result.error) - end - - return - end - - unless valid_release?(result.value!) - run.dispatch_fail!(reason: :invalid_release) - return - end - - create_or_update_external_release(result.value!) - - run.prepare_release! - run.event_stamp!(reason: :inflight_release_replaced, kind: :notice, data: {version: release_version}) if force - end - - # NOTE: moves to store submission - def submit_for_review! - return unless app_store_release? - - result = provider.submit_release(build_number, release_version) - - unless result.ok? - return run.update(failure_reason: result.error.reason) if result.error.reason.in? RETRYABLE_FAILURE_REASONS - return run.fail_with_error(result.error) - end - - run.submit_for_review! - end - - # NOTE: moves to store submission - def update_external_release - return unless run.step_run.active? && app_store_integration? - - result = find_release - - unless result.ok? - elog(result.error) - raise ExternalReleaseNotInTerminalState, "Retrying in some time..." - end - - release_info = result.value! - create_or_update_external_release(release_info) - - if release_info.success? - return run.ready_to_release! if app_store? - run.complete! - elsif release_info.failed? - run.dispatch_fail!(reason: :developer_rejected) - elsif release_info.waiting_for_review? && run.review_failed? - # A failed review was re-submitted or responded to outside Tramline - run.submit_for_review!(resubmission: true) - else - run.fail_review! if release_info.review_failed? && !run.review_failed? - raise ExternalReleaseNotInTerminalState, "Retrying in some time..." - end - end - - # NOTE: likely moves to rollout step - def start_release! - return unless app_store_release? - - run.engage_release! - - result = provider.start_release(build_number) - - unless result.ok? - run.fail_with_error(result.error) - return - end - - run.create_staged_rollout!(config: staged_rollout_config) if staged_rollout? - - Deployments::AppStoreConnect::FindLiveReleaseJob.perform_async(run.id) - end - - # NOTE: likely moves to rollout step - def track_live_release_status - return unless app_store_release? - - result = provider.find_live_release - - unless result.ok? - elog(result.error) - raise ReleaseNotFullyLive, "Retrying in some time..." - end - - release_info = result.value! - create_or_update_external_release(release_info) - - if release_info.live?(build_number) - return run.complete! unless staged_rollout? - run.staged_rollout.update_stage( - release_info.phased_release_stage, - finish_rollout: release_info.phased_release_complete? - ) - return if release_info.phased_release_complete? - end - - raise ReleaseNotFullyLive, "Retrying in some time..." - end - - # NOTE: likely moves to rollout step - def complete_phased_release! - return unless app_store_release? - - result = provider.complete_phased_release - - if result.ok? - create_or_update_external_release(result.value!) - else - run.fail_with_error(result.error) - end - - result - end - - # NOTE: likely moves to rollout step - def pause_phased_release! - return unless app_store_release? - - result = provider.pause_phased_release - - if result.ok? - release_info = result.value! - create_or_update_external_release(release_info) - run.staged_rollout.update_stage( - release_info.phased_release_stage, - finish_rollout: release_info.phased_release_complete? - ) - end - - result - end - - # NOTE: likely moves to rollout step - def resume_phased_release! - return unless app_store_release? - - result = provider.resume_phased_release - - if result.ok? - release_info = result.value! - create_or_update_external_release(release_info) - run.staged_rollout.update_stage( - release_info.phased_release_stage, - finish_rollout: release_info.phased_release_complete? - ) - end - - result - end - - # NOTE: likely moves to rollout step - def halt_phased_release! - return unless app_store_release? - - provider.halt_phased_release - end - - private - - # NOTE: likely moves to internal/beta step - def internal_release! - result = find_release - unless result.ok? - run.fail_with_error(result.error) - return - end - - release_info = result.value! - create_or_update_external_release(release_info) - run.complete! - end - - def find_release - return provider.find_release(build_number) if app_store? - provider.find_build(build_number) - end - - def create_or_update_external_release(release_info) - (run.external_release || run.build_external_release).update(release_info.attributes) - end - - def valid_release?(release_info) - release_info.valid?(build_number, release_version, staged_rollout?) - end - end - end -end diff --git a/app/libs/deployments/google_firebase/release.rb b/app/libs/deployments/google_firebase/release.rb deleted file mode 100644 index 82803d1a3..000000000 --- a/app/libs/deployments/google_firebase/release.rb +++ /dev/null @@ -1,113 +0,0 @@ -module Deployments - module GoogleFirebase - class Release - include Loggable - - UploadNotComplete = Class.new(StandardError) - - def self.kickoff!(deployment_run) - new(deployment_run).kickoff! - end - - def self.upload!(deployment_run) - new(deployment_run).upload! - end - - def self.update_upload_status!(deployment_run, op_name) - new(deployment_run).update_upload_status!(op_name) - end - - def self.update_build_notes!(deployment_run, release_id) - new(deployment_run).update_build_notes!(release_id) - end - - def self.start_release!(deployment_run) - new(deployment_run).start_release! - end - - def initialize(deployment_run) - @deployment_run = deployment_run - end - - attr_reader :deployment_run - alias_method :run, :deployment_run - delegate :provider, - :deployment_channel, - :build_number, - :release_version, - :google_firebase_integration?, - :release_platform, - :step_run, - :deployment_notes, - to: :run - delegate :platform, to: :release_platform - - def kickoff! - if (similar_run = step_run.similar_deployment_runs_for(run).find(&:has_uploaded?)) - run.create_external_release(similar_run.external_release.attributes.except("id", "created_at", "updated_at")) - return run.upload! - end - Deployments::GoogleFirebase::UploadJob.perform_later(run.id) - end - - def upload! - return unless google_firebase_integration? - - run.with_lock do - return if run.uploaded? - - run.build_artifact.with_open do |file| - result = provider.upload(file, run.build_artifact.file.filename.to_s, platform:) - if result.ok? - run.start_upload!(op_name: result.value!) - else - run.fail_with_error(result.error) - end - end - end - end - - def update_upload_status!(op_name) - return unless google_firebase_integration? - - result = provider.get_upload_status(op_name) - unless result.ok? - run.fail_with_error(result.error) - return - end - - op_info = result.value! - raise UploadNotComplete unless op_info.done? - - release_info = op_info.release - run.create_external_release(external_id: release_info.id, - name: release_info.name, - build_number: release_info.build_number, - added_at: release_info.added_at, - status: op_info.status, - external_link: release_info.console_link) - run.upload! - Deployments::GoogleFirebase::UpdateBuildNotesJob.perform_later(run.id, release_info.id) - end - - def update_build_notes!(release_id) - provider.update_release_notes(release_id, deployment_notes) - end - - def start_release! - return unless google_firebase_integration? - - return run.complete! if deployment_channel == GoogleFirebaseIntegration::EMPTY_CHANNEL[:id].to_s - - result = provider.release(run.external_release.external_id, [deployment_channel]) - - unless result.ok? - run.fail_with_error(result.error) - return - end - - run.complete! - end - end - end -end diff --git a/app/libs/deployments/google_play_store/release.rb b/app/libs/deployments/google_play_store/release.rb deleted file mode 100644 index 2244fc37e..000000000 --- a/app/libs/deployments/google_play_store/release.rb +++ /dev/null @@ -1,181 +0,0 @@ -module Deployments - module GooglePlayStore - class Release - include Loggable - - def self.kickoff!(deployment_run) - new(deployment_run).kickoff! - end - - def self.upload!(deployment_run) - new(deployment_run).upload! - end - - def self.start_release!(deployment_run) - new(deployment_run).start_release! - end - - def self.release_with(deployment_run, rollout_value:) - new(deployment_run).release_with(rollout_value:) - end - - def self.halt_release!(deployment_run) - new(deployment_run).halt_release! - end - - def self.release_to_all!(deployment_run) - new(deployment_run).release_to_all! - end - - def initialize(deployment_run) - @deployment_run = deployment_run - end - - attr_reader :deployment_run - alias_method :run, :deployment_run - delegate :provider, - :deployment_channel, - :build_number, - :release_version, - :staged_rollout?, - :release_metadatum, - :google_play_store_integration?, - :staged_rollout_config, - :production_channel?, - :deployment_notes, - :one_percent_beta_release?, - to: :run - - def kickoff! - return run.upload! if run.step_run.similar_deployment_runs_for(run).any?(&:has_uploaded?) - Deployments::GooglePlayStore::Upload.perform_later(run.id) - end - - # NOTE: likely moves to internal/beta step - def upload! - return unless google_play_store_integration? - - run.with_lock do - return if run.uploaded? - - run.build_artifact.with_open do |file| - result = provider.upload(file) - if result.ok? - run.upload! - else - run.fail_with_error(result.error) - end - end - end - end - - # NOTE: likely moves to rollout step - def start_release! - return unless google_play_store_integration? - - skip_release = run.step_run.deployment_restarted? && provider.build_present_in_channel?(deployment_channel, build_number) - - if staged_rollout? - run.engage_release! - create_draft_release!(skip_release:) - else - fully_release!(skip_release:) - end - end - - # NOTE: likely moves to rollout step - def halt_release! - return unless google_play_store_integration? - return unless run.rollout_started? - - provider.halt_release( - deployment_channel, - build_number, - release_version, - run.staged_rollout.last_rollout_percentage - ) - end - - # NOTE: likely moves to rollout step - def release_to_all! - return unless google_play_store_integration? - - result = provider.rollout_release( - deployment_channel, - build_number, - release_version, - Deployment::FULL_ROLLOUT_VALUE, - release_notes - ) - - run.fail_with_error(result.error) unless result.ok? - result - end - - # NOTE: likely moves to rollout step - def release_with(rollout_value:) - return unless google_play_store_integration? - - result = provider.rollout_release( - deployment_channel, - build_number, - release_version, - rollout_value, - release_notes - ) - - run.fail_with_error(result.error) unless result.ok? - result - end - - private - - # NOTE: likely moves to rollout step - def fully_release!(skip_release: false) - return run.complete! if skip_release - - rollout_value = one_percent_beta_release? ? BigDecimal("1") : Deployment::FULL_ROLLOUT_VALUE - result = provider.rollout_release( - deployment_channel, - build_number, - release_version, - rollout_value, - release_notes - ) - - if result.ok? - run.complete! - else - run.fail_with_error(result.error) - end - end - - # NOTE: moves to submission step - def create_draft_release!(skip_release: false) - return run.create_staged_rollout!(config: staged_rollout_config) if skip_release - - result = provider.create_draft_release( - deployment_channel, - build_number, - release_version, - release_notes - ) - - if result.ok? - run.create_staged_rollout!(config: staged_rollout_config) - else - run.fail_with_error(result.error) - end - end - - def release_notes - return [] if deployment_notes.blank? - - [{ - language: release_metadatum.locale, - text: deployment_notes - }] - end - end - end -end diff --git a/app/libs/notifiers/slack/builder.rb b/app/libs/notifiers/slack/builder.rb index c58bc8fe6..1d12bc460 100644 --- a/app/libs/notifiers/slack/builder.rb +++ b/app/libs/notifiers/slack/builder.rb @@ -4,25 +4,11 @@ class Builder # there are individual classes for each message so that any state, if necessary, can be encapsulated # think of them as view components RENDERERS = { - deployment_finished: Renderers::DeploymentFinished, release_ended: Renderers::ReleaseEnded, release_stopped: Renderers::ReleaseStopped, release_started: Renderers::ReleaseStarted, - step_started: Renderers::StepStarted, - step_failed: Renderers::StepFailed, - build_available: Renderers::BuildAvailable, - submit_for_review: Renderers::SubmitForReview, - review_approved: Renderers::ReviewApproved, - review_failed: Renderers::ReviewFailed, - staged_rollout_updated: Renderers::StagedRolloutUpdated, release_scheduled: Renderers::ReleaseScheduled, backmerge_failed: Renderers::BackmergeFailed, - staged_rollout_paused: Renderers::StagedRolloutPaused, - staged_rollout_resumed: Renderers::StagedRolloutResumed, - staged_rollout_halted: Renderers::StagedRolloutHalted, - staged_rollout_completed: Renderers::StagedRolloutCompleted, - staged_rollout_fully_released: Renderers::StagedRolloutFullyReleased, - deployment_failed: Renderers::DeploymentFailed, release_health_events: Renderers::ReleaseHealthEvents, build_available_v2: Renderers::BuildAvailableV2, internal_release_finished: Renderers::InternalReleaseFinished, diff --git a/app/libs/notifiers/slack/renderers/base.rb b/app/libs/notifiers/slack/renderers/base.rb index 70f47eb00..012261ef3 100644 --- a/app/libs/notifiers/slack/renderers/base.rb +++ b/app/libs/notifiers/slack/renderers/base.rb @@ -1,13 +1,11 @@ class Notifiers::Slack::Renderers::Base include Rails.application.routes.url_helpers - include DeploymentsHelper include ActionView::Helpers::JavaScriptHelper NOTIFIERS_RELATIVE_PATH = "app/views/notifiers/slack".freeze ROOT_PATH = Rails.root.join(NOTIFIERS_RELATIVE_PATH) HEADER_TEMPLATE = "header.json.erb".freeze FOOTER_TEMPLATE = "footer.json.erb".freeze - FOOTER_V2_TEMPLATE = "footer_v2.json.erb".freeze def self.render_json(**args) new(**args).render_json @@ -40,7 +38,7 @@ def render_notification end def render_footer - file = File.read(File.join(ROOT_PATH, @is_v2 ? FOOTER_V2_TEMPLATE : FOOTER_TEMPLATE)) + file = File.read(File.join(ROOT_PATH, FOOTER_TEMPLATE)) ERB.new(file).result(binding) end @@ -48,11 +46,6 @@ def template_file File.read(File.join(ROOT_PATH, self.class::TEMPLATE_FILE)) end - def deployment_channel_display_name - return unless @deployment_channel - deployment_channel_name(@deployment_channel) - end - def safe_string(s) = escape_javascript(s) def google_managed_publishing_text diff --git a/app/libs/notifiers/slack/renderers/build_available.rb b/app/libs/notifiers/slack/renderers/build_available.rb deleted file mode 100644 index 5e5b2abc9..000000000 --- a/app/libs/notifiers/slack/renderers/build_available.rb +++ /dev/null @@ -1,7 +0,0 @@ -module Notifiers - module Slack - class Renderers::BuildAvailable < Renderers::Base - TEMPLATE_FILE = "build_available.json.erb".freeze - end - end -end diff --git a/app/libs/notifiers/slack/renderers/deployment_failed.rb b/app/libs/notifiers/slack/renderers/deployment_failed.rb deleted file mode 100644 index 69ae36bfd..000000000 --- a/app/libs/notifiers/slack/renderers/deployment_failed.rb +++ /dev/null @@ -1,7 +0,0 @@ -module Notifiers - module Slack - class Renderers::DeploymentFailed < Renderers::Base - TEMPLATE_FILE = "deployment_failed.json.erb".freeze - end - end -end diff --git a/app/libs/notifiers/slack/renderers/deployment_finished.rb b/app/libs/notifiers/slack/renderers/deployment_finished.rb deleted file mode 100644 index 65393e1f8..000000000 --- a/app/libs/notifiers/slack/renderers/deployment_finished.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Notifiers - module Slack - class Renderers::DeploymentFinished < Renderers::Base - TEMPLATE_FILE = "deployment_finished.json.erb".freeze - - def sanitized_build_notes - safe_string("*Build notes*\n```#{@build_notes}```") - end - - def sanitized_release_notes - safe_string(":spiral_note_pad: *What's New*\n\n```#{@release_notes}```") - end - end - end -end diff --git a/app/libs/notifiers/slack/renderers/release_ended.rb b/app/libs/notifiers/slack/renderers/release_ended.rb index 7d65eb543..8cc3ffbeb 100644 --- a/app/libs/notifiers/slack/renderers/release_ended.rb +++ b/app/libs/notifiers/slack/renderers/release_ended.rb @@ -1,7 +1,14 @@ module Notifiers module Slack + include ActionView::Helpers::DateHelper + class Renderers::ReleaseEnded < Renderers::Base TEMPLATE_FILE = "release_ended.json.erb".freeze end + + def total_run_time + return "N/A" if @release_completed_at.blank? || @release_started_at.blank? + distance_of_time_in_words(@release_started_at, @release_completed_at) + end end end diff --git a/app/libs/notifiers/slack/renderers/review_approved.rb b/app/libs/notifiers/slack/renderers/review_approved.rb deleted file mode 100644 index b7d360194..000000000 --- a/app/libs/notifiers/slack/renderers/review_approved.rb +++ /dev/null @@ -1,7 +0,0 @@ -module Notifiers - module Slack - class Renderers::ReviewApproved < Renderers::Base - TEMPLATE_FILE = "review_approved.json.erb".freeze - end - end -end diff --git a/app/libs/notifiers/slack/renderers/review_failed.rb b/app/libs/notifiers/slack/renderers/review_failed.rb deleted file mode 100644 index d04ace021..000000000 --- a/app/libs/notifiers/slack/renderers/review_failed.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Notifiers - module Slack - class Renderers::ReviewFailed < Renderers::Base - TEMPLATE_FILE = "review_failed.json.erb".freeze - - def apple_review_failed_text - "You can resolve the rejection from the App Store Connect, or, submit a new build for review." - end - end - end -end diff --git a/app/libs/notifiers/slack/renderers/staged_rollout_completed.rb b/app/libs/notifiers/slack/renderers/staged_rollout_completed.rb deleted file mode 100644 index 21214e025..000000000 --- a/app/libs/notifiers/slack/renderers/staged_rollout_completed.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Notifiers - module Slack - class Renderers::StagedRolloutCompleted < Renderers::Base - TEMPLATE_FILE = "staged_rollout_updated.json.erb".freeze - - def main_text - "Staged rollout for the release is now *complete* at stage *#{@current_stage} (#{@rollout_percentage}%)*." - end - - def secondary_text - "The rollout on this release is now locked on Tramline and cannot be altered further." - end - end - end -end diff --git a/app/libs/notifiers/slack/renderers/staged_rollout_fully_released.rb b/app/libs/notifiers/slack/renderers/staged_rollout_fully_released.rb deleted file mode 100644 index d3caac3e9..000000000 --- a/app/libs/notifiers/slack/renderers/staged_rollout_fully_released.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Notifiers - module Slack - class Renderers::StagedRolloutFullyReleased < Renderers::Base - TEMPLATE_FILE = "staged_rollout_updated.json.erb".freeze - - def main_text - "Your staged rollout has been accelerated to a *full release to all users* from stage #{@current_stage} (#{@rollout_percentage}%)." - end - - def secondary_text = nil - end - end -end diff --git a/app/libs/notifiers/slack/renderers/staged_rollout_halted.rb b/app/libs/notifiers/slack/renderers/staged_rollout_halted.rb deleted file mode 100644 index 13c42538e..000000000 --- a/app/libs/notifiers/slack/renderers/staged_rollout_halted.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Notifiers - module Slack - class Renderers::StagedRolloutHalted < Renderers::Base - TEMPLATE_FILE = "staged_rollout_updated.json.erb".freeze - - def main_text - "Staged rollout for the release was *halted* at *#{@current_stage} (#{@rollout_percentage}%)*." - end - - def secondary_text - "You can not change this release from Tramline any more." - end - end - end -end diff --git a/app/libs/notifiers/slack/renderers/staged_rollout_paused.rb b/app/libs/notifiers/slack/renderers/staged_rollout_paused.rb deleted file mode 100644 index 837cbae84..000000000 --- a/app/libs/notifiers/slack/renderers/staged_rollout_paused.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Notifiers - module Slack - class Renderers::StagedRolloutPaused < Renderers::Base - TEMPLATE_FILE = "staged_rollout_updated.json.erb".freeze - - def main_text - "Staged rollout for the release was *paused* at *#{@current_stage} (#{@rollout_percentage}%)*." - end - - def secondary_text - "You can choose to *Resume* or *Halt* your release from the live release page." - end - end - end -end diff --git a/app/libs/notifiers/slack/renderers/staged_rollout_resumed.rb b/app/libs/notifiers/slack/renderers/staged_rollout_resumed.rb deleted file mode 100644 index 00c6ef6f5..000000000 --- a/app/libs/notifiers/slack/renderers/staged_rollout_resumed.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Notifiers - module Slack - class Renderers::StagedRolloutResumed < Renderers::Base - TEMPLATE_FILE = "staged_rollout_updated.json.erb".freeze - - def main_text - "Staged rollout for the release was *resumed* at *#{@current_stage} (#{@rollout_percentage}%)*." - end - - def secondary_text - "You can choose to *Pause* or *Halt* your release from the live release page." - end - end - end -end diff --git a/app/libs/notifiers/slack/renderers/staged_rollout_updated.rb b/app/libs/notifiers/slack/renderers/staged_rollout_updated.rb deleted file mode 100644 index a3f958392..000000000 --- a/app/libs/notifiers/slack/renderers/staged_rollout_updated.rb +++ /dev/null @@ -1,39 +0,0 @@ -module Notifiers - module Slack - class Renderers::StagedRolloutUpdated < Renderers::Base - TEMPLATE_FILE = "staged_rollout_updated.json.erb".freeze - - def main_text - if @is_fully_released - "Your staged rollout is *complete*, and your update has rolled out to all users." - else - "Your staged rollout is *active*, and you are currently on stage *#{@current_stage} (#{@rollout_percentage}%)*." - end - end - - def secondary_text - if @is_fully_released - "View your release on the #{store}." - else - action_text - end - end - - def store - if @is_app_store_production - "App Store" - elsif @is_play_store_production - "Play Console" - end - end - - def action_text - if @is_app_store_production - "You can choose to *Pause*, *Halt*, or *Release to 100%*." - elsif @is_play_store_production - "You can choose to *Increase*, *Halt*, or *Release to 100%*." - end - end - end - end -end diff --git a/app/libs/notifiers/slack/renderers/step_failed.rb b/app/libs/notifiers/slack/renderers/step_failed.rb deleted file mode 100644 index 32c6fb046..000000000 --- a/app/libs/notifiers/slack/renderers/step_failed.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Notifiers - module Slack - class Renderers::StepFailed < Renderers::Base - TEMPLATE_FILE = "step_failed.json.erb".freeze - - def manual_submission_required_text - "- Due to a previous rejection, new changes cannot be submitted to the store from Tramline. Please submit the current build (#{@build_number}) for review manually from the Google Play Console by creating a release in a public track (eg. Closed testing, Open testing). Once that is done, you can sync the store status with Tramline and move forward with the release train." - end - end - end -end diff --git a/app/libs/notifiers/slack/renderers/step_started.rb b/app/libs/notifiers/slack/renderers/step_started.rb deleted file mode 100644 index 4defe076a..000000000 --- a/app/libs/notifiers/slack/renderers/step_started.rb +++ /dev/null @@ -1,7 +0,0 @@ -module Notifiers - module Slack - class Renderers::StepStarted < Renderers::Base - TEMPLATE_FILE = "step_started.json.erb".freeze - end - end -end diff --git a/app/libs/notifiers/slack/renderers/submit_for_review.rb b/app/libs/notifiers/slack/renderers/submit_for_review.rb deleted file mode 100644 index c38bf7573..000000000 --- a/app/libs/notifiers/slack/renderers/submit_for_review.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Notifiers - module Slack - class Renderers::SubmitForReview < Renderers::Base - TEMPLATE_FILE = "submit_for_review.json.erb".freeze - - def sanitized_release_notes - safe_string(":spiral_note_pad: *What's New*\n\n```#{@release_notes}```") - end - - def submitted_text - return "resubmitted" if @resubmission - "submitted" - end - end - end -end diff --git a/app/libs/queries/events.rb b/app/libs/queries/events.rb index 9c7822597..38b72b396 100644 --- a/app/libs/queries/events.rb +++ b/app/libs/queries/events.rb @@ -16,24 +16,10 @@ def initialize(release:, params:) attr_reader :release, :params def all - ids = release.is_v2? ? v2_stampable_ids : stampable_ids - Passport.where(stampable_id: ids).order(event_timestamp: :desc) + Passport.where(stampable_id: stampable_ids).order(event_timestamp: :desc) end def stampable_ids - release - .release_platform_runs - .joins(:release_platform) - .where(ActiveRecord::Base.sanitize_sql_for_conditions(params.filter_by(FILTER_MAPPING))) - .left_joins(step_runs: [:commit, [deployment_runs: :staged_rollout]]) - .pluck("commits.id, release_platform_runs.id, step_runs.id, deployment_runs.id, staged_rollouts.id") - .flatten - .uniq - .compact - .push(release.id) - end - - def v2_stampable_ids run_ids = release .release_platform_runs .joins(:release_platform) diff --git a/app/libs/queries/release_summary.rb b/app/libs/queries/release_summary.rb deleted file mode 100644 index d99288133..000000000 --- a/app/libs/queries/release_summary.rb +++ /dev/null @@ -1,216 +0,0 @@ -class Queries::ReleaseSummary - include Memery - include Loggable - - def self.warm(release_id) - new(release_id).warm - end - - def self.all(release_id) - new(release_id).all - end - - def initialize(release_id) - @release_id = release_id - end - - def warm - cache.write(cache_key, data) - rescue => e - elog(e) - end - - def all - cache.fetch(cache_key) - end - - def team_stability_commits - cache.fetch(team_stability_commits_cache_key) { release.stability_commits.count_by_team(release.organization) } - end - - def team_release_commits - cache.fetch(team_release_commits_cache_key) { release.release_changelog&.commits_by_team } - end - - class Queries::ReleaseSummary::Overall - include ActiveModel::Model - include ActiveModel::Attributes - - attribute :tag, :string - attribute :tag_url, :string - attribute :version, :string - attribute :kickoff_at, :datetime - attribute :finished_at, :datetime - attribute :backmerge_pr_count, :integer - attribute :backmerge_failure_count, :integer - attribute :commits_count, :integer - attribute :duration, :integer - attribute :is_hotfix, :boolean - attribute :hotfixed_from, :string - attribute :hotfixes, default: {} - - def self.from_release(release) - attributes = { - tag: release.tag_name, - tag_url: release.tag_url, - version: release.release_version, - kickoff_at: release.scheduled_at, - finished_at: release.completed_at, - backmerge_pr_count: release.backmerge_prs.size, - backmerge_failure_count: release.backmerge_failure_count, - commits_count: release.all_commits.size, - duration: release.duration&.seconds, - is_hotfix: release.hotfix?, - hotfixes: release.all_hotfixes.map { |r| [r.release_version, r.live_release_link] }.to_h - } - - if release.hotfix? - attributes[:hotfixed_from] = release.hotfixed_from.release_version - end - - new(attributes) - end - - def inspect - format( - "#", - attributes: attributes.map { |key, value| "#{key}=#{value}" }.join(" ") - ) - end - end - - class Queries::ReleaseSummary::StepsSummary - def self.from_release(release) - attributes = release.release_platform_runs.map do |pr| - release_phase_start = pr.step_runs_for(pr.release_platform.release_step)&.first&.scheduled_at - pr.release_platform.active_steps_for(release).map do |step| - step_runs = pr.step_runs_for(step).sequential - last_step_run = step_runs.last - started_at = step_runs.first&.scheduled_at - ended_at = (step.review? ? release_phase_start : step_runs.last&.updated_at) if last_step_run && !last_step_run.active? - { - name: step.name, - platform: pr.display_attr(:platform), - platform_raw: pr.platform, - started_at: started_at, - phase: step.kind, - ended_at: ended_at, - duration: (ActiveSupport::Duration.seconds(ended_at - started_at) if started_at && ended_at), - builds_created_count: step_runs.not_failed.size - } - end - end - - new(attributes.flatten.map { StepSummary.new(_1) }) - end - - def initialize(steps_summary) - @steps_summary = steps_summary - end - - def all = @steps_summary - - class StepSummary - include ActiveModel::Model - include ActiveModel::Attributes - - attribute :started_at, :datetime - attribute :ended_at, :datetime - attribute :duration, :integer - attribute :platform, :string - attribute :platform_raw, :string - attribute :phase, :string - attribute :builds_created_count, :integer - attribute :name, :string - end - end - - class Queries::ReleaseSummary::StoreVersions - def self.from_release(release) - attributes = release.deployment_runs.order(created_at: :desc).reached_production.map do |dr| - { - version: dr.step_run.build_version, - build_number: dr.step_run.build_number, - built_at: dr.scheduled_at, - submitted_at: dr.submitted_at, - release_started_at: dr.release_started_at, - staged_rollouts: dr.staged_rollout_events, - platform: dr.release_platform_run.display_attr(:platform) - } - end - - new(attributes.map { StoreVersion.new(_1) }) - end - - def initialize(store_versions) - @store_versions = store_versions - end - - def all = @store_versions - - class StoreVersion - include ActiveModel::Model - include ActiveModel::Attributes - - attribute :version, :string - attribute :build_number, :string - attribute :built_at, :datetime - attribute :submitted_at, :datetime - attribute :release_started_at, :datetime - attribute :staged_rollouts, array: true, default: [] - attribute :platform, :string - - def inspect - format( - "#", - attributes: attributes.map { |key, value| "#{key}=#{value}" }.join(" ") - ) - end - end - end - - private - - attr_reader :release_id - delegate :cache, to: Rails - - memoize def release - Release - .where(id: release_id) - .includes(:all_commits, - :pull_requests, - train: [:release_platforms], - release_platform_runs: {step_runs: {deployment_runs: [{deployment: [:integration]}, :staged_rollout]}}) - .sole - end - - def data - { - overall: Overall.from_release(release), - steps_summary: StepsSummary.from_release(release), - store_versions: StoreVersions.from_release(release), - pull_requests: release.pull_requests.automatic, - team_stability_commits: team_stability_commits, - team_release_commits: team_release_commits, - reldex: release.index_score - } - end - - def thaw - cache.delete(cache_key) - cache.delete(team_stability_commits_cache_key) - cache.delete(team_release_commits_cache_key) - end - - def cache_key - "release/#{release_id}/summary" - end - - def team_stability_commits_cache_key - "release/#{release_id}/team_stability_commits" - end - - def team_release_commits_cache_key - "release/#{release_id}/team_release_commits" - end -end diff --git a/app/libs/triggers/deployment.rb b/app/libs/triggers/deployment.rb deleted file mode 100644 index 3142b183f..000000000 --- a/app/libs/triggers/deployment.rb +++ /dev/null @@ -1,27 +0,0 @@ -class Triggers::Deployment - include Memery - delegate :transaction, to: ::DeploymentRun - - def self.call(step_run:, deployment:) - new(step_run:, deployment:).call - end - - def initialize(step_run:, deployment:) - @step_run = step_run - @deployment = deployment - @starting_time = Time.current - end - - def call - transaction do - step_run - .deployment_runs - .create!(deployment:, scheduled_at: starting_time) - .dispatch! - end - end - - private - - attr_reader :deployment, :step_run, :starting_time -end diff --git a/app/libs/triggers/step_run.rb b/app/libs/triggers/step_run.rb deleted file mode 100644 index 9d1ea6858..000000000 --- a/app/libs/triggers/step_run.rb +++ /dev/null @@ -1,29 +0,0 @@ -class Triggers::StepRun - def self.call(step, commit, release_platform_run) - new(step, commit, release_platform_run).call - end - - def initialize(step, commit, release_platform_run) - @step = step - @release_platform_run = release_platform_run - @commit = commit - end - - # FIXME: should we take a lock around this release? what is someone double triggers the run? - def call - release_platform_run.correct_version! - release_platform_run - .step_runs - .create!(step:, scheduled_at: Time.current, commit:, build_version:, sign_required: false) - end - - private - - attr_reader :step, :release_platform_run, :commit - - def build_version - version = release_platform_run.release_version - version += "-" + step.release_suffix if step.suffixable? - version - end -end diff --git a/app/libs/webhook_handlers/base.rb b/app/libs/webhook_handlers/base.rb deleted file mode 100644 index 1c3410cb7..000000000 --- a/app/libs/webhook_handlers/base.rb +++ /dev/null @@ -1,26 +0,0 @@ -class WebhookHandlers::Base - include SiteHttp - include Memery - - GITHUB = WebhookHandlers::Github - GITLAB = WebhookHandlers::Gitlab - - attr_reader :payload, :train - - def self.process(train, payload) - new(train, payload).process - end - - def initialize(train, payload) - @train = train - @payload = payload - end - - def release - @release ||= train.active_runs.for_branch(branch_name) - end - - private - - delegate :vcs_provider, to: :train -end diff --git a/app/libs/webhook_handlers/github/pull_request.rb b/app/libs/webhook_handlers/github/pull_request.rb deleted file mode 100644 index e10a20390..000000000 --- a/app/libs/webhook_handlers/github/pull_request.rb +++ /dev/null @@ -1,35 +0,0 @@ -class WebhookHandlers::Github::PullRequest - using RefinedHash - attr_reader :payload - - def initialize(payload) - @payload = payload - end - - def head_branch_name - payload[:pull_request][:head][:ref] - end - - def base_branch_name - payload[:pull_request][:base][:ref] - end - - def closed? - pull_request[:state] == "closed" - end - - def opened? - pull_request[:state] == "open" - end - - def repository_name - payload[:repository][:full_name] - end - - def pull_request - Installations::Response::Keys - .transform([payload[:pull_request]], GithubIntegration::PR_TRANSFORMATIONS) - .first - .merge_if_present(source: :github) - end -end diff --git a/app/libs/webhook_handlers/github/push.rb b/app/libs/webhook_handlers/github/push.rb deleted file mode 100644 index 63b75e44c..000000000 --- a/app/libs/webhook_handlers/github/push.rb +++ /dev/null @@ -1,48 +0,0 @@ -class WebhookHandlers::Github::Push - attr_reader :payload - - def initialize(payload) - @payload = payload - end - - def head_commit - head_commit_payload - end - - def rest_commits - rest_commits_payload.reject { |commit| commit[:commit_hash] == head_commit[:commit_hash] } - end - - def valid_branch? - payload["ref"]&.include?("refs/heads/") - end - - # github adds tag events as part of the push events - def valid_tag? - payload["ref"]&.include?("refs/tags/") - end - - def branch_name - payload["ref"].delete_prefix("refs/heads/") if valid_branch? - end - - def repository_name - payload["repository"]["full_name"] - end - - private - - def commits - payload["commits"].presence || [] - end - - def head_commit_payload - Installations::Response::Keys - .transform([payload["head_commit"]], GithubIntegration::COMMITS_HOOK_TRANSFORMATIONS) - .first - end - - def rest_commits_payload - Installations::Response::Keys.transform(commits, GithubIntegration::COMMITS_HOOK_TRANSFORMATIONS) - end -end diff --git a/app/libs/webhook_handlers/gitlab/pull_request.rb b/app/libs/webhook_handlers/gitlab/pull_request.rb deleted file mode 100644 index a2a3cc060..000000000 --- a/app/libs/webhook_handlers/gitlab/pull_request.rb +++ /dev/null @@ -1,34 +0,0 @@ -class WebhookHandlers::Gitlab::PullRequest - attr_reader :payload - - def initialize(payload) - @payload = payload - end - - def head_branch_name - payload["object_attributes"]["source_branch"] - end - - def base_branch_name - payload["object_attributes"]["target_branch"] - end - - def closed? - pull_request[:state] == "closed" || pull_request[:state] == "merged" - end - - def opened? - pull_request[:state] == "opened" - end - - def repository_name - payload["project"]["path_with_namespace"] - end - - def pull_request - Installations::Response::Keys - .transform([payload["object_attributes"]], GitlabIntegration::WEBHOOK_PR_TRANSFORMATIONS) - .first - .merge(source: :gitlab) - end -end diff --git a/app/libs/webhook_handlers/gitlab/push.rb b/app/libs/webhook_handlers/gitlab/push.rb deleted file mode 100644 index 00304b460..000000000 --- a/app/libs/webhook_handlers/gitlab/push.rb +++ /dev/null @@ -1,54 +0,0 @@ -class WebhookHandlers::Gitlab::Push - attr_reader :payload, :train - - def initialize(payload, train) - @payload = payload - @train = train - end - - def head_commit - head_commit_payload - .find { |commit| commit[:commit_hash] == head_sha } - end - - def rest_commits - commits_payload - .reject { |commit| commit[:commit_hash] == head_sha } - end - - # we do not listen to gitlab tag events, they are not included in the push events as with github - def valid_tag? - false - end - - def valid_branch? - payload["ref"]&.include?("refs/heads/") - end - - def branch_name - payload["ref"].delete_prefix("refs/heads/") if valid_branch? - end - - def repository_name - payload["project"]["path_with_namespace"] - end - - private - - def commits - payload["commits"].presence || [] - end - - def head_sha - payload["checkout_sha"] - end - - def head_commit_payload - return [train.vcs_provider.get_commit(head_sha)] if commits.blank? - commits_payload - end - - def commits_payload - Installations::Response::Keys.transform(commits, GitlabIntegration::COMMITS_HOOK_TRANSFORMATIONS) - end -end diff --git a/app/libs/webhook_handlers/pull_request.rb b/app/libs/webhook_handlers/pull_request.rb deleted file mode 100644 index 9b874ebfd..000000000 --- a/app/libs/webhook_handlers/pull_request.rb +++ /dev/null @@ -1,32 +0,0 @@ -class WebhookHandlers::PullRequest < WebhookHandlers::Base - def process - return Response.new(:accepted, "Invalid repo/branch") unless valid_repo? - - if opened? && train.active_release_for?(base_branch_name) - WebhookProcessors::OpenPullRequestJob.perform_later(train.id, pull_request) - end - - if closed? && open_active_prs? - WebhookProcessors::ClosePullRequestJob.perform_later(train.id, pull_request) - end - - Response.new(:accepted) - end - - private - - delegate :pull_request, :closed?, :opened?, :head_branch_name, :base_branch_name, :repository_name, to: :runner - - memoize def runner - return GITHUB::PullRequest.new(payload) if vcs_provider.integration.github_integration? - GITLAB::PullRequest.new(payload) if vcs_provider.integration.gitlab_integration? - end - - memoize def open_active_prs? - train.open_active_prs_for?(head_branch_name) - end - - def valid_repo? - (train.app.config&.code_repository_name == repository_name) - end -end diff --git a/app/libs/webhook_handlers/push.rb b/app/libs/webhook_handlers/push.rb deleted file mode 100644 index 25e3e0243..000000000 --- a/app/libs/webhook_handlers/push.rb +++ /dev/null @@ -1,29 +0,0 @@ -class WebhookHandlers::Push < WebhookHandlers::Base - def process - return Response.new(:accepted) if valid_tag? - return Response.new(:accepted, "No release") unless release - return Response.new(:accepted) unless release.committable? - return Response.new(:accepted, "Skipping the commit") unless relevant_commit? - return Response.new(:accepted, "Invalid repo/branch") unless valid_repo_and_branch? - - WebhookProcessors::PushJob.perform_later(release.id, head_commit, rest_commits) - Response.new(:accepted) - end - - private - - delegate :branch_name, :repository_name, :valid_tag?, :head_commit, :rest_commits, to: :runner - - memoize def runner - return GITHUB::Push.new(payload) if vcs_provider.integration.github_integration? - GITLAB::Push.new(payload, train) if vcs_provider.integration.gitlab_integration? - end - - def relevant_commit? - release.release_branch == branch_name - end - - def valid_repo_and_branch? - (train.app.config&.code_repository_name == repository_name) if branch_name - end -end diff --git a/app/libs/webhook_processors/push.rb b/app/libs/webhook_processors/push.rb deleted file mode 100644 index 48daf782c..000000000 --- a/app/libs/webhook_processors/push.rb +++ /dev/null @@ -1,64 +0,0 @@ -class WebhookProcessors::Push - include Loggable - - def self.process(release, head_commit, rest_commits) - new(release, head_commit, rest_commits).process - end - - def initialize(release, head_commit, rest_commits = []) - @release = release - @head_commit = head_commit - @rest_commits = rest_commits - end - - def process - release.with_lock do - return unless release.committable? - release.close_pre_release_prs - - if release.created? - release.start! - release.release_platform_runs.each(&:start!) - end - - create_other_commits! - create_head_commit! - end - - # TODO: [V2] move this to trigger release - if release.all_commits.size.eql?(1) - release.notify!("New release has commenced!", :release_started, release.notification_params) - end - end - - private - - delegate :train, to: :release - attr_reader :release, :head_commit, :rest_commits - - def create_head_commit! - Commit.find_or_create_by!(commit_params(head_commit)).trigger! - end - - def create_other_commits! - rest_commits.each { Commit.find_or_create_by!(commit_params(_1)).add_to_build_queue!(is_head_commit: false) } - end - - def commit_params(attributes) - attributes - .slice(:commit_hash, :message, :timestamp, :author_name, :author_email, :author_login, :url) - .merge(release:) - .merge(parents: commit_log.find { _1[:commit_hash] == attributes[:commit_hash] }&.dig(:parents)) - end - - # TODO: fetch parents for Gitlab commits also - def commit_log - return @commit_log ||= [train.vcs_provider.get_commit(head_commit[:commit_hash])] if rest_commits.empty? - - @commit_log ||= train.vcs_provider.commit_log(rest_commits.first[:commit_hash], head_commit[:commit_hash]) - @commit_log << train.vcs_provider.get_commit(rest_commits.first[:commit_hash]) - rescue => e - elog(e) - @commit_log = [] - end -end diff --git a/app/libs/workflow_processors/workflow_run.rb b/app/libs/workflow_processors/workflow_run.rb deleted file mode 100644 index 04531f281..000000000 --- a/app/libs/workflow_processors/workflow_run.rb +++ /dev/null @@ -1,76 +0,0 @@ -class WorkflowProcessors::WorkflowRun - include Memery - GITHUB = WorkflowProcessors::Github::WorkflowRun - BITRISE = WorkflowProcessors::Bitrise::WorkflowRun - - class WorkflowRunUnknownStatus < StandardError; end - - def self.process(step_run) - new(step_run).process - end - - def initialize(step_run) - @step_run = step_run - end - - def process - return re_enqueue if in_progress? - update_status! - end - - private - - def re_enqueue - WorkflowProcessors::WorkflowRunJob.set(wait: wait_time).perform_later(step_run.id) - end - - attr_reader :step_run - delegate :release, :build_artifact_name_pattern, to: :step_run - delegate :in_progress?, :successful?, :failed?, :halted?, :artifacts_url, to: :runner - - def update_status! - if successful? - step_run.artifacts_url = artifacts_url - step_run.finish_ci! - step_run.event_stamp!(reason: :ci_finished, kind: :success, data: stamp_data) - elsif failed? - step_run.fail_ci! - step_run.event_stamp!(reason: :ci_workflow_failed, kind: :error, data: stamp_data) - elsif halted? - step_run.cancel_ci! - step_run.event_stamp!(reason: :ci_workflow_halted, kind: :error, data: stamp_data) - else - raise WorkflowRunUnknownStatus - end - end - - memoize def runner - return GITHUB.new(workflow_run) if github_integration? - BITRISE.new(step_run.ci_cd_provider, workflow_run, build_artifact_name_pattern) if bitrise_integration? - end - - delegate :github_integration?, :bitrise_integration?, to: :integration - - def integration - step_run.ci_cd_provider.integration - end - - memoize def workflow_run - step_run.get_workflow_run - end - - def wait_time - if Rails.env.development? - 1.minute - else - 2.minutes - end - end - - def stamp_data - { - ref: step_run.ci_ref, - url: step_run.ci_link - } - end -end diff --git a/app/models/app.rb b/app/models/app.rb index 05c086458..fe36beec6 100644 --- a/app/models/app.rb +++ b/app/models/app.rb @@ -33,13 +33,10 @@ class App < ApplicationRecord has_many :external_apps, inverse_of: :app, dependent: :destroy has_many :trains, -> { sequential }, dependent: :destroy, inverse_of: :app has_many :releases, through: :trains - has_many :step_runs, through: :releases - has_many :deployment_runs, through: :releases has_many :production_store_rollouts, -> { production }, through: :releases has_many :builds, through: :releases has_many :release_platforms, dependent: :destroy has_many :release_platform_runs, through: :releases - has_many :steps, through: :release_platforms validate :no_trains_are_running, on: :update validates :bundle_identifier, uniqueness: {scope: [:platform, :organization_id]} @@ -117,21 +114,13 @@ def ready? end def guided_train_setup? - trains.none? || train_in_creation&.product_v2? + trains.none? || train_in_creation.present? end def train_in_creation trains.first if trains.size == 1 end - def latest_store_step_runs - deployment_runs - .reached_production - .group_by(&:platform) - .to_h { |platform, runs| [platform, runs.max_by(&:updated_at)&.step_run] } - .values - end - # NOTE: fetches and uses latest build numbers from the stores, if added, # to reduce build upload rejection probability def bump_build_number!(release_version: nil) diff --git a/app/models/app_config.rb b/app/models/app_config.rb index 1e0211e4d..d0721d53c 100644 --- a/app/models/app_config.rb +++ b/app/models/app_config.rb @@ -10,7 +10,6 @@ # code_repository :json # firebase_android_config :jsonb # firebase_ios_config :jsonb -# notification_channel :json # created_at :datetime not null # updated_at :datetime not null # app_id :uuid not null, indexed @@ -18,11 +17,10 @@ # class AppConfig < ApplicationRecord has_paper_trail - include Notifiable include AppConfigurable PLATFORM_AWARE_CONFIG_SCHEMA = Rails.root.join("config/schema/platform_aware_integration_config.json") - self.ignored_columns += %w[bugsnag_project_id firebase_crashlytics_android_config firebase_crashlytics_ios_config] + self.ignored_columns += %w[bugsnag_project_id firebase_crashlytics_android_config firebase_crashlytics_ios_config notification_channel] belongs_to :app has_many :variants, class_name: "AppVariant", dependent: :destroy @@ -102,11 +100,21 @@ def further_setup_by_category? end def bugsnag_project(platform) - pick_bugsnag_project_id(platform) + case platform + when "android" then bugsnag_android_config["project_id"] + when "ios" then bugsnag_ios_config["project_id"] + else + raise ArgumentError, INVALID_PLATFORM_ERROR + end end def bugsnag_release_stage(platform) - pick_bugsnag_release_stage(platform) + case platform + when "android" then bugsnag_android_config["release_stage"] + when "ios" then bugsnag_ios_config["release_stage"] + else + raise ArgumentError, INVALID_PLATFORM_ERROR + end end def ci_cd_workflows diff --git a/app/models/app_store_integration.rb b/app/models/app_store_integration.rb index 436249c81..bd5f05465 100644 --- a/app/models/app_store_integration.rb +++ b/app/models/app_store_integration.rb @@ -23,9 +23,9 @@ class AppStoreIntegration < ApplicationRecord include Displayable include Loggable - delegate :app, to: :integration + delegate :integrable, to: :integration delegate :cache, to: Rails - delegate :refresh_external_app, to: :app + delegate :refresh_external_app, :bundle_identifier, to: :integrable validate :correct_key, on: :create before_create :set_external_details_on_app @@ -96,20 +96,12 @@ class AppStoreIntegration < ApplicationRecord APP_STORE_CONNECT_URL_TEMPLATE = Addressable::Template.new("https://appstoreconnect.apple.com/apps/{app_id}/testflight/ios/{external_id}") - unless Set.new(BUILD_TRANSFORMATIONS.keys).superset?(Set.new(ExternalRelease.minimum_required)) - raise InvalidTransformations - end - - unless Set.new(RELEASE_TRANSFORMATIONS.keys).superset?(Set.new(ExternalRelease.minimum_required)) - raise InvalidTransformations - end - def access_key OpenSSL::PKey::EC.new(p8_key) end def installation - Installations::Apple::AppStoreConnect::Api.new(app.bundle_identifier, key_id, issuer_id, access_key) + Installations::Apple::AppStoreConnect::Api.new(bundle_identifier, key_id, issuer_id, access_key) end def creatable? @@ -145,7 +137,7 @@ def public_icon_img end def connection_data - "Bundle Identifier: #{app.bundle_identifier}" + "Bundle Identifier: #{bundle_identifier}" end def find_build(build_number) @@ -242,7 +234,7 @@ def build_channels(with_production:) end def build_channels_cache_key - "app/#{app.id}/app_store_integration/#{id}/build_channels" + "app/#{integrable.id}/app_store_integration/#{id}/build_channels" end def to_s @@ -250,23 +242,23 @@ def to_s end def deep_link(_, _) - "itms-beta://beta.itunes.apple.com/v1/app/#{app.external_id}" + "itms-beta://beta.itunes.apple.com/v1/app/#{integrable.external_id}" end def inflight_store_link - "https://appstoreconnect.apple.com/apps/#{app.external_id}/distribution/ios/version/inflight" + "https://appstoreconnect.apple.com/apps/#{integrable.external_id}/distribution/ios/version/inflight" end def deliverable_store_link - "https://appstoreconnect.apple.com/apps/#{app.external_id}/distribution/ios/version/deliverable" + "https://appstoreconnect.apple.com/apps/#{integrable.external_id}/distribution/ios/version/deliverable" end def build_info(build_info) - TestFlightInfo.new(build_info.merge(external_link: APP_STORE_CONNECT_URL_TEMPLATE.expand(app_id: app.external_id, external_id: build_info[:external_id]).to_s)) + TestFlightInfo.new(build_info.merge(external_link: APP_STORE_CONNECT_URL_TEMPLATE.expand(app_id: integrable.external_id, external_id: build_info[:external_id]).to_s)) end def release_info(build_info) - AppStoreReleaseInfo.new(build_info.merge(external_link: APP_STORE_CONNECT_URL_TEMPLATE.expand(app_id: app.external_id, external_id: build_info[:external_id]).to_s)) + AppStoreReleaseInfo.new(build_info.merge(external_link: APP_STORE_CONNECT_URL_TEMPLATE.expand(app_id: integrable.external_id, external_id: build_info[:external_id]).to_s)) end def correct_key @@ -276,7 +268,7 @@ def correct_key end def set_external_details_on_app - app.set_external_details(find_app[:id]) + integrable.set_external_details(find_app[:id]) end class TestFlightInfo diff --git a/app/models/app_store_submission.rb b/app/models/app_store_submission.rb index 40e875ffe..70253ba37 100644 --- a/app/models/app_store_submission.rb +++ b/app/models/app_store_submission.rb @@ -343,7 +343,7 @@ def on_fail!(args = nil) end def update_store_version - # TODO: [V2] [post-alpha] update store version details when release metadata changes or build is updated + # TODO: update store version details when release metadata changes # update whats new, build end diff --git a/app/models/app_variant.rb b/app/models/app_variant.rb index 32a688cb0..58712a793 100644 --- a/app/models/app_variant.rb +++ b/app/models/app_variant.rb @@ -17,7 +17,6 @@ class AppVariant < ApplicationRecord include AppConfigurable belongs_to :app_config - has_many :steps, dependent: :nullify validates :bundle_identifier, presence: true, uniqueness: {scope: :app_config_id} validate :duplicate_bundle_identifier diff --git a/app/models/bitbucket_integration.rb b/app/models/bitbucket_integration.rb index 42abff800..fa5625657 100644 --- a/app/models/bitbucket_integration.rb +++ b/app/models/bitbucket_integration.rb @@ -25,7 +25,7 @@ class BitbucketIntegration < ApplicationRecord attr_accessor :code before_create :complete_access - delegate :app, to: :integration + delegate :integrable, to: :integration delegate :code_repository_name, to: :app_config delegate :cache, to: Rails @@ -367,7 +367,7 @@ def set_tokens(tokens) end def app_config - app.config + integrable.config end def redirect_uri @@ -379,6 +379,6 @@ def events_url(params) end def workflows_cache_key(branch_name) - "app/#{app.id}/bitbucket_integration/#{id}/workflows/#{branch_name}" + "app/#{integrable.id}/bitbucket_integration/#{id}/workflows/#{branch_name}" end end diff --git a/app/models/bitrise_integration.rb b/app/models/bitrise_integration.rb index 35d29000e..3aa47c8f2 100644 --- a/app/models/bitrise_integration.rb +++ b/app/models/bitrise_integration.rb @@ -56,7 +56,7 @@ class BitriseIntegration < ApplicationRecord encrypts :access_token, deterministic: true - delegate :app, to: :integration + delegate :integrable, to: :integration delegate :bitrise_project, to: :app_config alias_method :project, :bitrise_project delegate :cache, to: Rails @@ -162,7 +162,7 @@ def workflow_retriable? private def app_config - app.config + integrable.config end def correct_key @@ -172,6 +172,6 @@ def correct_key end def workflows_cache_key - "app/#{app.id}/bitrise_integration/#{id}/workflows" + "app/#{integrable.id}/bitrise_integration/#{id}/workflows" end end diff --git a/app/models/bugsnag_integration.rb b/app/models/bugsnag_integration.rb index 4ac838991..8556d22d8 100644 --- a/app/models/bugsnag_integration.rb +++ b/app/models/bugsnag_integration.rb @@ -48,7 +48,7 @@ class BugsnagIntegration < ApplicationRecord encrypts :access_token, deterministic: true delegate :cache, to: Rails - delegate :app, to: :integration + delegate :integrable, to: :integration delegate :bugsnag_project, :bugsnag_release_stage, to: :app_config alias_method :project, :bugsnag_project alias_method :release_stage, :bugsnag_release_stage @@ -121,7 +121,7 @@ def project_id(platform) end def app_config - integration.app.config + integrable.config end def correct_key @@ -131,6 +131,6 @@ def correct_key end def list_projects_cache_key - "app/#{app.id}/bugsnag_integration/#{id}/list_projects" + "app/#{integrable.id}/bugsnag_integration/#{id}/list_projects" end end diff --git a/app/models/build_artifact.rb b/app/models/build_artifact.rb index 3bf14eaf5..8f8a29d8d 100644 --- a/app/models/build_artifact.rb +++ b/app/models/build_artifact.rb @@ -8,15 +8,15 @@ # created_at :datetime not null # updated_at :datetime not null # build_id :uuid indexed -# step_run_id :uuid indexed # require "zip" class BuildArtifact < ApplicationRecord include Rails.application.routes.url_helpers - belongs_to :step_run, optional: true, inverse_of: :build_artifact - belongs_to :build, optional: true, inverse_of: :artifact + self.ignored_columns += ["step_run_id"] + + belongs_to :build, inverse_of: :artifact has_one_attached :file delegate :create_and_upload!, to: ActiveStorage::Blob @@ -30,10 +30,6 @@ def self.find_via_signed_id(signed_id) find_by(id: attachment.record_id) end - def parent - step_run || build - end - def save_file!(artifact_stream) transaction do self.file = create_and_upload!(io: artifact_stream.file, filename: gen_filename(artifact_stream.ext)) @@ -43,7 +39,7 @@ def save_file!(artifact_stream) end def gen_filename(ext) - "#{app.slug}-#{parent.build_version}-build#{ext}" + "#{app.slug}-#{build.build_version}-build#{ext}" end def get_filename @@ -65,7 +61,7 @@ def download_url end def app - parent.release_platform_run.app + build.release_platform_run.app end delegate :organization, to: :app diff --git a/app/models/build_queue.rb b/app/models/build_queue.rb index e8ef38dba..cc7fe7206 100644 --- a/app/models/build_queue.rb +++ b/app/models/build_queue.rb @@ -20,17 +20,6 @@ class BuildQueue < ApplicationRecord after_create_commit :schedule_kickoff! - def apply! - head_commit&.trigger_step_runs - update!(applied_at: Time.current, is_active: false) - release.create_build_queue! - end - - def add_commit!(commit, can_apply: true) - commits << commit - apply! if commits.size >= build_queue_size && can_apply - end - def add_commit_v2!(commit, can_apply: true) commits << commit if commits.size >= build_queue_size && can_apply diff --git a/app/models/commit.rb b/app/models/commit.rb index 497a97a1f..f8a5cf889 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -25,7 +25,6 @@ class Commit < ApplicationRecord self.implicit_order_column = :timestamp - has_many :step_runs, dependent: :nullify, inverse_of: :commit has_many :release_platform_runs, dependent: :nullify, inverse_of: :last_commit belongs_to :release, inverse_of: :all_commits belongs_to :build_queue, inverse_of: :commits, optional: true @@ -62,13 +61,6 @@ def self.count_by_team(org) res end - def self.between(base_step_run, head_step_run) - return none if head_step_run.nil? - return none if base_step_run.nil? && head_step_run.nil? - - between_commits(base_step_run&.commit, head_step_run.commit) - end - def self.between_commits(base_commit, head_commit) return none if head_commit.nil? return none if base_commit.nil? && head_commit.nil? @@ -98,10 +90,6 @@ def author_url url end - def run_for(step, release_platform_run) - step_runs.where(step:, release_platform_run:).last - end - def stale? release.applied_commits.last != self end @@ -110,43 +98,6 @@ def short_sha commit_hash[0, 7] end - def step_runs_for(platform_run) - step_runs.where(release_platform_run: platform_run).includes(:step) - end - - def applied_at - step_runs.map(&:created_at).min - end - - def trigger_step_runs_for(platform_run, force: false) - return if release.hotfix? && !force - platform_run.bump_version! - platform_run.update!(last_commit: self) - - platform_run.release_platform.ordered_steps_until(platform_run.current_step_number).each do |step| - next if release.hotfix? || step.manual_trigger_only? - Triggers::StepRun.call(step, self, platform_run) - end - end - - def trigger_step_runs - return unless applicable? - - release_platform_runs.have_not_submitted_production.each do |run| - trigger_step_runs_for(run) - end - end - - def add_to_build_queue!(is_head_commit: true) - return unless release.queue_commit?(self) - release.active_build_queue.add_commit!(self, can_apply: is_head_commit) - end - - def trigger! - return add_to_build_queue! if release.queue_commit?(self) - trigger_step_runs - end - def notification_params release.notification_params.merge( { diff --git a/app/models/concerns/app_configurable.rb b/app/models/concerns/app_configurable.rb index 99e69cd1a..dd2b12aaa 100644 --- a/app/models/concerns/app_configurable.rb +++ b/app/models/concerns/app_configurable.rb @@ -1,7 +1,12 @@ module AppConfigurable - include PlatformAwareness + INVALID_PLATFORM_ERROR = "platform must be valid" def firebase_app(platform) - pick_firebase_app_id(platform) + case platform + when "android" then firebase_android_config["app_id"] + when "ios" then firebase_ios_config["app_id"] + else + raise ArgumentError, INVALID_PLATFORM_ERROR + end end end diff --git a/app/models/concerns/commitable.rb b/app/models/concerns/commitable.rb index ab6f89eef..7954dbac1 100644 --- a/app/models/concerns/commitable.rb +++ b/app/models/concerns/commitable.rb @@ -57,7 +57,7 @@ def parents = commit["parents"] def message = commit["message"] - def team = nil # TODO: [V2] stub + def team = nil # TODO: stub def pull_request = nil diff --git a/app/models/concerns/notifiable.rb b/app/models/concerns/notifiable.rb deleted file mode 100644 index a5f7607db..000000000 --- a/app/models/concerns/notifiable.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Notifiable - def notification_channel_id - return if notification_channel.blank? - notification_channel["id"] - end - - def notification_channel_name - return if notification_channel.blank? - notification_channel["name"] - end -end diff --git a/app/models/concerns/platform_awareness.rb b/app/models/concerns/platform_awareness.rb deleted file mode 100644 index 2724706a0..000000000 --- a/app/models/concerns/platform_awareness.rb +++ /dev/null @@ -1,38 +0,0 @@ -module PlatformAwareness - INVALID_PLATFORM_ERROR = "platform must be valid" - def pick_firebase_app_id(platform) - case platform - when "android" then firebase_android_config["app_id"] - when "ios" then firebase_ios_config["app_id"] - else - raise ArgumentError, INVALID_PLATFORM_ERROR - end - end - - def pick_bugsnag_release_stage(platform) - case platform - when "android" then bugsnag_android_config["release_stage"] - when "ios" then bugsnag_ios_config["release_stage"] - else - raise ArgumentError, INVALID_PLATFORM_ERROR - end - end - - def pick_bugsnag_project_id(platform) - case platform - when "android" then bugsnag_android_config["project_id"] - when "ios" then bugsnag_ios_config["project_id"] - else - raise ArgumentError, INVALID_PLATFORM_ERROR - end - end - - def pick_firebase_crashlytics_app_id(platform) - case platform - when "android" then firebase_crashlytics_android_config["app_id"] - when "ios" then firebase_crashlytics_ios_config["app_id"] - else - raise ArgumentError, INVALID_PLATFORM_ERROR - end - end -end diff --git a/app/models/config/release_platform.rb b/app/models/config/release_platform.rb index 3e1bcfcb9..e1781c9dd 100644 --- a/app/models/config/release_platform.rb +++ b/app/models/config/release_platform.rb @@ -68,6 +68,12 @@ def as_json(options = {}) } end + def has_restricted_public_channels? + return false if ios? + return true if production_release.present? + internal_release&.submissions&.any?(&:restricted_public_channel?) || beta_release&.submissions&.any?(&:restricted_public_channel?) + end + def pick_internal_workflow internal_workflow || release_candidate_workflow end diff --git a/app/models/config/submission.rb b/app/models/config/submission.rb index 6424420d0..38134f562 100644 --- a/app/models/config/submission.rb +++ b/app/models/config/submission.rb @@ -60,6 +60,11 @@ def submission_class submission_type.constantize end + def restricted_public_channel? + return false unless submission_type == "GooglePlayStoreSubmission" + GooglePlayStoreIntegration::PUBLIC_CHANNELS.include?(submission_external.identifier) + end + def next release_step_config.submissions.where("number > ?", number).order(:number).first end diff --git a/app/models/deployment.rb b/app/models/deployment.rb deleted file mode 100644 index b4ef9ed2a..000000000 --- a/app/models/deployment.rb +++ /dev/null @@ -1,202 +0,0 @@ -# == Schema Information -# -# Table name: deployments -# -# id :uuid not null, primary key -# build_artifact_channel :jsonb indexed => [integration_id, step_id] -# deployment_number :integer default(0), not null, indexed => [step_id] -# discarded_at :datetime indexed -# is_staged_rollout :boolean default(FALSE) -# notes :string default("no_notes"), not null -# send_build_notes :boolean -# staged_rollout_config :decimal(, ) default([]), is an Array -# created_at :datetime not null -# updated_at :datetime not null -# integration_id :uuid indexed => [build_artifact_channel, step_id], indexed -# step_id :uuid not null, indexed => [build_artifact_channel, integration_id], indexed => [deployment_number], indexed -# -class Deployment < ApplicationRecord - has_paper_trail - include Displayable - include Discard::Model - - self.implicit_order_column = :deployment_number - - has_many :deployment_runs, dependent: :destroy - belongs_to :step, inverse_of: :deployments - belongs_to :integration, optional: true - - enum :notes, {build_notes: "build_notes", release_notes: "release_notes", no_notes: "no_notes"} - - validates :deployment_number, presence: true - validates :build_artifact_channel, uniqueness: {scope: [:integration_id, :step_id], conditions: -> { kept }} - validate :staged_rollout_is_allowed - validate :correct_staged_rollout_config, if: :staged_rollout?, on: :create - validate :non_prod_build_channel, if: -> { step.review? } - - delegate :google_play_store_integration?, - :slack_integration?, - :store?, - :app_store_integration?, - :controllable_rollout?, - :google_firebase_integration?, :project_link, to: :integration, allow_nil: true - delegate :train, :app, :notify!, :release_platform, to: :step - - scope :sequential, -> { order("deployments.deployment_number ASC") } - - before_save :set_deployment_number, if: :new_record? - before_save :set_default_staged_rollout, if: [:new_record?, :app_store_integration?, :staged_rollout?] - before_save :set_default_prod_notes_config, if: [:new_record?, :production_channel?] - - FULL_ROLLOUT_VALUE = BigDecimal("100") - - def staged_rollout? = is_staged_rollout - - def send_notes? - !no_notes? - end - - def set_deployment_number - self.deployment_number = step.all_deployments.maximum(:deployment_number).to_i + 1 - end - - def external? - integration.nil? - end - - def uploadable? - slack_integration? || google_firebase_integration? || google_play_store_integration? || (release_platform.android? && external?) - end - - def findable? - release_platform.ios? && app_store_integration? - end - - def first? - step.deployments.minimum(:deployment_number).to_i == deployment_number - end - - def last? - step.deployments.maximum(:deployment_number).to_i == deployment_number - end - - def next - step.deployments.where("deployment_number > ?", deployment_number)&.first - end - - def previous - step.deployments.where(deployment_number: ...deployment_number).last - end - - def deployment_channel - build_artifact_channel["id"] - end - - def deployment_channel_name - build_artifact_channel["name"] - end - - def production_channel? - store? && build_artifact_channel["is_production"] - end - - def internal_channel? - build_artifact_channel["is_internal"] - end - - def requires_review? - google_play_store_integration? && deployment_channel.in?(%w[production beta alpha]) - end - - def integration_type - return :app_store if app_store? - return :testflight if test_flight? - return :google_play_store if google_play_store_integration? - return :slack if slack_integration? - return :firebase if google_firebase_integration? - :external - end - - def display_channel? - !external? && !app_store? - end - - def test_flight? - !production_channel? && app_store_integration? - end - - def app_store? - production_channel? && app_store_integration? - end - - def notification_params - step.notification_params - .merge(train.notification_params) - .merge( - { - is_staged_rollout_deployment: staged_rollout?, - is_production_channel: production_channel?, - is_play_store_production: production_channel? && google_play_store_integration?, - is_app_store_production: app_store?, - deployment_channel_type: integration_type&.to_s&.titleize, - deployment_channel: build_artifact_channel, - deployment_channel_asset_link: integration&.public_icon_img, - requires_review: requires_review? - } - ) - end - - def replicate(new_step) - new_deployment = dup - new_step.deployments << new_deployment - end - - def one_percent_beta_release? - train.one_percent_beta_release? && google_play_store_integration? && deployment_channel == "beta" - end - - private - - def set_default_prod_notes_config - self.notes = "release_notes" - end - - def set_default_staged_rollout - self.staged_rollout_config = AppStoreIntegration::DEFAULT_PHASED_RELEASE_SEQUENCE - end - - def staged_rollout_is_allowed - if is_staged_rollout && !production_channel? - errors.add(:is_staged_rollout, :prod_only) - end - end - - def correct_staged_rollout_config - if app_store_integration? - errors.add(:staged_rollout_config, :not_allowed) if staged_rollout_config.present? - return - end - - if staged_rollout_config.size < 1 - errors.add(:staged_rollout_config, :at_least_one) - end - - if staged_rollout_config[0]&.zero? - errors.add(:staged_rollout_config, :zero_rollout) - end - - if staged_rollout_config.sort != staged_rollout_config - errors.add(:staged_rollout_config, :increasing_order) - end - - if staged_rollout_config.any? { |value| value > FULL_ROLLOUT_VALUE } - errors.add(:staged_rollout_config, :max_100) - end - end - - def non_prod_build_channel - if production_channel? - errors.add(:build_artifact_channel, :prod_channel_in_review_not_allowed) - end - end -end diff --git a/app/models/deployment_run.rb b/app/models/deployment_run.rb deleted file mode 100644 index fcbcbe3de..000000000 --- a/app/models/deployment_run.rb +++ /dev/null @@ -1,578 +0,0 @@ -# == Schema Information -# -# Table name: deployment_runs -# -# id :uuid not null, primary key -# failure_reason :string -# initial_rollout_percentage :decimal(8, 5) -# scheduled_at :datetime not null -# status :string -# created_at :datetime not null -# updated_at :datetime not null -# deployment_id :uuid not null, indexed => [step_run_id] -# step_run_id :uuid not null, indexed => [deployment_id], indexed -# -class DeploymentRun < ApplicationRecord - has_paper_trail - include AASM - include Passportable - include Loggable - include Displayable - using RefinedArray - using RefinedString - - belongs_to :step_run, inverse_of: :deployment_runs - belongs_to :deployment, inverse_of: :deployment_runs - has_one :staged_rollout, dependent: :destroy - has_one :external_release, dependent: :destroy - has_many :release_health_metrics, dependent: :destroy, inverse_of: :deployment_run - has_many :release_health_events, dependent: :destroy, inverse_of: :deployment_run - - validates :deployment_id, uniqueness: {scope: :step_run_id} - - delegate :step, - :release, - :release_platform_run, - :commit, - :build_number, - :build_artifact, - :build_version, - :build_display_name, - to: :step_run - delegate :deployment_number, - :notify!, - :integration, - :deployment_channel, - :deployment_channel_name, - :external?, - :google_play_store_integration?, - :slack_integration?, - :app_store_integration?, - :app_store?, - :test_flight?, - :store?, - :send_notes?, - :release_notes?, - :build_notes?, - :staged_rollout?, - :staged_rollout_config, - :google_firebase_integration?, - :production_channel?, - :release_platform, - :internal_channel?, - :one_percent_beta_release?, - to: :deployment - delegate :train, :app, to: :release - delegate :release_version, :release_metadatum, :platform, to: :release_platform_run - delegate :release_health_rules, to: :release_platform - - STAMPABLE_REASONS = %w[ - created - release_failed - prepare_release_failed - inflight_release_replaced - submitted_for_review - resubmitted_for_review - review_approved - release_started - released - review_failed - skipped - ] - - STATES = { - created: "created", - started: "started", - preparing_release: "preparing_release", - prepared_release: "prepared_release", - submitted_for_review: "submitted_for_review", - failed_prepare_release: "failed_prepare_release", - uploading: "uploading", - uploaded: "uploaded", - ready_to_release: "ready_to_release", - rollout_started: "rollout_started", - released: "released", - review_failed: "review_failed", - failed_with_action_required: "failed_with_action_required", - failed: "failed" - } - - JOB_FREQUENCY = { - "Crashlytics" => 30.minutes, - "Bugsnag" => 5.minutes - } - - READY_STATES = [STATES[:rollout_started], STATES[:ready_to_release], STATES[:released]] - STORE_SUBMISSION_STATES = READY_STATES + [STATES[:submitted_for_review], STATES[:review_failed]] - FAILED_STATES = [STATES[:failed], STATES[:failed_prepare_release], STATES[:failed_with_action_required]] - - enum :status, STATES - enum :failure_reason, { - developer_rejected: "developer_rejected", - invalid_release: "invalid_release", - unknown_failure: "unknown_failure" - }.merge( - *[ - Installations::Apple::AppStoreConnect::Error.reasons, - Installations::Google::PlayDeveloper::Error.reasons, - Installations::Google::Firebase::Error.reasons, - Installations::Google::Firebase::OpError.reasons - ].map(&:zip_map_self) - ) - - aasm safe_state_machine_params do - state :created, initial: true, before_enter: -> { step_run.startable_deployment?(deployment) } - state(*STATES.keys) - - event :dispatch, after_commit: :kickoff! do - after { step_run.start_deploy! if first? } - transitions from: :created, to: :started - end - - event :start_prepare_release, after_commit: ->(args = {force: false}) { Deployments::AppStoreConnect::PrepareForReleaseJob.perform_async(id, args.fetch(:force, false)) } do - transitions from: [:started, :failed_prepare_release], to: :preparing_release do - guard { |_| app_store? } - end - end - - event :prepare_release, guard: :app_store? do - transitions from: :preparing_release, to: :prepared_release - end - - event :fail_prepare_release, before: :set_reason, after_commit: -> { event_stamp!(reason: :prepare_release_failed, kind: :error, data: stamp_data) } do - transitions from: :preparing_release, to: :failed_prepare_release do - guard { |_| app_store? } - end - end - - event :submit_for_review, after_commit: ->(args = {resubmission: false}) { after_submission(args.fetch(:resubmission, false)) } do - transitions from: [:started, :prepared_release, :review_failed], to: :submitted_for_review - end - - event :start_upload, after: :get_upload_status do - transitions from: :started, to: :uploading - end - - event :upload, after_commit: -> { Deployments::ReleaseJob.perform_later(id) } do - transitions from: [:started, :uploading], to: :uploaded - end - - event :ready_to_release, after_commit: :mark_reviewed do - transitions from: [:submitted_for_review, :review_failed], to: :ready_to_release - end - - event :engage_release, after_commit: :on_release_started do - transitions from: [:uploaded, :ready_to_release], to: :rollout_started - end - - event :fail_review, after_commit: :after_review_failure do - transitions from: :submitted_for_review, to: :review_failed - end - - event :fail_with_sync_option, before: :set_reason do - transitions from: [:started, :prepared_release, :uploading, :uploaded, :submitted_for_review, :ready_to_release, :rollout_started, :failed_prepare_release, :failed_with_action_required], to: :failed_with_action_required - after { step_run.fail_deployment_with_sync_option! } - end - - event :skip, after_commit: -> { event_stamp!(reason: :skipped, kind: :notice, data: stamp_data) } do - transitions from: :failed_with_action_required, to: :released - after { step_run.finish_deployment!(deployment) } - end - - event :dispatch_fail, before: :set_reason, after_commit: :release_failed do - transitions from: [:started, :prepared_release, :uploading, :uploaded, :submitted_for_review, :ready_to_release, :rollout_started, :preparing_release, :failed_prepare_release], to: :failed - after { step_run.fail_deployment!(deployment) } - end - - event :complete, after_commit: :release_success do - after { step_run.finish_deployment!(deployment) } - transitions from: [:created, :uploaded, :started, :submitted_for_review, :rollout_started, :ready_to_release, :review_failed], to: :released - end - end - - scope :for_ids, ->(ids) { includes(deployment: :integration).where(id: ids) } - scope :matching_runs_for, ->(integration) { includes(:deployment).where(deployments: {integration: integration}) } - scope :has_begun, -> { where.not(status: :created) } - scope :failed, -> { where(status: FAILED_STATES) } - scope :not_failed, -> { where.not(status: [:failed, :failed_prepare_release]) } - scope :ready, -> { where(status: READY_STATES) } - - after_commit -> { create_stamp!(data: stamp_data) }, on: :create - - UnknownStoreError = Class.new(StandardError) - - def self.reached_production - ready.includes(:step_run, :deployment).select(&:production_channel?) - end - - def self.reached_submission - where(status: STORE_SUBMISSION_STATES).includes([:staged_rollout, {step_run: [:commit], deployment: [:integration]}]).select(&:production_channel?) - end - - def failure? - status.in?(FAILED_STATES) - end - - def check_release_health - return unless latest_health_data&.fresh? - latest_health_data.check_release_health - end - - def show_health? - return true if latest_health_data&.fresh? - false - end - - def unhealthy? - !healthy? - end - - def deployment_notes - return step_run.build_notes.truncate(ReleaseMetadata::NOTES_MAX_LENGTH) if build_notes? - release_metadatum.release_notes if release_notes? - end - - def healthy? - return true if release_health_rules.blank? - return true if release_health_events.blank? - - release_health_rules.all? do |rule| - event = release_health_events.where(release_health_rule: rule).last - event.blank? || event.healthy? - end - end - - def fetch_health_data! - return if app.monitoring_provider.blank? - return unless production_channel? - - release_data = app.monitoring_provider.find_release(platform, build_version, build_number) - return if release_data.blank? - - release_health_metrics.create(fetched_at: Time.current, **release_data) - end - - def latest_health_data - release_health_metrics.order(fetched_at: :desc).first - end - - def staged_rollout_events - return [] unless staged_rollout? - - staged_rollout.passports.where(reason: [:started, :increased, :fully_released]).order(:event_timestamp).map do |p| - { - timestamp: p.event_timestamp, - rollout_percentage: (p.reason == "fully_released") ? "100%" : p.metadata["rollout_percentage"] - } - end - end - - def rollout_percentage_at(day) - return 100.0 unless staged_rollout - last_event = staged_rollout - .passports - .where(reason: [:started, :increased, :fully_released]) - .where("DATE_TRUNC('day', event_timestamp) <= ?", day) - .order(:event_timestamp) - .last - return 0.0 unless last_event - return 100.0 if last_event.reason == "fully_released" - last_event.metadata["rollout_percentage"].safe_float - end - - def submitted_at - return unless production_channel? - - if google_play_store_integration? - release_started_at - elsif app_store_integration? - passports.where(reason: :submitted_for_review).last&.event_timestamp - end - end - - def release_started_at - return unless production_channel? - - passport = passports.where(reason: :release_started).last - - return passport.event_timestamp if passport - - # NOTE: closest timestamp for releases finished before the above passport was added - return staged_rollout.created_at if staged_rollout - passports.where(reason: :released).last&.event_timestamp - end - - def first? - step_run.deployment_runs.first == self - end - - def after_submission(resubmission = false) - notify!("Submitted for review!", :submit_for_review, notification_params.merge(resubmission:)) - if resubmission - event_stamp!(reason: :resubmitted_for_review, kind: :notice, data: stamp_data) - else - event_stamp!(reason: :submitted_for_review, kind: :notice, data: stamp_data) - end - Deployments::AppStoreConnect::UpdateExternalReleaseJob.perform_async(id) - end - - def after_review_failure - notify!("Review failed", :review_failed, notification_params) - event_stamp!(reason: :review_failed, kind: :error, data: stamp_data) - end - - def get_upload_status(args) - Deployments::GoogleFirebase::UpdateUploadStatusJob.perform_async(id, args&.fetch(:op_name)) - end - - def kickoff! - return complete! if external? - return Deployments::SlackJob.perform_later(id) if slack_integration? - return Deployments::AppStoreConnect::Release.kickoff!(self) if app_store_integration? - return Deployments::GooglePlayStore::Release.kickoff!(self) if google_play_store_integration? - Deployments::GoogleFirebase::Release.kickoff!(self) if google_firebase_integration? - end - - def start_release! - release_platform_run.with_lock do - return unless release_startable? - - if google_play_store_integration? - Deployments::GooglePlayStore::Release.start_release!(self) - elsif app_store_integration? - Deployments::AppStoreConnect::Release.start_release!(self) - elsif google_firebase_integration? - Deployments::GoogleFirebase::Release.start_release!(self) - else - raise UnknownStoreError - end - end - end - - def on_fully_release! - return unless store? - - release_platform_run.with_lock do - return unless rolloutable? - - if google_play_store_integration? - result = Deployments::GooglePlayStore::Release.release_to_all!(self) - elsif app_store_integration? - result = Deployments::AppStoreConnect::Release.complete_phased_release!(self) - else - raise UnknownStoreError - end - - yield result - end - end - - def on_release(rollout_value:) - return unless store? && google_play_store_integration? - - release_platform_run.with_lock do - return unless controllable_rollout? - - yield Deployments::GooglePlayStore::Release.release_with(self, rollout_value:) - end - end - - def on_halt_release! - return unless store? - - release_platform_run.with_lock do - return unless rolloutable? - - if google_play_store_integration? - result = Deployments::GooglePlayStore::Release.halt_release!(self) - elsif app_store_integration? - result = Deployments::AppStoreConnect::Release.halt_phased_release!(self) - else - raise UnknownStoreError - end - - yield result - end - end - - def on_pause_release! - return unless store? && app_store_integration? - - release_platform_run.with_lock do - return unless automatic_rollout? - - yield Deployments::AppStoreConnect::Release.pause_phased_release!(self) - end - end - - def on_resume_release! - return unless store? - - release_platform_run.with_lock do - return unless rolloutable? - - if google_play_store_integration? - result = Deployments::GooglePlayStore::Release.release_with(self, rollout_value: staged_rollout.last_rollout_percentage) - elsif app_store_integration? - result = Deployments::AppStoreConnect::Release.resume_phased_release!(self) - else - raise UnknownStoreError - end - - yield result - end - end - - def promotable? - step_run.active? && store? && (uploaded? || rollout_started?) - end - - def release_startable? - step_run.active? && may_engage_release? - end - - def rolloutable? - step.release? && - promotable? && - deployment.staged_rollout? && - rollout_started? - end - - def controllable_rollout? - rolloutable? && deployment.controllable_rollout? - end - - def automatic_rollout? - rolloutable? && !deployment.controllable_rollout? - end - - def app_store_release? - step.release? && step_run.active? && deployment.app_store? - end - - def test_flight_release? - step_run.active? && deployment.test_flight? - end - - def reviewable? - app_store_release? && prepared_release? - end - - def releasable? - app_store_release? && may_engage_release? - end - - def rollout_percentage - return unless store? - return staged_rollout.last_rollout_percentage if staged_rollout? && staged_rollout.present? - return BigDecimal("1") if one_percent_beta_release? - initial_rollout_percentage || Deployment::FULL_ROLLOUT_VALUE if deployment.controllable_rollout? - end - - def has_uploaded? - uploaded? || failed? || released? - end - - ## slack - # - def push_to_slack! - return unless slack_integration? - - with_lock do - return if released? - provider.deploy!(deployment_channel, notification_params) - complete! - end - end - - def provider - integration&.providable - end - - def fail_with_error(error) - elog(error) - if error.is_a?(Installations::Error) - if error.reason == :app_review_rejected - fail_with_sync_option!(reason: error.reason) - else - dispatch_fail!(reason: error.reason) - end - else - dispatch_fail! - end - end - - def notification_params - deployment - .notification_params - .merge(step_run.notification_params) - .merge( - { - project_link: external_link, - deep_link: provider&.deep_link(external_release&.external_id, release_platform.platform) - } - ) - end - - def production_release_happened? - return false unless production_channel? - status.in?(READY_STATES) && (staged_rollout.blank? || staged_rollout.active?) - end - - def production_release_submitted? - production_channel? && status.in?(STORE_SUBMISSION_STATES) - end - - def external_link - external_release&.external_link.presence || deployment.project_link - end - - private - - def on_release_started - event_stamp!(reason: :release_started, kind: :notice, data: stamp_data) - ReleasePlatformRuns::CreateTagJob.perform_later(release_platform_run.id) if production_channel? && train.tag_all_store_releases? - Releases::FetchHealthMetricsJob.perform_later(id, JOB_FREQUENCY[app.monitoring_provider.display]) if app.monitoring_provider.present? - end - - def mark_reviewed - external_release.update(reviewed_at: Time.current) - event_stamp!(reason: :review_approved, kind: :success, data: stamp_data) - notify!("Review approved!", :review_approved, notification_params) - end - - def set_reason(args = nil) - self.failure_reason = args&.fetch(:reason, :unknown_failure) - end - - def release_failed - event_stamp!(reason: :release_failed, kind: :error, data: stamp_data) - notify!("Deployment failed", :deployment_failed, notification_params) - end - - def release_success - if external_release - now = Time.current - external_release.update(released_at: now, reviewed_at: external_release.reviewed_at.presence || now) - end - - event_stamp!(reason: :released, kind: :success, data: stamp_data) - return if external? - - train.notify_with_snippet!("Deployment was successful!", - :deployment_finished, - notification_params, - step_run.build_notes, - "Changes since the last release:") - end - - def stamp_data - { - version: build_version, - chan: deployment_channel_name, - provider: integration&.providable&.display, - file: build_artifact&.get_filename, - failure_reason: (display_attr(:failure_reason) if failure_reason.present?) - } - end -end diff --git a/app/models/external_build.rb b/app/models/external_build.rb index 2f75887fc..a8ee65127 100644 --- a/app/models/external_build.rb +++ b/app/models/external_build.rb @@ -2,19 +2,19 @@ # # Table name: external_builds # -# id :uuid not null, primary key -# metadata :jsonb not null -# created_at :datetime not null -# updated_at :datetime not null -# build_id :uuid indexed -# step_run_id :uuid indexed +# id :uuid not null, primary key +# metadata :jsonb not null +# created_at :datetime not null +# updated_at :datetime not null +# build_id :uuid indexed # class ExternalBuild < ApplicationRecord has_paper_trail METADATA_SCHEMA = Rails.root.join("config/schema/external_build_metadata.json") - belongs_to :step_run, inverse_of: :external_build, optional: true + self.ignored_columns += ["step_run_id"] + belongs_to :build, inverse_of: :external_build, optional: true # rubocop:disable Rails/SkipsModelValidations @@ -22,7 +22,7 @@ def update_or_insert!(new_metadata) validate_metadata_schema(new_metadata) return self if errors.present? - unique_by = step_run.present? ? [:step_run_id] : [:build_id] + unique_by = [:build_id] ExternalBuild.upsert_all( [attributes_for_upsert(new_metadata)], @@ -34,7 +34,6 @@ def update_or_insert!(new_metadata) def attributes_for_upsert(new_metadata) {metadata: new_metadata.index_by { |item| item[:identifier] }, - step_run_id: step_run_id, build_id: build_id} end diff --git a/app/models/external_release.rb b/app/models/external_release.rb deleted file mode 100644 index 096ac8d32..000000000 --- a/app/models/external_release.rb +++ /dev/null @@ -1,30 +0,0 @@ -# == Schema Information -# -# Table name: external_releases -# -# id :uuid not null, primary key -# added_at :datetime -# build_number :string -# external_link :string -# name :string -# released_at :datetime -# reviewed_at :datetime -# size_in_bytes :integer -# status :string -# created_at :datetime not null -# updated_at :datetime not null -# deployment_run_id :uuid not null, indexed -# external_id :string -# -class ExternalRelease < ApplicationRecord - belongs_to :deployment_run - delegate :app, :app_store_integration?, :build_version, to: :deployment_run - - def self.minimum_required - column_names.map(&:to_sym).filter { |name| name.in? [:name, :status, :build_number, :external_id, :added_at] } - end - - def review_time - ActiveSupport::Duration.build(reviewed_at - added_at) - end -end diff --git a/app/models/github_integration.rb b/app/models/github_integration.rb index efc66daed..d83cf262e 100644 --- a/app/models/github_integration.rb +++ b/app/models/github_integration.rb @@ -19,8 +19,8 @@ class GithubIntegration < ApplicationRecord validates :installation_id, presence: true delegate :code_repository_name, :code_repo_namespace, :code_repo_name_only, to: :app_config - delegate :app, to: :integration - delegate :organization, to: :app + delegate :integrable, to: :integration + delegate :organization, to: :integrable delegate :cache, to: Rails BASE_INSTALLATION_URL = @@ -367,7 +367,7 @@ def update_webhook!(id, url_params) end def app_config - app.config + integrable.config end def events_url(params) @@ -379,6 +379,6 @@ def events_url(params) end def workflows_cache_key - "app/#{app.id}/github_integration/#{id}/workflows" + "app/#{integrable.id}/github_integration/#{id}/workflows" end end diff --git a/app/models/gitlab_integration.rb b/app/models/gitlab_integration.rb index a42938d5a..16ba43ce7 100644 --- a/app/models/gitlab_integration.rb +++ b/app/models/gitlab_integration.rb @@ -321,7 +321,7 @@ def set_tokens(tokens) end def app_config - integration.app.config + integration.integrable.config end def redirect_uri diff --git a/app/models/google_play_store_integration.rb b/app/models/google_play_store_integration.rb index 6273c9f01..2245a08b3 100644 --- a/app/models/google_play_store_integration.rb +++ b/app/models/google_play_store_integration.rb @@ -16,8 +16,8 @@ class GooglePlayStoreIntegration < ApplicationRecord include Displayable include Loggable - delegate :app, to: :integration, allow_nil: true - delegate :refresh_external_app, to: :app + delegate :integrable, to: :integration, allow_nil: true + delegate :refresh_external_app, :bundle_identifier, to: :integrable, allow_nil: true validate :correct_key, on: :create @@ -50,7 +50,7 @@ def access_key end def installation - Installations::Google::PlayDeveloper::Api.new(app.bundle_identifier, access_key) + Installations::Google::PlayDeveloper::Api.new(bundle_identifier, access_key) end def create_draft_release(channel, build_number, version, release_notes, retry_on_review_fail: false) @@ -104,7 +104,7 @@ def further_setup? end def draft_check - app&.set_draft_status! + integrable&.set_draft_status! end def draft_check? @@ -126,7 +126,7 @@ def to_s end def connection_data - "Bundle Identifier: #{app.bundle_identifier}" + "Bundle Identifier: #{bundle_identifier}" end def channels diff --git a/app/models/integration.rb b/app/models/integration.rb index 7c8666836..eb83c4fd0 100644 --- a/app/models/integration.rb +++ b/app/models/integration.rb @@ -11,7 +11,6 @@ # status :string indexed => [integrable_id, category, providable_type] # created_at :datetime not null # updated_at :datetime not null -# app_id :uuid indexed # integrable_id :uuid indexed => [category, providable_type, status] # providable_id :uuid # @@ -21,7 +20,8 @@ class Integration < ApplicationRecord using RefinedString include Discard::Model - # self.ignored_columns += %w[app_id] + self.ignored_columns += %w[app_id] + belongs_to :app, optional: true PROVIDER_TYPES = %w[GithubIntegration GitlabIntegration SlackIntegration AppStoreIntegration GooglePlayStoreIntegration BitriseIntegration GoogleFirebaseIntegration BugsnagIntegration BitbucketIntegration CrashlyticsIntegration] diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 9463dd3b2..2a0ce20ae 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -18,26 +18,13 @@ class NotificationSetting < ApplicationRecord belongs_to :train - # NOTE: These will be removed after Product v2 is released - DEPRECATED_KINDS = { - deployment_finished: "deployment_finished", - deployment_failed: "deployment_failed", - step_started: "step_started", - build_available: "build_available", - step_failed: "step_failed", - submit_for_review: "submit_for_review", - review_approved: "review_approved", - review_failed: "review_failed", - staged_rollout_updated: "staged_rollout_updated", - staged_rollout_paused: "staged_rollout_paused", - staged_rollout_resumed: "staged_rollout_resumed", - staged_rollout_halted: "staged_rollout_halted", - staged_rollout_completed: "staged_rollout_completed", - staged_rollout_fully_released: "staged_rollout_fully_released" - } - - # Product v2 notifications - V2_KINDS = { + enum :kind, { + release_started: "release_started", + release_stopped: "release_stopped", + release_ended: "release_ended", + release_scheduled: "release_scheduled", + release_health_events: "release_health_events", + backmerge_failed: "backmerge_failed", build_available_v2: "build_available_v2", internal_release_finished: "internal_release_finished", internal_release_failed: "internal_release_failed", @@ -60,15 +47,6 @@ class NotificationSetting < ApplicationRecord workflow_run_unavailable: "workflow_run_unavailable" } - enum :kind, { - release_started: "release_started", - release_stopped: "release_stopped", - release_ended: "release_ended", - release_scheduled: "release_scheduled", - release_health_events: "release_health_events", - backmerge_failed: "backmerge_failed" - }.merge(DEPRECATED_KINDS).merge(V2_KINDS) - scope :active, -> { where(active: true) } delegate :app, to: :train delegate :notification_provider, to: :app diff --git a/app/models/play_store_submission.rb b/app/models/play_store_submission.rb index c62bd3a59..6fccbe045 100644 --- a/app/models/play_store_submission.rb +++ b/app/models/play_store_submission.rb @@ -80,7 +80,7 @@ class PlayStoreSubmission < StoreSubmission transitions from: :preparing, to: :prepared end - # TODO: [V2] [post-alpha] This is currently not used, should be hooked up as an action from the user + # TODO: This is currently not used, should be hooked up as an action from the user event :reject do after { set_rejected_at! } transitions from: :prepared, to: :review_failed diff --git a/app/models/production_release.rb b/app/models/production_release.rb index 6035960cf..9273754b6 100644 --- a/app/models/production_release.rb +++ b/app/models/production_release.rb @@ -163,6 +163,11 @@ def show_health? false end + def check_release_health + return unless latest_health_data&.fresh? + latest_health_data.check_release_health + end + def conf = Config::ReleaseStep.from_json(config) def production? = true diff --git a/app/models/release.rb b/app/models/release.rb index a9cf14150..1130cf072 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -32,7 +32,6 @@ class Release < ApplicationRecord include Versionable include Displayable include Linkable - include ActionView::Helpers::DateHelper using RefinedString @@ -108,8 +107,6 @@ class Release < ApplicationRecord has_many :release_metadata, through: :release_platform_runs has_many :all_commits, dependent: :destroy, inverse_of: :release, class_name: "Commit" has_many :pull_requests, dependent: :destroy, inverse_of: :release - has_many :step_runs, through: :release_platform_runs - has_many :deployment_runs, through: :step_runs has_many :builds, through: :release_platform_runs has_many :build_queues, dependent: :destroy has_one :active_build_queue, -> { active }, class_name: "BuildQueue", inverse_of: :release, dependent: :destroy @@ -166,14 +163,12 @@ class Release < ApplicationRecord before_create :set_internal_notes after_create :create_platform_runs! after_create :create_build_queue!, if: -> { train.build_queue_enabled? } - after_commit -> { Releases::PreReleaseJob.perform_later(id) }, on: :create - after_commit -> { Releases::FetchCommitLogJob.perform_later(id) }, on: :create after_commit -> { create_stamp!(data: {version: original_release_version}) }, on: :create attr_accessor :has_major_bump, :hotfix_platform, :custom_version friendly_id :human_slug, use: :slugged - delegate :versioning_strategy, :patch_version_bump_only, :product_v2?, to: :train + delegate :versioning_strategy, :patch_version_bump_only, to: :train delegate :app, :vcs_provider, :release_platforms, :notify!, :continuous_backmerge?, :approvals_enabled?, to: :train delegate :platform, :organization, to: :app @@ -192,13 +187,7 @@ def index_score return if hotfix? return unless finished? - reldex_params = - if is_v2? - Queries::ReldexParameters.call(self) - else - Computations::Release::ReldexParameters.call(self) - end - + reldex_params = Queries::ReldexParameters.call(self) train.release_index&.score(**reldex_params) end @@ -254,13 +243,6 @@ def duration ActiveSupport::Duration.build(completed_at - scheduled_at) end - def all_store_step_runs - deployment_runs - .reached_production - .map(&:step_run) - .sort_by(&:updated_at) - end - def unmerged_commits all_commits.where(backmerge_failure: true) end @@ -387,14 +369,6 @@ def pull_requests_url(open = false) train.vcs_provider&.pull_requests_url(branch_name, open:) end - def metadata_editable? - release_platform_runs.any?(&:metadata_editable?) - end - - def release_step_started? - release_platform_runs.any?(&:release_step_started?) - end - class PreReleaseUnfinishedError < StandardError; end def close_pre_release_prs @@ -411,25 +385,10 @@ def close_pre_release_prs end end - def patch_fix? - return false unless committable? - release_platform_runs.any?(&:patch_fix?) - end - def ready_to_be_finalized? release_platform_runs.all? { |prun| prun.finished? || prun.stopped? } end - def finalize_phase_metadata - { - total_run_time: distance_of_time_in_words(created_at, completed_at), - release_tag: tag_name, - release_tag_url: tag_url, - store_url: (app.store_link unless app.cross_platform?), - final_artifact_url: (release_platform_runs.first&.final_build_artifact&.download_url unless app.cross_platform?) - } - end - def live_release_link return if Rails.env.test? release_url(self, link_params) @@ -442,7 +401,9 @@ def notification_params release_branch_url: branch_url, release_url: live_release_link, release_version: release_version, - is_release_unhealthy: unhealthy? + is_release_unhealthy: unhealthy?, + release_completed_at: completed_at, + release_started_at: scheduled_at } ) end @@ -583,13 +544,13 @@ def create_platform_runs! end def set_version - if train.freeze_version? && custom_version.blank? - self.original_release_version = train.version_current + if custom_version.present? + self.original_release_version = custom_version return end - if custom_version.present? - self.original_release_version = custom_version + if train.freeze_version? + self.original_release_version = train.version_current return end diff --git a/app/models/release_health_event.rb b/app/models/release_health_event.rb index 03f0f429b..17feecb51 100644 --- a/app/models/release_health_event.rb +++ b/app/models/release_health_event.rb @@ -9,7 +9,6 @@ # notification_triggered :boolean default(FALSE) # created_at :datetime not null # updated_at :datetime not null -# deployment_run_id :uuid indexed => [release_health_rule_id, release_health_metric_id], indexed # production_release_id :uuid indexed => [release_health_rule_id, release_health_metric_id], indexed # release_health_metric_id :uuid not null, indexed => [deployment_run_id, release_health_rule_id], indexed => [production_release_id, release_health_rule_id], indexed # release_health_rule_id :uuid not null, indexed => [deployment_run_id, release_health_metric_id], indexed => [production_release_id, release_health_metric_id], indexed @@ -18,34 +17,30 @@ class ReleaseHealthEvent < ApplicationRecord include Displayable self.implicit_order_column = :event_timestamp + self.ignored_columns += ["deployment_run_id"] enum :health_status, {healthy: "healthy", unhealthy: "unhealthy"} - belongs_to :deployment_run, optional: true - belongs_to :production_release, optional: true + belongs_to :production_release belongs_to :release_health_rule belongs_to :release_health_metric scope :for_rule, ->(rule) { where(release_health_rule: rule) } - delegate :notify!, to: :parent + delegate :notify!, to: :production_release after_create_commit :notify_health_rule_triggered private - def parent - deployment_run || production_release - end - def notify_health_rule_triggered return if previous_event.blank? && healthy? - return if healthy? && parent.unhealthy? + return if healthy? && production_release.unhealthy? notify!("One of the release health rules has been triggered", :release_health_events, notification_params) end def notification_params - parent.notification_params.merge( + production_release.notification_params.merge( { release_health_rule_filters: rule_filters, release_health_rule_triggers: rule_triggers @@ -62,6 +57,6 @@ def rule_triggers end def previous_event - parent.release_health_events.for_rule(release_health_rule).where.not(id:).reorder("event_timestamp").last + production_release.release_health_events.for_rule(release_health_rule).where.not(id:).reorder("event_timestamp").last end end diff --git a/app/models/release_health_metric.rb b/app/models/release_health_metric.rb index 430670ae3..6ee2b3313 100644 --- a/app/models/release_health_metric.rb +++ b/app/models/release_health_metric.rb @@ -14,18 +14,16 @@ # total_sessions_in_last_day :bigint # created_at :datetime not null # updated_at :datetime not null -# deployment_run_id :uuid indexed # external_release_id :string # production_release_id :uuid indexed # class ReleaseHealthMetric < ApplicationRecord - belongs_to :deployment_run, optional: true - belongs_to :production_release, optional: true - has_many :release_health_events, dependent: :nullify + self.ignored_columns += ["deployment_run_id"] - validate :at_least_one_parent + belongs_to :production_release + has_many :release_health_events, dependent: :nullify - delegate :release_health_rules, to: :parent + delegate :release_health_rules, to: :production_release after_create_commit :check_release_health @@ -58,7 +56,7 @@ def adoption_rate end def staged_rollout - parent.rollout_percentage + production_release.rollout_percentage end def check_release_health @@ -90,14 +88,6 @@ def metric_healthy?(metric_name) private - def at_least_one_parent - errors.add(:base, "Release health metrics must have at least one of production_release or deployment_run") if parent.blank? - end - - def parent - deployment_run || production_release - end - def create_health_event(release_health_rule) return unless release_health_rule.actionable?(self) @@ -106,11 +96,11 @@ def create_health_event(release_health_rule) current_status = is_healthy ? ReleaseHealthEvent.health_statuses[:healthy] : ReleaseHealthEvent.health_statuses[:unhealthy] return if last_event.present? && last_event.health_status == current_status - release_health_events.create(deployment_run:, production_release:, release_health_rule:, health_status: current_status, event_timestamp: Time.current) + release_health_events.create(production_release:, release_health_rule:, health_status: current_status, event_timestamp: Time.current) end def last_event_for(rule) - parent.release_health_events.where(release_health_rule: rule).last + production_release.release_health_events.where(release_health_rule: rule).last end def healthy_for_triggers?(rule, metric_name) diff --git a/app/models/release_metadata.rb b/app/models/release_metadata.rb index 2c98a5492..896677e88 100644 --- a/app/models/release_metadata.rb +++ b/app/models/release_metadata.rb @@ -20,7 +20,6 @@ class ReleaseMetadata < ApplicationRecord belongs_to :release, inverse_of: :release_metadata belongs_to :release_platform_run, inverse_of: :release_metadata, optional: true - NOTES_MAX_LENGTH = 4000 # TODO [V2]: remove this and use the platform-specific notes max length IOS_NOTES_MAX_LENGTH = 4000 ANDROID_NOTES_MAX_LENGTH = 500 # NOTE: Refer to https://www.regular-expressions.info/unicode.html for supporting more unicode characters diff --git a/app/models/release_platform.rb b/app/models/release_platform.rb index e54d1add8..2b9d46d9c 100644 --- a/app/models/release_platform.rb +++ b/app/models/release_platform.rb @@ -2,23 +2,14 @@ # # Table name: release_platforms # -# id :uuid not null, primary key -# branching_strategy :string -# description :string -# name :string not null -# platform :string -# release_backmerge_branch :string -# release_branch :string -# slug :string -# status :string -# version_current :string -# version_seeded_with :string -# working_branch :string -# created_at :datetime not null -# updated_at :datetime not null -# app_id :uuid not null, indexed -# train_id :uuid -# vcs_webhook_id :string +# id :uuid not null, primary key +# name :string not null +# platform :string +# slug :string +# created_at :datetime not null +# updated_at :datetime not null +# app_id :uuid not null, indexed +# train_id :uuid # class ReleasePlatform < ApplicationRecord @@ -27,8 +18,8 @@ class ReleasePlatform < ApplicationRecord extend FriendlyId include Displayable - # self.ignored_columns += %w[branching_strategy description release_backmerge_branch release_branch version_current version_seeded_with working_branch vcs_webhook_id status] - self.ignored_columns += %w[config] + self.ignored_columns += %w[branching_strategy description release_backmerge_branch release_branch version_current version_seeded_with working_branch vcs_webhook_id status config] + NATURAL_ORDER = Arel.sql("CASE WHEN platform = 'android' THEN 1 WHEN platform = 'ios' THEN 2 ELSE 3 END") DEFAULT_PROD_RELEASE_CONFIG = { android: { @@ -59,9 +50,6 @@ class ReleasePlatform < ApplicationRecord has_many :release_health_rules, -> { kept }, dependent: :destroy, inverse_of: :release_platform has_many :all_release_health_rules, dependent: :destroy, inverse_of: :release_platform, class_name: "ReleaseHealthRule" has_many :release_platform_runs, inverse_of: :release_platform, dependent: :destroy - has_many :steps, -> { kept.order(:step_number) }, inverse_of: :release_platform, dependent: :destroy - has_many :all_steps, -> { order(:step_number) }, class_name: "Step", inverse_of: :release_platform, dependent: :destroy - has_many :deployments, through: :steps scope :sequential, -> { order(NATURAL_ORDER) } @@ -74,6 +62,7 @@ class ReleasePlatform < ApplicationRecord delegate :integrations, :ci_cd_provider, to: :train delegate :ready?, :default_locale, to: :app + delegate :has_restricted_public_channels?, to: :platform_config def self.allowed_platforms { @@ -82,47 +71,10 @@ def self.allowed_platforms }.invert end - def active_steps_for(release) - # no release - return steps unless release - return steps if release.active? - - # historical release only - all_steps - .where(created_at: ..release.end_time) - .where("discarded_at IS NULL OR discarded_at >= ?", release.end_time) - end - - def has_release_step? - steps.release.any? - end - - alias_method :startable?, :has_release_step? - - def has_production_deployment? - release_step&.has_production_deployment? - end - - def has_review_steps? - steps.review.exists? - end - - def release_step - steps.release.first - end - def display_name name&.parameterize end - def ordered_steps_until(step_number) - steps.where(step_number: ..step_number).order(:step_number) - end - - def valid_steps? - steps.release.size == 1 - end - def store_provider if ios? app.ios_store_provider @@ -137,12 +89,6 @@ def production_ready? store_provider.present? end - def build_channel_integrations - integrations - .build_channel - .where(providable_type: Integration::ALLOWED_INTEGRATIONS_FOR_APP[platform][:build_channel]) - end - def active_locales app.latest_external_apps[platform.to_sym]&.active_locales end @@ -155,6 +101,8 @@ def production_submission_type DEFAULT_PROD_RELEASE_CONFIG[platform.to_sym][:submissions].first[:submission_type] if production_ready? end + private + # setup production if the store integrations are added # otherwise use the first build channel integration and setup beta releases def set_default_config diff --git a/app/models/release_platform_run.rb b/app/models/release_platform_run.rb index d35ea12b3..e8c316523 100644 --- a/app/models/release_platform_run.rb +++ b/app/models/release_platform_run.rb @@ -51,11 +51,6 @@ class ReleasePlatformRun < ApplicationRecord has_many :production_store_rollouts, -> { production }, class_name: "StoreRollout", dependent: :destroy, inverse_of: :release_platform_run has_many :production_store_submissions, -> { production }, class_name: "StoreSubmission", dependent: :destroy, inverse_of: :release_platform_run - # NOTE: deprecated after v2 - has_many :step_runs, dependent: :destroy, inverse_of: :release_platform_run - has_many :deployment_runs, through: :step_runs - has_many :running_steps, through: :step_runs, source: :step - scope :sequential, -> { order("release_platform_runs.created_at ASC") } scope :have_not_submitted_production, -> { on_track.reject(&:production_release_submitted?) } @@ -70,19 +65,15 @@ class ReleasePlatformRun < ApplicationRecord enum :status, STATES - before_create :set_config, if: -> { release.is_v2? } + before_create :set_config after_create :set_default_release_metadata scope :pending_release, -> { where.not(status: [:finished, :stopped]) } delegate :all_commits, :original_release_version, :hotfix?, :versioning_strategy, :organization, :release_branch, to: :release - delegate :steps, :train, :app, :platform, :active_locales, :store_provider, :ios?, :android?, :default_locale, :ci_cd_provider, to: :release_platform + delegate :train, :app, :platform, :active_locales, :store_provider, :ios?, :android?, :default_locale, :ci_cd_provider, to: :release_platform def external_builds - if release.is_v2? - ExternalBuild.where(build_id: builds.select(:id)) - else - ExternalBuild.where(step_run_id: step_runs.select(:id)) - end + ExternalBuild.where(build_id: builds.select(:id)) end def start! @@ -131,7 +122,7 @@ def latest_beta_release(finished: false) (finished ? beta_releases.finished : beta_releases).order(created_at: :desc).first end - # TODO: [V2] eager loading here is too expensive + # TODO: eager loading here is too expensive def latest_internal_release(finished: false) (finished ? internal_releases.finished : internal_releases) .includes(:commit, :store_submissions, triggered_workflow_run: {build: [:commit, :artifact]}, release_platform_run: [:release]) @@ -159,7 +150,7 @@ def older_beta_releases beta_releases.order(created_at: :desc).offset(1) end - # TODO: [V2] eager loading here is too expensive + # TODO: eager loading here is too expensive def older_internal_releases internal_releases .order(created_at: :desc) @@ -200,7 +191,7 @@ def next_build_sequence_number end def check_release_health - deployment_runs.each(&:check_release_health) + production_releases.each(&:check_release_health) end def release_metadatum @@ -212,23 +203,14 @@ def default_release_metadata end def show_health? - if release.is_v2? - latest_production_release&.show_health? - else - deployment_runs.any?(&:show_health?) - end + latest_production_release&.show_health? end def unhealthy? - if release.is_v2? - latest_production_release&.unhealthy? - else - latest_store_release&.unhealthy? - end + latest_production_release&.unhealthy? end def failure? - return step_runs.last&.failure? unless release.is_v2? return false if latest_production_release.present? pre_prod_releases.reorder(created_at: :desc).first&.failure? @@ -275,8 +257,6 @@ def corrected_release_version train.hotfix_release.next_version if train.hotfix_release&.version_ahead?(self) end - # TODO: [V2] this is a workaround to handle drifted cross-platform releases - # Figure out of a way to deprecate last_commit from rpr and rely on release instead def update_last_commit!(commit) return if commit.blank? return if last_commit&.commit_hash == commit.commit_hash @@ -314,113 +294,18 @@ def newest_release_version upcoming.release_version end - def metadata_editable? - on_track? && !started_store_release? - end - def production_release_in_pre_review? return unless active? return if active_production_release.present? && inflight_production_release.blank? inflight_production_release.blank? || inflight_production_release.store_submission.pre_review? end - alias_method :metadata_editable_v2?, :production_release_in_pre_review? - - # FIXME: move to release and change it for proper movement UI - def overall_movement_status - steps.to_h do |step| - run = last_commit&.run_for(step, self) - [step, run.present? ? run.status_summary : {not_started: true}] - end - end - - def manually_startable_step?(step) - return false if train.inactive? - return false unless on_track? - return false if last_commit.blank? - return false if ongoing_release_step?(step) && train.hotfix_release.present? - return true if (hotfix? || patch_fix?) && last_commit.run_for(step, self).blank? - return false if upcoming_release_step?(step) - return true if step.first? && step_runs_for(step).empty? - return false if step.first? - - (next_step == step) && previous_step_run_for(step).success? - end - - def step_start_blocked?(step) - return false if train.inactive? - return false unless on_track? - return false if last_commit.blank? - return true if train.hotfix_release.present? && train.hotfix_release != release && step.release? - - (next_step == step) && previous_step_run_for(step)&.success? && upcoming_release_step?(step) - end + alias_method :metadata_editable?, :production_release_in_pre_review? def temporary_unblock_upcoming? Flipper.enabled?(:temporary_unblock_upcoming, self) end - def upcoming_release_step?(step) - step.release? && release.upcoming? && !temporary_unblock_upcoming? - end - - def ongoing_release_step?(step) - step.release? && release.ongoing? - end - - def step_runs_for(step) - step_runs.where(step:) - end - - def previous_step_run_for(step) - last_run_for(step.previous) - end - - def finalizable? - on_track? && finished_steps? - end - - def next_step - return steps.first if step_runs.empty? || last_commit.blank? - return steps.first if last_commit.step_runs_for(self).empty? - last_commit.step_runs_for(self).joins(:step).order(:step_number).last.step.next - end - - def running_step? - step_runs.on_track.exists? - end - - def last_run_for(step) - return if last_commit.blank? - last_commit.step_runs_for(self).where(step: step).sequential.last - end - - def current_step_number - return if steps.blank? - return steps.minimum(:step_number) if running_steps.blank? - running_steps.order(:step_number).last.step_number - end - - def finished_steps? - return false if release_platform.release_step.blank? - return false if last_commit.blank? - - last_commit.run_for(release_platform.release_step, self)&.success? - end - - def last_good_step_run - step_runs - .where(status: StepRun.statuses[:success]) - .joins(:step) - .order(step_number: :desc, updated_at: :desc) - .first - end - - def final_build_artifact - return unless finished? - last_good_step_run&.build_artifact - end - def tag_url train.vcs_provider&.tag_url(tag_name) end @@ -449,54 +334,7 @@ def create_tag!(commit, input_tag_name = base_tag_name) # Patch fix commit: no bump required # -- def version_bump_required? - if release.is_v2? - latest_production_release&.version_bump_required? - else - store_release = latest_deployed_store_release - store_release&.status&.in?(DeploymentRun::READY_STATES) && store_release.step_run.basic_build_version == release_version - end - end - - def patch_fix? - on_track? && in_store_resubmission? - end - - def release_step_started? - step_runs_for(release_platform.release_step).present? - end - - def production_release_happened? - step_runs - .includes(:deployment_runs) - .where(step: release_platform.release_step) - .not_failed - .any?(&:production_release_happened?) - end - - def production_release_submitted? - step_runs - .includes(:deployment_runs) - .where(step: release_platform.release_step) - .not_failed - .any?(&:production_release_submitted?) - end - - def commit_applied?(commit) - step_runs.exists?(commit: commit) - end - - def previous_successful_run_before(step_run) - step_runs - .where(step: step_run.step) - .where(scheduled_at: ..step_run.scheduled_at) - .where.not(id: step_run.id) - .success - .order(scheduled_at: :asc) - .last - end - - def commits_between(older_step_run, newer_step_run) - all_commits.between(older_step_run, newer_step_run) + latest_production_release&.version_bump_required? end def notification_params @@ -509,14 +347,6 @@ def notification_params ) end - def store_releases - deployment_runs.reached_production.sort_by(&:scheduled_at).reverse - end - - def store_submitted_releases - deployment_runs.reached_submission.sort_by(&:scheduled_at).reverse - end - def block_play_store_submissions! update!(play_store_blocked: true) end @@ -549,32 +379,6 @@ def base_tag_name "v#{release_version}-#{platform}" end - def started_store_release? - latest_store_release.present? - end - - def latest_store_release - last_run_for(release_platform.release_step) - &.deployment_runs - &.not_failed - &.find { |dr| dr.deployment.production_channel? } - end - - def latest_deployed_store_release - last_successful_run_for(release_platform.release_step) - &.deployment_runs - &.not_failed - &.find { |dr| dr.deployment.production_channel? } - end - - def last_successful_run_for(step) - step_runs - .where(step: step) - .not_failed - .order(scheduled_at: :asc) - .last - end - def set_config self.config = release_platform.platform_config.as_json end diff --git a/app/models/slack_integration.rb b/app/models/slack_integration.rb index 253c43a75..f9771bb11 100644 --- a/app/models/slack_integration.rb +++ b/app/models/slack_integration.rb @@ -19,7 +19,7 @@ class SlackIntegration < ApplicationRecord include Loggable include Rails.application.routes.url_helpers - delegate :app, to: :integration + delegate :integrable, to: :integration delegate :cache, to: Rails attr_accessor :code @@ -105,14 +105,8 @@ def channels .map { |c| c.slice(:id, :name, :is_private) } end - def build_channels(with_production:) - cache - .fetch(channels_cache_key, expires_in: CACHE_EXPIRY) { get_all_channels } - .map { |c| c.slice(:id, :name) } - end - def channels_cache_key - "app/#{app.id}/slack_integration/#{id}/channels" + "app/#{integrable.id}/slack_integration/#{id}/channels" end def notify!(channel, message, type, params, file_id = nil, file_title = nil) @@ -142,10 +136,6 @@ def upload_file!(file, file_name) elog(e) end - def deploy!(channel, params) - notify!(channel, DEPLOY_MESSAGE, :deployment_finished, params) - end - def notifier(type, params) Notifiers::Slack::Builder.build(type, **params) end diff --git a/app/models/staged_rollout.rb b/app/models/staged_rollout.rb deleted file mode 100644 index 24f20a37f..000000000 --- a/app/models/staged_rollout.rb +++ /dev/null @@ -1,221 +0,0 @@ -# == Schema Information -# -# Table name: staged_rollouts -# -# id :uuid not null, primary key -# config :decimal(8, 5) default([]), is an Array -# current_stage :integer -# status :string -# created_at :datetime not null -# updated_at :datetime not null -# deployment_run_id :uuid not null, indexed -# -class StagedRollout < ApplicationRecord - has_paper_trail - include AASM - include Loggable - include Passportable - - belongs_to :deployment_run - - validates :current_stage, numericality: {greater_than_or_equal_to: 0, allow_nil: true} - - delegate :notify!, :platform, to: :deployment_run - - STAMPABLE_REASONS = %w[ - started - paused - failed - failed_before_any_rollout - resumed - increased - completed - halted - fully_released - ] - - STATES = { - created: "created", - started: "started", - paused: "paused", - failed: "failed", - completed: "completed", - stopped: "stopped", - fully_released: "fully_released" - } - - enum :status, STATES - aasm safe_state_machine_params do - state :created, initial: true, before_enter: -> { deployment_run.rolloutable? } - state(*STATES.keys) - - event :start, guard: -> { deployment_run.rolloutable? }, after_commit: -> { event_stamp!(reason: :started, kind: :notice, data: stamp_data) } do - transitions from: :created, to: :started - end - - event :pause, guard: -> { deployment_run.automatic_rollout? }, after_commit: -> { event_stamp!(reason: :paused, kind: :notice, data: stamp_data) } do - transitions from: :started, to: :paused - end - - event :resume, after_commit: -> { event_stamp!(reason: :resumed, kind: :success, data: stamp_data) } do - transitions from: :paused, to: :started, guard: -> { deployment_run.automatic_rollout? } - transitions from: :stopped, to: :started, guard: -> { deployment_run.controllable_rollout? } - end - - event :fail, after_commit: -> { fail_stamp } do - transitions from: [:started, :failed, :created], to: :failed - end - - event :retry, guard: -> { deployment_run.rolloutable? } do - transitions from: :failed, to: :started - end - - event :halt, guard: -> { deployment_run.rolloutable? }, after_commit: -> { event_stamp!(reason: :halted, kind: :notice, data: stamp_data) } do - transitions from: [:started, :paused, :failed], to: :stopped - end - - event :complete, after_commit: -> { event_stamp!(reason: :completed, kind: :success, data: stamp_data) } do - after { notify!("Staged rollout was completed!", :staged_rollout_completed, notification_params) } - after { deployment_run.complete! } - transitions from: [:failed, :started, :paused], to: :completed - end - - event :full_rollout, after_commit: -> { event_stamp!(reason: :fully_released, kind: :success, data: {rollout_percentage: "%.2f" % config[current_stage]}) } do - after { deployment_run.complete! } - transitions from: [:failed, :started], to: :fully_released - end - end - - def active? - %w[started completed fully_released].include?(status) - end - - def display_current_stage - return 0 if created? - (current_stage || 0).succ - end - - def update_stage(stage, finish_rollout: false) - return if stage == current_stage && !finish_rollout - - update!(current_stage: stage) - - if created? - start! - else - event_stamp!(reason: :increased, kind: :notice, data: stamp_data) - end - - retry! if failed? - return complete! if finish_rollout && finished? - notify!("Staged rollout was updated!", :staged_rollout_updated, notification_params) - end - - def last_rollout_percentage - return Deployment::FULL_ROLLOUT_VALUE if fully_released? - return if created? || current_stage.nil? - return config.last if finished? - config[current_stage] - end - - def next_rollout_percentage - return config.first if created? - config[current_stage.succ] - end - - def finished? - next_rollout_percentage.nil? - end - - def next_stage - created? ? 0 : current_stage.succ - end - - def move_to_next_stage! - return if completed? || fully_released? - - deployment_run.on_release(rollout_value: next_rollout_percentage) do |result| - if result.ok? - update_stage(next_stage, finish_rollout: true) - else - fail! - elog(result.error) - end - end - end - - def halt_release! - return if created? - return if completed? || fully_released? - - deployment_run.on_halt_release! do |result| - if result.ok? - halt! - notify!("Release was halted!", :staged_rollout_halted, notification_params) - else - elog(result.error) - end - end - end - - def fully_release! - return if created? || completed? || stopped? - - deployment_run.on_fully_release! do |result| - if result.ok? - full_rollout! - notify!("Staged rollout was accelerated to a full rollout!", :staged_rollout_fully_released, notification_params) - else - elog(result.error) - end - end - end - - def pause_release! - return unless started? - - deployment_run.on_pause_release! do |result| - if result.ok? - pause! - notify!("Staged rollout was paused!", :staged_rollout_paused, notification_params) - else - elog(result.error) - end - end - end - - def resume_release! - return if (deployment_run.automatic_rollout? && !paused?) || (deployment_run.controllable_rollout? && !stopped?) - - deployment_run.on_resume_release! do |result| - if result.ok? - unless completed? - resume! - notify!("Staged rollout was resumed!", :staged_rollout_resumed, notification_params) - end - else - elog(result.error) - end - end - end - - def notification_params - deployment_run.notification_params.merge(stamp_data) - end - - private - - def fail_stamp - if last_rollout_percentage - event_stamp!(reason: :failed, kind: :error, data: stamp_data) - else - event_stamp!(reason: :failed_before_any_rollout, kind: :error, data: stamp_data) - end - end - - def stamp_data - data = {current_stage: display_current_stage, is_fully_released: fully_released?} - data[:rollout_percentage] = "%.2f" % config[current_stage] if current_stage.present? - data - end -end diff --git a/app/models/step.rb b/app/models/step.rb deleted file mode 100644 index 3d565a0e4..000000000 --- a/app/models/step.rb +++ /dev/null @@ -1,193 +0,0 @@ -# == Schema Information -# -# Table name: steps -# -# id :uuid not null, primary key -# auto_deploy :boolean default(TRUE) -# build_artifact_name_pattern :string -# ci_cd_channel :jsonb not null, indexed => [release_platform_id] -# description :string not null -# discarded_at :datetime indexed -# kind :string -# name :string not null -# release_suffix :string -# slug :string -# status :string not null -# step_number :integer default(0), not null, indexed => [release_platform_id] -# created_at :datetime not null -# updated_at :datetime not null -# app_variant_id :uuid -# integration_id :uuid indexed -# release_platform_id :uuid not null, indexed => [ci_cd_channel], indexed, indexed => [step_number] -# -class Step < ApplicationRecord - has_paper_trail - extend FriendlyId - include Discard::Model - - self.implicit_order_column = :step_number - - belongs_to :release_platform, inverse_of: :steps - belongs_to :app_variant, inverse_of: :steps, optional: true - belongs_to :integration, optional: true - has_many :step_runs, inverse_of: :step, dependent: :destroy - has_many :deployments, -> { kept.sequential }, inverse_of: :step, dependent: :destroy - has_many :all_deployments, -> { sequential }, class_name: "Deployment", inverse_of: :step, dependent: :destroy - has_many :deployment_runs, through: :deployments - validates :ci_cd_channel, presence: true, uniqueness: {scope: :release_platform_id, conditions: -> { kept }, message: "you have already used this in another step of this train!"} - validates :release_suffix, format: {with: /\A[a-zA-Z\-_]+\z/, message: "only allows letters and underscore"}, if: -> { release_suffix.present? } - validates :deployments, presence: true, on: :create - validate :unique_deployments, on: :create - validate :unique_store_deployments_per_train, on: :create - validate :auto_deployment_allowed, on: :create - - after_initialize :set_default_status, if: :new_record? - before_validation :set_step_number, if: :new_record? - before_save -> { self.build_artifact_name_pattern = build_artifact_name_pattern.downcase }, if: -> { build_artifact_name_pattern.present? } - after_create :set_ci_cd_provider - - enum :status, { - active: "active", - inactive: "inactive" - } - - enum :kind, { - review: "review", - release: "release" - } - - friendly_id :name, use: :slugged - normalizes :name, with: ->(name) { name.squish } - normalizes :build_artifact_name_pattern, with: ->(pattern) { pattern.squish } - accepts_nested_attributes_for :deployments, allow_destroy: false, reject_if: :reject_deployments? - - delegate :app, :train, to: :release_platform - delegate :android?, to: :app - delegate :notify!, to: :train - - def ci_cd_provider - integration.providable - end - - def set_ci_cd_provider - update(integration: train.ci_cd_provider.integration) - end - - def active_deployments_for(release, step_run = nil) - # no release - return deployments unless release - - # ongoing release - return step_run.deployment_runs.map(&:deployment) if release.end_time.blank? && step_run&.success? - return deployments if release.end_time.blank? - - # historical release only - all_deployments - .where(created_at: ..release.end_time) - .where("discarded_at IS NULL OR discarded_at >= ?", release.end_time) - end - - def suffixable? - release_suffix.present? && release_platform.android? - end - - def set_step_number - all_steps = release_platform.all_steps - - if review? - self.step_number = all_steps.review.maximum(:step_number).to_i + 1 - release_platform.release_step&.update!(step_number: step_number.succ) - else - self.step_number = all_steps.maximum(:step_number).to_i + 1 - end - end - - def set_default_status - self.status = Step.statuses[:active] - end - - def manual_trigger_only? - release? && train.manual_release? && release_platform.has_review_steps? - end - - def first? - release_platform.steps.minimum(:step_number).to_i == step_number - end - - def last? - release_platform.steps.maximum(:step_number).to_i == step_number - end - - def next - release_platform.steps.where("step_number > ?", step_number)&.first - end - - def previous - release_platform.steps.where(step_number: ...step_number).last - end - - def notification_params - train.notification_params.merge( - { - step_type: kind.titleize, - step_name: name - } - ) - end - - def has_production_deployment? - deployments.any?(&:production_channel?) - end - - def workflow_id - ci_cd_channel["id"] - end - - def workflow_name - ci_cd_channel["name"] - end - - def replicate(new_release_platform) - new_step = dup - new_step.release_platform = new_release_platform - deployments.each { |deployment| deployment.replicate(new_step) } - new_step.save! - end - - def has_uploadables? - deployments.any?(&:uploadable?) - end - - def has_findables? - deployments.any?(&:findable?) - end - - private - - def reject_deployments?(attributes) - attributes["build_artifact_channel"].blank? || !attributes["build_artifact_channel"].is_a?(Hash) - end - - def unique_deployments - duplicates = - deployments - .group_by { |deployment| deployment.values_at(:build_artifact_channel, :integration_id, :step_id) } - .values - .detect { |arr| arr.size > 1 } - - errors.add(:deployments, "should be designed to have unique providers and channels") if duplicates - end - - def unique_store_deployments_per_train - duplicates = - deployments - .filter(&:store?) - .any? { |deployment| release_platform.deployments.exists?(build_artifact_channel: deployment.build_artifact_channel, integration: deployment.integration) } - - errors.add(:deployments, "cannot have repeated store configurations across steps in the same train") if duplicates - end - - def auto_deployment_allowed - errors.add(:auto_deploy, "cannot turn off auto deployment for review step") if review? && !auto_deploy? - end -end diff --git a/app/models/step_run.rb b/app/models/step_run.rb deleted file mode 100644 index cdb692934..000000000 --- a/app/models/step_run.rb +++ /dev/null @@ -1,519 +0,0 @@ -# == Schema Information -# -# Table name: step_runs -# -# id :uuid not null, primary key -# approval_status :string default("pending"), not null -# build_notes_raw :text default([]), is an Array -# build_number :string indexed => [build_version] -# build_version :string not null, indexed => [build_number] -# ci_link :string -# ci_ref :string -# scheduled_at :datetime not null -# sign_required :boolean default(TRUE) -# status :string not null -# created_at :datetime not null -# updated_at :datetime not null -# commit_id :uuid not null, indexed, indexed => [step_id] -# release_platform_run_id :uuid not null, indexed -# slack_file_id :string -# step_id :uuid not null, indexed, indexed => [commit_id] -# -class StepRun < ApplicationRecord - has_paper_trail - include AASM - include Passportable - - self.ignored_columns += ["initial_rollout_percentage"] - self.implicit_order_column = :scheduled_at - - BASE_WAIT_TIME = 10.seconds - - belongs_to :step, inverse_of: :step_runs - belongs_to :release_platform_run - belongs_to :commit, inverse_of: :step_runs - has_one :build_artifact, inverse_of: :step_run, dependent: :destroy - has_one :external_build, inverse_of: :step_run, dependent: :destroy - has_many :deployment_runs, -> { includes(:deployment).merge(Deployment.sequential) }, inverse_of: :step_run, dependent: :destroy - has_many :deployments, through: :step - has_many :running_deployments, through: :deployment_runs, source: :deployment - - validates :step_id, uniqueness: {scope: :commit_id} - after_create_commit :handle_post_create_tasks - - STAMPABLE_REASONS = %w[ - created - ci_triggered - ci_retriggered - ci_workflow_unavailable - ci_finished - ci_workflow_failed - ci_workflow_halted - build_available - build_unavailable - build_not_found_in_store - build_found_in_store - deployment_restarted - finished - failed_with_action_required - ] - - # TODO: deprecate this - STAMPABLE_REASONS.concat(["status_changed"]) - - STATES = { - on_track: "on_track", - ci_workflow_triggered: "ci_workflow_triggered", - ci_workflow_unavailable: "ci_workflow_unavailable", - ci_workflow_started: "ci_workflow_started", - ci_workflow_failed: "ci_workflow_failed", - ci_workflow_halted: "ci_workflow_halted", - build_ready: "build_ready", - build_found_in_store: "build_found_in_store", - build_not_found_in_store: "build_not_found_in_store", - build_available: "build_available", - build_unavailable: "build_unavailable", - deployment_started: "deployment_started", - deployment_failed: "deployment_failed", - success: "success", - cancelling: "cancelling", - cancelled: "cancelled", - cancelled_before_start: "cancelled_before_start", - failed_with_action_required: "failed_with_action_required", - deployment_restarted: "deployment_restarted" - } - - END_STATES = STATES.slice( - :ci_workflow_unavailable, - :ci_workflow_failed, - :ci_workflow_halted, - :build_unavailable, - :build_not_found_in_store, - :deployment_failed, - :success, - :cancelled, - :cancelled_before_start - ).keys - - WORKFLOW_NOT_STARTED = [:on_track] - WORKFLOW_IN_PROGRESS = [:ci_workflow_triggered, :ci_workflow_started] - WORKFLOW_IMMUTABLE = STATES.keys - END_STATES - WORKFLOW_IN_PROGRESS - WORKFLOW_NOT_STARTED - FAILED_STATES = %w[ci_workflow_failed ci_workflow_halted build_not_found_in_store build_unavailable deployment_failed failed_with_action_required cancelled_before_start] - - enum :status, STATES - - aasm safe_state_machine_params do - state :on_track, initial: true - state(*STATES.keys) - - event :trigger_ci, after_commit: :after_trigger_ci do - transitions from: :on_track, to: :ci_workflow_triggered - end - - event :ci_start, after_commit: -> { WorkflowProcessors::WorkflowRunJob.perform_later(id) } do - transitions from: [:ci_workflow_triggered], to: :ci_workflow_started - end - - event(:ci_unavailable, after_commit: -> { notify_on_failure!("Could not find the CI workflow!") }) do - transitions from: [:on_track, :ci_workflow_triggered], to: :ci_workflow_unavailable - end - - event(:fail_ci, after_commit: -> { notify_on_failure!("CI workflow failed!") }) do - transitions from: :ci_workflow_started, to: :ci_workflow_failed - end - - event(:cancel_ci, after_commit: -> { notify_on_failure!("CI workflow was halted!") }) do - transitions from: :ci_workflow_started, to: :ci_workflow_halted - end - - event(:retry_ci, after_commit: :after_retrigger_ci) do - before :retry_workflow_run - transitions from: [:ci_workflow_failed, :ci_workflow_halted], to: :ci_workflow_started - end - - event(:finish_ci, after_commit: :after_finish_ci) { transitions from: :ci_workflow_started, to: :build_ready } - event(:build_found, after_commit: :trigger_deployment) { transitions from: :build_ready, to: :build_found_in_store } - - event(:upload_artifact, after_commit: :after_artifact_uploaded) do - before { add_build_artifact(artifacts_url) } - transitions from: :build_ready, to: :build_available - end - - event(:build_not_found, after_commit: -> { notify_on_failure!("Build not found in store!") }) do - transitions from: :build_ready, to: :build_not_found_in_store - end - event(:build_upload_failed) { transitions from: :build_ready, to: :build_unavailable } - event(:start_deploy) { transitions from: [:build_available, :build_found_in_store, :build_ready], to: :deployment_started } - event(:restart_deploy, after_commit: :resume_deployments) do - transitions from: [:failed_with_action_required], to: :deployment_restarted - end - - event(:fail_deploy) do - transitions from: [:deployment_started, :deployment_restarted], to: :deployment_failed - end - - event :fail_deployment_with_sync_option, after_commit: :after_manual_submission_required do - transitions from: [:deployment_started, :deployment_restarted], to: :failed_with_action_required - end - - event(:finish, after_commit: :finalize_release) do - transitions from: [:deployment_started, :deployment_restarted], to: :success - end - - event(:cancel, after_commit: -> { Releases::CancelWorkflowRunJob.perform_later(id) }) do - transitions from: WORKFLOW_IMMUTABLE, to: :cancelled - transitions from: WORKFLOW_IN_PROGRESS, to: :cancelling - transitions from: WORKFLOW_NOT_STARTED, to: :cancelled_before_start - end - end - - enum :approval_status, {pending: "pending", approved: "approved", rejected: "rejected"}, prefix: "approval" - - attr_accessor :current_user - attr_accessor :artifacts_url - - delegate :release_platform, :release, :platform, to: :release_platform_run - delegate :release_branch, :release_version, to: :release - delegate :train, :store_provider, to: :release_platform - delegate :app, :notify!, to: :train - delegate :organization, to: :app - delegate :commit_hash, to: :commit - delegate :download_url, to: :build_artifact, allow_nil: true - delegate :ci_cd_provider, :workflow_id, :workflow_name, :step_number, :build_artifact_name_pattern, :has_uploadables?, :has_findables?, :name, :app_variant, to: :step - scope :not_failed, -> { where.not(status: FAILED_STATES) } - scope :failed, -> { where(status: FAILED_STATES) } - scope :sequential, -> { order("step_runs.scheduled_at ASC") } - - def failure? - status.in?(FAILED_STATES) || last_deployment_run&.failure? - end - - def basic_build_version - build_version.split("-").first - end - - def after_manual_submission_required - event_stamp!(reason: :failed_with_action_required, kind: :error, data: stamp_data) - notify_on_failure!("manual submission required!") - end - - def build_size - build_artifact&.file_size_in_mb - end - - # TODO: move these explicit state checks to use a constant, perhaps END_STATES - def active? - release_platform_run.on_track? && !cancelled? && !success? && !status.in?(FAILED_STATES) - end - - def find_build - store_provider.find_build(build_number) - end - - def get_workflow_run - ci_cd_provider.get_workflow_run(ci_ref) - end - - def get_build_artifact(artifacts_url) - ci_cd_provider.get_artifact(artifacts_url, build_artifact_name_pattern) - end - - def fetching_build? - may_finish_ci? && build_artifact.blank? - end - - def build_artifact_available? - build_artifact.present? - end - - def startable_deployment?(deployment) - return false unless active? - return true if deployment.first? && deployment_runs.empty? - - next_deployment == deployment - end - - def manually_startable_deployment?(deployment) - return false if deployment.first? - return false if step.review? - startable_deployment?(deployment) && (last_deployment_run&.released? || release_platform_run.patch_fix? || release.hotfix?) - end - - def deployment_start_blocked?(deployment) - release.upcoming? && deployment.production_channel? && !release_platform_run.temporary_unblock_upcoming? - end - - def last_deployment_run - deployment_runs.last - end - - def last_run_for(deployment) - deployment_runs.where(deployment: deployment).last - end - - def next_deployment - return step.deployments.first if deployment_runs.empty? - last_deployment_run.deployment.next - end - - def similar_deployment_runs_for(deployment_run) - deployment_runs - .where.not(id: deployment_run) - .matching_runs_for(deployment_run.integration) - .has_begun - end - - def in_progress? - on_track? || ci_workflow_triggered? || ci_workflow_started? || build_ready? || deployment_started? || deployment_restarted? - end - - def blocked? - ci_workflow_failed? || ci_workflow_halted? || failed_with_action_required? - end - - def failed? - build_unavailable? || ci_workflow_unavailable? || deployment_failed? - end - - def done? - success? - end - - def status_summary - { - in_progress: in_progress?, - done: done?, - failed: failed? - } - end - - def first_deployment - step.deployments.order(deployment_number: :asc).first - end - - def finished_deployments? - deployment_runs.released.size == step.deployments.size - end - - def finish_deployment!(deployment) - return finish! if finished_deployments? || deployment.next.blank? - return if deployment.next.production_channel? - return unless step.auto_deploy? - - # trigger the next deployment if available - trigger_deployment(deployment.next) - end - - def fail_deployment!(deployment) - return if deployment.next - - fail_deploy! - end - - def trigger_deployment(deployment = first_deployment) - Triggers::Deployment.call(step_run: self, deployment: deployment) - end - - def resume_deployments - event_stamp!(reason: :deployment_restarted, kind: :notice, data: stamp_data) - failed_deployment_run = deployment_runs.failed_with_action_required.sole - failed_deployment_run.skip! - end - - def notification_params - step.notification_params - .merge(release_platform_run.notification_params) - .merge( - { - ci_link: ci_link, - build_number: build_number, - commit_sha: commit.short_sha, - commit_message: commit.message, - commit_url: commit.url, - artifact_download_link: build_artifact&.download_url, - build_notes: build_notes, - manual_submission_required: status == STATES[:failed_with_action_required] - } - ) - end - - def production_release_happened? - deployment_runs - .not_failed - .any?(&:production_release_happened?) - end - - def production_release_submitted? - deployment_runs - .not_failed - .any?(&:production_release_submitted?) - end - - def relevant_changes - previous_step_run = release_platform_run.previous_successful_run_before(self) - - changes_since_last_release = release.release_changelog&.commit_messages(organization.merge_only_build_notes?) - changes_since_last_run = release_platform_run - .commits_between(previous_step_run, self) - .commit_messages(organization.merge_only_build_notes?) - - return changes_since_last_run if previous_step_run.present? - - (changes_since_last_run || []) + (changes_since_last_release || []) - end - - def build_notes - build_notes_raw - .map { |str| str&.strip } - .flat_map { |line| train.compact_build_notes? ? line.split("\n").first : line.split("\n") } - .map { |line| line.gsub(/\p{Emoji_Presentation}\s*/, "") } - .map { |line| line.gsub('"', "\\\"") } - .reject { |line| line =~ /\AMerge|\ACo-authored-by|\A---------/ } - .compact_blank - .uniq - .map { |str| "ā€¢ #{str}" } - .join("\n").presence || "Nothing new" - end - - def cancel_ci_workflow! - ci_cd_provider.cancel_workflow_run!(ci_ref) - end - - def workflow_found? - ci_ref.present? - end - - def find_and_update_workflow_run - return if workflow_found? - find_workflow_run.then { |wr| update_ci_metadata!(wr) } - end - - def trigger_ci_worfklow_run! - trigger_workflow_run - trigger_ci! - end - - def release_info - slice(:build_version, :build_number, :updated_at, :platform) - end - - def sync_store_status! - return unless failed_with_action_required? - restart_deploy! if store_provider.build_present_in_public_track?(build_number) - end - - def build_display_name - "#{build_version} (#{build_number})" - end - - private - - def handle_post_create_tasks - populate_build_notes - # FIXME: solve this correctly, we rely on wait time to ensure steps are triggered in correct order - Releases::TriggerWorkflowRunJob.set(wait: BASE_WAIT_TIME * step_number).perform_later(id) - create_stamp!(data: stamp_data) - end - - def populate_build_notes - return if build_notes_raw.present? - update(build_notes_raw: relevant_changes) - end - - def previous_step_run - release_platform_run - .step_runs_for(step) - .where(scheduled_at: ...scheduled_at) - .where.not(id: id) - .order(:scheduled_at) - .last - end - - def find_workflow_run - ci_cd_provider.find_workflow_run(workflow_id, release_branch, commit_hash) - end - - def update_ci_metadata!(workflow_run) - return if workflow_run.try(:[], :ci_ref).blank? - update!(ci_ref: workflow_run[:ci_ref], ci_link: workflow_run[:ci_link]) - end - - def trigger_workflow_run(retrigger: false) - update_build_number! unless retrigger - - deploy_action_enabled = organization.deploy_action_enabled? || app.deploy_action_enabled? || train.deploy_action_enabled? - - ci_cd_provider - .trigger_workflow_run!(workflow_id, release_branch, workflow_inputs, commit_hash, deploy_action_enabled) - .then { |wr| update_ci_metadata!(wr) } - end - - def retry_workflow_run - return ci_cd_provider.retry_workflow_run!(ci_ref) if ci_cd_provider.workflow_retriable? - trigger_workflow_run(retrigger: true) - end - - def update_build_number! - update!(build_number: app.bump_build_number!) - end - - def workflow_inputs - data = {version_code: build_number, build_version: build_version} - data[:build_notes] = build_notes if organization.build_notes_in_workflow? - data - end - - def add_build_artifact(url) - return if build_artifact.present? - - # FIXME: this should be passed along from the CI workflow metadata - generated_at = Time.current - - get_build_artifact(url).with_open do |artifact_stream| - build_build_artifact(generated_at: generated_at).save_file!(artifact_stream) - artifact_stream.file.rewind - self.slack_file_id = train.upload_file_for_notifications!(artifact_stream.file, build_artifact.get_filename) - end - end - - def stamp_data - { - name: step.name, - sha: commit.short_sha, - workflow_name:, - version: build_version - } - end - - def notify_on_failure!(message) - notify!(message, :step_failed, notification_params.merge(step_fail_reason: message)) - end - - def after_trigger_ci - Releases::FindWorkflowRun.perform_async(id) - event_stamp!(reason: :ci_triggered, kind: :notice, data: stamp_data) - notify!("Step has been triggered!", :step_started, notification_params) - Releases::CancelStepRun.perform_later(previous_step_run.id) if previous_step_run&.may_cancel? - end - - def after_retrigger_ci - WorkflowProcessors::WorkflowRunJob.perform_later(id) - event_stamp!(reason: :ci_retriggered, kind: :notice, data: stamp_data) - end - - def after_artifact_uploaded - notify!("A new build is available!", :build_available, notification_params, slack_file_id, build_display_name) if slack_file_id - trigger_deployment - end - - def after_finish_ci - return Releases::FindBuildJob.perform_async(id) if has_findables? - return Releases::UploadArtifact.perform_async(id, artifacts_url) if has_uploadables? - trigger_deployment - end - - def finalize_release - event_stamp!(reason: :finished, kind: :success, data: stamp_data) - Coordinators::FinishPlatformRun.call(release_platform_run) if release_platform_run.finalizable? - end -end diff --git a/app/models/train.rb b/app/models/train.rb index 232265e7a..7afe96c22 100644 --- a/app/models/train.rb +++ b/app/models/train.rb @@ -13,7 +13,6 @@ # description :string # freeze_version :boolean default(FALSE) # kickoff_at :datetime -# manual_release :boolean default(FALSE) # name :string not null # notification_channel :jsonb # patch_version_bump_only :boolean default(FALSE), not null @@ -42,10 +41,11 @@ class Train < ApplicationRecord using RefinedString extend FriendlyId include Rails.application.routes.url_helpers - include Notifiable include Versionable include Loggable + self.ignored_columns += ["manual_release"] + BRANCHING_STRATEGIES = { almost_trunk: "Almost Trunk", release_backmerge: "Release with Backmerge", @@ -55,13 +55,9 @@ class Train < ApplicationRecord belongs_to :app has_many :releases, -> { sequential }, inverse_of: :train, dependent: :destroy has_many :active_runs, -> { pending_release.includes(:all_commits) }, class_name: "Release", inverse_of: :train, dependent: :destroy - has_many :deployment_runs, through: :releases - has_many :external_releases, through: :deployment_runs has_many :release_platforms, -> { sequential }, dependent: :destroy, inverse_of: :train has_many :release_platform_runs, -> { sequential }, through: :releases has_many :integrations, through: :app - has_many :steps, through: :release_platforms - has_many :deployments, through: :steps has_many :scheduled_releases, dependent: :destroy has_many :notification_settings, inverse_of: :train, dependent: :destroy has_one :release_index, dependent: :destroy @@ -95,7 +91,6 @@ class Train < ApplicationRecord validate :build_queue_config validate :backmerge_config validate :tag_release_config - validate :valid_train_configuration, on: :activate_context validate :working_branch_presence, on: :create validate :ci_cd_workflows_presence, on: :create validates :name, format: {with: /\A[a-zA-Z0-9\s_\/-]+\z/, message: :invalid} @@ -146,6 +141,7 @@ def one_percent_beta_release? Flipper.enabled?(:one_percent_beta_release, self) end + # TODO: remove this after full removal of v2, it is used only for one-off rake tasks def product_v2? Flipper.enabled?(:product_v2, self) end @@ -323,13 +319,6 @@ def cancel_scheduled_releases! scheduled_releases.pending&.delete_all end - # TODO [V2]: Remove this method - def startable? - return false unless app.ready? - return true if product_v2? - release_platforms.all?(&:startable?) - end - def activatable? automatic? && startable? && !active? end @@ -338,23 +327,11 @@ def deactivatable? automatic? && active? && active_runs.none? end - def manually_startable? - startable? && !inactive? - end - def upcoming_release_startable? - if product_v2? - manually_startable? && - ongoing_release.present? && - ongoing_release.production_release_started? && - upcoming_release.blank? - else - manually_startable? && - release_platforms.any?(&:has_production_deployment?) && - ongoing_release.present? && - (release_platforms.all?(&:has_review_steps?) || ongoing_release.production_release_happened?) && - upcoming_release.blank? - end + !inactive? && + ongoing_release.present? && + ongoing_release.production_release_started? && + upcoming_release.blank? end def continuous_backmerge? @@ -365,10 +342,6 @@ def branching_strategy_name BRANCHING_STRATEGIES[branching_strategy.to_sym] end - def build_channel_integrations - integrations.build_channel - end - def active_release_for?(branch_name) active_runs.exists?(branch_name: branch_name) end @@ -430,8 +403,7 @@ def notification_params train_current_version: version_current, train_next_version: next_version, train_url: train_link, - working_branch:, - is_v2: product_v2? + working_branch: } ) end @@ -445,36 +417,25 @@ def schedule_editable? end def hotfixable? - return false unless startable? return false unless has_production_deployment? return false if hotfix_release.present? return false if hotfix_from.blank? return true if ongoing_release.blank? - if product_v2? - !ongoing_release.production_release_active? - else - !ongoing_release.production_release_happened? - end + !ongoing_release.production_release_active? end def devops_report - return Queries::DevopsReport.new(self) if product_v2? - Charts::DevopsReport.new(self) + Queries::DevopsReport.new(self) end def has_production_deployment? - if product_v2? - release_platforms.any? { |rp| rp.platform_config.production_release? } - else - release_platforms.any?(&:has_production_deployment?) - end + release_platforms.any? { |rp| rp.platform_config.production_release? } end def has_restricted_public_channels? return false if app.ios? - - deployments.any? { |d| GooglePlayStoreIntegration::PUBLIC_CHANNELS.include?(d.deployment_channel) } + release_platforms.any(&:has_restricted_public_channels?) end def stop_failed_ongoing_release! @@ -563,12 +524,6 @@ def ensure_deletable errors.add(:trains, "cannot delete a train if there are releases made from it!") if releases.present? end - def valid_train_configuration - unless release_platforms.all?(&:valid_steps?) - errors.add(:train, "there should be one release step for all platforms") - end - end - def valid_schedule if kickoff_at.present? || repeat_duration.present? errors.add(:repeat_duration, "invalid schedule, provide both kickoff and period for repeat") unless kickoff_at.present? && repeat_duration.present? diff --git a/app/presenters/devops_report_presenter.rb b/app/presenters/devops_report_presenter.rb index cad703e50..0b39640e3 100644 --- a/app/presenters/devops_report_presenter.rb +++ b/app/presenters/devops_report_presenter.rb @@ -70,32 +70,26 @@ class DevopsReportPresenter < SimpleDelegator } def duration - return v1_formatter(:mobile_devops, :duration) if v1? formatter(:duration) end def frequency - return v1_formatter(:mobile_devops, :frequency) if v1? formatter(:frequency) end def time_in_review - return v1_formatter(:mobile_devops, :time_in_review) if v1? formatter(:time_in_review) end def patch_fixes - return v1_formatter(:mobile_devops, :hotfixes) if v1? formatter(:patch_fixes) end def hotfixes - return nil if v1? formatter(:hotfixes) end def time_in_phases - return v1_formatter(:mobile_devops, :time_in_phases) if v1? chart_data = formatter(:time_in_phases) # The data is in the following format: # { @@ -113,7 +107,6 @@ def time_in_phases end def reldex_scores - return v1_formatter(:mobile_devops, :reldex_scores) if v1? formatter(:reldex_scores, { y_annotations: [ {y: 0..train.release_index.tolerable_range.min, text: "Mediocre", color: "mediocre"}, @@ -123,24 +116,20 @@ def reldex_scores end def stability_contributors - return v1_formatter(:operational_efficiency, :stability_contributors) if v1? formatter(:stability_contributors) end def contributors - return v1_formatter(:operational_efficiency, :contributors) if v1? formatter(:contributors) end def team_stability_contributors - return v1_formatter(:operational_efficiency, :team_stability_contributors) if v1? formatter(:team_stability_contributors, { colors: team_colors }) end def team_contributors - return v1_formatter(:operational_efficiency, :team_contributors) if v1? formatter(:team_contributors, { colors: team_colors }) @@ -150,18 +139,10 @@ def team_colors @team_colors ||= organization.team_colors end - def v1_formatter(parent, key) - all[parent][key] - end - def formatter(key, params = {}) return if all.blank? FORMATTING_DATA[key].merge(data: all[key]).merge(params) end - def v1? - !train.product_v2? - end - delegate :present?, to: :all end diff --git a/app/presenters/release_presenter.rb b/app/presenters/release_presenter.rb index ebfbbb009..33d2b374f 100644 --- a/app/presenters/release_presenter.rb +++ b/app/presenters/release_presenter.rb @@ -45,8 +45,7 @@ def release_status end memoize def breakdown - return Queries::ReleaseBreakdown.new(id) if is_v2? - Queries::ReleaseSummary.all(id) + Queries::ReleaseBreakdown.new(id) end memoize def platform_runs @@ -57,12 +56,7 @@ def release_status release_version end - def reldex - return breakdown.reldex if is_v2? - breakdown&.fetch(:reldex, nil) - end - - delegate :team_release_commits, :team_stability_commits, to: :breakdown + delegate :team_release_commits, :team_stability_commits, :reldex, to: :breakdown def hotfix_badge if hotfix? diff --git a/app/views/app_configs/notification.html+turbo_frame.erb b/app/views/app_configs/notification.html+turbo_frame.erb deleted file mode 100644 index e145187d2..000000000 --- a/app/views/app_configs/notification.html+turbo_frame.erb +++ /dev/null @@ -1,21 +0,0 @@ -<%= render V2::EnhancedTurboFrameComponent.new("#{@integration_category}_config") do %> - <% if @app.notifications_set_up? %> - <%= render V2::FormComponent.new(model: [@app, @config], url: app_app_config_path(@app), method: :patch) do |f| %> - <% f.with_section(heading: "Select Channel") do |section| %> - <% section.with_description do %> - This will be your base notification channel. Later, when you create trains, you can add more granularity. - <% end %> - - <%= render partial: "shared/notifications_form", - locals: { form: f.F, - app: @app, - channels: @notification_channels, - current: @config.notification_channel } %> - <% end %> - - <% f.with_action do %> - <%= f.F.authz_submit "Update", "plus.svg", size: :xs %> - <% end %> - <% end %> - <% end %> -<% end %> diff --git a/app/views/apps/_setup_progress.html.erb b/app/views/apps/_setup_progress.html.erb index fb2d3c2b2..3d0af36a7 100644 --- a/app/views/apps/_setup_progress.html.erb +++ b/app/views/apps/_setup_progress.html.erb @@ -9,7 +9,7 @@
<% if is_app_completed %> - <%= inline_svg("progress_check.svg", classname: "w-8 fill-current text-white") %> + <%= render V2::IconComponent.new("progress_check.svg", size: :xl_3, classes: "fill-current text-white") %> <% end %>
@@ -24,7 +24,7 @@
<% if is_version_control_completed %> - <%= inline_svg("progress_check.svg", classname: "w-8 fill-current text-white") %> + <%= render V2::IconComponent.new("progress_check.svg", size: :xl_3, classes: "fill-current text-white") %> <% end %>
@@ -46,7 +46,7 @@
<% if is_ci_cd_completed %> - <%= inline_svg("progress_check.svg", classname: "w-8 fill-current text-white") %> + <%= render V2::IconComponent.new("progress_check.svg", size: :xl_3, classes: "fill-current text-white") %> <% end %>
@@ -69,7 +69,7 @@
<% if is_build_channel_completed %> - <%= inline_svg("progress_check.svg", classname: "w-8 fill-current text-white") %> + <%= render V2::IconComponent.new("progress_check.svg", size: :xl_3, classes: "fill-current text-white") %> <% end %>
@@ -116,7 +116,7 @@
<% if completed %> - <%= inline_svg("progress_check.svg", classname: "w-8 fill-current text-white") %> + <%= render V2::IconComponent.new("progress_check.svg", size: :xl_3, classes: "fill-current text-white") %> <% end %>
diff --git a/app/views/integrations/build_artifact_channels.turbo_stream.erb b/app/views/integrations/build_artifact_channels.turbo_stream.erb deleted file mode 100644 index e0b587a06..000000000 --- a/app/views/integrations/build_artifact_channels.turbo_stream.erb +++ /dev/null @@ -1,3 +0,0 @@ -<%= turbo_stream.update @target do %> - <%= options_for_select(display_channels(@build_channels) { |chan| deployment_channel_name(chan) }) %> -<% end %> diff --git a/app/views/notifiers/slack/build_available.json.erb b/app/views/notifiers/slack/build_available.json.erb deleted file mode 100644 index b1aec4420..000000000 --- a/app/views/notifiers/slack/build_available.json.erb +++ /dev/null @@ -1,20 +0,0 @@ -{ - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "New build *<%= @release_version %> (<%= @build_number %>)* from <%= @step_type %> step *<%= @step_name %>* is now available! :female-construction-worker:" - } - }, - { - "type": "context", - "elements": [ - { - "type": "mrkdwn", - "text": "Additionally, a direct download of the build artifact will become available in this message thread." - } - ] - } - ] -} diff --git a/app/views/notifiers/slack/deployment_failed.json.erb b/app/views/notifiers/slack/deployment_failed.json.erb deleted file mode 100644 index e43533868..000000000 --- a/app/views/notifiers/slack/deployment_failed.json.erb +++ /dev/null @@ -1,11 +0,0 @@ -{ - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "Deployment of *<%= @deployment_channel_type %>* build *<%= @release_version %> (<%= @build_number %>)* to *<%= deployment_channel_display_name %>* failed! :rotating_light:" - } - } - ] -} diff --git a/app/views/notifiers/slack/deployment_finished.json.erb b/app/views/notifiers/slack/deployment_finished.json.erb deleted file mode 100644 index 8417e6fe5..000000000 --- a/app/views/notifiers/slack/deployment_finished.json.erb +++ /dev/null @@ -1,40 +0,0 @@ -{ - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "New *<%= @deployment_channel_type %>* build *<%= @release_version %> (<%= @build_number %>)* is now available to *<%= deployment_channel_display_name %>* :sparkles:" - } - }, - <% if @is_production_channel %> - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "<%= sanitized_release_notes %>" - } - }, - <% end %> - { - "type": "context", - "elements": [ - { - "type": "mrkdwn", - "text": "- Changes since the last release to the channel are sent in the message thread." - } - <% if @requires_review %> - , - { - "type": "mrkdwn", - "text": "<%= google_managed_publishing_text %>" - }, - { - "type": "mrkdwn", - "text": "<%= google_unmanaged_publishing_text %>" - } - <% end %> - ] - } - ] -} diff --git a/app/views/notifiers/slack/footer.json.erb b/app/views/notifiers/slack/footer.json.erb index a6c1ee53c..826a6b131 100644 --- a/app/views/notifiers/slack/footer.json.erb +++ b/app/views/notifiers/slack/footer.json.erb @@ -7,10 +7,10 @@ { "type": "context", "elements": [ - <% if @deployment_channel_asset_link && @project_link %> + <% if @submission_asset_link && @project_link %> { "type": "image", - "image_url": "<%= @deployment_channel_asset_link %>", + "image_url": "<%= @submission_asset_link %>", "alt_text": "console" }, { @@ -18,10 +18,10 @@ "text": "<<%= @project_link %>|Console>" }, <% end %> - <% if @deployment_channel_asset_link && @deep_link %> + <% if @submission_asset_link && @deep_link %> { "type": "image", - "image_url": "<%= @deployment_channel_asset_link %>", + "image_url": "<%= @submission_asset_link %>", "alt_text": "console" }, { @@ -40,6 +40,7 @@ "text": "<<%= @artifact_download_link %>|Download from Tramline>" }, <% end %> + { "type": "image", "image_url": "https://storage.googleapis.com/tramline-public-assets/tramline-small.png", @@ -69,30 +70,6 @@ "text": "Train: <%= @train_name %>", "emoji": true } - <% if @step_name.present? %> - ,{ - "type": "plain_text", - "text": "ā€¢", - "emoji": false - }, - { - "type": "plain_text", - "text": "Step: <%= @step_name %>", - "emoji": true - } - <% end %> - <% if @deployment_channel.present? %> - ,{ - "type": "plain_text", - "text": "ā€¢", - "emoji": false - }, - { - "type": "plain_text", - "text": "Deployment: <%= @deployment_channel_type %> + <%= deployment_channel_display_name %>", - "emoji": true - } - <% end %> ] }, { diff --git a/app/views/notifiers/slack/footer_v2.json.erb b/app/views/notifiers/slack/footer_v2.json.erb deleted file mode 100644 index 826a6b131..000000000 --- a/app/views/notifiers/slack/footer_v2.json.erb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "blocks": [ - { - "type": "divider" - }, - <% if @release_url.present? %> - { - "type": "context", - "elements": [ - <% if @submission_asset_link && @project_link %> - { - "type": "image", - "image_url": "<%= @submission_asset_link %>", - "alt_text": "console" - }, - { - "type": "mrkdwn", - "text": "<<%= @project_link %>|Console>" - }, - <% end %> - <% if @submission_asset_link && @deep_link %> - { - "type": "image", - "image_url": "<%= @submission_asset_link %>", - "alt_text": "console" - }, - { - "type": "mrkdwn", - "text": "<<%= @deep_link %>|Install>" - }, - <% end %> - <% if @artifact_download_link %> - { - "type": "image", - "image_url": "https://storage.googleapis.com/tramline-public-assets/download_link_ico.png", - "alt_text": "build" - }, - { - "type": "mrkdwn", - "text": "<<%= @artifact_download_link %>|Download from Tramline>" - }, - <% end %> - - { - "type": "image", - "image_url": "https://storage.googleapis.com/tramline-public-assets/tramline-small.png", - "alt_text": "tramline" - }, - { - "type": "mrkdwn", - "text": "<<%= @release_url %>|Release>" - }, - { - "type": "image", - "image_url": "<%= @vcs_public_icon_img %>", - "alt_text": "vcs" - }, - { - "type": "mrkdwn", - "text": "<<%= @release_branch_url %>|<%= @release_branch %>>" - } - ] - }, - <% end %> - { - "type": "context", - "elements": [ - { - "type": "plain_text", - "text": "Train: <%= @train_name %>", - "emoji": true - } - ] - }, - { - "type": "divider" - } - ] -} diff --git a/app/views/notifiers/slack/release_ended.json.erb b/app/views/notifiers/slack/release_ended.json.erb index b31367c6d..a4de96ce9 100644 --- a/app/views/notifiers/slack/release_ended.json.erb +++ b/app/views/notifiers/slack/release_ended.json.erb @@ -11,17 +11,8 @@ "type": "section", "text": { "type": "mrkdwn", - "text": "The *total run time* was *<%= @total_run_time %>*" + "text": "The *total run time* was *<%= total_run_time %>*" } } - <% if @final_artifact_url %> - ,{ - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Download* the <<%= @final_artifact_url %>|final artifact>" - } - } - <% end %> ] } diff --git a/app/views/notifiers/slack/review_approved.json.erb b/app/views/notifiers/slack/review_approved.json.erb deleted file mode 100644 index 807e18468..000000000 --- a/app/views/notifiers/slack/review_approved.json.erb +++ /dev/null @@ -1,23 +0,0 @@ -{ - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*<%= @app_name %> (<%= @app_platform %>)* <%= @release_version %> (<%= @build_number %>) has been approved by the store to distribute to <%= @deployment_channel_type %> (<%= deployment_channel_display_name %>)! :tada:" - } - } - <% if @is_app_store_production %> - ,{ - "type": "context", - "elements": [ - { - "type": "plain_text", - "text": "<%= apple_publishing_text %>", - "emoji": false - } - ] - } - <% end %> - ] -} diff --git a/app/views/notifiers/slack/review_failed.json.erb b/app/views/notifiers/slack/review_failed.json.erb deleted file mode 100644 index 814eb2c34..000000000 --- a/app/views/notifiers/slack/review_failed.json.erb +++ /dev/null @@ -1,23 +0,0 @@ -{ - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*<%= @app_name %> (<%= @app_platform %>)* <%= @release_version %> (<%= @build_number %>) has been rejected by the store! :octagonal_sign: " - } - } - <% if @is_app_store_production %> - ,{ - "type": "context", - "elements": [ - { - "type": "plain_text", - "text": "<%= apple_review_failed_text %>", - "emoji": false - } - ] - } - <% end %> - ] -} diff --git a/app/views/notifiers/slack/staged_rollout_updated.json.erb b/app/views/notifiers/slack/staged_rollout_updated.json.erb deleted file mode 100644 index 31336dd9e..000000000 --- a/app/views/notifiers/slack/staged_rollout_updated.json.erb +++ /dev/null @@ -1,42 +0,0 @@ -{ - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "<%= main_text %>" - } - } - <% if secondary_text.present? %> - ,{ - "type": "section", - "text": { - "type": "mrkdwn", - "text": "<%= secondary_text %>" - } - } - <% end %> - <% if @is_play_store_production %> - ,{ - "type": "context", - "elements": [ - { - "type": "plain_text", - "text": "<%= google_managed_publishing_text %>", - "emoji": false - } - ] - }, - { - "type": "context", - "elements": [ - { - "type": "plain_text", - "text": "<%= google_unmanaged_publishing_text %>", - "emoji": false - } - ] - } - <% end %> - ] -} diff --git a/app/views/notifiers/slack/step_failed.json.erb b/app/views/notifiers/slack/step_failed.json.erb deleted file mode 100644 index 95261a5df..000000000 --- a/app/views/notifiers/slack/step_failed.json.erb +++ /dev/null @@ -1,23 +0,0 @@ -{ - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "The *<%= @step_type %>* step *<%= @step_name %>* failed for <<%= @commit_url %>|<%= @commit_sha %>> because ā€“ <%= @step_fail_reason %> :rotating_light:" - } - } - <% if @manual_submission_required %> - ,{ - "type": "context", - "elements": [ - { - "type": "plain_text", - "text": "<%= manual_submission_required_text %>", - "emoji": false - } - ] - } - <% end %> - ] -} diff --git a/app/views/notifiers/slack/step_started.json.erb b/app/views/notifiers/slack/step_started.json.erb deleted file mode 100644 index 699d551a0..000000000 --- a/app/views/notifiers/slack/step_started.json.erb +++ /dev/null @@ -1,18 +0,0 @@ -{ - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "The *<%= @step_type %>* step *<%= @step_name %>* has started! :sparkles:" - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "The next *build <%= @build_number %>* will be built against the commit *<<%= @commit_url %>|<%= @commit_sha %>>*" - } - } - ] -} diff --git a/app/views/notifiers/slack/submit_for_review.json.erb b/app/views/notifiers/slack/submit_for_review.json.erb deleted file mode 100644 index cfbe867c7..000000000 --- a/app/views/notifiers/slack/submit_for_review.json.erb +++ /dev/null @@ -1,30 +0,0 @@ -{ - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "<%= @app_name %> (<%= @app_platform %>) <%= @release_version %> (<%= @build_number %>) has been *<%= submitted_text %>* for review to <%= @deployment_channel_type %> (<%= deployment_channel_display_name %>)! :tada:" - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "<%= sanitized_release_notes %>" - } - } - <% if @is_app_store_production %> - ,{ - "type": "context", - "elements": [ - { - "type": "plain_text", - "text": "<%= apple_publishing_text %>", - "emoji": false - } - ] - } - <% end %> - ] -} diff --git a/app/views/releases/show.html.erb b/app/views/releases/show.html.erb deleted file mode 100644 index 7d68867a6..000000000 --- a/app/views/releases/show.html.erb +++ /dev/null @@ -1,140 +0,0 @@ -<% release_component = V2::BaseReleaseComponent.new(@release) %> - -<% if @release.finished? || @release.stopped? || @release.stopped_after_partial_finish? %> - <% content_for :sticky_top_message do %> - <%= render V2::AlertComponent.new(kind: :banner, type: :notice, title: "Release Locked", dismissible: false) do %> - This release was <%= @release.display_attr(:status) %> and is now locked. You cannot make any more changes to it. - <% end %> - <% end %> -<% end %> - -<%= render V2::ContainerComponent.new(title: @release.release_version) do |container| %> - <% container.with_back_button(to: "the train", path: app_train_releases_path(@app, @train)) %> - <% container.with_side_action do %> - <%= render(release_component.hotfix_badge) unless release_component.hotfix_badge.nil? %> - <% end %> - - <% container.with_sub_action do %> - <%= render V2::ButtonComponent.new( - scheme: :supporting, - type: :link, - size: :xxs, - options: timeline_release_path(@release), - authz: false, - label: "Release Activity", - arrow: :none) do |b| - b.with_icon("v2/activity.svg") - end %> - <% end %> - - <% if @release.partially_finished? %> - <% container.with_action do %> - <%= render V2::ButtonComponent.new( - scheme: :light, - type: :link, - size: :xxs, - options: finish_release_release_path(@release), - label: "Mark release as finished", - html_options: { method: :delete, - data: { turbo_method: :post, - turbo_confirm: "You have finished release to only one of the platforms. Are you sure you want to finish the release?" } }) do |b| - b.with_icon("v2/list_checks.svg") - end %> - <% end %> - <% end %> - - <% if @release.active? %> - <% container.with_action do %> - <%= render V2::ButtonComponent.new( - scheme: :danger, - type: :button, - size: :xxs, - label: "Stop release", - options: release_path(@release), - turbo: false, - html_options: { method: :delete, data: { turbo_method: :delete, turbo_confirm: stop_release_warning(@release) } }) do |b| - b.with_icon("v2/stop.svg") - end %> - <% end %> - <% end %> - - <% container.with_body do %> - <% if @release.finished? %> -
- <%= render FinalSummaryComponent.new(release: @release) %> -
- - <%= render partial: "shared/live_release/separator" %> - <% end %> - -
-
- <%= render partial: "shared/live_release/kick_off", locals: { release: @release, release_train: @train } %> - - <% if @pre_release_prs.present? %> - <%= render partial: "shared/live_release/separator", locals: { margin_only: true } %> - <%= render partial: "shared/live_release/pre_release_prs", locals: { pre_release_prs: @pre_release_prs } %> - <% end %> - - <%= render partial: "shared/live_release/separator", locals: { margin_only: true } %> -
- -
<%= render partial: "shared/live_release/commit_log", locals: { release: @release } %>
-
- -
- <% @release.release_platform_runs.each do |release_platform_run| %> - <%= render partial: "shared/live_release/release_metadata", - locals: { release_platform_run: release_platform_run } %> - <% end %> -
- - <%= render partial: "shared/live_release/separator" %> - -
- <%= render partial: "shared/live_release/section_title", locals: { heading: "Build Stability", subheading: "#{time_ago_in_words(@release.updated_at)} ago" } %> -
- -
- <% @release.release_platform_runs.each do |run| %> - <%= render partial: "shared/live_release/stability", locals: { platform: "#{run.display_attr(:platform)} release", release: run, steps: run.release_platform.active_steps_for(@release) } %> - <% end %> -
- - <% if @release.active_build_queue.present? || @release.continuous_backmerge? %> - <%= render partial: "shared/live_release/separator" %> - <% end %> - -
- <% if @release.active_build_queue.present? %> - <%= render partial: "shared/live_release/build_queue", locals: { release: @release, build_queue: @release.active_build_queue } %> - <% end %> - - <% if @release.continuous_backmerge? %> - <%= render partial: "shared/live_release/backmerge", locals: { release: @release } %> - <% end %> -
- - <% if @release.active? && (current_organization.teams.any? || @mid_release_prs.open.any?) %> - <%= render partial: "shared/live_release/separator" %> - -
- <%= render partial: "shared/live_release/teams", locals: { release: @release, commits: @release.stability_commits, pull_requests: @mid_release_prs } %> -
- <% end %> - - <%= render partial: "shared/live_release/separator" %> - -
- <%= render partial: "shared/live_release/builds", locals: { release: @release, commits: @commits, pull_requests: @mid_release_prs } %> -
- - <% unless @release.finished? %> - <%= render partial: "shared/live_release/separator" %> - -
- <%= render partial: "shared/live_release/finalize", locals: { release: @release, open_ongoing_prs: @ongoing_open_release_prs, post_release_prs: @post_release_prs } %> -
- <% end %> - <% end %> -<% end %> diff --git a/app/views/shared/_build_details.html.erb b/app/views/shared/_build_details.html.erb deleted file mode 100644 index 36fc20d21..000000000 --- a/app/views/shared/_build_details.html.erb +++ /dev/null @@ -1,25 +0,0 @@ -
-
- <%= step_run.build_version %> - ā€¢ Build <%= step_run.build_number %> - ā€¢ Commit <%= step_run.commit.short_sha %> -
-
- <% if step_run&.external_build %> - <%= render V2::ModalComponent.new(title: "#{step_run.build_version} (#{step_run.build_number})", authz: false) do |modal| %> - <% button = modal.with_button(scheme: :naked_icon, type: :action) %> - <% button.with_icon("v2/info.svg") %> - <% modal.with_body do %> - <%= render BuildMetadataComponent.new(step_run:) %> - <% end %> - <% end %> - <% end %> -
-
- -<% if step_run.build_artifact.present? && with_artifact %> -
- <%= "Built " + ago_in_words(step_run.build_artifact.generated_at) %> - ā€¢ <%= "Uploaded to Tramline " + ago_in_words(step_run.build_artifact.uploaded_at) %> -
-<% end %> diff --git a/app/views/shared/_deployment.html.erb b/app/views/shared/_deployment.html.erb deleted file mode 100644 index ad076bab7..000000000 --- a/app/views/shared/_deployment.html.erb +++ /dev/null @@ -1,22 +0,0 @@ -<%= image_tag("corner_down_right.svg", width: 13, class: "inline-flex mr-1 mb-2") %> - -<%= render partial: "shared/display_channels", - locals: { name: show_deployment(deployment), - provider_name: deployment.integration_type.to_s } %> - -<% if deployment.build_notes? %> -
- <%= inline_svg("clipboard_copy.svg", classname: "w-4 inline-flex") %> - + - <%= inline_svg("bolt.svg", classname: "w-4 inline-flex") %> -
-<% end %> -<% if deployment.release_notes? %> -
- <%= inline_svg("clipboard_copy.svg", classname: "w-4 inline-flex") %> -
-<% end %> - -<% if deployment.staged_rollout? %> - <%= status_badge("Staged rollout enabled", :neutral) %> -<% end %> diff --git a/app/views/shared/_deployments.html.erb b/app/views/shared/_deployments.html.erb deleted file mode 100644 index 9e3c1f02f..000000000 --- a/app/views/shared/_deployments.html.erb +++ /dev/null @@ -1,20 +0,0 @@ -
    - <% step.active_deployments_for(platform_run&.release, step_run).each do |deployment| %> -
  1. -
    - <%= render partial: "shared/deployment", locals: { deployment: deployment } %> - - <% if show_deployment_status %> - <%= render partial: "shared/live_release/deployment_status", - locals: { step_run: step_run, deployment: deployment, platform_run: platform_run } %> - <% else %> - <% if deployment.staged_rollout? %> -
    - <%= render StagedRollout::ConfigComponent.new(config: deployment.staged_rollout_config) %> -
    - <% end %> - <% end %> -
    -
  2. - <% end %> -
diff --git a/app/views/shared/_display_channels.html.erb b/app/views/shared/_display_channels.html.erb deleted file mode 100644 index b50cd13b0..000000000 --- a/app/views/shared/_display_channels.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -
- <%= image_tag("integrations/logo_#{provider_name}.png", width: 18) %> - <%= name %> -
- diff --git a/app/views/shared/_note_box.html.erb b/app/views/shared/_note_box.html.erb deleted file mode 100644 index f70375ac4..000000000 --- a/app/views/shared/_note_box.html.erb +++ /dev/null @@ -1,10 +0,0 @@ -<% type ||= :info %> - -
-
-
- <%= inline_svg("info_icon.svg", classname: "w-4 mt-[3px] mr-3 shrink-0 fill-current #{note_box_color(type)}") %> -
<%= message %>
-
-
-
diff --git a/app/views/shared/_per_step_metadata.html.erb b/app/views/shared/_per_step_metadata.html.erb deleted file mode 100644 index e71064c65..000000000 --- a/app/views/shared/_per_step_metadata.html.erb +++ /dev/null @@ -1,59 +0,0 @@ -
-
-
-
-

<%= step.name %>

- <%= status_badge "##{step.step_number}", %w[text-xs ml-2], :neutral %> - <%= status_badge step.kind&.humanize, %w[text-xs ml-2], :neutral %> - <%= status_badge auto_deploy_status_badge(step), %w[text-xs ml-2], :neutral %> -
- - <% if editable %> - <%= render partial: "trains/edit_step_button", locals: { editable: editable, step: step } %> - <% end %> - - <% if platform_run.present? %> - <%= render partial: "shared/live_release/step_run_status", locals: { step_run: step_run, step: step } %> - <% end %> -
- -
- <% if step.app_variant.present? %> -
- App Variant ā€“ <%= step.app_variant.display_text %> -
- <% end %> - - <% if step.description.present? %> -
-
<%= safe_simple_format(step.description) %>
-
- <% end %> - - <% if step_run&.build_version %> -
- <%= render partial: "shared/build_details", locals: { step_run:, with_artifact: false } %> -
- <% end %> -
- -
-
- <%= inline_svg("workflow.svg", classname: "w-4 inline-flex mr-1") %> - <%= render partial: "shared/display_channels", - locals: { name: show_ci_cd(step), - provider_name: step.ci_cd_provider.to_s } %> -
- -
- <%== build_artifact_pattern_text(step) %> -
- -
-
- <%= render partial: "shared/deployments", - locals: { step: step, step_run: step_run, platform_run:, show_deployment_status: platform_run.present? } %> -
-
-
-
diff --git a/app/views/shared/_step_tree_connector.html.erb b/app/views/shared/_step_tree_connector.html.erb deleted file mode 100644 index d950f4feb..000000000 --- a/app/views/shared/_step_tree_connector.html.erb +++ /dev/null @@ -1 +0,0 @@ -
  • diff --git a/app/views/shared/_step_tree_viz.html.erb b/app/views/shared/_step_tree_viz.html.erb deleted file mode 100644 index 44e176272..000000000 --- a/app/views/shared/_step_tree_viz.html.erb +++ /dev/null @@ -1,23 +0,0 @@ -
      - - <% release_platform.steps.review.includes(deployments: [:integration]).order(:step_number).each do |step| %> -
    1. <%= render partial: "shared/per_step_metadata", locals: { editable: editable, step: step, step_run: nil, platform_run: nil } %>
    2. - <%= render partial: "shared/step_tree_connector", locals: { color: step_color(step.kind) } %> - <% end %> - - -
    3. <%= render partial: "trains/new_step_button", locals: { train:, release_platform:, kind: "review" } %>
    4. - - - <% release_step = release_platform.steps.release.first %> - <% if release_step %> - <%= render partial: "shared/step_tree_connector", locals: { color: step_color("review") } %> -
    5. <%= render partial: "shared/per_step_metadata", locals: { editable: editable, step: release_step, step_run: nil, platform_run: nil } %>
    6. - <% end %> - - - <% unless release_platform.has_release_step? %> - <%= render partial: "shared/step_tree_connector", locals: { color: "emerald" } %> -
    7. <%= render partial: "trains/new_step_button", locals: { train:, release_platform:, kind: "release" } %>
    8. - <% end %> -
    diff --git a/app/views/shared/live_release/_backmerge.html.erb b/app/views/shared/live_release/_backmerge.html.erb deleted file mode 100644 index f999473ff..000000000 --- a/app/views/shared/live_release/_backmerge.html.erb +++ /dev/null @@ -1,12 +0,0 @@ -
    -
    -

    Release Backmerge

    - <% open_prs = release.pull_requests.ongoing.open %> - <% if open_prs.present? %> - You have <%= open_prs.size %> unmerged backmerge PRs pending. - <%= render partial: "shared/live_release/pull_requests_thin", locals: { prs: open_prs, title_size: 60 } %> - <% else %> - No unmerged changes. <%= release.train.working_branch %> is up to date with all commits from <%= release.release_branch %> - <% end %> -
    -
    diff --git a/app/views/shared/live_release/_build_queue.html.erb b/app/views/shared/live_release/_build_queue.html.erb deleted file mode 100644 index f4b53a373..000000000 --- a/app/views/shared/live_release/_build_queue.html.erb +++ /dev/null @@ -1,51 +0,0 @@ -
    - <% commits = build_queue.commits.sequential %> - <% commits_count = commits.size %> -
    -
    -

    Build Queue

    - - <%= commits_count %> commit(s) in the queue. - These will be applied in <%= time_in_words(build_queue.scheduled_at) %> or after <%= build_queue.build_queue_size %> commits. - -
    - <% if commits.present? %> -
    - <% if release.committable? %> - <%= authz_button_to :blue, "Apply commits", apply_release_build_queue_path(release, build_queue), class: "btn-xs", data: { turbo_confirm: "This will trigger steps for the HEAD of the queue, are you sure?" } %> - <% else %> - <%= authz_button_to :disabled, "Apply commits", apply_release_build_queue_path(release, build_queue), class: "btn-xs" %> - <% end %> -
    - <% end %> -
    - <% if commits.present? %> - - - - - - - - <% commits.each_with_index do |commit, index| %> - - - - <% end %> - -
    -
    commits
    -
    -
    - <%= link_to_external commit.message.truncate(60), commit.url, class: "underline font-medium" %> - #<%= commits_count - index %> - -
    - <%= formatted_commit_info(commit) %> -
    -
    - - <%= render partial: "shared/live_release/commit_backmerge_status", locals: {commit: commit} %> -
    - <% end %> -
    diff --git a/app/views/shared/live_release/_builds.html.erb b/app/views/shared/live_release/_builds.html.erb deleted file mode 100644 index 730672f99..000000000 --- a/app/views/shared/live_release/_builds.html.erb +++ /dev/null @@ -1,13 +0,0 @@ -
    -
    - <%= render partial: "shared/live_release/section_title", locals: { heading: "Commits after release kick-off", subheading: nil } %> - -

    - Press - - e to show/hide commits -

    -
    - - <%= render LiveRelease::CommitsComponent.new(commits) %> -
    diff --git a/app/views/shared/live_release/_commit_backmerge_status.html.erb b/app/views/shared/live_release/_commit_backmerge_status.html.erb deleted file mode 100644 index b95d27e51..000000000 --- a/app/views/shared/live_release/_commit_backmerge_status.html.erb +++ /dev/null @@ -1,13 +0,0 @@ -<% if commit.pull_request.present? %> -
    - <%= image_tag("git_pull_request.svg", width: 20, class: "inline-flex") %> - - <%= link_to_external("##{commit.pull_request.number}", commit.pull_request.url, class: "underline") %> - - <%= pull_request_badge(commit.pull_request) %> -
    -<% end %> - -<% if commit.backmerge_failure? %> - <%= status_badge("backmerge failed", :inert) %> -<% end %> diff --git a/app/views/shared/live_release/_commit_log.erb b/app/views/shared/live_release/_commit_log.erb deleted file mode 100644 index f6f50cfad..000000000 --- a/app/views/shared/live_release/_commit_log.erb +++ /dev/null @@ -1,51 +0,0 @@ -
    -
    - <%= render partial: "shared/live_release/section_title", locals: {heading: "Commits since last release", subheading: nil} %> -
    - - <% commits = release.release_changelog&.normalized_commits %> - <% commits_count = commits&.size %> - -
    - <% if release.release_changelog.present? %> - Diff from <%= release.release_changelog.from_ref %> - <% else %> - No data found - <% end %> -
    - - <% if commits.present? %> -
    - <%= toggle_for(false, full_width: true) do %> -
    - <%= commits_count %> commits -
    - <% end %> -
    -
    - - - <% commits.each_with_index do |commit, index| %> - - - - <% end %> - -
    -
    -
    - #<%= commits_count - index %> -
    -
    -
    - <%= link_to_external commit.truncated_message, commit.url, class: "underline font-medium text-black" %> -
    -
    - <%= formatted_commit_info(commit) %> -
    -
    -
    -
    -
    - <% end %> -
    diff --git a/app/views/shared/live_release/_deployment_status.html.erb b/app/views/shared/live_release/_deployment_status.html.erb deleted file mode 100644 index b0a93100b..000000000 --- a/app/views/shared/live_release/_deployment_status.html.erb +++ /dev/null @@ -1,58 +0,0 @@ -<% if step_run.present? %> - <% if step_run.deployment_start_blocked?(deployment) %> -
    - <%= authz_button_to :disabled, - "Start this deployment", - start_release_step_run_deployment_path(platform_run, step_run, deployment), - { class: 'mt-2 btn-xs' } %> -
    - You cannot start this release step until the <%= blocked_step_release_link(step_run.release) %> is - finished. -
    -
    - <% else %> - <% if step_run.manually_startable_deployment?(deployment) %> - <%= authz_button_to :blue, - "Start this deployment", - start_release_step_run_deployment_path(platform_run, step_run, deployment), - { class: 'mt-2 btn-xs' } %> - <% end %> - <% end %> - - - <% deployment_run = step_run.last_run_for(deployment) %> - - <% if deployment_run.present? %> - <%= deployment_run_status_badge(deployment_run) %> - - <% if deployment_run.reviewable? %> - <%= authz_button_to :blue, - "Submit for review", - submit_for_review_deployment_run_path(deployment_run), - { method: :patch, class: 'mt-2 btn-xs' } %> - <% end %> - - <% if deployment_run.releasable? %> - <%= authz_button_to :blue, - "Start release", - start_release_deployment_run_path(deployment_run), - { method: :patch, class: 'mt-2 btn-xs' } %> - <% end %> - - <% if deployment_run.failed_prepare_release? %> - <%= authz_button_to :blue, - "Replace inflight release", - prepare_release_deployment_run_path(deployment_run), - { method: :patch, - class: 'mt-2 btn-xs', - params: { deployment_run: { force: true } }, - data: { turbo_confirm: "This will over-write the current inflight release submission, are you sure?" } } %> - <% end %> - - <% if deployment_run.staged_rollout.present? %> -
    - <%= render StagedRolloutComponent.new(deployment_run.staged_rollout) %> -
    - <% end %> - <% end %> -<% end %> diff --git a/app/views/shared/live_release/_external_release.html.erb b/app/views/shared/live_release/_external_release.html.erb deleted file mode 100644 index 9d2bad5b1..000000000 --- a/app/views/shared/live_release/_external_release.html.erb +++ /dev/null @@ -1,40 +0,0 @@ -<% if deployment_run.external_release.present? %> - <% - deployment = deployment_run.deployment - external_release = deployment_run.external_release - %> -
    -
    -
    -

    - <%= "Added to #{deployment.display_attr(:integration_type)} " + ago_in_words(external_release.added_at) %> -

    -
    - -
    - Version - <%= external_release.name %> - ā€¢ Build - <%= external_release.build_number %> -
    - -
    -
    - <%= external_release_status_timestamp(external_release) %> -
    - -
    - - <%= external_release.status.titleize.upcase %> - - -
    - <%= link_to_external "Store dashboard ā†—", - external_release.external_link, - class: "underline text-sm font-medium text-indigo-500 hover:text-indigo-600" %> -
    -
    -
    -
    -
    -<% end %> diff --git a/app/views/shared/live_release/_finalize.html.erb b/app/views/shared/live_release/_finalize.html.erb deleted file mode 100644 index 2b598b31f..000000000 --- a/app/views/shared/live_release/_finalize.html.erb +++ /dev/null @@ -1,48 +0,0 @@ -
    - Finalize - pending - - <% if release.post_release_started? %> -
    -
    - <%= image_tag("cube.svg", class: "glowing cube", width: 20) %> -
    -

    Finishing up, give us a few!

    - <%= decorated_button_tag :blue, "Refresh", onclick: "window.location.reload();" %> -
    - <% end %> - - <% if release.post_release_failed? %> - <% if open_ongoing_prs.present? %> -
    <%= render "shared/note_box", type: :error, message: "We can't finalize this release, please merge/close the release change PRs and try finalizing again." %>
    -
    -
    Pull Requests
    -
    - <%= render partial: "shared/live_release/pull_requests_thin", locals: { prs: open_ongoing_prs, title_size: 80 } %> -
    <%= authz_button_to :blue, "Retry finalize", post_release_release_path(release), params: { release: { force_finalize: false } }, class: "btn-sm" %>
    - <% elsif post_release_prs.open.present? %> -
    <%= render "shared/note_box", type: :error, message: "We couldn't fully finalize this release, please fix any merge conflicts and/or protection rules applied for your branches and try finalizing again." %>
    -
    -
    Pull Requests
    -
    - <%= render partial: "shared/live_release/pull_requests", locals: { prs: post_release_prs.open } %> -
    <%= authz_button_to :blue, "Retry finalize", post_release_release_path(release), params: { release: { force_finalize: false } }, class: "btn-sm" %>
    - <% else %> - <% if release.unmerged_commits.exists? %> -
    -
    Unmerged Commits
    -
    - - You have <%= release.unmerged_commits.size %> commit(s) that were not automatically merged by Tramline.
    - You can see the current diff between the branches <%= link_to_external "here", release.compare_url, class: "underline" %> - .
    - Please ensure that those changes have been manually merged back on <%= release.train.working_branch %> before completing the finalize phase. -
    - <% end %> - -
    <%= authz_button_to :blue, "Complete finalize phase", post_release_release_path(release), params: { release: { force_finalize: true } }, class: "btn-sm" %>
    - - <%= render partial: "shared/live_release/pull_requests", locals: { prs: post_release_prs } %> - <% end %> - <% end %> -
    diff --git a/app/views/shared/live_release/_kick_off.html.erb b/app/views/shared/live_release/_kick_off.html.erb deleted file mode 100644 index 487bb1384..000000000 --- a/app/views/shared/live_release/_kick_off.html.erb +++ /dev/null @@ -1,26 +0,0 @@ -
    -
    - <%= render partial: "shared/live_release/section_title", locals: { heading: "Kick-off", subheading: "#{time_ago_in_words(release.created_at)} ago" } %> -
    - - <%= render MetaTableComponent.new do |mt| %> - <% mt.with_description("Branching Strategy") do %> - <%= release_train.branching_strategy_name %> - <% end %> - - <% mt.with_description("Release Branch") do %> - <%= link_to_external "#{release.branch_name} ā†—", release.branch_url %> - <% end %> - - <% if release.hotfix? %> - <% mt.with_description("Release Type") do %> - <%= release.display_attr(:release_type) %> - <%= hotfixed_from(release) %> - <% end %> - <% end %> - - <% mt.with_description("Backmerge Config") do %> - <%== backmerge_text(release.train) %> - <% end %> - <% end %> -
    diff --git a/app/views/shared/live_release/_pre_release_prs.html.erb b/app/views/shared/live_release/_pre_release_prs.html.erb deleted file mode 100644 index 8596994ea..000000000 --- a/app/views/shared/live_release/_pre_release_prs.html.erb +++ /dev/null @@ -1,15 +0,0 @@ -
    - Pre-release PRs - <% if pre_release_prs&.exists? %> - <% if pre_release_prs.any?(&:open?) %> -
    - <%= render "shared/note_box", message: "Please merge the pre-release PR to continue the release.", type: :error %> -
    - <% end %> - <%= render partial: "shared/live_release/pull_requests", locals: { prs: pre_release_prs } %> - <% else %> -
    - None. -
    - <% end %> -
    diff --git a/app/views/shared/live_release/_pull_request_thin.html.erb b/app/views/shared/live_release/_pull_request_thin.html.erb deleted file mode 100644 index 5209d8abc..000000000 --- a/app/views/shared/live_release/_pull_request_thin.html.erb +++ /dev/null @@ -1,40 +0,0 @@ -<%= link_to(pr.url, target: "_blank", rel: "nofollow") do %> -
    -
    -
    - <%= image_tag("integrations/logo_#{pr.source}.png", width: 22, class: "inline-flex") %> -

    - <%= pr.title.truncate(title_size) %> -

    -
    - -
    - #<%= pr.number %> - <%= pull_request_badge(pr) %> -
    -
    - -
    -
    -
    - <% if pr.base_ref == pr.head_ref %> - <%= short_sha(pr.head_ref) %> - <% else %> - <%= pr.base_ref %> ā† <%= pr.head_ref %> - <% end %> -
    - - <% if pr.commit.present? %> - <%= " ā€¢ " %> - <%= pr.commit.author_name || pr.commit.author_login %> - <% end %> -
    - -
    - <% pr.labels&.each do |label| %> - <%= status_badge(label["name"], :neutral) %> - <% end %> -
    -
    -
    -<% end %> diff --git a/app/views/shared/live_release/_pull_requests.html.erb b/app/views/shared/live_release/_pull_requests.html.erb deleted file mode 100644 index 346b4729c..000000000 --- a/app/views/shared/live_release/_pull_requests.html.erb +++ /dev/null @@ -1,32 +0,0 @@ -
    - <% prs.each do |pr| %> -
    - <%= image_tag("integrations/logo_#{pr.source}.png", width: 40, class: "mb-4") %> - -
    -

    - <%= pr.title %>(#<%= link_to_external pr.number, pr.url %>) -

    - -
    - <%= image_tag("git_pull_request.svg", width: 20, class: "inline-flex") %> - <%= pull_request_badge(pr) %> -
    - -
    - <% if pr.base_ref == pr.head_ref %> - HEAD ā€“ <%= short_sha(pr.head_ref) %> - <% else %> - <%= pr.base_ref %> ā† <%= pr.head_ref %> - <% end %> -
    - - <% if pr.closed_at.nil? %> -
    Opened at ā€“ <%= pr.opened_at.to_formatted_s(:short) %>
    - <% else %> -
    Closed at ā€“ <%= pr.closed_at.to_formatted_s(:short) %>
    - <% end %> -
    -
    - <% end %> -
    diff --git a/app/views/shared/live_release/_pull_requests_thin.html.erb b/app/views/shared/live_release/_pull_requests_thin.html.erb deleted file mode 100644 index 2ba60ae3d..000000000 --- a/app/views/shared/live_release/_pull_requests_thin.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -
    - <% prs.each do |pr| %> - <%= render partial: "shared/live_release/pull_request_thin", locals: { pr:, title_size: 65 } %> - <% end %> -
    diff --git a/app/views/shared/live_release/_release_metadata.html.erb b/app/views/shared/live_release/_release_metadata.html.erb deleted file mode 100644 index 1a1ab744b..000000000 --- a/app/views/shared/live_release/_release_metadata.html.erb +++ /dev/null @@ -1,24 +0,0 @@ -<% release_metadatum = release_platform_run.release_metadatum %> -<% release_platform = release_platform_run.release_platform %> -<% release = release_platform_run.release %> - -
    -
    - <%= render partial: "shared/live_release/section_title", locals: {heading: "#{release_platform.display_attr(:platform)} Metadata", subheading: nil} %> - <%= authz_link_to (release_platform_run.metadata_editable? ? :neutral : :disabled), - "Edit", - edit_release_release_platform_release_metadatum_path(release, release_platform, release_metadatum) %> -
    - - <%= render MetaTableComponent.new do |mt| %> - <% mt.with_description("Release Notes") do %> -

    <%= release_metadatum.release_notes %>

    - <% end %> - - <% unless release_platform.android? %> - <% mt.with_description("Promotional Text") do %> -

    <%= release_metadatum.promo_text %>

    - <% end %> - <% end %> - <% end %> -
    diff --git a/app/views/shared/live_release/_section_title.html.erb b/app/views/shared/live_release/_section_title.html.erb deleted file mode 100644 index 70a9d44ad..000000000 --- a/app/views/shared/live_release/_section_title.html.erb +++ /dev/null @@ -1,4 +0,0 @@ -<%= heading %> -<% if subheading %> - <%= subheading %> -<% end %> diff --git a/app/views/shared/live_release/_separator.html.erb b/app/views/shared/live_release/_separator.html.erb deleted file mode 100644 index 76710ec83..000000000 --- a/app/views/shared/live_release/_separator.html.erb +++ /dev/null @@ -1,8 +0,0 @@ -<% margin_only ||= false %> - -<% if margin_only %> -
    -<% else %> -
    -<% end %> - diff --git a/app/views/shared/live_release/_stability.html.erb b/app/views/shared/live_release/_stability.html.erb deleted file mode 100644 index 5ce87dbc9..000000000 --- a/app/views/shared/live_release/_stability.html.erb +++ /dev/null @@ -1,121 +0,0 @@ -
    -
    - <%= platform %> ā€“ <%= release.release_version %> -
    - - <% if release.patch_fix? %> -
    -
    - <%= inline_svg("band_aid.svg", classname: "w-10 opacity-80 inline-flex align-bottom") %> - release is now in patch mode -
    -
    - <% end %> - - <% if release.finished? || release.stopped? %> -
    -
    -
    This release was <%= release.display_attr(:status) %> and is now - locked. -
    - <% if release.tag_name.present? %> - <%= link_to_external release.tag_name, release.tag_url, class: "underline text-slate-500" %> - <% end %> -
    -
    - <% end %> - -
    -
      - <% steps.each do |step| %> - <% step_run = release.last_run_for(step) %> - -
    1. -
      - <% if release.manually_startable_step?(step) %> - <%= authz_button_to :blue, - "Move to this step", - start_release_step_run_path(release, step), - { class: "btn-xs mb-2" } %> - <% end %> - - <% if release.step_start_blocked?(step) %> -
      - <%= authz_button_to :disabled, - "Move to this step", - start_release_step_run_path(release, step), - { class: "btn-xs mb-1" } %> -
      - You cannot start this release step until the <%= blocked_step_release_link(release.release) %> is - finished. -
      -
      - <% end %> - - <% if step_run&.may_retry_ci? && release.on_track? %> - <%= authz_button_to :blue, - "Retry CI Workflow", - retry_ci_workflow_release_step_run_path(release, step_run), - method: :patch, - data: { turbo_method: :patch, - turbo_confirm: "This will re-run the CI workflow. Are you sure?" }, - class: "btn-xs mb-2" %> - <% end %> - - <% if step_run&.failed_with_action_required? && release.on_track? %> -
      - <%= render "shared/note_box", type: :error, message: "Due to a previous rejection, new changes cannot be submitted to the store from Tramline. Please submit the current build (#{step_run.build_number}) for review manually from the Google Play Console by creating a release in a public track (eg. Closed testing, Open testing). Once that is done, you can sync the store status with Tramline and move forward with the release train." %> - <%= authz_button_to :blue, - "Sync store status", - sync_store_status_release_step_run_path(release, step_run), - method: :patch, - data: { turbo_method: :patch, - turbo_confirm: "Please ensure that you have manually submitted the build for review by creating a release in at least one public (alpha, beta, production) channel. Continue?" }, - class: "btn-xs mb-2" %> -
      - <% end %> -
      - -
      - <%= render partial: "shared/per_step_metadata", - locals: { editable: false, platform_run: release, step: step, step_run: step_run } %> -
      -
    2. - - <%= render partial: "shared/step_tree_connector", locals: { color: step_color(step.kind) } %> - <% end %> -
    -
    - - <% if release.production_release_happened? && release.store_releases.any?(&:latest_health_data) %> -
    - <%= toggle_for(true, full_width: true) do %> -
    Release Health
    - <% end %> - - -
    - <% end %> - - <% if release.external_builds.size > 1 %> -
    - <%= toggle_for(true, full_width: true) do %> -
    - Build Health -
    - <% end %> - - -
    - <% end %> -
    diff --git a/app/views/shared/live_release/_step_run_status.html.erb b/app/views/shared/live_release/_step_run_status.html.erb deleted file mode 100644 index 95df91983..000000000 --- a/app/views/shared/live_release/_step_run_status.html.erb +++ /dev/null @@ -1,15 +0,0 @@ -<% ring_color = step_color(step.kind) %> - -
    - <% if step_run&.in_progress? %> - - <% elsif step_run&.blocked? %> - - <% elsif step_run&.done? %> - - <% elsif step_run&.failed? %> - - <% else %> - - <% end %> -
    diff --git a/app/views/shared/live_release/_teams.html.erb b/app/views/shared/live_release/_teams.html.erb deleted file mode 100644 index d5ee1fa15..000000000 --- a/app/views/shared/live_release/_teams.html.erb +++ /dev/null @@ -1,46 +0,0 @@ -<% if current_organization.teams.any? %> -
    - <%= render partial: "shared/live_release/section_title", locals: { heading: "Team Analysis", subheading: nil } %> -
    - <%= render ChartComponent.new({ data: commits.count_by_team(current_organization).reject { |_, value| value.zero? }, - colors: current_organization.team_colors, - type: "polar-area", - value_format: "number", - name: "team.build_stability", - show_x_axis: false, - show_y_axis: false }) %> -
    -
    -<% end %> - -<% if pull_requests.open.exists? %> -
    - <%= render partial: "shared/live_release/section_title", locals: { heading: "Ongoing Work", subheading: nil } %> - -
    -
    - <%= decorated_link_to :neutral, "View all (#{pull_requests.size}) on #{@release.vcs_provider.display} ā†—", @release.pull_requests_url, { target: "_blank", rel: "nofollow noopener" } %> - <% if pull_requests.open.exists? %> - <%= decorated_link_to :neutral, "View open (#{pull_requests.open.size}) on #{@release.vcs_provider.display} ā†—", @release.pull_requests_url(true), { target: "_blank", rel: "nofollow noopener" } %> - <% end %> -
    - - -
    - <%= toggle_for(false, full_width: true) do %> -
    - open pull requests (<%= pull_requests.open.size %>) -
    - <% end %> -
    - -
    -
    - <% @mid_release_prs.open.each do |pr| %> - <%= render partial: "shared/live_release/pull_request_thin", locals: { pr:, title_size: 65 } %> - <% end %> -
    -
    -
    -
    -<% end %> diff --git a/app/views/steps/_deployment.html.erb b/app/views/steps/_deployment.html.erb deleted file mode 100644 index 17887afbf..000000000 --- a/app/views/steps/_deployment.html.erb +++ /dev/null @@ -1,81 +0,0 @@ -<% stream_url = app_integrations_build_artifact_channels_url(step.app.id, with_production: step.release?) %> - -
    - -
    -
    - <%= render V2::IconComponent.new("grip.svg", size: :lg, classes: "handle cursor-grabbing") %> - -
    - <%= form.select_without_label :integration_id, - options_for_select(@build_channel_integrations, @selected_integration), - {}, - data: { controller: "input-select", - action: "dropdown-stream#fetchDynamicSelect domain--staged-rollout-help#clear" } %> -
    - -
    - <%= form.select_without_label :build_artifact_channel, - options_for_select(display_channels(@selected_build_channels) { |chan| deployment_channel_name(chan) }), - {}, - { data: { dropdown_stream_target: "dynamicSelect", - action: "dropdown-stream#showElementOnDynamicSelectChange domain--staged-rollout-help#clear", - controller: "input-select" } } %> -
    - -
    - <%= render V2::ButtonComponent.new( - scheme: :naked_icon, - type: :action, - size: :none, - html_options: { data: { action: "nested-form-ext#remove" } }) do |b| - b.with_icon("v2/trash.svg", size: :md) - end %> - - <%= form.hidden_field :_destroy %> -
    -
    - - -
    -
    -
    - <%= form.labeled_radio_option(:notes, "build_notes", "Send auto-generated build notes") %> - <%= form.labeled_radio_option(:notes, "release_notes", "Send Release Notes") %> - <%= form.labeled_radio_option(:notes, "no_notes", "Send No Notes") %> -
    -
    - -
    -
    -
    diff --git a/app/views/steps/_form.html.erb b/app/views/steps/_form.html.erb deleted file mode 100644 index 30c9205cf..000000000 --- a/app/views/steps/_form.html.erb +++ /dev/null @@ -1,65 +0,0 @@ -<% form.with_section(heading: "What should we call it?") do |section| %> - <% section.with_description do %> - A name that defines this part of the release process. - <% end %> - -
    -
    <%= section.F.labeled_text_field :name, "Name", required: true %>
    -
    <%= section.F.labeled_textarea :description, "Description" %>
    - <%= section.F.hidden_field :kind, value: step.kind %> - <% if release_platform.android? %> -
    - <%= section.F.labeled_text_field :release_suffix, - "Release Suffix", - placeholder: "Eg: qa-staging", - data: { domain__release_suffix_help_target: "input", - action: "domain--release-suffix-help#set" } %> -
    - This is appended to the version name of the app, as follows:  - -
    -
    - <% end %> -
    -<% end %> - -<% form.with_section(heading: "How do we build your app?") do |section| %> - <% section.with_description do %> - The generated build artifact will be moved across the configured <%= Deployment.display.downcase %> channels below. - <% end %> - -
    -
    - <%= section.F.labeled_select :ci_cd_channel, "CI workflow", - options_for_select(display_channels(ci_actions) { |chan| chan[:name] }, - step.ci_cd_channel.to_json), - {}, - data: { controller: "input-select" } %> -
    - This CI workflow should generate a valid build artifact (aab/apk/ipa). -
    -
    - -
    - <%= section.F.labeled_text_field :build_artifact_name_pattern, "Build Artifact Name (Optional)" %> -
    -

    - If your CI workflow generates multiple artifacts, provide a name to choose the correct build artifact - (aab/apk/ipa) among the files generated.

    -

    - When left blank, Tramline will choose the largest file generated as the build artifact. -

    -

    - To understand more about build artifact - selection, <%= link_to_external "check out the docs.", "https://docs.tramline.app/integrations/ci-cd/#build-artifact-selection", class: "underline" %> -

    -
    -
    - - <% if @app.variants.exists? %> -
    <%= section.F.labeled_select :app_variant_id, "Pick an app variant", options_for_select(@app.variant_options, step.app_variant&.id) %>
    - <% end %> -
    -<% end %> diff --git a/app/views/steps/_new_deployments.html.erb b/app/views/steps/_new_deployments.html.erb deleted file mode 100644 index 87962fdeb..000000000 --- a/app/views/steps/_new_deployments.html.erb +++ /dev/null @@ -1,83 +0,0 @@ -<% form.with_section(heading: "How should we distribute?") do |section| %> - <% section.with_description do %> - <%= "#{Deployment.display.downcase.pluralize.titleize} are run in the specified order, you can drag them around to change the order." %> -

    Refresh your channels, if you can't find them in the list.

    - <% if app.slack_build_channel_provider.present? %> -
    - <%= render V2::ButtonComponent.new( - scheme: :light, - type: :link, - size: :xxs, - options: refresh_channels_app_integration_slack_integration_path( - app, - app.slack_build_channel_provider.integration, - app.slack_build_channel_provider - ), - label: "Refresh Slack Channels", - html_options: { method: :post, data: { turbo_method: :post }, class: "-ml-1" }, - arrow: :none) %> -
    - <% end %> - - <% if app.firebase_build_channel_provider.present? %> -
    - <%= render V2::ButtonComponent.new( - scheme: :light, - type: :link, - size: :xxs, - options: refresh_channels_app_integration_google_firebase_integration_path( - app, - app.firebase_build_channel_provider.integration, - app.firebase_build_channel_provider - ), - label: "Refresh Firebase Channels", - html_options: { method: :post, data: { turbo_method: :post }, class: "-ml-1" }, - arrow: :none) %> -
    - <% end %> - <% end %> - -
    -
    - <%= render V2::ButtonComponent.new( - scheme: :light, - type: :action, - size: :xs, - label: "Add a new #{Deployment.display.downcase}", - html_options: { data: { action: "nested-form-ext#add" } }, - arrow: :none) do |b| b.with_icon("plus.svg", rounded: false) end %> -
    - -
      -
    • - <%= section.F.fields_for :deployments, Deployment.new do |deployment_form| %> - <%= render partial: "deployment", locals: { form: deployment_form, step: @step } %> - <%= deployment_form.hidden_field :deployment_number, value: 1, data: { list_position_target: "position" } %> - <% end %> -
    • - - - -
      - - <% if @step.release? %> - - <% end %> -
    -
    -<% end %> diff --git a/app/views/steps/new.html.erb b/app/views/steps/new.html.erb deleted file mode 100644 index 81e9d52bf..000000000 --- a/app/views/steps/new.html.erb +++ /dev/null @@ -1,14 +0,0 @@ -<%= render V2::ContainerComponent.new(title: "Create a new #{@step.kind} step šŸŖœ", - subtitle: platform_subtitle(@app, @step), - error_resource: @step) do |container| %> - <% container.with_back_button %> - <% container.with_body do %> - <%= render V2::FormComponent.new(model: @step, url: app_train_platform_steps_path(@app, @train, @release_platform)) do |form| %> - <%= render "form", form: form, step: @step, release_platform: @release_platform, ci_actions: @ci_actions, train: @train %> - <%= render "new_deployments", app: @app, form: form %> - <% form.with_action do %> - <%= form.F.authz_submit "Save", "plus.svg" %> - <% end %> - <% end %> - <% end %> -<% end %> diff --git a/app/views/trains/_all_steps.html.erb b/app/views/trains/_all_steps.html.erb deleted file mode 100644 index c38e60aec..000000000 --- a/app/views/trains/_all_steps.html.erb +++ /dev/null @@ -1,22 +0,0 @@ -<% if @edit_not_allowed %> - <%= render V2::AlertComponent.new(kind: :banner, type: :notice, title: "Steps not editable", full_screen: false) do %> - The steps can not be edited while there are active releases. Please stop or finish the releases to make changes. - <% end %> -<% end %> - -
    - <% train.release_platforms.each do |release_platform| %> -
    -
    <%= steps_heading(release_platform) %>
    - <% if release_platform.steps.size > 0 && release_platform.persisted? %> -
    - <%= render partial: "shared/step_tree_viz", locals: {train: train, release_platform:, editable: true} %> -
    - <% elsif release_platform.persisted? %> -
    - <%= render partial: "trains/step_creation", locals: {app: app, train: train, release_platform:} %> -
    - <% end %> -
    - <% end %> -
    diff --git a/app/views/trains/_edit_step_button.html.erb b/app/views/trains/_edit_step_button.html.erb deleted file mode 100644 index 93bd2a56d..000000000 --- a/app/views/trains/_edit_step_button.html.erb +++ /dev/null @@ -1,44 +0,0 @@ -<% if editable %> -
    - <% if step.train.active_runs.none? %> - <%= render V2::ModalComponent.new(title: "Edit step") do |modal| %> - <% modal.with_button(scheme: :light, type: :action, size: :xxs, arrow: :none) - .with_icon("edit.svg", size: :md) %> - <% modal.with_body do %> - <%= render V2::FormComponent.new(model: step, url: app_train_platform_step_path(step.app, step.train, step.release_platform, step), method: :patch) do |f| %> - <%= render 'steps/form', form: f, step: step, release_platform: step.release_platform, ci_actions: step.ci_cd_provider.workflows, train: step.train %> - -
    -
    -

    - <%= Deployment.display.pluralize %> -

    -
    - - <% if step.deployments.size > 1 %> -
    - Automatic distribution to all non-production distribution channels without any manual approval is - <%= auto_deploy_status(step) %> -
    - <% end %> - -
    - <%= render partial: "shared/deployments", locals: { step: step, show_deployment_status: false, platform_run: nil, step_run: nil } %> -
    -
    - - <% f.with_action do %> - <% f.F.authz_submit "Update", "v2/archive.svg" %> - <% end %> - <% end %> - <% end %> - <% end %> - <% else %> -
    - -
    - <% end %> -
    -<% end %> diff --git a/app/views/trains/_form.html.erb b/app/views/trains/_form.html.erb index a51b62f78..a32330532 100644 --- a/app/views/trains/_form.html.erb +++ b/app/views/trains/_form.html.erb @@ -7,8 +7,7 @@ <% end %> <% end %> -
    +
    <%= render V2::FormComponent.new(model: [app, train], url: url) do |f| %> <% f.with_section(heading: "Basic") do |section| %> <% section.with_description do %> @@ -124,8 +123,8 @@ <%= section.F.labeled_select :branching_strategy, "Strategy", options_for_select(Train::BRANCHING_STRATEGIES.invert, train.branching_strategy), {}, - { data: { action: 'branching-selector#change', - branching_selector_target: "branchingStrategy" }, + { data: { action: 'domain--branching-selector#change', + domain__branching_selector_target: "branchingStrategy" }, disabled: train.persisted? } %>
    @@ -136,10 +135,10 @@ disabled: train.persisted? %>
    - +
    -