fastlane-plugin-google_data_safety 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b1755c253f37377d06fe1c7c3cae9a182af4d59fec7b758c6b233349bc240ac
4
- data.tar.gz: f10c4e8cab62e4aa3aa0a333971af80403c330081735e3c85b49fc6bc853226b
3
+ metadata.gz: 4c996cc8d59b250f9fd4c899a6b3d426b74b7644c514fd045e4bf03c335f3c5b
4
+ data.tar.gz: 9ff7950db800e8506ecd23fc970e421d910096c828209205286766bf27e103ad
5
5
  SHA512:
6
- metadata.gz: 26b72d0673ed1f71be97ca89df7aecbde284e05cb5ee3245fe5b4e701a3790a675a31df103239fe1a3ab781d129997e64a2ba1e8a13f32a6e669a578cee65c2f
7
- data.tar.gz: 69be13bdb4299c5ce77bca00a2351bf2d22f5135bd2854ee2e3813ccbfea58c9194da889a3f3dddbe81626a617ff8db504e3c0f25681bdfc4e8a63e35850d2fe
6
+ metadata.gz: 5b64fb9a63d98823b7301c8a7c6cdb6c4dbe9259f414f16164fb690718de766d8512b5f6a9fc7ae20d02c94ee3373df38dd8dd56844f3d40ef33f66795194841
7
+ data.tar.gz: 7b66cc3fa41a90b82340e87df73f2477ab1387e5a53d22a40ddbe6c1c4b8b314fedc438d5ca6e2dc57eca300cf851bc1c60b60fcd3d81be48f38f16954c5fd55
data/README.md CHANGED
@@ -40,13 +40,14 @@ upload_google_data_safety(
40
40
 
41
41
  #### Parameters
42
42
 
43
- | Key | Description |
44
- | --- | ----------- |
45
- | json_key_data | The raw service account JSON data used to authenticate with Google |
46
- | json_key | The path to a file containing service account JSON, used to authenticate with Google |
47
- | package_name | The package name of the application to use |
48
- | csv_file | The path to a file containing Google data safety csv |
49
- | csv_content | The raw csv data used for Google data safety |
43
+ | Key | Environment Variable | Description |
44
+ | ------------- | -------------------- | ------------------------------------------------------------------------------------ |
45
+ | json_key_data | JSON_KEY_DATA | The raw service account JSON data used to authenticate with Google |
46
+ | json_key | JSON_KEY_FILE | The path to a file containing service account JSON, used to authenticate with Google |
47
+ | package_name | PACKAGE_NAME | The package name of the application to use |
48
+ | csv_file | CSV_FILE | The path to a file containing Google data safety csv |
49
+ | csv_content | CSV_CONTENT | The raw csv data used for Google data safety |
50
+ | debug | DEBUG | Provide debug log messaging |
50
51
 
51
52
  ### prompt_create_data_safety_csv
52
53
 
@@ -63,9 +64,9 @@ prompt_create_data_safety_csv(
63
64
 
64
65
  #### Parameters
65
66
 
66
- | Key | Description |
67
- | --- | ----------- |
68
- | csv_file | The path to save csv file for upload to Google Play Console |
67
+ | Key | Environment Variable | Description |
68
+ | -------- | -------------------- | ----------------------------------------------------------- |
69
+ | csv_file | CSV_FILE | The path to save csv file for upload to Google Play Console |
69
70
 
70
71
  #### Issue reporting
71
72
 
@@ -8,8 +8,12 @@ require 'csv'
8
8
  module Fastlane
9
9
  module Actions
10
10
  class PromptCreateDataSafetyCsvAction < Action
11
- def self.run(params)
11
+ FORMULA_CHARS = %w[= + - @].freeze
12
12
 
13
+ # Run the prompt_create_data_safety_csv action.
14
+ #
15
+ # @param param [hash] Fastlane parameters
16
+ def self.run(params)
13
17
  if !UI.interactive?
14
18
  UI.error("Prompts questions can only display in interactive.")
15
19
  end
@@ -24,21 +28,26 @@ module Fastlane
24
28
 
25
29
  template_data = Helper::PromptCreateDataSafetyHelper.template_array()
26
30
 
27
- template_data.each_with_index do | row |
31
+ safety_data = template_data.each_with_index do | row |
28
32
  hash_key = "#{row[0]},#{row[1]}"
29
33
  if hash.key?(hash_key)
30
34
  row[2] = hash[hash_key]
31
35
  end
32
36
  end
33
37
 
34
- CSV.open(csv_file_path, "w") do |csv|
35
- template_data.each do |row|
36
- csv << row
37
- end
38
- end
39
-
38
+ self.saveCsvFile(data: safety_data, csv_file_path: csv_file_path)
40
39
  end
41
40
 
41
+ # Array of valid answer strings for true.
42
+ def self.valid_true_answers = ["y", "yes", "true", "t"]
43
+ # Array of valid answer strings for false.
44
+ def self.valid_false_answers = ["n", "no", "false", "f"]
45
+ # Array of valid answer strings for ninety days selection (delete account data).
46
+ def self.valid_90_day_answers = ["90", "ninety"]
47
+
48
+ # Displays terminal header of Fastlane action and about the upcoming prompts.
49
+ #
50
+ # @param csv_file [String] The CSV file location that display to user where the file will be saved in
42
51
  def self.headerDisplay(csv_file: nil)
43
52
  UI.important("")
44
53
  UI.important(" ____ _ ____ _ ____ __ _ ")
@@ -51,85 +60,144 @@ module Fastlane
51
60
  UI.important("By: Owen Bean")
52
61
  UI.important("Report problems at https://github.com/owenbean400/fastlane-plugin-google_data_safety/issues")
53
62
  UI.important("")
54
- UI.important("Answer the following prompt below to generate CSV file to upload at #{csv_file}")
63
+ UI.important("Answer the following prompts below to generate CSV file to upload at #{csv_file}")
55
64
  UI.important("")
56
65
  end
57
66
 
67
+ # Prompt questions for Google Data Safety sheet.
68
+ #
69
+ # @param hash [hash] The hash to save data of answers to questions
58
70
  def self.startingQuestion(hash: nil)
59
- dataInputAppInfo = UI.input("Does your app collect or share any of the required user data types? Y/N")
60
-
61
- self.addToHash(hash: hash, question_id: "PSL_DATA_COLLECTION_COLLECTS_PERSONAL_DATA", response_id: "", value: self.trueOrFalse(str_response: dataInputAppInfo))
62
-
63
- if dataInputAppInfo.downcase == "y" || dataInputAppInfo.downcase == "yes"
64
-
65
- dataInputEncryption = UI.input("Is all of the user data collected by your app encrypted in transit? Y/N")
66
- self.addToHash(hash: hash, question_id: "PSL_DATA_COLLECTION_ENCRYPTED_IN_TRANSIT", response_id: "", value: self.trueOrFalse(str_response: dataInputEncryption))
67
-
68
- dataInputAccountCreate = UI.input("Does the app allow for account creation from within the app? Y/N")
69
-
70
- if dataInputAccountCreate.downcase == "y" || dataInputAccountCreate.downcase == "yes"
71
-
72
- dataInputCreateUserPass = UI.input("Does the app allow for account creation from username and password? Y/N")
73
- self.addToHash(hash: hash, question_id: "PSL_SUPPORTED_ACCOUNT_CREATION_METHODS", response_id: "PSL_ACM_USER_ID_PASSWORD", value: self.trueOrBlank(str_response: dataInputCreateUserPass))
74
-
75
- dataInputCreateUserAuth = UI.input("Does the app allow for account creation from username and other authentication? Y/N")
76
- self.addToHash(hash: hash, question_id: "PSL_SUPPORTED_ACCOUNT_CREATION_METHODS", response_id: "PSL_ACM_USER_ID_OTHER_AUTH", value: self.trueOrBlank(str_response: dataInputCreateUserAuth))
77
-
78
- dataInputCreateUserPassAuth = UI.input("Does the app allow for account creation from username, password, and other authentication? Y/N")
79
- self.addToHash(hash: hash, question_id: "PSL_SUPPORTED_ACCOUNT_CREATION_METHODS", response_id: "PSL_ACM_USER_ID_PASSWORD_OTHER_AUTH", value: self.trueOrBlank(str_response: dataInputCreateUserPassAuth))
80
-
81
- dataInputCreateOauth = UI.input("Does the app allow for account creation from OAuth? Y/N")
82
- self.addToHash(hash: hash, question_id: "PSL_SUPPORTED_ACCOUNT_CREATION_METHODS", response_id: "PSL_ACM_OAUTH", value: self.trueOrBlank(str_response: dataInputCreateOauth))
83
-
84
- dataInputCreateOther = UI.input("Does the app allow for other account creation method? Y/N")
85
- self.addToHash(hash: hash, question_id: "PSL_SUPPORTED_ACCOUNT_CREATION_METHODS", response_id: "PSL_ACM_OTHER", value: self.trueOrBlank(str_response: dataInputCreateOther))
86
-
87
- if dataInputCreateOther.downcase == "y" || dataInputCreateOther.downcase == "yes"
88
-
89
- dataInputCreateOtherDesc = UI.input("Describe the method of account creation that your app supports")
90
- self.addToHash(hash: hash, question_id: "PSL_ACM_SPECIFY", response_id: "", value: dataInputCreateOtherDesc)
91
-
71
+ isDataInputAppInfo = self.promptForAnswerBoolean(
72
+ question: "Does your app collect or share any of the required user data types?",
73
+ hash: hash,
74
+ question_id: "PSL_DATA_COLLECTION_COLLECTS_PERSONAL_DATA",
75
+ response_id: "",
76
+ is_false_blank: false)
77
+
78
+ if isDataInputAppInfo
79
+
80
+ self.promptForAnswerBoolean(
81
+ question: "Is all of the user data collected by your app encrypted in transit?",
82
+ hash: hash,
83
+ question_id: "PSL_DATA_COLLECTION_ENCRYPTED_IN_TRANSIT",
84
+ response_id: "",
85
+ is_false_blank: false)
86
+
87
+ isDataInputAccountCreate = self.promptForAnswerBoolean(
88
+ question: "Does the app allow for account creation from within the app?",
89
+ hash: nil,
90
+ question_id: nil,
91
+ response_id: nil,
92
+ is_false_blank: false)
93
+
94
+ if isDataInputAccountCreate
95
+ self.promptForAnswerBoolean(
96
+ question: "Does the app allow for account creation from username and password?",
97
+ hash: hash,
98
+ question_id: "PSL_SUPPORTED_ACCOUNT_CREATION_METHODS",
99
+ response_id: "PSL_ACM_USER_ID_PASSWORD",
100
+ is_false_blank: true)
101
+
102
+ self.promptForAnswerBoolean(
103
+ question: "Does the app allow for account creation from username and other authentication?",
104
+ hash: hash,
105
+ question_id: "PSL_SUPPORTED_ACCOUNT_CREATION_METHODS",
106
+ response_id: "PSL_ACM_USER_ID_OTHER_AUTH",
107
+ is_false_blank: true)
108
+
109
+ self.promptForAnswerBoolean(
110
+ question: "Does the app allow for account creation from username, password, and other authentication?",
111
+ hash: hash,
112
+ question_id: "PSL_SUPPORTED_ACCOUNT_CREATION_METHODS",
113
+ response_id: "PSL_ACM_USER_ID_PASSWORD_OTHER_AUTH",
114
+ is_false_blank: true)
115
+
116
+ self.promptForAnswerBoolean(
117
+ question: "Does the app allow for account creation from OAuth?",
118
+ hash: hash,
119
+ question_id: "PSL_SUPPORTED_ACCOUNT_CREATION_METHODS",
120
+ response_id: "PSL_ACM_OAUTH",
121
+ is_false_blank: true)
122
+
123
+ isDataInputCreateOther = self.promptForAnswerBoolean(
124
+ question: "Does the app allow for other account creation method?",
125
+ hash: hash,
126
+ question_id: "PSL_SUPPORTED_ACCOUNT_CREATION_METHODS",
127
+ response_id: "PSL_ACM_OTHER",
128
+ is_false_blank: true)
129
+
130
+ if isDataInputCreateOther
131
+ self.promptForAnswer(
132
+ question: "Describe the method of account creation that your app supports",
133
+ hash: hash,
134
+ question_id: "PSL_ACM_SPECIFY",
135
+ response_id: "")
92
136
  end
93
137
 
94
- dataInputAccountDeletionURL = UI.input("Added valid URL for account deletion")
95
- self.addToHash(hash: hash, question_id: "PSL_ACCOUNT_DELETION_URL", response_id: "", value: dataInputAccountDeletionURL)
96
-
138
+ self.promptForUrl(
139
+ question: "Add a valid URL for account deletion",
140
+ hash: hash,
141
+ question_id: "PSL_ACCOUNT_DELETION_URL",
142
+ response_id: "")
97
143
  else
98
- self.addToHash(hash: hash, question_id: "PSL_SUPPORTED_ACCOUNT_CREATION_METHODS", response_id: "PSL_ACM_NONE", value: "true")
99
-
100
- dataInputLoginOutside = UI.input("Can users login to your app with accounts created outside of the app?")
101
- self.addToHash(hash: hash, question_id: "PSL_HAS_OUTSIDE_APP_ACCOUNTS", response_id: "", value: self.trueOrBlank(str_response: dataInputLoginOutside))
102
-
103
- if dataInputLoginOutside.downcase == "y" || dataInputLoginOutside.downcase == "yes"
104
-
105
- dataInputLoginOutsideOut = UI.input("Can users create account outside of app by app identification (e.g. SIM binding, service subscription)? Y/N")
106
- self.addToHash(hash: hash, question_id: "PSL_OUTSIDE_APP_ACCOUNT_TYPES", response_id: "PSL_LOGIN_WITH_OUTSIDE_APP_ID", value: self.trueOrBlank(str_response: dataInputLoginOutsideOut))
107
-
108
- dataInputLoginOutsideAccount = UI.input("Can users create account outside of app by employment or enterprise account?")
109
- self.addToHash(hash: hash, question_id: "PSL_OUTSIDE_APP_ACCOUNT_TYPES", response_id: "PSL_LOGIN_THROUGH_EMPLOYMENT_OR_ENTERPRISE_ACCOUNT", value: self.trueOrBlank(str_response: dataInputLoginOutsideAccount))
110
-
111
- dataInputLoginOutsideOther = UI.input("Is there other way users can create account outside of app?")
112
- self.addToHash(hash: hash, question_id: "PSL_OUTSIDE_APP_ACCOUNT_TYPES", response_id: "PSL_OUTSIDE_APP_ACCOUNT_TYPE_OTHER", value: self.trueOrBlank(str_response: dataInputLoginOutsideOther))
113
-
114
- if dataInputLoginOutsideOther.downcase == "y" || dataInputLoginOutsideOther.downcase == "yes"
115
-
116
- dataInputLoginOutsideOther = UI.input("Describe how these accounts are created")
117
- self.addToHash(hash: hash, question_id: "PSL_OUTSIDE_APP_ACCOUNT_TYPE_SPECIFY", response_id: "", value: dataInputLoginOutsideOther)
118
-
144
+ isDataInputLoginOutside = self.promptForAnswerBoolean(
145
+ question: "Can users login to your app with accounts created outside of the app?",
146
+ hash: hash,
147
+ question_id: "PSL_HAS_OUTSIDE_APP_ACCOUNTS",
148
+ response_id: "",
149
+ is_false_blank: true)
150
+
151
+ if isDataInputLoginOutside
152
+ self.promptForAnswerBoolean(
153
+ question: "Can users create account outside of app by app identification (e.g. SIM binding, service subscription)?",
154
+ hash: hash,
155
+ question_id: "PSL_OUTSIDE_APP_ACCOUNT_TYPES",
156
+ response_id: "PSL_LOGIN_WITH_OUTSIDE_APP_ID",
157
+ is_false_blank: true)
158
+
159
+ self.promptForAnswerBoolean(
160
+ question: "Can users create account outside of app by employment or enterprise account?",
161
+ hash: hash,
162
+ question_id: "PSL_OUTSIDE_APP_ACCOUNT_TYPES",
163
+ response_id: "PSL_LOGIN_THROUGH_EMPLOYMENT_OR_ENTERPRISE_ACCOUNT",
164
+ is_false_blank: true)
165
+
166
+ isDataInputLoginOutsideOther = self.promptForAnswerBoolean(
167
+ question: "Is there other way users can create account outside of app?",
168
+ hash: hash,
169
+ question_id: "PSL_OUTSIDE_APP_ACCOUNT_TYPES",
170
+ response_id: "PSL_OUTSIDE_APP_ACCOUNT_TYPE_OTHER",
171
+ is_false_blank: true)
172
+
173
+ if isDataInputLoginOutsideOther
174
+ self.promptForAnswer(
175
+ question: "Describe how these accounts are created",
176
+ hash: hash,
177
+ question_id: "PSL_OUTSIDE_APP_ACCOUNT_TYPE_SPECIFY",
178
+ response_id: "")
119
179
  end
120
-
121
180
  end
122
-
123
181
  end
124
182
 
125
183
  dataInputDeleteData = UI.input("Do you provide a way for users to request that their data is deleted? Type 90 for deletion after 90 days. Y/N/90")
126
184
 
127
- if dataInputDeleteData.downcase == "y" || dataInputDeleteData == "yes"
185
+ while !self.valid_true_answers.include?(dataInputDeleteData.downcase) &&
186
+ !self.valid_false_answers.include?(dataInputDeleteData.downcase) &&
187
+ !self.valid_90_day_answers.include?(dataInputDeleteData.downcase)
188
+ UI.important("Please provide an answer of either yes, no, or 90")
189
+ dataInputDeleteData = UI.input("Do you provide a way for users to request that their data is deleted? Type 90 for deletion after 90 days. Y/N/90")
190
+ end
191
+
192
+ if self.valid_true_answers.include?(dataInputDeleteData.downcase)
128
193
  self.addToHash(hash: hash, question_id: "PSL_SUPPORT_DATA_DELETION_BY_USER", response_id: "DATA_DELETION_YES", value: "true")
129
194
 
130
- dataInputDeleteLink = UI.input("What URL link can users request to delete data?")
131
- self.addToHash(hash: hash, question_id: "PSL_DATA_DELETION_URL", response_id: "", value: dataInputDeleteLink)
132
- elsif dataInputDeleteData.downcase == "90"
195
+ self.promptForUrl(
196
+ question: "What URL link can users request to delete data?",
197
+ hash: hash,
198
+ question_id: "PSL_DATA_DELETION_URL",
199
+ response_id: "")
200
+ elsif self.valid_90_day_answers.include?(dataInputDeleteData.downcase)
133
201
  self.addToHash(hash: hash, question_id: "PSL_SUPPORT_DATA_DELETION_BY_USER", response_id: "DATA_DELETION_NO_AUTO_DELETED", value: "true")
134
202
  else
135
203
  self.addToHash(hash: hash, question_id: "PSL_SUPPORT_DATA_DELETION_BY_USER", response_id: "DATA_DELETION_NO", value: "true")
@@ -139,7 +207,14 @@ module Fastlane
139
207
  end
140
208
  end
141
209
 
210
+ # Prompt question data type group question for Google Data Safety sheet.
211
+ #
212
+ # @param hash [hash] The hash to save data of answers to questions
142
213
  def self.questionDataInfo(hash: nil)
214
+ UI.important("*** Below questions will be asked about location data ***")
215
+ self.questionDataType(title: "approximate location", code: "PSL_APPROX_LOCATION", group_code: "PSL_DATA_TYPES_LOCATION", hash: hash)
216
+ self.questionDataType(title: "precise location", code: "PSL_PRECISE_LOCATION", group_code: "PSL_DATA_TYPES_LOCATION", hash: hash)
217
+
143
218
  UI.important("*** Below questions will be asked about personal information data ***")
144
219
  self.questionDataType(title: "name", code: "PSL_NAME", group_code: "PSL_DATA_TYPES_PERSONAL", hash: hash)
145
220
  self.questionDataType(title: "email", code: "PSL_EMAIL", group_code: "PSL_DATA_TYPES_PERSONAL", hash: hash)
@@ -157,12 +232,9 @@ module Fastlane
157
232
  self.questionDataType(title: "credit score", code: "PSL_CREDIT_SCORE", group_code: "PSL_DATA_TYPES_FINANCIAL", hash: hash)
158
233
  self.questionDataType(title: "other financial info", code: "PSL_OTHER", group_code: "PSL_DATA_TYPES_FINANCIAL", hash: hash)
159
234
 
160
- UI.important("*** Below questions will be asked about location data ***")
161
- self.questionDataType(title: "approximate location", code: "PSL_APPROX_LOCATION", group_code: "PSL_DATA_TYPES_LOCATION", hash: hash)
162
- self.questionDataType(title: "precise location", code: "PSL_PRECISE_LOCATION", group_code: "PSL_DATA_TYPES_LOCATION", hash: hash)
163
-
164
- UI.important("*** Below questions will be asked about location data ***")
165
- self.questionDataType(title: "web browser history", code: "PSL_WEB_BROWSING_HISTORY", group_code: "PSL_DATA_TYPES_SEARCH_AND_BROWSING", hash: hash)
235
+ UI.important("\n*** Below questions will be asked about audio and music data ***")
236
+ self.questionDataType(title: "health info", code: "PSL_HEALTH", group_code: "PSL_DATA_TYPES_HEALTH_AND_FITNESS", hash: hash)
237
+ self.questionDataType(title: "fitness info", code: "PSL_FITNESS", group_code: "PSL_DATA_TYPES_HEALTH_AND_FITNESS", hash: hash)
166
238
 
167
239
  UI.important("*** Below questions will be asked about email and text data ***")
168
240
  self.questionDataType(title: "email", code: "PSL_EMAILS", group_code: "PSL_DATA_TYPES_EMAIL_AND_TEXT", hash: hash)
@@ -178,127 +250,300 @@ module Fastlane
178
250
  self.questionDataType(title: "music files", code: "PSL_MUSIC", group_code: "PSL_DATA_TYPES_AUDIO", hash: hash)
179
251
  self.questionDataType(title: "Other audio files", code: "PSL_OTHER_AUDIO", group_code: "PSL_DATA_TYPES_AUDIO", hash: hash)
180
252
 
181
- UI.important("\n*** Below questions will be asked about audio and music data ***")
182
- self.questionDataType(title: "health info", code: "PSL_HEALTH", group_code: "PSL_DATA_TYPES_HEALTH_AND_FITNESS", hash: hash)
183
- self.questionDataType(title: "fitness info", code: "PSL_FITNESS", group_code: "PSL_DATA_TYPES_HEALTH_AND_FITNESS", hash: hash)
184
-
185
- UI.important("*** Below questions will be asked about contact data ***")
186
- self.questionDataType(title: "contacts", code: "PSL_CONTACTS", group_code: "PSL_DATA_TYPES_CONTACTS", hash: hash)
253
+ UI.important("*** Below questions will be asked about files and documents data ***")
254
+ self.questionDataType(title: "files and docs", code: "PSL_FILES_AND_DOCS", group_code: "PSL_DATA_TYPES_FILES_AND_DOCS", hash: hash)
187
255
 
188
256
  UI.important("*** Below questions will be asked about calendar data ***")
189
257
  self.questionDataType(title: "calendar events", code: "PSL_CALENDAR", group_code: "PSL_DATA_TYPES_CALENDAR", hash: hash)
190
258
 
191
- UI.important("*** Below questions will be asked about app performance data ***")
192
- self.questionDataType(title: "crash log", code: "PSL_CRASH_LOGS", group_code: "PSL_DATA_TYPES_APP_PERFORMANCE", hash: hash)
193
- self.questionDataType(title: "performance diagnostics", code: "PSL_PERFORMANCE_DIAGNOSTICS", group_code: "PSL_DATA_TYPES_APP_PERFORMANCE", hash: hash)
194
- self.questionDataType(title: "other app performance data", code: "PSL_OTHER_PERFORMANCE", group_code: "PSL_DATA_TYPES_APP_PERFORMANCE", hash: hash)
195
-
196
- UI.important("*** Below questions will be asked about files and documents data ***")
197
- self.questionDataType(title: "files and docs", code: "PSL_FILES_AND_DOCS", group_code: "PSL_DATA_TYPES_FILES_AND_DOCS", hash: hash)
259
+ UI.important("*** Below questions will be asked about contact data ***")
260
+ self.questionDataType(title: "contacts", code: "PSL_CONTACTS", group_code: "PSL_DATA_TYPES_CONTACTS", hash: hash)
198
261
 
199
262
  UI.important("*** Below questions will be asked about app activity data ***")
200
263
  self.questionDataType(title: "app interactions", code: "PSL_USER_INTERACTION", group_code: "PSL_DATA_TYPES_APP_ACTIVITY", hash: hash)
201
- self.questionDataType(title: "installed apps", code: "PSL_IN_APP_SEARCH_HISTORY", group_code: "PSL_DATA_TYPES_APP_ACTIVITY", hash: hash)
264
+ self.questionDataType(title: "in-app search history", code: "PSL_IN_APP_SEARCH_HISTORY", group_code: "PSL_DATA_TYPES_APP_ACTIVITY", hash: hash)
265
+ self.questionDataType(title: "installed apps", code: "PSL_APPS_ON_DEVICE", group_code: "PSL_DATA_TYPES_APP_ACTIVITY", hash: hash)
202
266
  self.questionDataType(title: "other user-generated content", code: "PSL_USER_GENERATED_CONTENT", group_code: "PSL_DATA_TYPES_APP_ACTIVITY", hash: hash)
203
267
  self.questionDataType(title: "other actions", code: "PSL_OTHER_APP_ACTIVITY", group_code: "PSL_DATA_TYPES_APP_ACTIVITY", hash: hash)
204
268
 
269
+ UI.important("*** Below questions will be asked about location data ***")
270
+ self.questionDataType(title: "web browser history", code: "PSL_WEB_BROWSING_HISTORY", group_code: "PSL_DATA_TYPES_SEARCH_AND_BROWSING", hash: hash)
271
+
272
+ UI.important("*** Below questions will be asked about app performance data ***")
273
+ self.questionDataType(title: "crash log", code: "PSL_CRASH_LOGS", group_code: "PSL_DATA_TYPES_APP_PERFORMANCE", hash: hash)
274
+ self.questionDataType(title: "performance diagnostics", code: "PSL_PERFORMANCE_DIAGNOSTICS", group_code: "PSL_DATA_TYPES_APP_PERFORMANCE", hash: hash)
275
+ self.questionDataType(title: "other app performance data", code: "PSL_OTHER_PERFORMANCE", group_code: "PSL_DATA_TYPES_APP_PERFORMANCE", hash: hash)
276
+
205
277
  UI.important("*** Below questions will be asked about identifier data ***")
206
278
  self.questionDataType(title: "device or other IDs", code: "PSL_DEVICE_ID", group_code: "PSL_DATA_TYPES_IDENTIFIERS", hash: hash)
207
279
  end
208
280
 
281
+ # Prompt question of a specific data type about data sharing and data collection for Google Data Safety sheet.
282
+ #
283
+ # @param title [String] The data type title
284
+ # @param code [String] Google Data Safety code name for CSV file format
285
+ # @param group_code [String] Google Data Safety group code name for CSV file format
286
+ # @param hash [hash] The hash to save data of answers to questions
209
287
  def self.questionDataType(title: "", code: "", group_code: "", hash: nil)
210
- dataInputCollectOrShare = UI.input("Is #{title} data being collected or shared? Y/N")
211
- self.addToHash(hash: hash, question_id: group_code, response_id: code, value: self.trueOrBlank(str_response: dataInputCollectOrShare))
212
-
213
- if dataInputCollectOrShare.downcase == "y" || dataInputCollectOrShare.downcase == "yes"
214
- dataInputShared = UI.input("Is #{title} data being shared? Y/N")
215
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:PSL_DATA_USAGE_COLLECTION_AND_SHARING", response_id: "PSL_DATA_USAGE_ONLY_SHARED", value: self.trueOrBlank(str_response: dataInputShared))
216
-
217
- # Data Shared Question
218
- if dataInputShared.downcase == "y" || dataInputShared.downcase == "yes"
219
- dataInputShareAppFunctionality = UI.input("Is #{title} shared for app functionality? Y/N")
220
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_SHARING_PURPOSE", response_id: "PSL_APP_FUNCTIONALITY", value: self.trueOrBlank(str_response: dataInputShareAppFunctionality))
221
-
222
- dataInputShareAnalytics = UI.input("Is #{title} shared for analytics? Y/N")
223
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_SHARING_PURPOSE", response_id: "PSL_ANALYTICS", value: self.trueOrBlank(str_response: dataInputShareAnalytics))
224
-
225
- dataInputShareDeveloperComm = UI.input("Is #{title} shared for developer communications? Y/N")
226
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_SHARING_PURPOSE", response_id: "PSL_DEVELOPER_COMMUNICATIONS", value: self.trueOrBlank(str_response: dataInputShareDeveloperComm))
227
-
228
- dataInputShareAdvertise = UI.input("Is #{title} shared for advertising or marketing? Y/N")
229
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_SHARING_PURPOSE", response_id: "PSL_ADVERTISING", value: self.trueOrBlank(str_response: dataInputShareAdvertise))
230
-
231
- dataInputShareFraud = UI.input("Is #{title} shared for fraud prevention, security, or compliance? Y/N")
232
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_SHARING_PURPOSE", response_id: "PSL_FRAUD_PREVENTION_SECURITY", value: self.trueOrBlank(str_response: dataInputShareFraud))
233
-
234
- dataInputSharePersonal = UI.input("Is #{title} shared for personalizing the app? Y/N")
235
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_SHARING_PURPOSE", response_id: "PSL_PERSONALIZATION", value: self.trueOrBlank(str_response: dataInputSharePersonal))
236
-
237
- dataInputShareAccount = UI.input("Is #{title} shared to manage account? Y/N")
238
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_SHARING_PURPOSE", response_id: "PSL_ACCOUNT_MANAGEMENT", value: self.trueOrBlank(str_response: dataInputShareAccount))
288
+ isDataInputCollectOrShare = self.promptForAnswerBoolean(
289
+ question: "Is #{title} data being collected or shared?",
290
+ hash: hash,
291
+ question_id: group_code,
292
+ response_id: code,
293
+ is_false_blank: true)
294
+
295
+ if isDataInputCollectOrShare
296
+ isDataInputShared = self.promptForAnswerBoolean(
297
+ question: "Is #{title} data being shared?",
298
+ hash: hash,
299
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:PSL_DATA_USAGE_COLLECTION_AND_SHARING",
300
+ response_id: "PSL_DATA_USAGE_ONLY_SHARED",
301
+ is_false_blank: true)
302
+
303
+ if isDataInputShared
304
+ self.promptForAnswerBoolean(
305
+ question: "Is #{title} shared for app functionality?",
306
+ hash: hash,
307
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_SHARING_PURPOSE",
308
+ response_id: "PSL_APP_FUNCTIONALITY",
309
+ is_false_blank: true)
310
+
311
+ self.promptForAnswerBoolean(
312
+ question: "Is #{title} shared for analytics?",
313
+ hash: hash,
314
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_SHARING_PURPOSE",
315
+ response_id: "PSL_ANALYTICS",
316
+ is_false_blank: true)
317
+
318
+ self.promptForAnswerBoolean(
319
+ question: "Is #{title} shared for developer communications?",
320
+ hash: hash,
321
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_SHARING_PURPOSE",
322
+ response_id: "PSL_DEVELOPER_COMMUNICATIONS",
323
+ is_false_blank: true)
324
+
325
+ self.promptForAnswerBoolean(
326
+ question: "Is #{title} shared for advertising or marketing?",
327
+ hash: hash,
328
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_SHARING_PURPOSE",
329
+ response_id: "PSL_ADVERTISING",
330
+ is_false_blank: true)
331
+
332
+ self.promptForAnswerBoolean(
333
+ question: "Is #{title} shared for fraud prevention, security, or compliance?",
334
+ hash: hash,
335
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_SHARING_PURPOSE",
336
+ response_id: "PSL_FRAUD_PREVENTION_SECURITY",
337
+ is_false_blank: true)
338
+
339
+ self.promptForAnswerBoolean(
340
+ question: "Is #{title} shared for personalizing the app?",
341
+ hash: hash,
342
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_SHARING_PURPOSE",
343
+ response_id: "PSL_PERSONALIZATION",
344
+ is_false_blank: true)
345
+
346
+ self.promptForAnswerBoolean(
347
+ question: "Is #{title} shared to manage account?",
348
+ hash: hash,
349
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_SHARING_PURPOSE",
350
+ response_id: "PSL_ACCOUNT_MANAGEMENT",
351
+ is_false_blank: true)
239
352
  end
240
353
 
241
- dataInputCollected = UI.input("Is #{title} data being collected? Y/N")
242
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:PSL_DATA_USAGE_COLLECTION_AND_SHARING", response_id: "PSL_DATA_USAGE_ONLY_COLLECTED", value: self.trueOrBlank(str_response: dataInputCollected))
243
-
244
- # Data Collection Question
245
- if dataInputCollected.downcase == "y" || dataInputCollected.downcase == "yes"
246
- dataInputEphemeral = UI.input("Is #{title} data processed ephemerally? Y/N")
247
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:PSL_DATA_USAGE_EPHEMERAL", response_id: "", value: self.trueOrBlank(str_response: dataInputEphemeral))
248
-
249
- dataInputRequired = UI.input("Is #{title} data required for your app? Y/N")
250
- if dataInputRequired.downcase == "y" || dataInputRequired == "yes"
354
+ isDataInputCollected = self.promptForAnswerBoolean(
355
+ question: "Is #{title} data being collected?",
356
+ hash: hash,
357
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:PSL_DATA_USAGE_COLLECTION_AND_SHARING",
358
+ response_id: "PSL_DATA_USAGE_ONLY_COLLECTED",
359
+ is_false_blank: true)
360
+
361
+ if isDataInputCollected
362
+ self.promptForAnswerBoolean(
363
+ question: "Is #{title} data processed ephemerally?",
364
+ hash: hash,
365
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:PSL_DATA_USAGE_EPHEMERAL",
366
+ response_id: "",
367
+ is_false_blank: false)
368
+
369
+ isDataInputRequired = self.promptForAnswerBoolean(
370
+ question: "Is #{title} data required for your app?",
371
+ hash: nil,
372
+ question_id: nil,
373
+ response_id: nil,
374
+ is_false_blank: false)
375
+
376
+ if isDataInputRequired
251
377
  self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_USER_CONTROL", response_id: "PSL_DATA_USAGE_USER_CONTROL_REQUIRED", value: "true")
252
378
  else
253
379
  self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_USER_CONTROL", response_id: "PSL_DATA_USAGE_USER_CONTROL_OPTIONAL", value: "true")
254
380
  end
255
381
 
382
+ self.promptForAnswerBoolean(
383
+ question: "Is #{title} collected for app functionality?",
384
+ hash: hash,
385
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_COLLECTION_PURPOSE",
386
+ response_id: "PSL_APP_FUNCTIONALITY",
387
+ is_false_blank: true)
388
+
389
+ self.promptForAnswerBoolean(
390
+ question: "Is #{title} collected for analytics?",
391
+ hash: hash,
392
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_COLLECTION_PURPOSE",
393
+ response_id: "PSL_ANALYTICS",
394
+ is_false_blank: true)
395
+
396
+ self.promptForAnswerBoolean(
397
+ question: "Is #{title} collected for developer communications?",
398
+ hash: hash,
399
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_COLLECTION_PURPOSE",
400
+ response_id: "PSL_DEVELOPER_COMMUNICATIONS",
401
+ is_false_blank: true)
402
+
403
+ self.promptForAnswerBoolean(
404
+ question: "Is #{title} collected for advertising or marketing?",
405
+ hash: hash,
406
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_COLLECTION_PURPOSE",
407
+ response_id: "PSL_ADVERTISING",
408
+ is_false_blank: true)
409
+
410
+ self.promptForAnswerBoolean(
411
+ question: "Is #{title} collected for fraud prevention, security, or compliance?",
412
+ hash: hash,
413
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_COLLECTION_PURPOSE",
414
+ response_id: "PSL_FRAUD_PREVENTION_SECURITY",
415
+ is_false_blank: true)
416
+
417
+ self.promptForAnswerBoolean(
418
+ question: "Is #{title} collected for personalizing the app?",
419
+ hash: hash,
420
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_COLLECTION_PURPOSE",
421
+ response_id: "PSL_PERSONALIZATION",
422
+ is_false_blank: true)
423
+
424
+ self.promptForAnswerBoolean(
425
+ question: "Is #{title} collected to manage account?",
426
+ hash: hash,
427
+ question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_COLLECTION_PURPOSE",
428
+ response_id: "PSL_ACCOUNT_MANAGEMENT",
429
+ is_false_blank: true)
430
+ end
431
+ end
432
+ end
256
433
 
257
- dataInputCollectAppFunctionality = UI.input("Is #{title} collected for app functionality? Y/N")
258
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_COLLECTION_PURPOSE", response_id: "PSL_APP_FUNCTIONALITY", value: self.trueOrBlank(str_response: dataInputCollectAppFunctionality))
434
+ # Save CSV file with data sanitation.
435
+ #
436
+ # @param data [hash] Google Data Safety hash of CSV content
437
+ # @param csv_file_path [String] CSV file path to save file at
438
+ def self.saveCsvFile(data: nil, csv_file_path: "")
439
+ FileUtils.mkdir_p(File.dirname(csv_file_path))
259
440
 
260
- dataInputCollectAnalytics = UI.input("Is #{title} collected for analytics? Y/N")
261
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_COLLECTION_PURPOSE", response_id: "PSL_ANALYTICS", value: self.trueOrBlank(str_response: dataInputCollectAnalytics))
441
+ CSV.open(csv_file_path, "w") do |csv|
442
+ data.each do |row|
443
+ sanitized_row = row.map do |cell|
444
+ if cell.is_a?(String)
445
+ cleaned = cell.gsub(/[\t\r\n]/, '').strip
262
446
 
263
- dataInputCollectDeveloperComm = UI.input("Is #{title} collected for developer communications? Y/N")
264
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_COLLECTION_PURPOSE", response_id: "PSL_DEVELOPER_COMMUNICATIONS", value: self.trueOrBlank(str_response: dataInputCollectDeveloperComm))
447
+ if cleaned.lstrip.start_with?(*FORMULA_CHARS)
448
+ cleaned = "'#{cleaned}"
449
+ end
265
450
 
266
- dataInputCollectAdvertise = UI.input("Is #{title} collected for advertising or marketing? Y/N")
267
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_COLLECTION_PURPOSE", response_id: "PSL_ADVERTISING", value: self.trueOrBlank(str_response: dataInputCollectAdvertise))
451
+ cleaned
452
+ else
453
+ cell
454
+ end
455
+ end
268
456
 
269
- dataInputCollectFraud = UI.input("Is #{title} collected for fraud prevention, security, or compliance? Y/N")
270
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_COLLECTION_PURPOSE", response_id: "PSL_FRAUD_PREVENTION_SECURITY", value: self.trueOrBlank(str_response: dataInputCollectFraud))
457
+ csv << sanitized_row
458
+ end
459
+ end
460
+ end
271
461
 
272
- dataInputCollectPersonal = UI.input("Is #{title} collected for personalizing the app? Y/N")
273
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_COLLECTION_PURPOSE", response_id: "PSL_PERSONALIZATION", value: self.trueOrBlank(str_response: dataInputCollectPersonal))
462
+ # Prompt question to terminal for a yes or no answer. Reprompt user if answer is not a valid yes or no.
463
+ #
464
+ # @param question [String] The question that displays in terminal
465
+ # @param hash [hash] The hash data for Google Data Safety.
466
+ # @param question_id [String] The question id to add in the hash data. Review CSV file from Google Data Safety sheet for more details.
467
+ # @param response_id [String] The response id to add in the hash data. Review CSV file from Google Data Safety sheet for more details.
468
+ # @param is_false_blank [Boolean] Whether the false answer should be false or blank in CSV file. It is dependent on the question ID and response ID for Google Data Safety sheet format.
469
+ # @return [Boolean] The user's answer to the question
470
+ def self.promptForAnswerBoolean(question: "", hash: nil, question_id: nil, response_id: "", is_false_blank: false)
471
+ answer = UI.input(question + " Y/N")
472
+
473
+ while !self.valid_true_answers.include?(answer.downcase) && !self.valid_false_answers.include?(answer.downcase)
474
+ UI.important("Please provide \"y\", \"yes\", \"true\", or \"t\" for yes; and \"n\", \"no\", \"false\", \"f\" for no")
475
+ answer = UI.input(question + " Y/N")
476
+ end
274
477
 
275
- dataInputCollectAccount = UI.input("Is #{title} collected to manage account? Y/N")
276
- self.addToHash(hash: hash, question_id: "PSL_DATA_USAGE_RESPONSES:#{code}:DATA_USAGE_COLLECTION_PURPOSE", response_id: "PSL_ACCOUNT_MANAGEMENT", value: self.trueOrBlank(str_response: dataInputCollectAccount))
277
- end
478
+ if self.valid_true_answers.include?(answer.downcase)
479
+ self.addToHash(hash: hash, question_id: question_id, response_id: response_id, value: "true")
480
+ return true
481
+ else
482
+ self.addToHash(hash: hash, question_id: question_id, response_id: response_id, value: is_false_blank ? "" : "false")
483
+ return false
278
484
  end
279
485
  end
280
486
 
281
- def self.addToHash(hash: nil, question_id: nil, response_id: "", value: nil)
282
- if !value.nil?
283
- hash["#{question_id},#{response_id}"] = value
487
+ # Prompt question to terminal for a string answer. Reprompt user if answer is empty string.
488
+ #
489
+ # @param question [String] The question that displays in terminal
490
+ # @param hash [hash] The hash data for Google Data Safety
491
+ # @param question_id [String] The question id to add in the hash data. Review CSV file from Google Data Safety sheet for more details.
492
+ # @param response_id [String] The response id to add in the hash data. Review CSV file from Google Data Safety sheet for more details.
493
+ # @return [String] The user's answer to question
494
+ def self.promptForAnswer(question: "", hash: nil, question_id: nil, response_id: "")
495
+ answer = UI.input(question)
496
+
497
+ while answer.empty?
498
+ UI.important("Please provide an answer")
499
+ answer = UI.input(question)
284
500
  end
501
+
502
+ self.addToHash(hash: hash, question_id: question_id, response_id: response_id, value: answer)
503
+ return answer
285
504
  end
286
505
 
287
- def self.trueOrBlank(str_response: nil)
288
- if str_response.downcase == "y" || str_response.downcase == "yes"
289
- "true"
290
- else
291
- nil
506
+ # Prompt question to terminal for URL answer. Reprompt user if answer is not a valid URL.
507
+ #
508
+ # @param question [String] The question that displays in terminal
509
+ # @param hash [hash] The hash data for Google Data Safety
510
+ # @param question_id [String] The question id to add in the hash data. Review CSV file from Google Data Safety sheet for more details.
511
+ # @param response_id [String] The response id to add in the hash data. Review CSV file from Google Data Safety sheet for more details.
512
+ # @return [String] The user's answer to question
513
+ def self.promptForUrl(question: "", hash: nil, question_id: nil, response_id: "")
514
+ answer = UI.input(question)
515
+
516
+ while !self.validUrl?(answer)
517
+ UI.important("Please provide a proper URL")
518
+ answer = UI.input(question)
292
519
  end
520
+
521
+ self.addToHash(hash: hash, question_id: question_id, response_id: response_id, value: answer)
522
+ return answer
293
523
  end
294
524
 
295
- def self.trueOrFalse(str_response: nil)
296
- if str_response.downcase == "y" || str_response.downcase == "yes"
297
- "true"
298
- else
299
- "false"
525
+ # Detect if URL is valid format.
526
+ #
527
+ # @param url [String] The url to validate
528
+ # @return [Boolean] The url is valid
529
+ def self.validUrl?(url)
530
+ uri = URI.parse(url)
531
+ uri.is_a?(URI::HTTP) && !uri.host.nil?
532
+ rescue URI::InvalidURIError
533
+ false
534
+ end
535
+
536
+ # Prompt question and answer to Google Data Safety hash
537
+ #
538
+ # @param hash [hash] The hash data for Google Data Safety
539
+ # @param question_id [String] The question id to add in the hash data. Review CSV file from Google Data Safety sheet for more details.
540
+ # @param response_id [String] The response id to add in the hash data. Review CSV file from Google Data Safety sheet for more details.
541
+ # @param value [String] The answer value
542
+ def self.addToHash(hash: nil, question_id: nil, response_id: "", value: nil)
543
+ if !value.nil? && !hash.nil?
544
+ hash["#{question_id},#{response_id}"] = value
300
545
  end
301
- end
546
+ end
302
547
 
303
548
  def self.description
304
549
  "Prompt user questions to generate Google data safety sheet"
@@ -17,7 +17,13 @@ module Fastlane
17
17
 
18
18
  access_token = body["access_token"]
19
19
 
20
- self.send_data_safety_sheet(package_name: params[:package_name], auth_token: access_token, csv_content: csv_content)
20
+ debug = false
21
+
22
+ if !params[:debug].nil?
23
+ debug = params[:debug]
24
+ end
25
+
26
+ self.send_data_safety_sheet(package_name: params[:package_name], auth_token: access_token, csv_content: csv_content, debug: debug)
21
27
 
22
28
  end
23
29
 
@@ -79,7 +85,7 @@ module Fastlane
79
85
  token
80
86
  end
81
87
 
82
- def self.send_data_safety_sheet(package_name: nil, auth_token: nil, csv_content: nil)
88
+ def self.send_data_safety_sheet(package_name: nil, auth_token: nil, csv_content: nil, debug: false)
83
89
  if package_name.nil?
84
90
  if UI.interactive?
85
91
  UI.important("To not be asked package name, you can specify it using 'package_name'.")
@@ -111,6 +117,11 @@ module Fastlane
111
117
  else
112
118
  UI.error("Google Data Safety sheet upload error with API")
113
119
  end
120
+
121
+ if debug
122
+ UI.important("Google Data Safety API response body")
123
+ UI.important(response.body)
124
+ end
114
125
  end
115
126
 
116
127
  end
@@ -169,6 +180,13 @@ module Fastlane
169
180
  optional: true,
170
181
  type: Hash
171
182
  ),
183
+ FastlaneCore::ConfigItem.new(
184
+ key: :debug,
185
+ env_name: "DEBUG",
186
+ description: "Set to debug mode for troubleshooting",
187
+ optional: true,
188
+ type: Boolean
189
+ ),
172
190
  ]
173
191
  end
174
192
 
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module GoogleDataSafety
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,30 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-google_data_safety
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Owen Bean
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-12-16 00:00:00.000000000 Z
11
+ date: 2026-08-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: googleauth
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 1.8.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.8.1
27
- description:
27
+ description:
28
28
  email: owenbean400@gmail.com
29
29
  executables: []
30
30
  extensions: []
@@ -43,7 +43,7 @@ licenses:
43
43
  - MIT
44
44
  metadata:
45
45
  rubygems_mfa_required: 'true'
46
- post_install_message:
46
+ post_install_message:
47
47
  rdoc_options: []
48
48
  require_paths:
49
49
  - lib
@@ -58,8 +58,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
58
58
  - !ruby/object:Gem::Version
59
59
  version: '0'
60
60
  requirements: []
61
- rubygems_version: 3.0.3.1
62
- signing_key:
61
+ rubygems_version: 3.5.22
62
+ signing_key:
63
63
  specification_version: 4
64
64
  summary: Google safety data Fastlane plugin for automation.
65
65
  test_files: []