Commit b3dd3699 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch 'improve-feature-flag-tool' into 'master'

Add `template` and `MR` question to `feature-flag`

See merge request gitlab-org/gitlab!39580
parents 0409cb36 7a908a0f
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
require 'optparse' require 'optparse'
require 'yaml' require 'yaml'
require 'fileutils' require 'fileutils'
require 'cgi' require 'uri'
require_relative '../lib/feature/shared' unless defined?(Feature::Shared) require_relative '../lib/feature/shared' unless defined?(Feature::Shared)
...@@ -105,10 +105,11 @@ class FeatureFlagOptionParser ...@@ -105,10 +105,11 @@ class FeatureFlagOptionParser
end end
def read_group def read_group
$stdout.puts
$stdout.puts ">> Please specify the group introducing feature flag, like `group::apm`:" $stdout.puts ">> Please specify the group introducing feature flag, like `group::apm`:"
loop do loop do
$stdout.print "\n?> " $stdout.print "?> "
group = $stdin.gets.strip group = $stdin.gets.strip
group = nil if group.empty? group = nil if group.empty?
return group if group.nil? || group.start_with?('group::') return group if group.nil? || group.start_with?('group::')
...@@ -121,6 +122,7 @@ class FeatureFlagOptionParser ...@@ -121,6 +122,7 @@ class FeatureFlagOptionParser
# if there's only one type, do not ask, return # if there's only one type, do not ask, return
return TYPES.first.first if TYPES.one? return TYPES.first.first if TYPES.one?
$stdout.puts
$stdout.puts ">> Please specify the type of your feature flag:" $stdout.puts ">> Please specify the type of your feature flag:"
$stdout.puts $stdout.puts
TYPES.each do |type, data| TYPES.each do |type, data|
...@@ -128,7 +130,7 @@ class FeatureFlagOptionParser ...@@ -128,7 +130,7 @@ class FeatureFlagOptionParser
end end
loop do loop do
$stdout.print "\n?> " $stdout.print "?> "
type = $stdin.gets.strip.to_sym type = $stdin.gets.strip.to_sym
return type if TYPES[type] return type if TYPES[type]
...@@ -137,27 +139,41 @@ class FeatureFlagOptionParser ...@@ -137,27 +139,41 @@ class FeatureFlagOptionParser
end end
end end
def read_issue_url(options) def read_introduced_by_url
$stdout.puts
$stdout.puts ">> If you have MR open, can you paste the URL here? (or enter to skip)"
loop do
$stdout.print "?> "
introduced_by_url = $stdin.gets.strip
introduced_by_url = nil if introduced_by_url.empty?
return introduced_by_url if introduced_by_url.nil? || introduced_by_url.start_with?('https://')
$stderr.puts "URL needs to start with https://"
end
end
def read_rollout_issue_url(options)
return unless TYPES.dig(options.type, :rollout_issue) return unless TYPES.dig(options.type, :rollout_issue)
url = "https://gitlab.com/gitlab-org/gitlab/-/issues/new" url = "https://gitlab.com/gitlab-org/gitlab/-/issues/new"
title = "[Feature flag] Rollout of `#{options.name}`" title = "[Feature flag] Rollout of `#{options.name}`"
description = File.read('.gitlab/issue_templates/Feature Flag Roll Out.md')
description.sub!(':feature_name', options.name)
issue_new_url = url + "?" + params = {
"issue[title]=" + CGI.escape(title) + "&" 'issue[title]' => "[Feature flag] Rollout of `#{options.name}`",
# TODO: We should be able to pick `issueable_template` 'issuable_template' => 'Feature Flag Roll Out',
# + "issue[description]=" + CGI.escape(description) }
issue_new_url = url + "?" + URI.encode_www_form(params)
$stdout.puts
$stdout.puts ">> Open this URL and fill the rest of details:" $stdout.puts ">> Open this URL and fill the rest of details:"
$stdout.puts issue_new_url $stdout.puts issue_new_url
$stdout.puts $stdout.puts
$stdout.puts ">> Paste URL here, or enter to skip:" $stdout.puts ">> Paste URL of `rollout issue` here, or enter to skip:"
loop do loop do
$stdout.print "\n?> " $stdout.print "?> "
created_url = $stdin.gets.strip created_url = $stdin.gets.strip
created_url = nil if created_url.empty? created_url = nil if created_url.empty?
return created_url if created_url.nil? || created_url.start_with?('https://') return created_url if created_url.nil? || created_url.start_with?('https://')
...@@ -185,7 +201,8 @@ class FeatureFlagCreator ...@@ -185,7 +201,8 @@ class FeatureFlagCreator
# Read type from $stdin unless is already set # Read type from $stdin unless is already set
options.type ||= FeatureFlagOptionParser.read_type options.type ||= FeatureFlagOptionParser.read_type
options.group ||= FeatureFlagOptionParser.read_group options.group ||= FeatureFlagOptionParser.read_group
options.rollout_issue_url ||= FeatureFlagOptionParser.read_issue_url(options) options.introduced_by_url ||= FeatureFlagOptionParser.read_introduced_by_url
options.rollout_issue_url ||= FeatureFlagOptionParser.read_rollout_issue_url(options)
$stdout.puts "\e[32mcreate\e[0m #{file_path}" $stdout.puts "\e[32mcreate\e[0m #{file_path}"
$stdout.puts contents $stdout.puts contents
......
...@@ -78,19 +78,21 @@ Only feature flags that have a YAML definition file can be used when running the ...@@ -78,19 +78,21 @@ Only feature flags that have a YAML definition file can be used when running the
```shell ```shell
$ bin/feature-flag my-feature-flag $ bin/feature-flag my-feature-flag
>> Please specify the group introducing feature flag, like `group::apm`: >> Please specify the group introducing feature flag, like `group::apm`:
?> group::memory ?> group::memory
>> Open this URL and fill the rest of details:
https://gitlab.com/gitlab-org/gitlab/-/issues/new?issue[title]=%5BFeature+flag%5D+Rollout+of+%60my-feature-flag%60&
>> Paste URL here, or enter to skip: >> If you have MR open, can you paste the URL here? (or enter to skip)
?> https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38602
>> Open this URL and fill the rest of details:
https://gitlab.com/gitlab-org/gitlab/-/issues/new?issue%5Btitle%5D=%5BFeature+flag%5D+Rollout+of+%60test-flag%60&issuable_template=Feature+Flag+Roll+Out
?> >> Paste URL of `rollout issue` here, or enter to skip:
create config/feature_flags/development/my_feature_flag.yml ?> https://gitlab.com/gitlab-org/gitlab/-/issues/232533
create config/feature_flags/development/test-flag.yml
--- ---
name: my_feature_flag name: test-flag
introduced_by_url: introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38602
rollout_issue_url: rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/232533
group: group::memory group: group::memory
type: development type: development
default_enabled: false default_enabled: false
......
...@@ -8,7 +8,7 @@ RSpec.describe 'bin/feature-flag' do ...@@ -8,7 +8,7 @@ RSpec.describe 'bin/feature-flag' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
describe FeatureFlagCreator do describe FeatureFlagCreator do
let(:argv) { %w[feature-flag-name -t development -g group::memory -i https://url] } let(:argv) { %w[feature-flag-name -t development -g group::memory -i https://url -m http://url] }
let(:options) { FeatureFlagOptionParser.parse(argv) } let(:options) { FeatureFlagOptionParser.parse(argv) }
let(:creator) { described_class.new(options) } let(:creator) { described_class.new(options) }
let(:existing_flag) { File.join('config', 'feature_flags', 'development', 'existing-feature-flag.yml') } let(:existing_flag) { File.join('config', 'feature_flags', 'development', 'existing-feature-flag.yml') }
...@@ -183,15 +183,51 @@ RSpec.describe 'bin/feature-flag' do ...@@ -183,15 +183,51 @@ RSpec.describe 'bin/feature-flag' do
end end
end end
describe '.rollout_issue_url' do describe '.read_introduced_by_url' do
let(:url) { 'https://merge-request' }
it 'reads type from $stdin' do
expect($stdin).to receive(:gets).and_return(url)
expect do
expect(described_class.read_introduced_by_url).to eq('https://merge-request')
end.to output(/can you paste the URL here/).to_stdout
end
context 'empty URL given' do
let(:url) { '' }
it 'skips entry' do
expect($stdin).to receive(:gets).and_return(url)
expect do
expect(described_class.read_introduced_by_url).to be_nil
end.to output(/can you paste the URL here/).to_stdout
end
end
context 'invalid URL given' do
let(:url) { 'invalid' }
it 'shows error message and retries' do
expect($stdin).to receive(:gets).and_return(url)
expect($stdin).to receive(:gets).and_raise('EOF')
expect do
expect { described_class.read_introduced_by_url }.to raise_error(/EOF/)
end.to output(/can you paste the URL here/).to_stdout
.and output(/URL needs to start with/).to_stderr
end
end
end
describe '.read_rollout_issue_url' do
let(:options) { OpenStruct.new(name: 'foo', type: :development) } let(:options) { OpenStruct.new(name: 'foo', type: :development) }
let(:url) { 'https://issue' } let(:url) { 'https://issue' }
it 'reads type from $stdin' do it 'reads type from $stdin' do
expect($stdin).to receive(:gets).and_return(url) expect($stdin).to receive(:gets).and_return(url)
expect do expect do
expect(described_class.read_issue_url(options)).to eq('https://issue') expect(described_class.read_rollout_issue_url(options)).to eq('https://issue')
end.to output(/Paste URL here/).to_stdout end.to output(/Paste URL of `rollout issue` here/).to_stdout
end end
context 'invalid URL given' do context 'invalid URL given' do
...@@ -202,8 +238,8 @@ RSpec.describe 'bin/feature-flag' do ...@@ -202,8 +238,8 @@ RSpec.describe 'bin/feature-flag' do
expect($stdin).to receive(:gets).and_raise('EOF') expect($stdin).to receive(:gets).and_raise('EOF')
expect do expect do
expect { described_class.read_issue_url(options) }.to raise_error(/EOF/) expect { described_class.read_rollout_issue_url(options) }.to raise_error(/EOF/)
end.to output(/Paste URL here/).to_stdout end.to output(/Paste URL of `rollout issue` here/).to_stdout
.and output(/URL needs to start/).to_stderr .and output(/URL needs to start/).to_stderr
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment