dri 0.4.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,34 +2,29 @@
2
2
 
3
3
  require_relative '../../command'
4
4
  require_relative '../../utils/table'
5
+ require_relative '../../utils/feature_flag_consts'
6
+ require_relative '../../feature_flag_report'
5
7
 
6
8
  module Dri
7
9
  module Commands
8
10
  class Fetch
9
11
  class FeatureFlags < Dri::Command
10
12
  include Dri::Utils::Table
11
-
12
- PRODUCTION = 'host::gitlab.com'
13
- STAGING = 'host::staging.gitlab.com'
14
- STAGING_REF = 'host::staging-ref.gitlab.com'
15
- PREPROD = 'host::pre.gitlab.com'
13
+ include Dri::Utils::FeatureFlagConsts
16
14
 
17
15
  def initialize(options)
18
16
  @options = options
19
17
  @today_iso_format = Time.now.strftime('%Y-%m-%dT00:00:00Z')
20
18
  end
21
19
 
22
- def execute(input: $stdin, output: $stdout) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
20
+ def execute(input: $stdin, output: $stdout) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
23
21
  verify_config_exists
24
22
 
25
23
  summary = add_color('Summary', :bright_yellow)
26
24
  changed_on = add_color('Changed(UTC)', :bright_yellow)
27
25
  url = add_color('URL', :bright_yellow)
28
26
 
29
- prod_feature_flags = []
30
- staging_feature_flags = []
31
- staging_ref_feature_flags = []
32
- preprod_feature_flags = []
27
+ report = Dri::FeatureFlagReport.new
33
28
 
34
29
  headers = [summary, changed_on, url]
35
30
 
@@ -37,42 +32,23 @@ module Dri
37
32
 
38
33
  spinner.run do
39
34
  response = api_client.fetch_feature_flag_logs(@today_iso_format)
35
+
40
36
  if response.empty?
41
37
  logger.info 'It\'s been quiet...no feature flag changes for today 👀'
42
38
  break
43
39
  end
44
40
 
45
41
  response.each do |feature_flag|
46
- summary = feature_flag.title
47
-
48
- substrings = ["set to \"true\"", "set to \"false\""]
49
- next unless substrings.any? { |substr| summary.include?(substr) }
50
-
51
- changed_on = feature_flag.description[/(?<=Changed on \(in UTC\): ).+?(?=\n)/].delete('`')
52
- url = feature_flag.web_url
53
-
54
- feature_flag_data = [summary, changed_on, url]
55
-
56
- labels = feature_flag.labels
57
- host_label = labels.select { |label| /^host::/.match(label) }.join('')
42
+ next unless TITLE_SUBSTRINGS.any? { |substr| feature_flag.title.include?(substr) }
58
43
 
59
- case host_label
60
- when PRODUCTION
61
- prod_feature_flags << feature_flag_data
62
- when STAGING
63
- staging_feature_flags << feature_flag_data
64
- when STAGING_REF
65
- staging_ref_feature_flags << feature_flag_data
66
- when PREPROD
67
- preprod_feature_flags << feature_flag_data
68
- end
44
+ report.add_change(feature_flag)
69
45
  end
70
46
  end
71
47
 
72
- print_results('Production', headers, prod_feature_flags, output) unless prod_feature_flags.empty?
73
- print_results('Staging', headers, staging_feature_flags, output) unless staging_feature_flags.empty?
74
- print_results('Staging Ref', headers, staging_ref_feature_flags, output) unless staging_ref_feature_flags.empty? # rubocop:disable Layout/LineLength
75
- print_results('Preprod', headers, preprod_feature_flags, output) unless preprod_feature_flags.empty?
48
+ print_results('Production', headers, report.prod, output) unless report.prod.empty?
49
+ print_results('Staging', headers, report.staging, output) unless report.staging.empty?
50
+ print_results('Staging Ref', headers, report.staging_ref, output) unless report.staging_ref.empty?
51
+ print_results('Preprod', headers, report.preprod, output) unless report.preprod.empty?
76
52
  end
