Skip to content

Commit

Permalink
Add update_asana_tasks_for_public_release
Browse files Browse the repository at this point in the history
  • Loading branch information
ayoy committed Oct 5, 2024
1 parent d38833a commit 5db4e03
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,7 @@ def self.return_value
end

def self.details
<<-DETAILS
This action performs the following tasks:
* increments the project build number,
* pushes the changes to the remote repository,
* if Asana release task is provided, updates the description with tasks included in the release.
DETAILS
"This action increments the project build number and pushes the changes to the remote repository."
end

def self.available_options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def self.run(params)
release_task_id = Helper::AsanaHelper.create_release_task(options[:platform], options[:version], options[:asana_user_id], options[:asana_access_token])
options[:release_task_id] = release_task_id

Helper::AsanaHelper.update_asana_tasks_for_release(options)
Helper::AsanaHelper.update_asana_tasks_for_internal_release(options)
end

def self.description
Expand Down Expand Up @@ -65,8 +65,8 @@ def self.available_options
description: "Github user handle",
optional: false,
type: String),
FastlaneCore::ConfigItem.new(key: :validation_section_id,
description: "Validation section ID",
FastlaneCore::ConfigItem.new(key: :target_section_id,
description: "Section ID in Asana where tasks included in the release should be moved",
optional: false,
type: String)
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "fastlane/action"
require "fastlane_core/configuration/config_item"
require "octokit"
require_relative "asana_create_action_item_action"
require_relative "../helper/asana_helper"
require_relative "../helper/ddg_apple_automation_helper"
require_relative "../helper/git_helper"
Expand All @@ -13,7 +14,19 @@ def self.run(params)
params[:platform] ||= Actions.lane_context[Actions::SharedValues::PLATFORM_NAME]
options = params.values
options[:version] = Helper::DdgAppleAutomationHelper.current_version
Helper::AsanaHelper.update_asana_tasks_for_release(options)

if options[:release_type] == 'internal'
Helper::AsanaHelper.update_asana_tasks_for_internal_release(options)
else
announcement_task_html_notes = Helper::AsanaHelper.update_asana_tasks_for_public_release(options)
Fastlane::Actions::AsanaCreateActionItemAction.run(
asana_access_token: options[:asana_access_token],
task_url: Helper::AsanaHelper.asana_task_url(options[:release_task_id]),
html_notes: announcement_task_html_notes,
github_handle: options[:github_handle],
is_scheduled_release: options[:is_scheduled_release]
)
end
end

def self.description
Expand All @@ -40,13 +53,25 @@ def self.available_options
[
FastlaneCore::ConfigItem.asana_access_token,
FastlaneCore::ConfigItem.github_token,
FastlaneCore::ConfigItem.is_scheduled_release,
FastlaneCore::ConfigItem.platform,
FastlaneCore::ConfigItem.new(key: :github_handle,
description: "Github user handle - required when release_type is 'public'",
optional: true,
type: String),
FastlaneCore::ConfigItem.new(key: :release_task_id,
description: "Asana release task ID",
optional: false,
type: String),
FastlaneCore::ConfigItem.new(key: :validation_section_id,
description: "Validation section ID",
FastlaneCore::ConfigItem.new(key: :release_type,
description: "Release type - 'internal' or 'public' (use 'public' for hotfixes)",
optional: true,
type: String,
verify_block: proc do |value|
UI.user_error!("release_type must be equal to 'internal' or 'public'") unless ['internal', 'public'].include?(value.to_s)
end),
FastlaneCore::ConfigItem.new(key: :target_section_id,
description: "Section ID in Asana where tasks included in the release should be moved",
optional: false,
type: String)
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<body>
As the last step of the process, post a message to <a href='https://app.asana.com/0/11984721910118/1204991209236659'>REVIEW / RELEASE</a> Asana project:
<ul>
<li>Set the title to <strong>macOS App Release <%= marketing_version %></strong></li>
<li>Copy the content below (between separators) and paste as the message body.</li>
</ul>
<hr>
<h1>Release notes</h1>
<%= release_notes %>
<h2>This release includes:</h2>
<ul>
<% task_ids.each do |task_id| %>
<li><a data-asana-gid="<%= task_id %>"/></li>
<% end %>
</ul>
<strong>Rollout</strong><br>
This is now rolling out to users. New users will receive this release immediately,
existing users will receive this gradually over the next few days. You can force an update now
by going to the DuckDuckGo menu in the menu bar and selecting "Check For Updates".
<hr>
</body>
119 changes: 113 additions & 6 deletions lib/fastlane/plugin/ddg_apple_automation/helper/asana_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ class AsanaHelper
IOS_APP_DEVELOPMENT_RELEASE_SECTION_ID = "1138897754570756"
MACOS_APP_DEVELOPMENT_RELEASE_SECTION_ID = "1202202395298964"

INCIDENTS_PARENT_TASK_ID = "1135688560894081"
CURRENT_OBJECTIVES_PROJECT_ID = "72649045549333"

def self.make_asana_client(asana_access_token)
Asana::Client.new do |c|
c.authentication(:access_token, asana_access_token)
Expand Down Expand Up @@ -206,15 +209,15 @@ def self.create_release_task(platform, version, assignee_id, asana_access_token)
task_id
end

# Updates asana tasks for a release
# Updates asana tasks for an internal release
#
# @param github_token [String] GitHub token
# @param asana_access_token [String] Asana access token
# @param release_task_id [String] Asana access token
# @param validation_section_id [String] ID of the 'Validation' section in the Asana project
# @param target_section_id [String] ID of the 'Validation' section in the Asana project
# @param version [String] version number
#
def self.update_asana_tasks_for_release(params)
def self.update_asana_tasks_for_internal_release(params)
UI.message("Checking latest public release in GitHub")
client = Octokit::Client.new(access_token: params[:github_token])
latest_public_release = client.latest_release(Helper::GitHelper.repo_name(params[:platform]))
Expand All @@ -239,7 +242,7 @@ def self.update_asana_tasks_for_release(params)
task_ids.append(params[:release_task_id])

UI.message("Moving tasks to Validation section")
move_tasks_to_section(task_ids, params[:validation_section_id], params[:asana_access_token])
move_tasks_to_section(task_ids, params[:target_section_id], params[:asana_access_token])
UI.success("All tasks moved to Validation section")

tag_name = release_tag_name(params[:version], params[:platform])
Expand All @@ -252,6 +255,81 @@ def self.update_asana_tasks_for_release(params)
UI.success("All tasks tagged with #{tag_name} tag")
end

# Updates asana tasks for a public release
#
# @param github_token [String] GitHub token
# @param asana_access_token [String] Asana access token
# @param release_task_id [String] Asana access token
# @param target_section_id [String] ID of the 'Done' section in the Asana project
# @param version [String] version number
#
def self.update_asana_tasks_for_public_release(params)
# Get the existing Asana tag for the release.
tag_name = release_tag_name(params[:version], params[:platform])
UI.message("Fetching #{tag_name} Asana tag")
tag_id = find_asana_release_tag(tag_name, params[:release_task_id], params[:asana_access_token])
UI.success("#{tag_name} tag URL: #{asana_tag_url(tag_id)}")

# Fetch task IDs for the release tag.
UI.message("Fetching tasks tagged with #{tag_name}")
task_ids = fetch_tasks_for_tag(tag_id, params[:asana_access_token])
UI.success("#{task_ids.count} task(s) found.")

# Move all tasks to Done section.
UI.message("Moving tasks to Done section")
move_tasks_to_section(task_ids, params[:target_section_id], params[:asana_access_token])
UI.success("All tasks moved to Done section")

# Complete tasks that don't require a post-mortem.
UI.message("Completing tasks")
complete_tasks(task_ids, params[:asana_access_token])
UI.message("Done completing tasks")

# Fetch current release notes from Asana release task.
UI.message("Fetching release notes from Asana release task (#{asana_task_url(params[:release_task_id])})")
release_notes = fetch_release_notes(params[:release_task_id], params[:asana_access_token])
UI.success("Release notes: #{release_notes}")

# Construct release announcement task description
UI.message("Preparing release announcement task")
task_ids.delete(params[:release_task_id])
Helper::ReleaseTaskHelper.construct_release_announcement_task_description(params[:version], release_notes, task_ids)
end

def self.fetch_tasks_for_tag(tag_id, asana_access_token)
asana_client = make_asana_client(asana_access_token)

task_ids = []
begin
response = asana_client.tasks.get_tasks_for_tag(tag_gid: tag_id, options: { fields: ["gid"] })
loop do
task_ids += response.map(&:gid)
response = response.next_page
break if response.nil?
end
rescue StandardError => e
UI.user_error!("Failed to fetch tasks for tag: #{e}")
end
task_ids
end

def self.fetch_subtasks(task_id, asana_access_token)
asana_client = make_asana_client(asana_access_token)

task_ids = []
begin
response = asana_client.tasks.get_subtasks_for_task(task_gid: task_id, options: { fields: ["gid"] })
loop do
task_ids += response.map(&:gid)
response = response.next_page
break if response.nil?
end
rescue StandardError => e
UI.user_error!("Failed to fetch subtasks of task #{task_id}: #{e}")
end
task_ids
end

def self.move_tasks_to_section(task_ids, section_id, asana_access_token)
asana_client = make_asana_client(asana_access_token)

Expand All @@ -270,16 +348,45 @@ def self.move_tasks_to_section(task_ids, section_id, asana_access_token)
end
end

def self.find_or_create_asana_release_tag(tag_name, release_task_id, asana_access_token)
def self.complete_tasks(task_ids, asana_access_token)
asana_client = make_asana_client(asana_access_token)

incident_task_ids = fetch_subtasks(INCIDENTS_PARENT_TASK_ID, asana_access_token)

task_ids.each do |task_id|
if incident_task_ids.include?(task_id)
UI.important("Not completing task #{task_id} because it's an incident task")
break
end

projects_ids = asana_client.projects.get_projects_for_task(task_gid: task_id, options: { fields: ["gid"] }).map(&:gid)
if projects_ids.include?(CURRENT_OBJECTIVES_PROJECT_ID)
UI.important("Not completing task #{task_id} because it's a Current Objective")
break
end

UI.message("Completing task #{task_id}")
asana_client.tasks.update_task(task_gid: task_id, completed: true)
UI.success("Task #{task_id} completed")
end
end

def self.find_asana_release_tag(tag_name, release_task_id, asana_access_token)
asana_client = make_asana_client(asana_access_token)
release_task_tags = asana_client.tasks.get_task(task_gid: release_task_id, options: { fields: ["tags"] }).tags

if (tag_id = release_task_tags.find { |t| t.name == tag_name }&.gid) && !tag_id.to_s.empty?
return tag_id
end
end

asana_client.tags.create_tag_for_workspace(workspace_gid: ASANA_WORKSPACE_ID, name: tag_name).gid
def self.find_or_create_asana_release_tag(tag_name, release_task_id, asana_access_token)
tag_id = find_asana_release_tag(tag_name, release_task_id, asana_access_token)
unless tag_id
asana_client = make_asana_client(asana_access_token)
tag_id = asana_client.tags.create_tag_for_workspace(workspace_gid: ASANA_WORKSPACE_ID, name: tag_name).gid
end
tag_id
end

def self.tag_tasks(tag_id, task_ids, asana_access_token)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ def self.construct_release_task_description(release_notes, task_ids)
Helper::AsanaHelper.sanitize_asana_html_notes(html_notes)
end

def self.construct_release_announcement_task_description(version, release_notes, task_ids)
template_file = Helper::DdgAppleAutomationHelper.path_for_asset_file("release_task_helper/templates/release_announcement_task_description.html.erb")
html_notes = Helper::DdgAppleAutomationHelper.process_erb_template(template_file, {
marketing_version: version,
release_notes: release_notes,
task_ids: task_ids
})
Helper::AsanaHelper.sanitize_asana_html_notes(html_notes)
end

def self.extract_release_notes(task_body, output_type: "html")
helper = AsanaReleaseNotesExtractor.new(output_type: "asana")
helper.extract_release_notes(task_body)
Expand Down

0 comments on commit 5db4e03

Please sign in to comment.