New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[supply] parallel uploads for meta per language #21474
Changes from 11 commits
5cca70f
1530c8f
4c6dbd9
8ef972f
72e2fb6
5d4c89a
2e0ca94
548b293
3307aa3
f5b6c1b
eec2964
f71a7c1
e5a4030
ed3b77d
31e9866
fd7568c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,10 @@ | ||
require 'fastlane_core' | ||
|
||
module Supply | ||
# rubocop:disable Metrics/ClassLength | ||
class Uploader | ||
UploadJob = Struct.new(:language, :version_code, :release_notes) | ||
|
||
def perform_upload | ||
FastlaneCore::PrintTable.print_values(config: Supply.config, hide_keys: [:issuer], mask_keys: [:json_key_data], title: "Summary for supply #{Fastlane::VERSION}") | ||
|
||
|
@@ -88,19 +92,15 @@ def perform_upload_meta(version_codes, track_name) | |
UI.user_error!("Unable to find the requested track - '#{Supply.config[:track]}'") unless track | ||
UI.user_error!("Could not find release for version code '#{version_code}' to update changelog") unless release | ||
|
||
release_notes = [] | ||
all_languages.each do |language| | ||
next if language.start_with?('.') # e.g. . or .. or hidden folders | ||
UI.message("Preparing to upload for language '#{language}'...") | ||
|
||
listing = client.listing_for_language(language) | ||
|
||
upload_metadata(language, listing) unless Supply.config[:skip_upload_metadata] | ||
upload_images(language) unless Supply.config[:skip_upload_images] | ||
upload_screenshots(language) unless Supply.config[:skip_upload_screenshots] | ||
release_notes << upload_changelog(language, version_code) unless Supply.config[:skip_upload_changelogs] | ||
end | ||
release_notes = Queue.new | ||
upload_worker = create_meta_upload_worker | ||
upload_worker.batch_enqueue( | ||
# skip . or .. or hidden folders | ||
all_languages.reject { |lang| lang.start_with?('.') }.map { |lang| UploadJob.new(lang, version_code, release_notes) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
) | ||
upload_worker.start | ||
|
||
release_notes = Array.new(release_notes.size) { release_notes.pop } # Queue to Array | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not a fan or reusing the same variable name for two different things. Maybe rename the original variable There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. makes sense. will change to |
||
upload_changelogs(release_notes, release, track, track_name) unless release_notes.empty? | ||
end | ||
end | ||
|
@@ -511,6 +511,21 @@ def obb_expansion_file_type(obb_file_path) | |
'patch' | ||
end | ||
end | ||
|
||
def create_meta_upload_worker | ||
FastlaneCore::QueueWorker.new do |job| | ||
UI.message("Preparing uploads for language '#{job.language}'...") | ||
start_time = Time.now | ||
listing = client.listing_for_language(job.language) | ||
upload_metadata(job.language, listing) unless Supply.config[:skip_upload_metadata] | ||
upload_images(job.language) unless Supply.config[:skip_upload_images] | ||
upload_screenshots(job.language) unless Supply.config[:skip_upload_screenshots] | ||
job.release_notes << upload_changelog(job.language, job.version_code) unless Supply.config[:skip_upload_changelogs] | ||
UI.message("Uploaded all items for language '#{job.language}'... (#{Time.now - start_time} secs)") | ||
rescue => error | ||
UI.abort_with_message!("#{job.language} - #{error}") | ||
end | ||
end | ||
end | ||
# rubocop:enable Metrics/ClassLength | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
en-US changelog 1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
en-US changelog 2 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
en-US changelog -1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
en-US full description |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
en-US short description |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
en-US title |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
en-US video |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
fr-FR changelog 1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
fr-FR changelog 2 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
fr-FR changelog -1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
fr-FR full description |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
fr-FR short description |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
fr-FR title |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
fr-FR video |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ja-JP changelog 1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ja-JP changelog 2 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ja-JP changelog -1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ja-JP full description |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ja-JP short description |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ja-JP title |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ja-JP video |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -203,22 +203,69 @@ def find_obbs | |||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
describe '#perform_upload' do | ||||||||||||||||||||||||||||
let(:client) { double('client') } | ||||||||||||||||||||||||||||
let(:config) { { apk: 'some/path/app.apk' } } | ||||||||||||||||||||||||||||
RSpec::Matchers.define(:same_localizedtext_as) do |expected_arr| | ||||||||||||||||||||||||||||
match do |actual_arr| | ||||||||||||||||||||||||||||
actual_arr.sort_by(&:language).zip(expected_arr.sort_by(&:language)).map do |actual_localization, expected_localization| | ||||||||||||||||||||||||||||
actual_localization.language == expected_localization.language && actual_localization.text == expected_localization.text | ||||||||||||||||||||||||||||
end.reduce(:&) | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't that be Also, I wonder if using the built-in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used As for the matcher, I actually had to write a new one because we lack the support for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Then in that case I'd rather have you use
What about declaring that class AndroidPublisher::LocalizedText
def ==(other)
self.language == other.language && self.text == other.text
end
end There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Besides, I think your implementation of that matcher has a hidden bug, because in $ pry
[1] pry(main)> a = [1,2]
=> [1, 2]
[2] pry(main)> b = [3,4,5]
=> [3, 4, 5]
[3] pry(main)> a.zip(b)
=> [[1, 3], [2, 4]] Which means that if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The I forgot about that we can open classes anywhere we want, that was a good call 😄 . I added the |
||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
before do | ||||||||||||||||||||||||||||
Supply.config = config | ||||||||||||||||||||||||||||
allow(Supply::Client).to receive(:make_from_config).and_return(client) | ||||||||||||||||||||||||||||
allow(client).to receive(:upload_apk).with(config[:apk]).and_return(1) # newly uploaded version code | ||||||||||||||||||||||||||||
allow(client).to receive(:begin_edit).and_return(nil) | ||||||||||||||||||||||||||||
allow(client).to receive(:commit_current_edit!).and_return(nil) | ||||||||||||||||||||||||||||
shared_examples 'run supply to upload metadata' do | ||||||||||||||||||||||||||||
describe '#perform_upload' do | ||||||||||||||||||||||||||||
subject(:release) { OpenStruct.new({ version_codes: version_codes }) } | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably worth using a |
||||||||||||||||||||||||||||
let(:client) { double('client') } | ||||||||||||||||||||||||||||
let(:config) { { apk_paths: version_codes.map { |v_code| "some/path/app-v#{v_code}.apk" }, metadata_path: 'supply/spec/fixtures/metadata/android', track: 'track-name' } } | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
before do | ||||||||||||||||||||||||||||
Supply.config = config | ||||||||||||||||||||||||||||
allow(Supply::Client).to receive(:make_from_config).and_return(client) | ||||||||||||||||||||||||||||
version_codes.each do |version_code| | ||||||||||||||||||||||||||||
allow(client).to receive(:upload_apk).with("some/path/app-v#{version_code}.apk").and_return(version_code) # newly uploaded version code | ||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||
allow(client).to receive(:upload_changelogs).and_return(nil) | ||||||||||||||||||||||||||||
allow(client).to receive(:tracks).with('track-name').and_return([OpenStruct.new({ releases: [ release ] })]) | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not tested, but I think you could use an object double like |
||||||||||||||||||||||||||||
languages.each do |lang| | ||||||||||||||||||||||||||||
allow(client).to receive(:listing_for_language).with(lang).and_return(Supply::Listing.new(client, lang)) | ||||||||||||||||||||||||||||
allow(client).to receive(:update_listing_for_language).with({ language: lang, full_description: "#{lang} full description", short_description: "#{lang} short description", title: "#{lang} title", video: "#{lang} video" }) | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this one be an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good call. will change. |
||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||
allow(client).to receive(:begin_edit).and_return(nil) | ||||||||||||||||||||||||||||
allow(client).to receive(:commit_current_edit!).and_return(nil) | ||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
it 'should update track with correct version codes and optional changelog' do | ||||||||||||||||||||||||||||
uploader = Supply::Uploader.new | ||||||||||||||||||||||||||||
expect(uploader).to receive(:update_track).with(version_codes).once | ||||||||||||||||||||||||||||
version_codes.each do |version_code| | ||||||||||||||||||||||||||||
expected_notes = [] | ||||||||||||||||||||||||||||
languages.each do |lang| | ||||||||||||||||||||||||||||
expected_notes << AndroidPublisher::LocalizedText.new( | ||||||||||||||||||||||||||||
language: lang, | ||||||||||||||||||||||||||||
text: "#{lang} changelog #{with_explicit_changelogs ? version_code : -1}" | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. haha, my procedural instinct blinded me from idiomatic ruby 😄 |
||||||||||||||||||||||||||||
# check if at least one of the assignments of release_notes is what we expect | ||||||||||||||||||||||||||||
expect(release).to receive(:release_notes=).with(same_localizedtext_as(expected_notes)) | ||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
uploader.perform_upload | ||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
describe '#peform_upload with metadata and explicit changelogs' do | ||||||||||||||||||||||||||||
it_behaves_like 'run supply to upload metadata' do | ||||||||||||||||||||||||||||
let(:version_codes) { [1, 2] } | ||||||||||||||||||||||||||||
let(:languages) { ['en-US', 'fr-FR', 'ja-JP'] } | ||||||||||||||||||||||||||||
let(:with_explicit_changelogs) { true } | ||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if it's just me being used to that pattern or an actual ruby/rspec codestyle recommendation, but I'm more used to see this kind of parametric tests be implemented as parameters passed to See https://railsware.com/blog/using-configurable-shared-examples-in-rspec/ for examples of such pattern with parametrized There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks for pointing that out. I'll update the test. |
||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
it 'should update track with correct version codes' do | ||||||||||||||||||||||||||||
uploader = Supply::Uploader.new | ||||||||||||||||||||||||||||
expect(uploader).to receive(:update_track).with([1]).once | ||||||||||||||||||||||||||||
uploader.perform_upload | ||||||||||||||||||||||||||||
describe '#peform_upload with metadata and default changelogs' do | ||||||||||||||||||||||||||||
it_behaves_like 'run supply to upload metadata' do | ||||||||||||||||||||||||||||
let(:version_codes) { [3] } | ||||||||||||||||||||||||||||
let(:languages) { ['en-US', 'fr-FR', 'ja-JP'] } | ||||||||||||||||||||||||||||
let(:with_explicit_changelogs) { false } | ||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One difference in behavior before/after this change is that before,
release_notes
was reset to[]
on each iteration of theversion_codes
loop, while now it's not, and thus is common to allversion_codes
.That might break the logic (and potentially mix up the release notes published when there are different version codes with different
changelogs/*.txt
files.You might thus want to keep this reset of
release_notes = []
inside the loop to cover for this case.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nicely observed 😄 I haven't been testing the multi version codes scenario. Will fix.