77
53
 
78
54
  private
@@ -0,0 +1,254 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require_relative '../../command'
5
+ require_relative '../../utils/table'
6
+ require_relative '../../utils/constants'
7
+
8
+ module Dri
9
+ module Commands
10
+ class Fetch
11
+ class Pipelines < Dri::Command # rubocop:disable Metrics/ClassLength
12
+ include Dri::Utils::Table
13
+ using Refinements
14
+
15
+ NUM_OF_TESTS_LIVE_ENV = 1000
16
+ NOT_FOUND = "Not found"
17
+
18
+ def initialize(options)
19
+ @options = options
20
+ end
21
+
22
+ def execute(input: $stdin, output: $stdout)
23
+ verify_config_exists
24
+ logger.info "Fetching pipelines' status, this might take a while..."
25
+ pipelines = []
26
+ table_labels = define_table_labels
27
+
28
+ spinner.run do
29
+ Dri::Utils::Constants::PIPELINE_ENVIRONMENTS.each do |environment, details|
30
+ logger.info "Fetching last executed #{environment} pipeline"
31
+ pipelines << fetch_pipeline(pipeline_name: environment.to_s, details: details)
32
+ logger.info "Fetching complete for #{environment}"
33
+ end
34
+ end
35
+
36
+ print_table(table_labels, pipelines, alignments: [:left, :center, :center, :left])
37
+ pipelines # Returning the array mainly for spec
38
+ end
39
+
40
+ private
41
+
42
+ # Format a past date
43
+ # @param [Integer] hours_ago the amount of hours from now
44
+ # @return [String] formatted datetime
45
+ def past_timestamp(hours_ago)
46
+ timestamp = Time.now - (hours_ago * 60 * 60)
47
+ timestamp.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
48
+ end
49
+
50
+ # Get the first downstream pipeline of a project
51
+ # @param [Integer] project_id the id of the project
52
+ # @param [Integer] pipeline_id the pipeline id
53
+ # @return [Gitlab::ObjectifiedHash,nil] nil if downstream (bridge) pipeline does not exist
54
+ def bridge_pipeline(project_id, pipeline_id)
55
+ bridges = api_client.pipeline_bridges(project_id, pipeline_id)
56
+ return if bridges.empty? # If downstream pipeline doesn't exist, which triggers the QA tests, return
57
+
58
+ bridges.first["downstream_pipeline"]
59
+ end
60
+
61
+ # Get jobs from a pipeline
62
+ # @param [Integer] project_id the id of the project
63
+ # @param [Integer] pipeline_id the pipeline id
64
+ # @param [Boolean] ops true if ops instance
65
+ # @return [Array::ObjectifiedHash,nil] nil if downstream (bridge) pipeline does not exist
66
+ def jobs(project_id:, pipeline_id:, ops: false)
67
+ api_client(ops: ops).pipeline_jobs(project_id, pipeline_id)
68
+ end
69
+
70
+ # Checks if tests count exceeds threshold in a pipeline
71
+ # @param [Integer] project_id the id of the project
72
+ # @param [Integer] pipeline_id the pipeline id
73
+ # @param [Boolean] ops true if ops instance
74
+ # @return [Boolean] true if count exceeds threshold defined in constant NUM_OF_TESTS_LIVE_ENV
75
+ def tests_exceed_threshold?(project_id:, pipeline_id:, ops: true)
76
+ api_client(ops: ops).pipeline_test_report(project_id, pipeline_id).total_count > NUM_OF_TESTS_LIVE_ENV
77
+ end
78
+
79
+ # Checks if a job is present in an array of jobs
80
+ # @param [Array] jobs
81
+ # @param [String] job_name name of the job
82
+ # @return [Boolean] true if job is present
83
+ def contains_job?(jobs, job_name:)
84
+ jobs.any? { |job| job["name"].include?(job_name) }
85
+ end
86
+
87
+ # Checks if a stage is present from a list of jobs
88
+ # @param [Array] jobs
89
+ # @param [String] stage_name name of the stage
90
+ # @return [Boolean] true if stage is present
91
+ def contains_stage?(jobs, stage_name)
92
+ jobs.any? { |job| job["stage"].include?(stage_name) }
93
+ end
94
+
95
+ # Checks if pipeline ran only the QA smoke tests
96
+ # @param [Array] jobs
97
+ # @param [Integer] project_id
98
+ # @param [Integer] pipeline_id
99
+ # @param [Boolean] ops true if ops instance
100
+ def smoke_run?(jobs:, project_id:, pipeline_id:, ops:)
101
+ contains_stage?(jobs, "sanity") &&
102
+ !tests_exceed_threshold?(project_id: project_id, pipeline_id: pipeline_id, ops: ops)
103
+ end
104
+
105
+ # Checks if pipeline ran full suite of qa tests
106
+ # @param [Array] jobs
107
+ # @param [Integer] project_id
108
+ # @param [Integer] pipeline_id
109
+ # @param [Boolean] ops true if ops instance
110
+ def full_run?(jobs:, project_id:, pipeline_id:, ops:)
111
+ if ops
112
+ (contains_stage?(jobs, "qa") || contains_stage?(jobs, "test")) &&
113
+ tests_exceed_threshold?(project_id: project_id, pipeline_id: pipeline_id, ops: ops)
114
+ else
115
+ contains_stage?(jobs, "qa") || contains_stage?(jobs, "test")
116
+ # Nightly pipeline does not execute full E2E suite if sanity fails so can't check tests count
117
+ end
118
+ end
119
+
120
+ # Combined logic to check if a pipeline was a sanity run for all pipeline types - ie., live environment
121
+ # and gitlab-qa-mirror pipelines
122
+ # @param [Array] pipeline_jobs
123
+ # @param [Object] pipeline
124
+ # @param [Boolean] ops
125
+ def sanity?(pipeline_jobs:, pipeline:, ops:)
126
+ return true if ops && smoke_run?(jobs: pipeline_jobs, project_id: pipeline.project_id,
127
+ pipeline_id: pipeline.id, ops: ops)
128
+
129
+ false if full_run?(jobs: pipeline_jobs, project_id: pipeline.project_id,
130
+ pipeline_id: pipeline.id, ops: ops)
131
+ end
132
+
133
+ # Constructs allure report url for each pipeline
134
+ # @param [String] pipeline_name
135
+ # @param [Integer] pipeline_id
136
+ # @param [Boolean] sanity
137
+ def allure_report(pipeline_name:, pipeline_id:, sanity:)
138
+ "https://storage.googleapis.com/gitlab-qa-allure-reports/#{allure_bucket_name(pipeline_name, sanity)}"\
139
+ "/master/#{pipeline_id}/index.html"
140
+ end
141
+
142
+ # Returns the GCP bucket name for different pipeline types
143
+ # @param [String] pipeline_name
144
+ # @param [Boolean] sanity
145
+ def allure_bucket_name(pipeline_name, sanity)
146
+ case pipeline_name
147
+ when "master"
148
+ "package-and-qa"
149
+ when "nightly"
150
+ pipeline_name
151
+ when "pre_prod"
152
+ "preprod-#{run_type(sanity)}"
153
+ else
154
+ "#{pipeline_name.sub('_', '-')}-#{run_type(sanity)}"
155
+ end
156
+ end
157
+
158
+ def run_type(sanity)
159
+ sanity == true ? "sanity" : "full"
160
+ end
161
+
162
+ # Returns table headers
163
+ # @return [Array]
164
+ def define_table_labels
165
+ name = add_color("Pipeline", :magenta)
166
+ pipeline_last_executed = add_color("Last executed at", :magenta)
167
+ url = add_color("Pipeline Url", :magenta)
168
+ report = add_color("Last report", :magenta)
169
+ result = add_color("Result", :magenta)
170
+ [name, pipeline_last_executed, url, report, result]
171
+ end
172
+
173
+ # Checks if pipeline is running on ops.gitlab.net or gitlab.com
174
+ # @param [String] url
175
+ def ops_pipeline?(url)
176
+ url.include?("ops.gitlab.net")
177
+ end
178
+
179
+ def notify_slack_job_name(pipeline_name, ops)
180
+ return "notify-slack-qa-fail" if ops
181
+
182
+ pipeline_name.to_s.include?("master") ? "notify_slack" : "notify-slack-fail"
183
+ end
184
+
185
+ # Returns child pipeline if it is master pipeline
186
+ # @param [Gitlab::ObjectifiedHash] pipeline
187
+ def pipeline_with_qa_tests(pipeline)
188
+ if pipeline.web_url.to_s.include?("gitlab-qa-mirror")
189
+ bridge_pipeline(pipeline.project_id, pipeline.id)
190
+ else
191
+ pipeline
192
+ end
193
+ end
194
+
195
+ # Returns query options for pipelines api call
196
+ # @param [Hash] details
197
+ # @param [Boolean] ops
198
+ def options(details, ops)
199
+ options = { order_by: "updated_at", scope: "finished",
200
+ updated_after: past_timestamp(details[:search_hours_ago]) }
201
+ options.merge(username: "gitlab-bot") if ops
202
+ options
203
+ end
204
+
205
+ def emoji_for_success_failure(status)
206
+ return add_color("✓", :green) if status.include?("success")
207
+
208
+ add_color("x", :red)
209
+ end
210
+
211
+ # @param [String] pipeline_name
212
+ # @param [Hash] details Pipeline environment details
213
+ # @return [Array] Array of last executed pipeline details
214
+ # rubocop:disable Metrics/PerceivedComplexity
215
+ def fetch_pipeline(pipeline_name:, details:) # rubocop:disable Metrics/CyclomaticComplexity
216
+ ops = ops_pipeline?(details[:url])
217
+ options = options(details, ops)
218
+ # instance is ops.gitlab.net or gitlab.com
219
+ response = api_client(ops: ops).pipelines(project_id: details[:project_id],
220
+ options: options, auto_paginate: true)
221
+ return [pipeline_name, NOT_FOUND, NOT_FOUND, NOT_FOUND, NOT_FOUND] if response.empty?
222
+
223
+ # Return empty data to the table if no matching pipelines were found from the query
224
+ response.each do |pipeline|
225
+ pipeline_to_scan = pipeline_with_qa_tests(pipeline) # Fetch child pipeline if it is master
226
+ next if pipeline_to_scan.nil?
227
+
228
+ pipeline_jobs = jobs(project_id: pipeline.project_id, pipeline_id: pipeline_to_scan.id, ops: ops)
229
+ next unless contains_job?(pipeline_jobs, job_name: notify_slack_job_name(pipeline_name, ops))
230
+
231
+ # Need to know if it is a sanity or a full run to construct allure report url
232
+ sanity = sanity?(pipeline_jobs: pipeline_jobs, pipeline: pipeline_to_scan,
233
+ ops: ops)
234
+
235
+ next if sanity.nil? # To filter out some "clean up" pipelines present in live environments
236
+
237
+ next if sanity && @options[:full_runs_only] # Filter out sanity runs if --full-runs-only option is passed
238
+
239
+ name = ops ? "#{pipeline_name}_#{run_type(sanity)}" : pipeline_name
240
+ pipeline_last_executed = pipeline_to_scan.updated_at
241
+ url = pipeline_to_scan.web_url
242
+ report = allure_report(pipeline_name: pipeline_name, pipeline_id: pipeline_to_scan.id, sanity: sanity)
243
+ result = emoji_for_success_failure(pipeline_to_scan.status)
244
+ return [name, pipeline_last_executed, url, report, result]
245
+ end
246
+
247
+ [pipeline_name, NOT_FOUND, NOT_FOUND, NOT_FOUND, NOT_FOUND] # Parsed through all of the response and
248
+ # no matching pipelines found
249
+ end
250
+ # rubocop:enable Metrics/PerceivedComplexity
251
+ end
252
+ end
253
+ end
254
+ end
@@ -82,6 +82,18 @@ module Dri
82
82
  require_relative 'fetch/quarantines'
