-
Notifications
You must be signed in to change notification settings - Fork 9
Add update_apps_cdn_build_metadata action #701
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
Open
iangmaia
wants to merge
11
commits into
trunk
Choose a base branch
from
iangmaia/add-update-apps-cdn-build-metadata-action
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
766f65e
Add update_apps_cdn_build_metadata action to update visibility of exi…
iangmaia 345dad8
Address PR review: add HTTP timeouts and fix changelog PR link
iangmaia 4094e58
Remove WebMock.allow_net_connect! leak from spec after hook
iangmaia 36b03d8
Switch update_apps_cdn_build_metadata from v1.1 to WP REST API v2
iangmaia 3276a26
Accept array of post_ids in update_apps_cdn_build_metadata
iangmaia f0a3021
Extract shared Apps CDN constants and URL helpers into AppsCdnHelper
iangmaia 6df3e3e
Return post ID directly from update_single_post instead of Hash
iangmaia d4d539d
Add YARD docs to update_single_post and lookup_visibility_term_id
iangmaia 185f5ed
Update `AppsCdnHelper` to return parsed URIs
iangmaia a79094e
Update code to move visibilities validation into AppsCdnHelper
iangmaia 1a4e9d3
Update code to make it clearer validation methods are parameters
iangmaia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
197 changes: 197 additions & 0 deletions
197
lib/fastlane/plugin/wpmreleasetoolkit/actions/common/update_apps_cdn_build_metadata.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require 'fastlane/action' | ||
| require 'net/http' | ||
| require 'json' | ||
| require_relative '../../helper/apps_cdn_helper' | ||
|
|
||
| module Fastlane | ||
| module Actions | ||
| class UpdateAppsCdnBuildMetadataAction < Action | ||
| def self.run(params) | ||
| post_ids = params[:post_ids] | ||
| UI.message("Updating Apps CDN build metadata for #{post_ids.size} post(s): #{post_ids.join(', ')}...") | ||
|
|
||
| # Build the base JSON body for the WP REST API v2 | ||
| body = {} | ||
| body['status'] = params[:post_status] if params[:post_status] | ||
|
|
||
| if params[:visibility] | ||
| term_id = lookup_visibility_term_id(site_id: params[:site_id], api_token: params[:api_token], visibility: params[:visibility]) | ||
| body['visibility'] = [term_id] | ||
| end | ||
|
|
||
| UI.user_error!('No metadata to update. Provide at least one of: visibility, post_status') if body.empty? | ||
|
|
||
| results = post_ids.map do |post_id| | ||
| update_single_post(site_id: params[:site_id], api_token: params[:api_token], post_id: post_id, body: body) | ||
| end | ||
|
|
||
| UI.success("Successfully updated Apps CDN build metadata for #{results.size} post(s)") | ||
| results | ||
| end | ||
|
|
||
| # Update a single CDN build post with the given body via the WP REST API v2. | ||
| # | ||
| # @param site_id [String] the WordPress.com site ID | ||
| # @param api_token [String] the WordPress.com API bearer token | ||
| # @param post_id [Integer] the ID of the post to update | ||
| # @param body [Hash] the JSON body to send in the POST request | ||
| # @return [Integer] the ID of the updated post | ||
| # @raise [FastlaneCore::Interface::FastlaneError] if the API returns a non-success response | ||
| def self.update_single_post(site_id:, api_token:, post_id:, body:) | ||
| uri = Helper::AppsCdnHelper.wp_v2_url(site_id: site_id, path: "a8c_cdn_build/#{post_id}") | ||
|
|
||
| request = Net::HTTP::Post.new(uri.request_uri) | ||
| request.body = JSON.generate(body) | ||
| request['Content-Type'] = 'application/json' | ||
| request['Accept'] = 'application/json' | ||
| request['Authorization'] = "Bearer #{api_token}" | ||
|
|
||
| response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http| | ||
iangmaia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| http.open_timeout = 10 | ||
| http.read_timeout = 30 | ||
| http.request(request) | ||
| end | ||
|
|
||
| case response | ||
| when Net::HTTPSuccess | ||
| result = JSON.parse(response.body) | ||
| updated_id = result['id'] | ||
|
|
||
| UI.message(" Updated post #{updated_id}") | ||
|
|
||
| updated_id | ||
| else | ||
| UI.error("Failed to update Apps CDN build metadata for post #{post_id}: #{response.code} #{response.message}") | ||
| UI.error(response.body) | ||
| UI.user_error!("Update of Apps CDN build metadata failed for post #{post_id}") | ||
| end | ||
| end | ||
|
|
||
| # Look up the taxonomy term ID for a visibility value (e.g. :internal -> 1316). | ||
| # | ||
| # @param site_id [String] the WordPress.com site ID | ||
| # @param api_token [String] the WordPress.com API bearer token | ||
| # @param visibility [Symbol] the visibility to look up (:internal or :external) | ||
| # @return [Integer] the taxonomy term ID for the given visibility | ||
| # @raise [FastlaneCore::Interface::FastlaneError] if no term is found or the API returns a non-success response | ||
| def self.lookup_visibility_term_id(site_id:, api_token:, visibility:) | ||
| slug = visibility.to_s.downcase | ||
| uri = Helper::AppsCdnHelper.wp_v2_url(site_id: site_id, path: "visibility?slug=#{slug}") | ||
|
|
||
| request = Net::HTTP::Get.new(uri.request_uri) | ||
| request['Accept'] = 'application/json' | ||
| request['Authorization'] = "Bearer #{api_token}" | ||
|
|
||
| response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http| | ||
| http.open_timeout = 10 | ||
| http.read_timeout = 30 | ||
| http.request(request) | ||
| end | ||
|
|
||
| case response | ||
| when Net::HTTPSuccess | ||
| terms = JSON.parse(response.body) | ||
| UI.user_error!("No visibility term found for '#{slug}'") if terms.empty? | ||
| terms.first['id'] | ||
| else | ||
| UI.user_error!("Failed to look up visibility term '#{slug}': #{response.code} #{response.message}") | ||
| end | ||
| end | ||
|
|
||
| def self.description | ||
| 'Updates metadata of one or more existing builds on the Apps CDN' | ||
| end | ||
|
|
||
| def self.authors | ||
| ['Automattic'] | ||
| end | ||
|
|
||
| def self.return_value | ||
| 'Returns an Array of post IDs (Integer) that were successfully updated. On error, raises a FastlaneError.' | ||
| end | ||
|
|
||
| def self.details | ||
| <<~DETAILS | ||
| Updates metadata (such as post status or visibility) for one or more existing build posts on a WordPress blog | ||
| that has the Apps CDN plugin enabled, using the WordPress.com REST API (WP v2). | ||
| When updating visibility for multiple posts, the visibility term ID is looked up only once. | ||
| See PCYsg-15tP-p2 internal a8c documentation for details about the Apps CDN plugin. | ||
| DETAILS | ||
| end | ||
|
|
||
| def self.available_options | ||
| [ | ||
| FastlaneCore::ConfigItem.new( | ||
| key: :site_id, | ||
| env_name: 'APPS_CDN_SITE_ID', | ||
| description: 'The WordPress.com CDN site ID where the build was uploaded', | ||
| optional: false, | ||
| type: String, | ||
| verify_block: proc do |value| | ||
| UI.user_error!('Site ID cannot be empty') if value.to_s.empty? | ||
| end | ||
| ), | ||
| FastlaneCore::ConfigItem.new( | ||
| key: :post_ids, | ||
| description: 'The IDs of the build posts to update', | ||
| optional: false, | ||
| type: Array, | ||
| verify_block: proc do |value| | ||
| UI.user_error!('Post IDs must be a non-empty array') unless value.is_a?(Array) && !value.empty? | ||
| value.each do |id| | ||
| UI.user_error!("Each post ID must be a positive integer, got: #{id.inspect}") unless id.is_a?(Integer) && id.positive? | ||
| end | ||
| end | ||
| ), | ||
| FastlaneCore::ConfigItem.new( | ||
| key: :api_token, | ||
| env_name: 'WPCOM_API_TOKEN', | ||
| description: 'The WordPress.com API token for authentication', | ||
| optional: false, | ||
| type: String, | ||
| verify_block: proc do |value| | ||
| UI.user_error!('API token cannot be empty') if value.to_s.empty? | ||
| end | ||
| ), | ||
| FastlaneCore::ConfigItem.new( | ||
| key: :visibility, | ||
| description: 'The new visibility for the build (:internal or :external)', | ||
| optional: true, | ||
| type: Symbol, | ||
| verify_block: Helper::AppsCdnHelper.verify_visibility_param | ||
| ), | ||
| FastlaneCore::ConfigItem.new( | ||
| key: :post_status, | ||
| description: "The new post status ('publish' or 'draft')", | ||
| optional: true, | ||
| type: String, | ||
| verify_block: Helper::AppsCdnHelper.verify_post_status_param | ||
| ), | ||
| ] | ||
| end | ||
|
|
||
| def self.is_supported?(platform) | ||
| true | ||
| end | ||
|
|
||
| def self.example_code | ||
| [ | ||
| 'update_apps_cdn_build_metadata( | ||
| site_id: "12345678", | ||
| api_token: ENV["WPCOM_API_TOKEN"], | ||
| post_ids: [98765], | ||
| post_status: "publish" | ||
| )', | ||
| 'update_apps_cdn_build_metadata( | ||
| site_id: "12345678", | ||
| api_token: ENV["WPCOM_API_TOKEN"], | ||
| post_ids: [12345, 67890, 11111], | ||
| visibility: :external | ||
| )', | ||
| ] | ||
| end | ||
| end | ||
| end | ||
| end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
lib/fastlane/plugin/wpmreleasetoolkit/helper/apps_cdn_helper.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require 'uri' | ||
|
|
||
| module Fastlane | ||
| module Helper | ||
| module AppsCdnHelper | ||
| API_BASE_URL = 'https://public-api.wordpress.com' | ||
|
|
||
| # See https://github.a8c.com/Automattic/wpcom/blob/trunk/wp-content/lib/a8c/cdn/src/enums/enum-visibility.php | ||
| VALID_VISIBILITIES = %i[internal external].freeze | ||
|
|
||
| # These are from the WordPress.com API, not the Apps CDN plugin | ||
| VALID_POST_STATUS = %w[publish draft].freeze | ||
|
|
||
| # Builds a WordPress.com REST API v1.1 URI scoped to a site. | ||
| # | ||
| # @param site_id [String] the WordPress.com site ID | ||
| # @param path [String] the API path relative to the site (e.g. 'media/new') | ||
| # @return [URI] the parsed full API URI | ||
| def self.rest_v1_1_url(site_id:, path:) | ||
| URI.parse("#{API_BASE_URL}/rest/v1.1/sites/#{site_id}/#{path}") | ||
| end | ||
AliSoftware marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # Builds a WordPress.com WP REST API v2 URI scoped to a site. | ||
| # | ||
| # @param site_id [String] the WordPress.com site ID | ||
| # @param path [String] the API path relative to the site (e.g. 'a8c_cdn_build/123') | ||
| # @return [URI] the parsed full API URI | ||
| def self.wp_v2_url(site_id:, path:) | ||
| URI.parse("#{API_BASE_URL}/wp/v2/sites/#{site_id}/#{path}") | ||
| end | ||
|
|
||
| # Returns a proc that validates a visibility parameter value against {VALID_VISIBILITIES}. | ||
| # Intended for use as a `verify_block` in Fastlane ConfigItem definitions. | ||
| # | ||
| # @return [Proc] a proc that raises FastlaneError if the value is invalid | ||
| def self.verify_visibility_param | ||
| proc do |value| | ||
| UI.user_error!("Visibility must be one of: #{VALID_VISIBILITIES.map { "`:#{_1}`" }.join(', ')}") unless VALID_VISIBILITIES.include?(value.to_s.downcase.to_sym) | ||
| end | ||
| end | ||
|
|
||
| # Returns a proc that validates a post status parameter value against {VALID_POST_STATUS}. | ||
| # Intended for use as a `verify_block` in Fastlane ConfigItem definitions. | ||
| # | ||
| # @return [Proc] a proc that raises FastlaneError if the value is invalid | ||
| def self.verify_post_status_param | ||
| proc do |value| | ||
| UI.user_error!("Post status must be one of: #{VALID_POST_STATUS.join(', ')}") unless VALID_POST_STATUS.include?(value) | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Probably more of a question for Hannah but it feels surprising to me that the API would technically allow an array if values for this metadata instead of a single one 🤔
Like, what would happen at the API level if we called the API with
{"visibility":[1345, 1367]}body (where, say, 1345 is internal and 1367 is external, for example) 😅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.
Just double checked using the API. It indeed accepts multiple term IDs and assigns both:
So a post would end up being both "internal" and "external" at the same time 😅 it just treats visibility as a taxonomy that supports multiple terms.
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.
Interesting! I wonder why we didn't have to pass an array but just a string in the API call that creates the build in
upload_build_to_apps_cdnthen 🤔 🤷Also, this doesn't match the table in the FieldGuide documentation (interal ref: PCYsg-15tP-p2#How-to-upload-a-build-to-a-CDN-blog), so I guess one of the 2 should probably be updated.
I'd vote for only updating the AppsCDN plugin for accepting only one visibility (both in the API call and on the visibility metadata stored for a post taxonomy), but I'm not sure if that's technically feasible/easy (vs if this is an inherent technical limitation of how taxonomies work in WordPress? cc @hannahtinkler).
Anyway, I think ultimately it's not really critical (given the way we use the API and this visibility taxonomy we always set only one value in practice in all our call sites); as long as we're sure that calling
update_build_to_apps_cdnwith a one-item array[term_id]on a post that already had a different value set forvisibilityindeed replaces the visibility of that post with a single visibility value ofterm_id—as opposed to adding it to the existing ones.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.
Hmm, I didn’t realise how annoying uploading via that endpoint would be with having to use the internal IDs, pass arrays etc. I looked at customising the update functionality for this post type, but it got complex to the point it seemed better to add a dedicated upload endpoint - you can find the docs for that here: PCYsg-15tP-p2 🙂
I also blocked edits via the standard REST post endpoints so the validation can be enforced - this should all be available in prod now! FYI, I also added a test site so we don't need to clean up testing calls in the prod CDN sites - you can find the details for that in AINFRA-2171.