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 6 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.') if days_until_release <= 0
UI.user_error!('days_until_submission must be greater than zero.') if days_until_submission <= 0
UI.user_error!('days_until_release must be greather than days_until_submission') if days_until_submission >= days_until_release
Copy link
Contributor

Choose a reason for hiding this comment

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

It feels to me that this would be more readable if the conditions matched the messages described in the user_error!, rather than being the negative of those ("must be greater than zero" --> use a days_until-release > 0 condition), otherwise it needs your brain to think what the negative should be to ensure that the if is correct and matching the message.

In fact, for the 3rd case I think the condition and messages are incorrect, because it could be valid to consider that the submission and the release could be on the same day (that's not a common/used setup for any of our apps currently, but might be one day, at least it shouldn't be invalid imho)

Suggested change
UI.user_error!('days_until_release must be greater than zero.') if days_until_release <= 0
UI.user_error!('days_until_submission must be greater than zero.') if days_until_submission <= 0
UI.user_error!('days_until_release must be greather than days_until_submission') if days_until_submission >= days_until_release
UI.user_error!('days_until_release must be greater than zero.') unless days_until_release > 0
UI.user_error!('days_until_submission must be greater than zero.') unless days_until_submission > 0
UI.user_error!('days_until_release must be greater or equal to days_until_submission') unless days_until_release >= days_until_submission

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the feedback, I was not aware of the unless keyword, indeed is more legible that way. 😄


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
76 changes: 61 additions & 15 deletions spec/github_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -217,31 +217,77 @@ 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-10-20T08:00:00Z'.to_time.utc
Copy link
Contributor

Choose a reason for hiding this comment

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

This example date feels dangerously close to the actual DST change date zone—e.g. in Europe, DST starts on the last Sunday of March and ends on the last Sunday of October, so it happened on Oct 30 this year. Which is still outside the Oct 20–Oct 27 date range that is used in this example, but still very close.

options = {
due_on: '2022-10-20T12:00:00Z',
description: "Code freeze: October 20, 2022\nApp Store submission: October 24, 2022\nRelease: October 27, 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 the due date is on the verge of a DST day change' do
# The PST to PDT (DST) change is made on the second Sunday in March and finishes on the first Sunday in November
Time.zone = 'Pacific Time (US & Canada)'
due_date = '2022-06-18T23:00:00Z'.to_time
Copy link
Contributor

Choose a reason for hiding this comment

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

# The PST to PDT (DST) change is made on the second Sunday in March and finishes on the first Sunday in November

Then that means that the date you used (2022-06-18 is not even close to a DST change then, since it's neither close to March nor November… so this actually tests the opposite case?

Copy link
Contributor

Choose a reason for hiding this comment

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

I wanted to check the PST to PDT details and landed on Wikipedia. Here's the link for future reference, just in case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had problems testing this properly, to be honest. It confuses me that the fact that PST/PDT changes are made at 2 am, Also for some reason, the date to not change when I use the 14 march (the actual DST change date) as my date, even forcing the timezone to be at PST. So I just used any date inside the DST changes, given the date that was put was on UTC.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Due to the Ci failure, I think that this is not the proper way to enforce the use of a specific timezone (although it passes here on my local test run). I'll try to build it differently, I was researching other ways to do this and found this one, which is wrapping the test on the timezone change block:

Time.use_zone('Europe/London') do
  due_date = Time.zone.parse('2022-03-27 23:00:00')
  # ...
end

TBH, I'm not sure if this is the correct way to do it either, as there are a lot of different answers on how to do this (most of them on rails), on StackOverflow. If you folks have suggestions on how to deal with this, I would love to hear @AliSoftware @mokagio 😄

Note: I'll use the DST from Europe this time, that makes more sense in my head than the American ones. 😅

Copy link
Contributor

Choose a reason for hiding this comment

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

From the top of my head I don't remember what's the right way to do it, though Time.use_zoen(…) do…end feels like a good lead. In any case, I'd recommend picking a date that you know is before both Europe and Pacific DST change dates, and use an offset for the date addition that is large enough to end up after both DST change dates as well. E.g. a date at start of Oct and an offset of 60 days, or something like that, to be sure to cover both cases anyway, even if Time.use_zone should do the trick.

options = {
due_on: '2022-06-19T12:00:00Z',
description: "Code freeze: June 19, 2022\nApp Store submission: June 21, 2022\nRelease: June 22, 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: 2, days_until_release: 3)
end

def create_milestone(need_submission:, milestone_duration:, days_code_freeze:)
it 'computes the correct dates when the due date is on DST but has no day change' do
# The PST to PDT (DST) change is made on the second Sunday in March and finishes on the first Sunday in November
Time.zone = 'Pacific Time (US & Canada)'
due_date = '2022-06-18T22:00:00Z'.to_time
options = {
due_on: '2022-06-18T12:00:00Z',
description: "Code freeze: June 18, 2022\nApp Store submission: June 20, 2022\nRelease: June 21, 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

context 'with input validation' do
Copy link
Contributor

Choose a reason for hiding this comment

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

Out curiosity, why did you choose to use a context here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought that will be more organized and improve the readability to have the input validations under the context, instead of having them grouped under the main describe block.

Although, now that you ask that question, it made me think if this is really useful at all 🤔
As I do not have any additional behavior to input on those methods, the context here was just misused. 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

I think Gio's question was more about why using context vs describe to group the tests. Functionally that doesn't make a difference, but semantically, context is more for when [in situation X] or with [given use case or situation]

Copy link
Contributor

Choose a reason for hiding this comment

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

@raafaelima @AliSoftware, a bit of both 😄

Why grouping the input validation? (You answered that already) And why with context vs describe?

It is my understanding that context is an alias for describe but I can't find the code where that happens. The closer I could get to was the DSL source code which is very meta, and the documentation which implies they are.

In particular, the example from the docs use context with a "when ..." like Olivier suggested above.

RSpec.describe "something" do
  context "when something is a certain way" do
    it "does something" do
      # example code goes here
    end
  end
end

That pattern matches my experience with and mental model of describe vs context and it's why seeing this code tickled my interest and made me ask the question 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

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 submission date is after the release date' 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 greather than days_until_submission')
end

it 'raises an error if submission date is equal to release date' do
due_date = '2022-10-20T08:00:00Z'.to_time.utc
expect { create_milestone(due_date: due_date, days_until_submission: 1, days_until_release: 1) }
.to raise_error(FastlaneCore::Interface::FastlaneError, 'days_until_release must be greather than days_until_submission')
end
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be a valid case imho (see other comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I was a bit confused if this should be or not be a valid case, Initially, I think it was, but as you explained here about the process, it feels that the submissions always need to be before release, and not fall in the same day. I'll change that test to be a valid case, thanks for the input. 😄

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