83
83
  Dri::Commands::Fetch::Quarantines.new(options, search: '[DEQUARANTINE]').execute
84
84
  end
85
+
86
+ desc 'pipelines', 'Display status of pipelines'
87
+ method_option :help, aliases: '-h', type: :boolean,
88
+ desc: 'Display pipelines usage information'
89
+ method_option :full_runs_only, type: :boolean,
90
+ desc: 'Displays full pipeline runs only'
91
+ def pipelines(*)
92
+ return invoke :help, %w[pipelines] if options[:help]
93
+
94
+ require_relative 'fetch/pipelines'
95
+ Dri::Commands::Fetch::Pipelines.new(options).execute
96
+ end
85
97
  end
86
98
  end
87
99
  end
@@ -31,16 +31,18 @@ module Dri
31
31
 
32
32
  @username = prompt.ask("What is your GitLab username?")
33
33
  @token = prompt.mask("Please provide your GitLab personal access token:")
34
+ @ops_token = prompt.mask("Please provide your ops.gitlab.net personal access token:")
34
35
  @timezone = prompt.select("Choose your current timezone?", %w[EMEA AMER APAC])
35
36
  @emoji = prompt.ask("Have a triage emoji?")
36
37
 
37
- if (@emoji || @token || @username).nil?
38
- logger.error "Please provide a username, token, timezone and emoji used for triage."
38
+ if (@emoji || @token || @username || @ops_token).nil?
39
+ logger.error "Please provide a username, gitlab token, ops token, timezone and emoji used for triage."
39
40
  exit 1
