Skip to content

Add named params and yard docs to create_milestone method at GithubHelper #426

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,27 @@ def self.run(params)

github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token])
last_stone = github_helper.get_last_milestone(repository)

UI.message("Last detected milestone: #{last_stone[:title]} due on #{last_stone[:due_on]}.")

milestone_duedate = last_stone[:due_on]
milestone_duration = params[:milestone_duration]
newmilestone_duedate = (milestone_duedate.to_datetime.next_day(milestone_duration).to_time).utc
newmilestone_number = Fastlane::Helper::Ios::VersionHelper.calc_next_release_version(last_stone[:title])
number_of_days_from_code_freeze_to_release = params[:number_of_days_from_code_freeze_to_release]
# Because of the app stores review process, we submit the binary 3 days before the intended release date.
# Using 3 days is mostly for historical reasons, for a long time, we've been submitting apps on Friday and releasing them on Monday.
days_until_submission = params[:need_appstore_submission] ? (number_of_days_from_code_freeze_to_release - 3) : milestone_duration

UI.message("Next milestone: #{newmilestone_number} due on #{newmilestone_duedate}.")
github_helper.create_milestone(repository, newmilestone_number, newmilestone_duedate, milestone_duration, number_of_days_from_code_freeze_to_release, params[:need_appstore_submission])

github_helper.create_milestone(
repository: repository,
title: newmilestone_number,
due_date: newmilestone_duedate,
days_until_submission: days_until_submission,
days_until_release: number_of_days_from_code_freeze_to_release
)
end

def self.description
Expand Down
28 changes: 18 additions & 10 deletions lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,23 @@ def get_last_milestone(repository)
last_stone
end

def create_milestone(repository, newmilestone_number, newmilestone_duedate, newmilestone_duration, number_of_days_from_code_freeze_to_release, need_submission)
# If there is a review process, we want to submit the binary 3 days before its release
#
# Using 3 days is mostly for historical reasons where we release the apps on Monday and submit them on Friday.
days_until_submission = need_submission ? (number_of_days_from_code_freeze_to_release - 3) : newmilestone_duration
submission_date = newmilestone_duedate.to_datetime.next_day(days_until_submission)
release_date = newmilestone_duedate.to_datetime.next_day(number_of_days_from_code_freeze_to_release)
# Creates a new milestone
#
# @param [String] repository The repository name, including the organization (e.g. `wordpress-mobile/wordpress-ios`)
# @param [String] title The name of the milestone we want to create (e.g.: `16.9`)
# @param [Time] due_date Milestone due date—which will also correspond to the code freeze date
# @param [Integer] days_until_submission Number of days from code freeze to submission to the App Store / Play Store
# @param [Integer] days_until_release Number of days from code freeze to release
#
def create_milestone(repository:, title:, due_date:, days_until_submission:, days_until_release:)
UI.user_error!('days_until_release must be greater than zero.') unless days_until_release.positive?
UI.user_error!('days_until_submission must be greater than zero.') unless days_until_submission.positive?
UI.user_error!('days_until_release must be greater or equal to days_until_submission.') unless days_until_release >= days_until_submission

