Class: Deliver::UploadAppClipDefaultExperienceMetadata

Inherits:
Object
  • Object
show all
Defined in:
deliver/lib/deliver/upload_app_clip_default_experience_metadata.rb

Overview

rubocop:disable Metrics/ClassLength

Constant Summary collapse

LOCALISED_APP_CLIP_DEFAULT_EXPERIENCE_VALUES =
{
  app_clip_default_experience_subtitle: "app_clip_default_experience_subtitle"
}
NON_LOCALISED_APP_CLIP_DEFAULT_EXPERIENCE_VALUES =
{
  app_clip_default_experience_action: "app_clip_default_experience_action"
}

Instance Method Summary collapse

Instance Method Details

#assign_defaults(options) ⇒ Object

If the user is using the ‘default’ language, then assign values where they are needed



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'deliver/lib/deliver/upload_app_clip_default_experience_metadata.rb', line 101

def assign_defaults(options)
  # Build a complete list of the required languages
  enabled_languages = detect_languages(options)

  return unless enabled_languages.include?("default")
  UI.message("Detected languages for app clip default experience: " + enabled_languages.to_s)

  LOCALISED_APP_CLIP_DEFAULT_EXPERIENCE_VALUES.keys.each do |key|
    current = options[key]
    next unless current && current.kind_of?(Hash)

    default = current["default"]
    next if default.nil?

    enabled_languages.each do |language|
      value = current[language]
      next unless value.nil?

      current[language] = default
    end
    current.delete("default")
  end
end

#copy_live_version_app_clip_default_experience_metadata(app:, platform:, edit_version:, app_clip:) ⇒ Object

Handle the case where an app clip exists, but the user did not specify any app clip default experience metadata options. We check if the current editable version already has the app clip default experience metadata. If not, copy over the live version’s app clip default experience metadata. This mimics the default behavior of creating an App Store version from the ASC UI.

This function will also produce warnings, but not outright fail, when it finds missing app clip metadata.

As of 2022-05-18, App Store versions created by the ASC API do not “carry over” the live version’s app clip default experience metadata, so we handle this for the user by default.



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'deliver/lib/deliver/upload_app_clip_default_experience_metadata.rb', line 208

def (app:, platform:, edit_version:, app_clip:)
  edit_version_default_experience = edit_version.app_clip_default_experience
  if !edit_version_default_experience
    live_version = fetch_live_app_store_version(app, platform)
    # no live version to carry over metadata from
    return if live_version.nil?

    live_version_default_experience = live_version.app_clip_default_experience
    # no live version default experience to carry over metadata from
    return if live_version_default_experience.nil?

    # create a default experience and use the live version default experience as a "template"
    Spaceship::ConnectAPI::AppClipDefaultExperience.create(app_clip_id: app_clip.id, app_store_version_id: edit_version.id, template_default_experience_id: live_version_default_experience.id)

    UI.message("Created the app clip default experience using the live version's app clip metadata as a template.")
  else
    # if the edit version app clip default experience already exists, just check it's values
    UI.important("ASC requires the app clip default experience to contain a valid `action` value. To specify this value in fastlane use deliver's `app_clip_default_experience_action` option.") if edit_version_default_experience.action.nil?
  end
end

#detect_languages(options) ⇒ Object



125
126
127
128
129
130
131
132
# File 'deliver/lib/deliver/upload_app_clip_default_experience_metadata.rb', line 125

def detect_languages(options)
  Languages.detect_languages(
    options: options,
    localized_values_keys: LOCALISED_APP_CLIP_DEFAULT_EXPERIENCE_VALUES.keys,
    metadata_path: options[:app_clip_default_experience_metadata_path],
    ignore_validation: options[:ignore_language_directory_validation]
  )
end

#fetch_edit_app_store_version(app, platform, wait_time: 10) ⇒ Object

from upload_metadata.rb



135
136
137
138
139
# File 'deliver/lib/deliver/upload_app_clip_default_experience_metadata.rb', line 135

def fetch_edit_app_store_version(app, platform, wait_time: 10)
  retry_if_nil("Cannot find edit app store version", wait_time: wait_time) do
    app.get_edit_app_store_version(platform: platform, includes: Spaceship::ConnectAPI::AppStoreVersion::ESSENTIAL_INCLUDES + ",appClipDefaultExperience")
  end
end

#fetch_live_app_store_version(app, platform, wait_time: 10) ⇒ Object



141
142
143
144
145
# File 'deliver/lib/deliver/upload_app_clip_default_experience_metadata.rb', line 141

def fetch_live_app_store_version(app, platform, wait_time: 10)
  retry_if_nil("Cannot find live app store version", wait_time: wait_time) do
    app.get_live_app_store_version(platform: platform, includes: Spaceship::ConnectAPI::AppStoreVersion::ESSENTIAL_INCLUDES + ",appClipDefaultExperience")
  end
end

#load_from_filesystem(options) ⇒ Object

Loads the app clip default experience metadata files and stores them into the options object



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'deliver/lib/deliver/upload_app_clip_default_experience_metadata.rb', line 74