40
41
  end
41
42
 
42
43
  config.set(:settings, :user, value: @username)
43
44
  config.set(:settings, :token, value: @token)
45
+ config.set(:settings, :ops_token, value: @ops_token)
44
46
  config.set(:settings, :timezone, value: @timezone)
45
47
  config.set(:settings, :emoji, value: @emoji)
46
48
  config.write(force: true)
@@ -38,6 +38,7 @@ module Dri
38
38
  def pretty_print_profile
39
39
  <<~PROFILE
40
40
  #{add_color('User:', :bright_cyan)} #{username}\n #{add_color('Token:', :bright_cyan)} #{token}
41
+ #{add_color('OpsToken:', :bright_cyan)} #{ops_token}
41
42
  #{add_color('Timezone:', :bright_cyan)} #{timezone}
42
43
  #{add_color('Emoji:', :bright_cyan)} #{emoji}
43
44
  PROFILE
@@ -2,21 +2,26 @@
2
2
 
3
3
  require_relative '../../command'
4
4
  require_relative '../../utils/markdown_lists'
5
- require_relative "../../report"
5
+ require_relative '../../utils/feature_flag_consts'
6
+ require_relative '../../report'
7
+ require_relative '../../feature_flag_report'
6
8
 
7
9
  require 'markdown-tables'
