diff --git a/lib/fastlane/plugin/ddg_apple_automation/actions/asana_add_comment_action.rb b/lib/fastlane/plugin/ddg_apple_automation/actions/asana_add_comment_action.rb
index d41420c..d2aaa13 100644
--- a/lib/fastlane/plugin/ddg_apple_automation/actions/asana_add_comment_action.rb
+++ b/lib/fastlane/plugin/ddg_apple_automation/actions/asana_add_comment_action.rb
@@ -33,7 +33,7 @@ def self.run(params)
template_content = Helper::DdgAppleAutomationHelper.load_file(template_file)
return unless template_content
- html_text = process_template_content(template_content)
+ html_text = Helper::DdgAppleAutomationHelper.sanitize_html_and_replace_env_vars(template_content)
create_story(asana_access_token, task_id, html_text: html_text)
end
end
@@ -110,14 +110,6 @@ def self.create_story(asana_access_token, task_id, text: nil, html_text: nil)
UI.user_error!("Failed to post comment: #{e}")
end
end
-
- def self.process_template_content(template_content)
- template_content.gsub(/\$\{(\w+)\}/) { ENV.fetch($1, '') } # replace environment variables
- .gsub(/\s+/, ' ') # replace multiple whitespaces with a single space
- .gsub(/>\s+, '><') # remove spaces between HTML tags
- .strip # remove leading and trailing whitespaces
- .gsub(%r{
}, "\n") # replace
tags with newlines
- end
end
end
end
diff --git a/lib/fastlane/plugin/ddg_apple_automation/actions/asana_create_action_item_action.rb b/lib/fastlane/plugin/ddg_apple_automation/actions/asana_create_action_item_action.rb
new file mode 100644
index 0000000..040410c
--- /dev/null
+++ b/lib/fastlane/plugin/ddg_apple_automation/actions/asana_create_action_item_action.rb
@@ -0,0 +1,135 @@
+require "fastlane/action"
+require "fastlane_core/configuration/config_item"
+require "asana"
+require "yaml"
+require_relative "../helper/ddg_apple_automation_helper"
+require_relative "../helper/github_actions_helper"
+require_relative "asana_add_comment_action"
+require_relative "asana_get_release_automation_subtask_id_action"
+require_relative "asana_get_user_id_for_github_handle_action"
+require_relative "asana_extract_task_id_action"
+require_relative "asana_extract_task_assignee_action"
+
+module Fastlane
+ module Actions
+ class AsanaCreateActionItemAction < Action
+ def self.run(params)
+ token = params[:asana_access_token]
+ task_url = params[:task_url]
+ task_name = params[:task_name]
+ notes = params[:notes]
+ html_notes = params[:html_notes]
+ template_name = params[:template_name]
+ is_scheduled_release = params[:is_scheduled_release]
+ github_handle = params[:github_handle]
+
+ task_id = AsanaExtractTaskIdAction.run(task_url: task_url)
+ automation_subtask_id = AsanaGetReleaseAutomationSubtaskIdAction.run(task_url: task_url, asana_access_token: token)
+ if is_scheduled_release
+ assignee_id = AsanaExtractTaskAssigneeAction.run(task_id: task_id, asana_access_token: token)
+ else
+ if github_handle.to_s.empty?
+ UI.user_error!("Github handle cannot be empty for manual release")
+ return
+ end
+ assignee_id = AsanaGetUserIdForGithubHandleAction.run(github_handle: github_handle, asana_access_token: token)
+ end
+
+ Helper::GitHubActionsHelper.set_output("asana_assignee_id", assignee_id)
+
+ if template_name
+ template_file = Helper::DdgAppleAutomationHelper.path_for_asset_file("asana_create_action_item/templates/#{template_name}.yml")
+ template_content = YAML.safe_load(Helper::DdgAppleAutomationHelper.load_file(template_file))
+ task_name = Helper::DdgAppleAutomationHelper.sanitize_html_and_replace_env_vars(template_content["name"])
+ html_notes = Helper::DdgAppleAutomationHelper.sanitize_html_and_replace_env_vars(template_content["html_notes"])
+ end
+
+ begin
+ subtask = create_subtask(
+ token: token,
+ task_id: automation_subtask_id,
+ assignee_id: assignee_id,
+ task_name: task_name,
+ notes: notes,
+ html_notes: html_notes
+ )
+ rescue StandardError => e
+ UI.user_error!("Failed to create subtask for task: #{e}")
+ end
+
+ Helper::GitHubActionsHelper.set_output("asana_new_task_id", subtask.gid) if subtask&.gid
+ end
+
+ def self.description
+ "Add a subtask to Asana Release Automation Task"
+ end
+
+ def self.authors
+ ["DuckDuckGo"]
+ end
+
+ def self.return_value
+ ""
+ end
+
+ def self.details
+ "Adds a task with an action item to the Asana release task's 'Automation' subtask"
+ end
+
+ def self.available_options
+ [
+ FastlaneCore::ConfigItem.asana_access_token,
+ FastlaneCore::ConfigItem.new(key: :task_url,
+ description: "Asana release task URL",
+ optional: false,
+ type: String),
+ FastlaneCore::ConfigItem.new(key: :task_name,
+ description: "Task name",
+ optional: true,
+ type: String),
+ FastlaneCore::ConfigItem.new(key: :notes,
+ description: "Task notes",
+ optional: true,
+ type: String),
+ FastlaneCore::ConfigItem.new(key: :html_notes,
+ description: "Task HTML notes",
+ optional: true,
+ type: String),
+ FastlaneCore::ConfigItem.new(key: :template_name,
+ description: "Name of a template file (without extension) for the task content. Templates can be found in assets/asana_create_action_item/templates subdirectory.
+ The file is processed before being sent to Asana",
+ optional: true,
+ type: String),
+ FastlaneCore::ConfigItem.new(key: :github_handle,
+ description: "Github user handle",
+ optional: true,
+ type: String),
+ FastlaneCore::ConfigItem.new(key: :is_scheduled_release,
+ description: "Indicates whether the release was scheduled or started manually",
+ optional: true,
+ type: Boolean,
+ default_value: false)
+ ]
+ end
+
+ def self.is_supported?(platform)
+ true
+ end
+
+ def self.create_subtask(token:, task_id:, assignee_id:, task_name:, notes: nil, html_notes: nil)
+ subtask_options = {
+ task_gid: task_id,
+ assignee: assignee_id,
+ name: task_name
+ }
+ subtask_options[:notes] = notes unless notes.nil?
+ subtask_options[:html_notes] = html_notes unless html_notes.nil?
+
+ asana_client = Asana::Client.new do |c|
+ c.authentication(:access_token, token)
+ end
+ asana_client.tasks.create_subtask_for_task(**subtask_options)
+ end
+ end
+ end
+end
diff --git a/lib/fastlane/plugin/ddg_apple_automation/actions/asana_upload_action.rb b/lib/fastlane/plugin/ddg_apple_automation/actions/asana_upload_action.rb
index 958913a..ff68d49 100644
--- a/lib/fastlane/plugin/ddg_apple_automation/actions/asana_upload_action.rb
+++ b/lib/fastlane/plugin/ddg_apple_automation/actions/asana_upload_action.rb
@@ -48,7 +48,7 @@ def self.available_options
optional: false,
type: String),
FastlaneCore::ConfigItem.new(key: :file_name,
- description: "Path to the file that will be uploaded",
+ description: "Path to a file that will be uploaded",
optional: false,
type: String)
]
diff --git a/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/appcast-failed-hotfix.yml b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/appcast-failed-hotfix.yml
new file mode 100644
index 0000000..156eb68
--- /dev/null
+++ b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/appcast-failed-hotfix.yml
@@ -0,0 +1,33 @@
+name: Generate appcast2.xml for ${TAG} hotfix release and upload assets to S3
+html_notes: |
+
release-notes.txt
on your disk.
+ appcastManager
:
+ ./scripts/appcast_manager/appcastManager.swift --release-hotfix-to-public-channel --dmg ~/Downloads/${DMG_NAME} --release-notes release-notes.txt
<sparkle:phasedRolloutInterval>43200</sparkle:phasedRolloutInterval>
upload_to_s3.sh
script:
+ ./scripts/upload_to_s3/upload_to_s3.sh --run --overwrite-duckduckgo-dmg ${VERSION}
release-notes.txt
on your disk.
+ appcastManager
:
+ ./scripts/appcast_manager/appcastManager.swift --release-to-internal-channel --dmg ~/Downloads/${DMG_NAME} --release-notes release-notes.txt
<sparkle:channel>internal-channel</sparkle:channel>
upload_to_s3.sh
script:
+ ./scripts/upload_to_s3/upload_to_s3.sh --run
release-notes.txt
on your disk.
+ appcastManager
:
+ ./scripts/appcast_manager/appcastManager.swift --release-to-public-channel --version ${VERSION} --release-notes release-notes.txt
<sparkle:phasedRolloutInterval>43200</sparkle:phasedRolloutInterval>
upload_to_s3.sh
script:
+ ./scripts/upload_to_s3/upload_to_s3.sh --run --overwrite-duckduckgo-dmg ${VERSION}
${TAG}
public release has been successfully tagged and published in GitHub releases,
+ but deleting ${BRANCH}
branch failed. Please delete it manually:
+ git push origin --delete ${BRANCH}
${TAG}
tag.${BASE_BRANCH}
manually.git fetch origin
git checkout ${BRANCH}
switch to the release branchgit pull origin ${BRANCH}
pull latest changesgit tag ${TAG}
tag the releasegit push origin ${TAG}
push the taggit checkout ${BASE_BRANCH}
switch to ${BASE_BRANCH}git pull origin ${BASE_BRANCH}
pull the latest codegit merge ${BRANCH}
+ git push origin ${BASE_BRANCH}
push merged branchgh release create ${TAG} --generate-notes --prerelease --notes-start-tag ${LAST_RELEASE_TAG}
${TAG}
release has been successfully tagged and published in GitHub releases,
+ but merging to ${BASE_BRANCH}
failed. Please resolve conflicts and merge ${BRANCH}
to ${BASE_BRANCH}
manually.git fetch origin
git checkout ${BRANCH}
switch to the release branchgit pull origin ${BRANCH}
pull latest changesgit checkout ${BASE_BRANCH}
switch to ${BASE_BRANCH}git pull origin ${BASE_BRANCH}
pull the latest codegit merge ${BRANCH}
+ git push origin ${BASE_BRANCH}
push merged branch${TAG}
tag.git fetch origin
git checkout ${BRANCH}
switch to the release branchgit pull origin ${BRANCH}
pull latest changesgit tag ${TAG}
tag the releasegit push origin ${TAG}
push the taggit checkout ${BASE_BRANCH}
switch to ${BASE_BRANCH}git push origin --delete ${BRANCH}
delete the release branchgh release create ${TAG} --generate-notes --latest --notes-start-tag ${LAST_RELEASE_TAG}
gh workflow run publish_dmg_release.yml --ref ${BRANCH} -f asana-task-url=${ASANA_TASK_URL} -f tag=${TAG} -f release-type=internal
+ ${BRANCH}
${ASANA_TASK_URL}
${TAG}
internal
appcastManager
to generate the new appcast2.xml fileaws --profile ddg-macos sso login
aws --profile ddg-macos s3 cp ${OLD_APPCAST_NAME} s3://${RELEASE_BUCKET_NAME}/${RELEASE_BUCKET_PREFIX}/appcast2.xml --acl public-read
aws --profile ddg-macos sso login
aws --profile ddg-macos s3 cp ${OLD_APPCAST_NAME} s3://${RELEASE_BUCKET_NAME}/${RELEASE_BUCKET_PREFIX}/appcast2.xml --acl public-read
Some notes
' } } + + before do + @asana_client_tasks = double + asana_client = double("Asana::Client") + allow(Asana::Client).to receive(:new).and_return(asana_client) + allow(asana_client).to receive(:tasks).and_return(@asana_client_tasks) + + allow(Fastlane::Actions::AsanaExtractTaskIdAction).to receive(:run).and_return(task_id) + allow(Fastlane::Actions::AsanaExtractTaskAssigneeAction).to receive(:run).and_return(assignee_id) + allow(Fastlane::Actions::AsanaGetReleaseAutomationSubtaskIdAction).to receive(:run).with(task_url: task_url, asana_access_token: anything).and_return(automation_subtask_id) + allow(Fastlane::Actions::AsanaGetUserIdForGithubHandleAction).to receive(:run).and_return(assignee_id) + allow(@asana_client_tasks).to receive(:create_subtask_for_task) + + allow(Fastlane::Helper::GitHubActionsHelper).to receive(:set_output) + end + + it "extracts assignee id from release task when is scheduled release" do + expect(Fastlane::Actions::AsanaExtractTaskIdAction).to receive(:run).with(task_url: task_url) + expect(Fastlane::Actions::AsanaExtractTaskAssigneeAction).to receive(:run).with( + task_id: task_id, + asana_access_token: anything + ) + test_action(task_url: task_url, is_scheduled_release: true) + expect(Fastlane::Helper::GitHubActionsHelper).to have_received(:set_output).with("asana_assignee_id", "11") + end + + it "takes assignee id from github handle when is manual release" do + expect(Fastlane::Actions::AsanaGetUserIdForGithubHandleAction).to receive(:run).with( + github_handle: github_handle, + asana_access_token: anything + ) + test_action(task_url: task_url, is_scheduled_release: false, github_handle: github_handle) + expect(Fastlane::Helper::GitHubActionsHelper).to have_received(:set_output).with("asana_assignee_id", "11") + end + + it "raises an error when github handle is empty and is manual release" do + expect(Fastlane::UI).to receive(:user_error!).with("Github handle cannot be empty for manual release") + test_action(task_url: task_url, is_scheduled_release: false, github_handle: "") + expect(Fastlane::Helper::GitHubActionsHelper).not_to have_received(:set_output) + end + + it "correctly builds payload if notes input is given" do + test_action(task_url: task_url, task_name: task_name, notes: "notes", is_scheduled_release: true) + expect(@asana_client_tasks).to have_received(:create_subtask_for_task).with( + task_gid: automation_subtask_id, + name: task_name, + notes: "notes", + assignee: assignee_id + ) + end + + it "correctly builds payload if html_notes input is given" do + test_action(task_url: task_url, task_name: task_name, html_notes: "html_notes", is_scheduled_release: true) + expect(@asana_client_tasks).to have_received(:create_subtask_for_task).with( + task_gid: automation_subtask_id, + name: task_name, + html_notes: "html_notes", + assignee: assignee_id + ) + end + + it "correctly builds payload if template_name input is given" do + allow(File).to receive(:read) + allow(YAML).to receive(:safe_load).and_return(parsed_yaml_content) + test_action(task_url: task_url, task_name: task_name, template_name: "template_name", is_scheduled_release: true) + expect(@asana_client_tasks).to have_received(:create_subtask_for_task).with( + task_gid: automation_subtask_id, + name: "test task", + html_notes: "Some notes
", + assignee: assignee_id + ) + end + + it "raises an error if adding subtask fails" do + allow(Fastlane::UI).to receive(:user_error!) + allow(@asana_client_tasks).to receive(:create_subtask_for_task).and_raise(StandardError, 'API Error') + test_action(task_url: task_url, task_name: task_name, notes: "notes", is_scheduled_release: true) + expect(Fastlane::UI).to have_received(:user_error!).with("Failed to create subtask for task: API Error") + end + + it "correctly sets output" do + allow(@asana_client_tasks).to receive(:create_subtask_for_task).and_return(double('subtask', gid: "42")) + test_action(task_url: task_url, task_name: task_name, notes: "notes", is_scheduled_release: true) + expect(Fastlane::Helper::GitHubActionsHelper).to have_received(:set_output).with("asana_new_task_id", "42") + end + end + + def test_action(task_url:, task_name: nil, notes: nil, html_notes: nil, template_name: nil, is_scheduled_release: false, github_handle: nil) + Fastlane::Actions::AsanaCreateActionItemAction.run( + task_url: task_url, + task_name: task_name, + notes: notes, + html_notes: html_notes, + template_name: template_name, + is_scheduled_release: is_scheduled_release, + github_handle: github_handle + ) + end +end diff --git a/spec/ddg_apple_automation_helper_spec.rb b/spec/ddg_apple_automation_helper_spec.rb index e80f24f..41b4563 100644 --- a/spec/ddg_apple_automation_helper_spec.rb +++ b/spec/ddg_apple_automation_helper_spec.rb @@ -1,3 +1,5 @@ +require "climate_control" + describe Fastlane::Helper::DdgAppleAutomationHelper do describe "#asana_task_url" do it "constructs Asana task URL" do @@ -15,4 +17,58 @@ def asana_task_url(task_id) Fastlane::Helper::DdgAppleAutomationHelper.asana_task_url(task_id) end end + + describe "#load_file" do + it "shows error if provided file does not exist" do + allow(Fastlane::UI).to receive(:user_error!) + allow(File).to receive(:read).and_raise(Errno::ENOENT) + load_file("file") + expect(Fastlane::UI).to have_received(:user_error!).with("Error: The file 'file' does not exist.") + end + + def load_file(file) + Fastlane::Helper::DdgAppleAutomationHelper.load_file(file) + end + end + + describe "#sanitize_html_and_replace_env_vars" do + it "substitutes all env variables" do + content = "