def load_from_filesystem(options)
   = options[:app_clip_default_experience_metadata_path]

  # Load localised data
  ignore_validation = options[:ignore_language_directory_validation]
  Loader.language_folders(, ignore_validation).each do |lang_folder|
    LOCALISED_APP_CLIP_DEFAULT_EXPERIENCE_VALUES.keys.each do |key|
      path = File.join(lang_folder.path, "#{key}.txt")
      next unless File.exist?(path)

      UI.message("Loading '#{path}'...")
      options[key] ||= {}
      options[key][lang_folder.basename] ||= File.read(path).strip
    end
  end

  # Load non localised data
  NON_LOCALISED_APP_CLIP_DEFAULT_EXPERIENCE_VALUES.keys.each do |key|
    path = File.join(, "#{key}.txt")
    next unless File.exist?(path)

    UI.message("Loading '#{path}'...")
    options[key] ||= File.read(path).strip
  end
end

#retry_if_nil(message, tries: 5, wait_time: 10) ⇒ Object

from upload_metadata.rb



148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'deliver/lib/deliver/upload_app_clip_default_experience_metadata.rb', line 148

def retry_if_nil(message, tries: 5, wait_time: 10)
  loop do
    tries -= 1

    value = yield
    return value if value

    UI.message("#{message}... Retrying after #{wait_time} seconds (remaining: #{tries})")
    sleep(wait_time)

    return nil if tries.zero?
  end
end

#upload_metadata(options) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'deliver/lib/deliver/upload_app_clip_default_experience_metadata.rb', line 19

def (options)
  # app clip default experience metadata is not editable in a live version
  return if options[:edit_live] || options[:app_clip_default_experience_metadata_path].nil?

  # load the metadata from the filesystem before uploading
  load_from_filesystem(options)

  # Assign default values to all languages
  assign_defaults(options)

  app = Deliver.cache[:app]
  platform = Spaceship::ConnectAPI::Platform.map(options[:platform])
  version = fetch_edit_app_store_version(app, platform)

  UI.important("Will begin uploading app clip default experience metadata for '#{version.version_string}' on App Store Connect")

  # Currently only one app clip target per app is supported
  app_clips = app.get_app_clips
  app_clip = app_clips.first

  # Validate options
  subtitle_localized = options[:app_clip_default_experience_subtitle]
  action = options[:app_clip_default_experience_action]
  has_options_specified = !subtitle_localized.nil? || !action.nil?

  if app_clip.nil?
    UI.user_error!("A build with an app clip must be uploaded to App Store Connect before uploading the default experience metadata") if has_options_specified
    # Nothing to do if the app clip is nil and no app clip options specified
    return
  end

  unless has_options_specified
    # Handle the default case where there is an app clip, but no options specified.
    return (app: app, platform: platform, edit_version: version, app_clip: app_clip)
  end

  UI.user_error!("You must provide at least the subtitle and action for an app clip default experience") if subtitle_localized.nil? || action.nil?

  # see if there's an existing experience for this version
  default_experience = version.app_clip_default_experience
  if default_experience
    # update the existing default experience
    default_experience.update(attributes: { action: action })
    UI.message("Updated app clip default experience")
  else
    # create a new default experience
    default_experience = Spaceship::ConnectAPI::AppClipDefaultExperience.create(app_clip_id: app_clip.id, app_store_version_id: version.id, attributes: { action: action })
    UI.important("Created default experience for version '#{version.version_string}'")
  end

  # update the subtitle localizations
  upload_subtitle_localizations(app_clip_default_experience: default_experience, subtitle_localizations: subtitle_localized)
end

#upload_subtitle_localizations(app_clip_default_experience:, subtitle_localizations:) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'deliver/lib/deliver/upload_app_clip_default_experience_metadata.rb', line 162

def upload_subtitle_localizations(app_clip_default_experience:, subtitle_localizations:)
  localized_subtitle_attributes_by_locale = {}
  subtitle_localizations.keys.each do |key|
    localized_subtitle_attributes_by_locale[key] = {}
    localized_subtitle_attributes_by_locale[key][:create_attributes] = { subtitle: subtitle_localizations[key], locale: key }
    localized_subtitle_attributes_by_locale[key][:update_attributes] = { subtitle: subtitle_localizations[key] }
  end

  # update the subtitle
  existing_localizations = Spaceship::ConnectAPI::AppClipDefaultExperienceLocalization.find_all(app_clip_default_experience_id: app_clip_default_experience.id)

  # from upload_metadata.rb
  app_info_worker = FastlaneCore::QueueWorker.new do |locale|
    UI.message("Uploading app clip default experience metadata to App Store Connect for localized subtitle '#{locale}'")

    # find an existing localization
    existing_localization = existing_localizations.find { |l| locale.to_s.eql?(l.locale) }

    if existing_localization
      # update existing
      attributes = localized_subtitle_attributes_by_locale[locale][:update_attributes]
      existing_localization.update(attributes: attributes)
      UI.verbose("[#{locale}] Updated existing to #{attributes}")
    else
      # create new
      attributes = localized_subtitle_attributes_by_locale[locale][:create_attributes]
      Spaceship::ConnectAPI::AppClipDefaultExperienceLocalization.create(default_experience_id: app_clip_default_experience.id, attributes: attributes)
      UI.verbose("[#{locale}] Created new with #{attributes}")
    end
  end
  app_info_worker.batch_enqueue(localized_subtitle_attributes_by_locale.keys)
  app_info_worker.start
end