8
10
  require 'fileutils'
9
- require "uri"
11
+ require 'uri'
10
12
 
11
13
  module Dri
12
14
  module Commands
13
15
  class Publish
14
- class Report < Dri::Command
16
+ class Report < Dri::Command # rubocop:disable Metrics/ClassLength
17
+ include Dri::Utils::FeatureFlagConsts
18
+
15
19
  def initialize(options)
16
20
  @options = options
17
21
 
18
22
  @date = Date.today
19
23
  @time = Time.now.to_i
24
+ @today_iso_format = Time.now.strftime('%Y-%m-%dT00:00:00Z')
20
25
  end
21
26
 
22
27
  def execute(input: $stdin, output: $stdout) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
@@ -26,7 +31,9 @@ module Dri
26
31
  logger.info "Fetching triaged failures with award emoji #{emoji}..."
27
32
 
28
33
  spinner.start
34
+
29
35
  issues = api_client.fetch_triaged_failures(emoji: emoji, state: 'opened')
36
+
30
37
  spinner.stop
31
38
 
32
39
  if issues.empty?
@@ -34,27 +41,28 @@ module Dri
34
41
  exit 1
35
42
  end
36
43
 
37
- logger.info "Assembling the report... "
44
+ logger.info 'Assembling the failures report... '
38
45
  # sets each failure on the table