submission_date = due_date.to_datetime.next_day(days_until_submission)
release_date = due_date.to_datetime.next_day(days_until_release)
comment = <<~MILESTONE_DESCRIPTION
Code freeze: #{newmilestone_duedate.to_datetime.strftime('%B %d, %Y')}
Code freeze: #{due_date.to_datetime.strftime('%B %d, %Y')}
App Store submission: #{submission_date.strftime('%B %d, %Y')}
Release: #{release_date.strftime('%B %d, %Y')}
MILESTONE_DESCRIPTION
Expand All @@ -96,9 +104,9 @@ def create_milestone(repository, newmilestone_number, newmilestone_duedate, newm
#
# This is a bug in the GitHub API, not in our date computation logic.
# To solve this, we trick it by forcing the time component of the ISO date we send to be `12:00:00Z`.
options[:due_on] = newmilestone_duedate.strftime('%Y-%m-%dT12:00:00Z')
options[:due_on] = due_date.strftime('%Y-%m-%dT12:00:00Z')
options[:description] = comment
client.create_milestone(repository, newmilestone_number, options)
client.create_milestone(repository, title, options)
end

# Creates a Release on GitHub as a Draft
Expand Down
161 changes: 146 additions & 15 deletions spec/github_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -217,31 +217,162 @@ def get_milestone(milestone_name:)
allow(Octokit::Client).to receive(:new).and_return(client)
end

it 'has the correct dates to code freeze without submission' do
comment = "Code freeze: October 22, 2022\nApp Store submission: November 15, 2022\nRelease: October 25, 2022\n"
options = { due_on: '2022-10-22T12:00:00Z', description: comment }
it 'computes the correct dates for standard period' do
due_date = '2022-12-02T08:00:00Z'.to_time.utc
options = {
due_on: '2022-12-02T12:00:00Z',
description: "Code freeze: December 02, 2022\nApp Store submission: December 06, 2022\nRelease: December 09, 2022\n"
}

expect(client).to receive(:create_milestone).with(test_repo, test_milestone_number, options)
create_milestone(need_submission: false, milestone_duration: 24, days_code_freeze: 3)
create_milestone(due_date: due_date, days_until_submission: 4, days_until_release: 7)
end

it 'has the correct dates to code freeze with submission' do
comment = "Code freeze: October 22, 2022\nApp Store submission: October 22, 2022\nRelease: October 25, 2022\n"
options = { due_on: '2022-10-22T12:00:00Z', description: comment }
it 'computes the correct dates when submission and release dates are in the same day' do
due_date = '2022-12-02T08:00:00Z'.to_time.utc
options = {
due_on: '2022-12-02T12:00:00Z',
description: "Code freeze: December 02, 2022\nApp Store submission: December 03, 2022\nRelease: December 03, 2022\n"
}

expect(client).to receive(:create_milestone).with(test_repo, test_milestone_number, options)
create_milestone(need_submission: true, milestone_duration: 19, days_code_freeze: 3)
create_milestone(due_date: due_date, days_until_submission: 1, days_until_release: 1)
end

it 'computes the correct dates when the due date is on the verge of a DST day change' do
# Europe DST starts on the last Sunday of March, and ends on the last Sunday of October
Time.use_zone('Europe/London') do
# March 27th, 2022 is the exact day that London switches to the DST (+1h)
# If the due date is too close to the next day, a day change will happen
# So, 2022-03-27 23:00:00Z will be exactly 2022-03-28 00:00:00 +0100 at the DST change
Comment on lines +245 to +247
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

due_date = Time.zone.parse('2022-03-27 23:00:00Z')
options = {
due_on: '2022-03-28T12:00:00Z',
Comment on lines +248 to +250
Copy link
Contributor

@AliSoftware AliSoftware Nov 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice edge case of having 2022-03-27 23:00:00Z being exactly 2022-03-28 00:00:00 +0100 because that day is the exact day of the DST change, when London has switched to +0100… while for your next test case on line 259, the 2022-03-26 23:00:00Z date still corresponds to 2022-03-26 23:00:00 +0000 because this is just before the DST change when London were still on +0000

Took me a while to figure that out though and to triple-check that this difference between the due_date (2022-03-27) and the due_on (2022-03-28) was the correct and expected behavior here, and not an incorrect value due to our date math being buggy or whatnot. Phew!

Could maybe benefit from some small code comment (here and on line 259) to give more context about those tricky details?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For sure! 😄

Besides the comments, I also added another test to cover the scenario of the due_date is not close enough to the end of the day to exist a date change (Line 259) and also add a new case, to test what happens if you have a date close to the day change, but not in DST (Line 278).

description: "Code freeze: March 28, 2022\nApp Store submission: March 30, 2022\nRelease: March 31, 2022\n"
}

expect(client).to receive(:create_milestone).with(test_repo, test_milestone_number, options)
create_milestone(due_date: due_date, days_until_submission: 2, days_until_release: 3)
end
end

it 'computes the correct dates when the due date is on DST but has no day change' do
# Europe DST starts on the last Sunday of March, and ends on the last Sunday of October
Time.use_zone('Europe/London') do
# March 27th, 2022 is the exact day that London switches to the DST (+1h)
# If the due date is not close enough at the day change, nothing will occur.
# So, 2022-03-27 22:00:00Z will be exactly 2022-03-27 23:00:00 +0100 at the DST change.
due_date = Time.zone.parse('2022-03-27 22:00:00Z')
options = {
due_on: '2022-03-27T12:00:00Z',
description: "Code freeze: March 27, 2022\nApp Store submission: March 29, 2022\nRelease: March 30, 2022\n"
}

expect(client).to receive(:create_milestone).with(test_repo, test_milestone_number, options)
create_milestone(due_date: due_date, days_until_submission: 2, days_until_release: 3)
end
end

it 'computes the correct dates when the due date is one day before a DST change' do
# Europe DST starts on the last Sunday of March, and ends on the last Sunday of October
Time.use_zone('Europe/London') do
# As London changes to DST on March 27th, the date shouldn't be changed
# So, 2022-03-26 23:00:00Z will be exactly 2022-03-26 23:00:00 +0000 at this Timezone.
due_date = Time.zone.parse('2022-03-26 23:00:00Z')
options = {
due_on: '2022-03-26T12:00:00Z',
description: "Code freeze: March 26, 2022\nApp Store submission: March 28, 2022\nRelease: March 29, 2022\n"
}

expect(client).to receive(:create_milestone).with(test_repo, test_milestone_number, options)
create_milestone(due_date: due_date, days_until_submission: 2, days_until_release: 3)
end
end

it 'computes the correct dates when the offset is between DST endings' do
# Europe DST starts on the last Sunday of March, and ends on the last Sunday of October
Time.use_zone('Europe/London') do
due_date = Time.zone.parse('2022-10-30 23:00:00Z')
options = {
due_on: '2022-10-30T12:00:00Z',
description: "Code freeze: October 30, 2022\nApp Store submission: March 19, 2023\nRelease: March 25, 2023\n"
}

expect(client).to receive(:create_milestone).with(test_repo, test_milestone_number, options)
create_milestone(due_date: due_date, days_until_submission: 140, days_until_release: 146)
end
end

it 'computes the correct dates when the release and submission dates are at the last day of a DST change' do
# Europe DST starts on the last Sunday of March, and ends on the last Sunday of October
Time.use_zone('Europe/London') do
due_date = Time.zone.parse('2022-03-27 23:00:00Z')
options = {
due_on: '2022-03-28T12:00:00Z',
description: "Code freeze: March 28, 2022\nApp Store submission: October 30, 2022\nRelease: October 31, 2022\n"
}

expect(client).to receive(:create_milestone).with(test_repo, test_milestone_number, options)
create_milestone(due_date: due_date, days_until_submission: 216, days_until_release: 217)
end
end

def create_milestone(need_submission:, milestone_duration:, days_code_freeze:)
it 'computes the correct dates when the due date is before Europe and USA DST changes and ends inside a DST period on Europe' do
# USA DST starts on the second Sunday in March. and ends on the first Sunday in November
# Europe DST starts on the last Sunday of March, and ends on the last Sunday in October
Time.use_zone('Europe/London') do
due_date = Time.zone.parse('2022-03-05 23:00:00Z')
options = {
due_on: '2022-03-05T12:00:00Z',
description: "Code freeze: March 05, 2022\nApp Store submission: May 04, 2022\nRelease: May 05, 2022\n"
}

expect(client).to receive(:create_milestone).with(test_repo, test_milestone_number, options)
create_milestone(due_date: due_date, days_until_submission: 60, days_until_release: 61)
end
end

it 'computes the correct dates when the due date is before Europe and USA DST changes and ends inside a DST period on USA' do
# USA DST starts on the second Sunday in March. and ends on the first Sunday in November
# Europe DST starts on the last Sunday of March, and ends on the last Sunday in October
Time.use_zone('America/Los_Angeles') do
due_date = Time.zone.parse('2022-03-05 23:00:00Z')
options = {
due_on: '2022-03-05T12:00:00Z',
description: "Code freeze: March 05, 2022\nApp Store submission: May 04, 2022\nRelease: May 05, 2022\n"
}

expect(client).to receive(:create_milestone).with(test_repo, test_milestone_number, options)
create_milestone(due_date: due_date, days_until_submission: 60, days_until_release: 61)
end
end

it 'raises an error if days_until_submission is less than or equal zero' do
due_date = '2022-10-20T08:00:00Z'.to_time.utc
expect { create_milestone(due_date: due_date, days_until_submission: 0, days_until_release: 5) }
.to raise_error(FastlaneCore::Interface::FastlaneError, 'days_until_submission must be greater than zero.')
end

it 'raises an error if days_until_release is less than or equal zero' do
due_date = '2022-10-20T08:00:00Z'.to_time.utc
expect { create_milestone(due_date: due_date, days_until_submission: 12, days_until_release: -8) }
.to raise_error(FastlaneCore::Interface::FastlaneError, 'days_until_release must be greater than zero.')
end

it 'raises an error if days_until_submission is greater than days_until_release' do
due_date = '2022-10-20T08:00:00Z'.to_time.utc
expect { create_milestone(due_date: due_date, days_until_submission: 14, days_until_release: 3) }
.to raise_error(FastlaneCore::Interface::FastlaneError, 'days_until_release must be greater or equal to days_until_submission.')
end

def create_milestone(due_date:, days_until_submission:, days_until_release:)
helper = described_class.new(github_token: 'Fake-GitHubToken-123')
helper.create_milestone(
test_repo,
test_milestone_number,
test_milestone_duedate.to_time.utc,
milestone_duration,
days_code_freeze,
need_submission
repository: test_repo,
title: test_milestone_number,
due_date: due_date,
days_until_submission: days_until_submission,
days_until_release: days_until_release
)
end
end
Expand Down