39
46
  action_options = [
40
- "pinged SET",
41
- "reproduced",
42
- "transient",
43
- "quarantined",
44
- "active investigation",
45
- "blocking pipelines",
46
- "awaiting for a fix to merge",
47
- "notified the team",
48
- "due to feature flag"
47
+ 'pinged SET',
48
+ 'reproduced',
49
+ 'transient',
50
+ 'quarantined',
51
+ 'active investigation',
52
+ 'blocking pipelines',
53
+ 'awaiting for a fix to merge',
54
+ 'notified the team',
55
+ 'due to feature flag'
49
56
  ]
50
57
 
51
58
  spinner.start
59
+
52
60
  issues.each do |issue|
53
61
  actions = []
54
62
 
55
63
  if @options[:actions]
56
64
  actions = prompt.multi_select(
57
- "Please mark the actions on #{add_color(issue['title'], :yellow)}: ",
65
+ "Please mark the actions on #{add_color(issue.title, :yellow)}: ",
58
66
  action_options,
59
67
  per_page: 9
60
68
  )
@@ -77,14 +85,59 @@ module Dri
77
85
  end
78
86
  end
79
87
 
88
+ spinner.stop
89
+
90
+ if @options[:feature_flags]
91
+ logger.info 'Fetching today\'s feature flag changes...'
92
+
93
+ feature_flag_report = Dri::FeatureFlagReport.new
94
+
95
+ spinner.start
96
+
97
+ feature_flags = api_client.fetch_feature_flag_logs(@today_iso_format)
98
+
99
+ feature_flags.each do |feature_flag|
100
+ next unless TITLE_SUBSTRINGS.any? { |substr| feature_flag.title.include?(substr) }
101
+
102
+ feature_flag_report.add_change(feature_flag)
103
+ end
104
+
105
+ spinner.stop
106
+
107
+ logger.info 'Assembling the feature flags report...'
108
+
109
+ spinner.start
110
+
111
+ feature_flag_note = "\n\n## Feature Flag Changes"
112
+ feature_flag_changes = ''
113
+
114
+ format_type = @options[:format] == 'list' ? :list : :table
115
+
116
+ feature_flag_report.get_all_flag_changes.each do |env, changes|
117
+ next if changes.empty?
118
+
119
+ feature_flag_changes += format_feature_flag_changes(
120
+ env, changes, feature_flag_report.labels, format_type
121
+ )
122
+ end
123
+
124
+ feature_flag_note += if feature_flag_changes.empty?
125
+ "\n\nNo changes found today"
126
+ else
127
+ "\n\n<details><summary>Click to expand</summary>#{feature_flag_changes}</details>"
128
+ end
129
+
130
+ spinner.stop
131
+ end
132
+
80
133
  report.set_header(timezone, username)
81
134
  note = "#{report.header}\n\n#{format_style}"
82
135
 
83
- spinner.stop
136
+ note += feature_flag_note if @options[:feature_flags]
84
137
 
85
138
  # creates an .md file with the report locally in /handover_reports
86
139
  if @options[:dry_run]
87
- logger.info "Downloading the report... "
140
+ logger.info 'Downloading the report... '
88
141
 
89
142
  spinner.start
90
143
 
@@ -104,7 +157,7 @@ module Dri
104
157
 
105
158
  # sends note to the weekly triage report
106
159
  issues = api_client.fetch_current_triage_issue
107
- current_issue_iid = issues[0]["iid"]
160
+ current_issue_iid = issues.first.iid
108
161
 
109
162
  api_client.post_triage_report_note(iid: current_issue_iid, body: note)
110
163
 
@@ -113,6 +166,27 @@ module Dri
113
166
  Thanks @#{username}, your report was posted at https://gitlab.com/gitlab-org/quality/pipeline-triage/-/issues/#{current_issue_iid} 🎉
114
167
  MSG
115
168
  end
169
+
170
+ private
171
+
172
+ def format_feature_flag_changes(env, changes, labels, format_type)
173
+ unless format_type == :table || format_type == :list
174
+ raise ArgumentError, 'format_type must be one of type :table or :list'
175
+ end
176
+
177
+ case format_type
178
+ when :list
179
+ formatted_changes = Utils::MarkdownLists.make_list(labels, changes)
180
+ when :table
181
+ formatted_changes = MarkdownTables.make_table(
182
+ labels,
183
+ changes,
184
+ is_rows: true, align: %w[l l l]
185
+ )
186
+ end
187
+
188
+ "\n\n### #{env.to_s.capitalize.tr('_', ' ')}\n\n#{formatted_changes}"
189
+ end
116
190
  end
117
191
  end
118
192
  end
@@ -14,6 +14,8 @@ module Dri
14
14
  desc: 'Formats the report'
15
15
  method_option :actions, type: :boolean,
16
16
  desc: 'Updates actions on failures'
17
+ method_option :feature_flags, type: :boolean,
18
+ desc: 'Adds summary of feature flag changes'
17
19
  def report(*)
18
20
  if options[:help]
19
21
  invoke :help, ['report']
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './utils/feature_flag_consts'
4
+
5
+ module Dri
6
+ class FeatureFlagReport
7
+ include Dri::Utils::FeatureFlagConsts
8
+
9
+ attr_reader :header, :labels, :prod, :staging, :staging_ref, :preprod
10
+
11
+ def initialize
12
+ @header = '## Feature Flag Changes'
13
+ @labels = %w[Summary Changed(UTC) URL]
14
+ @prod = []
15
+ @staging = []
16
+ @staging_ref = []
17
+ @preprod = []
18
+ end
19
+
20
+ def add_change(feature_flag)
21
+ summary = feature_flag.title
22
+
23
+ changed_on = feature_flag.description[/(?<=Changed on \(in UTC\): ).+?(?=\n)/].delete('`')
24
+ url = feature_flag.web_url
25
+
26
+ feature_flag_data = [summary, changed_on, url]
27
+
28
+ labels = feature_flag.labels
29
+ host_label = labels.select { |label| /^host::/.match(label) }.join('')
30
+
31
+ case host_label
32
+ when PRODUCTION
33
+ @prod << feature_flag_data
34
+ when STAGING
35
+ @staging << feature_flag_data
36
+ when STAGING_REF
37
+ @staging_ref << feature_flag_data
38
+ when PREPROD
39
+ @preprod << feature_flag_data
40
+ end
41
+ end
42
+
43
+ def get_all_flag_changes
44
+ { production: @prod, staging: @staging, staging_ref: @staging_ref, preprod: @preprod }
45
+ end
46
+ end
47
+ end
data/lib/dri/report.rb CHANGED
@@ -95,7 +95,7 @@ module Dri
95
95
  end
96
96
 
97
97
  unless pipeline_link.empty?
98
- pipeline_link_sanitized = pipeline_link.join.strip.chop
98
+ pipeline_link_sanitized = pipeline_link.join.strip
99
99
  pipeline_markdown = "[#{pipeline_markdown}](#{pipeline_link_sanitized})"
100
100
  end
101
101