analytics_ops 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +76 -0
- data/CONTRIBUTING.md +2 -1
- data/README.md +232 -41
- data/SECURITY.md +8 -8
- data/docs/api-support-matrix.md +9 -8
- data/docs/architecture.md +22 -4
- data/docs/authentication.md +112 -51
- data/docs/commands.md +71 -17
- data/docs/configuration-schema-v1.json +53 -7
- data/docs/configuration.md +33 -9
- data/docs/google-client-compatibility.md +10 -6
- data/docs/live-smoke-test.md +159 -0
- data/docs/plan-format.md +4 -0
- data/docs/plan-schema-v1.json +113 -22
- data/docs/rails.md +12 -3
- data/docs/reports.md +44 -6
- data/docs/safety.md +18 -6
- data/docs/troubleshooting.md +42 -13
- data/lib/analytics_ops/applier.rb +2 -1
- data/lib/analytics_ops/cli/options.rb +157 -0
- data/lib/analytics_ops/cli/presenter.rb +221 -0
- data/lib/analytics_ops/cli.rb +172 -201
- data/lib/analytics_ops/clients/admin.rb +130 -129
- data/lib/analytics_ops/clients/admin_normalization.rb +95 -0
- data/lib/analytics_ops/clients/data.rb +120 -63
- data/lib/analytics_ops/clients/error_translation.rb +45 -0
- data/lib/analytics_ops/configuration/document.rb +1 -1
- data/lib/analytics_ops/configuration/loader.rb +38 -5
- data/lib/analytics_ops/configuration/schema.rb +51 -8
- data/lib/analytics_ops/configuration/validator.rb +43 -8
- data/lib/analytics_ops/configuration/writer.rb +105 -0
- data/lib/analytics_ops/configuration.rb +1 -0
- data/lib/analytics_ops/connection.rb +93 -0
- data/lib/analytics_ops/desired_state.rb +2 -2
- data/lib/analytics_ops/plan.rb +94 -33
- data/lib/analytics_ops/planner.rb +10 -2
- data/lib/analytics_ops/rails/railtie.rb +13 -18
- data/lib/analytics_ops/redaction.rb +14 -9
- data/lib/analytics_ops/reports/catalog.rb +22 -5
- data/lib/analytics_ops/reports/definition.rb +74 -37
- data/lib/analytics_ops/reports/overview.rb +55 -0
- data/lib/analytics_ops/reports/overview_result.rb +54 -0
- data/lib/analytics_ops/reports/result.rb +38 -9
- data/lib/analytics_ops/reports.rb +2 -0
- data/lib/analytics_ops/resources.rb +2 -1
- data/lib/analytics_ops/service_account.rb +171 -0
- data/lib/analytics_ops/setup.rb +171 -0
- data/lib/analytics_ops/snapshot.rb +20 -5
- data/lib/analytics_ops/version.rb +1 -1
- data/lib/analytics_ops/workspace.rb +41 -12
- data/lib/analytics_ops.rb +4 -0
- data/lib/generators/analytics_ops/install_generator.rb +2 -0
- data/lib/generators/analytics_ops/templates/analytics_ops.yml +2 -15
- data/sig/analytics_ops.rbs +117 -10
- metadata +26 -1
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
# Official Admin API boundary.
|
|
4
4
|
|
|
5
|
+
require_relative "admin_normalization"
|
|
6
|
+
|
|
5
7
|
module AnalyticsOps
|
|
6
8
|
module Clients
|
|
7
9
|
# Narrow adapter around Google's generated Admin client.
|
|
8
10
|
class Admin
|
|
11
|
+
include AdminNormalization
|
|
12
|
+
|
|
9
13
|
PACKAGE_REQUIREMENT = Gem::Requirement.new("~> 0.8.0")
|
|
10
14
|
RETENTION_TO_GOOGLE = {
|
|
11
15
|
"2_months" => :TWO_MONTHS,
|
|
@@ -29,41 +33,53 @@ module AnalyticsOps
|
|
|
29
33
|
"custom_metrics" => :create_custom_metric
|
|
30
34
|
}.freeze
|
|
31
35
|
|
|
32
|
-
def initialize(client: nil,
|
|
33
|
-
|
|
36
|
+
def initialize(client: nil, service_account: nil, access: :read, transport: :grpc, timeout: nil, logger: nil)
|
|
37
|
+
unless service_account.nil? || service_account.is_a?(ServiceAccount)
|
|
38
|
+
raise ConfigurationError, "service_account must be an AnalyticsOps::ServiceAccount"
|
|
39
|
+
end
|
|
40
|
+
raise ConfigurationError, "access must be :read or :edit" unless %i[read edit].include?(access)
|
|
34
41
|
|
|
35
42
|
@client = client
|
|
36
|
-
@
|
|
37
|
-
@
|
|
38
|
-
@
|
|
43
|
+
@service_account = service_account
|
|
44
|
+
@access = access
|
|
45
|
+
@transport = validate_transport(transport)
|
|
46
|
+
@timeout = validate_timeout(timeout)
|
|
39
47
|
@logger = logger
|
|
40
48
|
end
|
|
41
49
|
|
|
42
|
-
def discover
|
|
50
|
+
def discover(include_streams: true)
|
|
51
|
+
raise ConfigurationError, "include_streams must be true or false" unless [true, false].include?(include_streams)
|
|
52
|
+
|
|
43
53
|
list(:list_account_summaries, page_size: 200).map do |summary|
|
|
54
|
+
account_name = remote_string(field(summary, :account), "account name")
|
|
44
55
|
properties = array_field(summary, :property_summaries).map do |property|
|
|
45
56
|
normalized = normalize_property(property, can_edit: field(property, :can_edit))
|
|
46
|
-
|
|
47
|
-
|
|
57
|
+
next normalized.to_h unless include_streams
|
|
58
|
+
|
|
59
|
+
normalized.to_h.merge("streams" => list_streams(normalized.id).map(&:to_h))
|
|
48
60
|
end
|
|
49
61
|
properties.sort_by! { |property| property.fetch("id") }
|
|
50
62
|
|
|
51
63
|
Resources::Account.new(
|
|
52
|
-
id: resource_id(
|
|
53
|
-
name:
|
|
54
|
-
display_name: field(summary, :display_name)
|
|
64
|
+
id: resource_id(account_name),
|
|
65
|
+
name: account_name,
|
|
66
|
+
display_name: remote_string(field(summary, :display_name), "account display name"),
|
|
55
67
|
properties:
|
|
56
68
|
)
|
|
57
|
-
end.sort_by(&:id)
|
|
69
|
+
end.sort_by(&:id).freeze
|
|
58
70
|
end
|
|
59
71
|
|
|
60
72
|
def property_access(property_id)
|
|
61
|
-
|
|
73
|
+
expected_name = property_name(property_id)
|
|
74
|
+
id = property_id
|
|
62
75
|
list(:list_account_summaries, page_size: 200).each do |summary|
|
|
63
76
|
array_field(summary, :property_summaries).each do |property|
|
|
64
77
|
next unless resource_id(field(property, :property)) == id
|
|
65
78
|
|
|
66
|
-
|
|
79
|
+
normalized = normalize_property(property, can_edit: field(property, :can_edit))
|
|
80
|
+
return normalized if normalized.name == expected_name
|
|
81
|
+
|
|
82
|
+
raise RemoteError, "Google Admin API returned a property name that does not match the request"
|
|
67
83
|
end
|
|
68
84
|
end
|
|
69
85
|
|
|
@@ -72,40 +88,63 @@ module AnalyticsOps
|
|
|
72
88
|
|
|
73
89
|
def snapshot(property_id)
|
|
74
90
|
property_name = property_name(property_id)
|
|
91
|
+
property = normalize_property(get(:get_property, name: property_name), can_edit: nil)
|
|
92
|
+
unless property.name == property_name
|
|
93
|
+
raise RemoteError, "Google Admin API returned a property name that does not match the request"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
streams = list_streams(property_id)
|
|
97
|
+
retention = normalize_retention(get(:get_data_retention_settings,
|
|
98
|
+
name: "#{property_name}/dataRetentionSettings"))
|
|
99
|
+
key_events = list(:list_key_events, parent: property_name, page_size: 200).map do |value|
|
|
100
|
+
normalize_key_event(value)
|
|
101
|
+
end
|
|
102
|
+
custom_dimensions = list(:list_custom_dimensions, parent: property_name, page_size: 200)
|
|
103
|
+
.map { |value| normalize_custom_dimension(value) }
|
|
104
|
+
custom_metrics = list(:list_custom_metrics, parent: property_name, page_size: 200)
|
|
105
|
+
.map { |value| normalize_custom_metric(value) }
|
|
106
|
+
validate_snapshot_names!(
|
|
107
|
+
property: property_name,
|
|
108
|
+
streams:,
|
|
109
|
+
retention:,
|
|
110
|
+
key_events:,
|
|
111
|
+
dimensions: custom_dimensions,
|
|
112
|
+
metrics: custom_metrics
|
|
113
|
+
)
|
|
114
|
+
|
|
75
115
|
Snapshot.new(
|
|
76
|
-
property
|
|
77
|
-
streams
|
|
78
|
-
retention
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
end,
|
|
83
|
-
custom_dimensions: list(:list_custom_dimensions, parent: property_name, page_size: 200)
|
|
84
|
-
.map { |value| normalize_custom_dimension(value) },
|
|
85
|
-
custom_metrics: list(:list_custom_metrics, parent: property_name, page_size: 200)
|
|
86
|
-
.map { |value| normalize_custom_metric(value) }
|
|
116
|
+
property:,
|
|
117
|
+
streams:,
|
|
118
|
+
retention:,
|
|
119
|
+
key_events:,
|
|
120
|
+
custom_dimensions:,
|
|
121
|
+
custom_metrics:
|
|
87
122
|
)
|
|
88
123
|
end
|
|
89
124
|
|
|
90
125
|
def capabilities
|
|
91
|
-
|
|
126
|
+
generated_client = translate_errors { client }
|
|
127
|
+
CAPABILITIES.to_h { |name, method| [name, generated_client.respond_to?(method)] }.freeze
|
|
92
128
|
end
|
|
93
129
|
|
|
94
130
|
def compatibility
|
|
95
131
|
specification = Gem::Specification.find_by_name("google-analytics-admin")
|
|
96
|
-
|
|
132
|
+
Canonical.immutable(
|
|
97
133
|
"package" => specification.name,
|
|
98
134
|
"version" => specification.version.to_s,
|
|
99
135
|
"requirement" => PACKAGE_REQUIREMENT.to_s,
|
|
100
136
|
"supported" => PACKAGE_REQUIREMENT.satisfied_by?(specification.version),
|
|
101
137
|
"transport" => @transport.to_s
|
|
102
|
-
|
|
138
|
+
)
|
|
103
139
|
rescue Gem::LoadError => error
|
|
104
140
|
raise UnsupportedCapabilityError, Redaction.message(error.message)
|
|
105
141
|
end
|
|
106
142
|
|
|
107
143
|
def apply_change(change, property_id:)
|
|
108
|
-
|
|
144
|
+
raise InvalidPlanError, "change must be an AnalyticsOps::Plan::Change" unless change.is_a?(Plan::Change)
|
|
145
|
+
|
|
146
|
+
property_name(property_id)
|
|
147
|
+
method_name, request = mutation(change, property_id)
|
|
109
148
|
get(method_name, request)
|
|
110
149
|
change
|
|
111
150
|
end
|
|
@@ -114,9 +153,13 @@ module AnalyticsOps
|
|
|
114
153
|
|
|
115
154
|
def client
|
|
116
155
|
@client ||= begin
|
|
156
|
+
unless @service_account
|
|
157
|
+
raise AuthenticationError, "Analytics Ops requires configured service-account credentials"
|
|
158
|
+
end
|
|
159
|
+
|
|
117
160
|
require "google/analytics/admin"
|
|
118
161
|
Google::Analytics::Admin.analytics_admin_service(transport: @transport) do |config|
|
|
119
|
-
config.credentials = @credentials
|
|
162
|
+
config.credentials = @service_account.__send__(:credentials, access: @access)
|
|
120
163
|
config.timeout = @timeout if @timeout
|
|
121
164
|
end
|
|
122
165
|
end
|
|
@@ -130,7 +173,7 @@ module AnalyticsOps
|
|
|
130
173
|
|
|
131
174
|
def list(method_name, request)
|
|
132
175
|
response = invoke(method_name, request)
|
|
133
|
-
response.respond_to?(:to_a) ? response.to_a : Array(response)
|
|
176
|
+
translate_errors { response.respond_to?(:to_a) ? response.to_a : Array(response) }
|
|
134
177
|
end
|
|
135
178
|
|
|
136
179
|
def get(method_name, request)
|
|
@@ -158,93 +201,8 @@ module AnalyticsOps
|
|
|
158
201
|
request.dig(:data_retention_settings, :name)
|
|
159
202
|
end
|
|
160
203
|
|
|
161
|
-
def translate_errors
|
|
162
|
-
|
|
163
|
-
rescue AnalyticsOps::Error
|
|
164
|
-
raise
|
|
165
|
-
rescue StandardError => error
|
|
166
|
-
translated = case error.class.name
|
|
167
|
-
when /Unauthenticated|Google::Auth|Signet::Authorization/
|
|
168
|
-
AuthenticationError
|
|
169
|
-
when /PermissionDenied|Forbidden/
|
|
170
|
-
AuthorizationError
|
|
171
|
-
when /ResourceExhausted|TooManyRequests/
|
|
172
|
-
QuotaError
|
|
173
|
-
when /DeadlineExceeded|Timeout|ETIMEDOUT/
|
|
174
|
-
TimeoutError
|
|
175
|
-
when /InvalidArgument|FailedPrecondition|NotFound|AlreadyExists/
|
|
176
|
-
InvalidRequestError
|
|
177
|
-
when /Google::Cloud::|GRPC::|Faraday::|HTTP/
|
|
178
|
-
RemoteError
|
|
179
|
-
end
|
|
180
|
-
raise unless translated
|
|
181
|
-
|
|
182
|
-
raise translated, Redaction.message(error.message)
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def normalize_property(value, can_edit:)
|
|
186
|
-
name = field(value, :property) || field(value, :name)
|
|
187
|
-
Resources::Property.new(
|
|
188
|
-
id: resource_id(name),
|
|
189
|
-
name: name.to_s,
|
|
190
|
-
display_name: field(value, :display_name).to_s,
|
|
191
|
-
parent: optional_string(field(value, :parent)),
|
|
192
|
-
property_type: normalize_enum(field(value, :property_type), prefix: "PROPERTY_TYPE_"),
|
|
193
|
-
can_edit:
|
|
194
|
-
)
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
def normalize_stream(value)
|
|
198
|
-
name = field(value, :name).to_s
|
|
199
|
-
type = STREAM_TYPES.fetch(enum_name(field(value, :type)), "unspecified")
|
|
200
|
-
web_data = field(value, :web_stream_data)
|
|
201
|
-
Resources::DataStream.new(
|
|
202
|
-
id: resource_id(name),
|
|
203
|
-
name:,
|
|
204
|
-
display_name: field(value, :display_name).to_s,
|
|
205
|
-
type:,
|
|
206
|
-
default_uri: optional_string(field(web_data, :default_uri)),
|
|
207
|
-
measurement_id: optional_string(field(web_data, :measurement_id))
|
|
208
|
-
)
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
def normalize_retention(value)
|
|
212
|
-
Resources::Retention.new(
|
|
213
|
-
name: field(value, :name).to_s,
|
|
214
|
-
event_data: normalize_retention_value(field(value, :event_data_retention)),
|
|
215
|
-
user_data: normalize_retention_value(field(value, :user_data_retention)),
|
|
216
|
-
reset_on_new_activity: boolean?(field(value, :reset_user_data_on_new_activity))
|
|
217
|
-
)
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
def normalize_key_event(value)
|
|
221
|
-
Resources::KeyEvent.new(
|
|
222
|
-
name: field(value, :name).to_s,
|
|
223
|
-
event_name: field(value, :event_name).to_s,
|
|
224
|
-
counting_method: normalize_enum(field(value, :counting_method))
|
|
225
|
-
)
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
def normalize_custom_dimension(value)
|
|
229
|
-
Resources::CustomDimension.new(
|
|
230
|
-
name: field(value, :name).to_s,
|
|
231
|
-
parameter_name: field(value, :parameter_name).to_s,
|
|
232
|
-
display_name: field(value, :display_name).to_s,
|
|
233
|
-
description: field(value, :description).to_s,
|
|
234
|
-
scope: normalize_enum(field(value, :scope), prefix: "DIMENSION_SCOPE_"),
|
|
235
|
-
disallow_ads_personalization: boolean?(field(value, :disallow_ads_personalization))
|
|
236
|
-
)
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
def normalize_custom_metric(value)
|
|
240
|
-
Resources::CustomMetric.new(
|
|
241
|
-
name: field(value, :name).to_s,
|
|
242
|
-
parameter_name: field(value, :parameter_name).to_s,
|
|
243
|
-
display_name: field(value, :display_name).to_s,
|
|
244
|
-
description: field(value, :description).to_s,
|
|
245
|
-
scope: normalize_enum(field(value, :scope), prefix: "METRIC_SCOPE_"),
|
|
246
|
-
measurement_unit: normalize_enum(field(value, :measurement_unit), prefix: "MEASUREMENT_UNIT_")
|
|
247
|
-
)
|
|
204
|
+
def translate_errors(&)
|
|
205
|
+
ErrorTranslation.call(&)
|
|
248
206
|
end
|
|
249
207
|
|
|
250
208
|
def mutation(change, property_id)
|
|
@@ -365,20 +323,24 @@ module AnalyticsOps
|
|
|
365
323
|
display_name: after.fetch("display_name"),
|
|
366
324
|
description: after.fetch("description"),
|
|
367
325
|
scope: :EVENT,
|
|
368
|
-
measurement_unit: after.fetch("measurement_unit").upcase.to_sym
|
|
326
|
+
measurement_unit: after.fetch("measurement_unit").upcase.to_sym,
|
|
327
|
+
restricted_metric_type: after.fetch("restricted_metric_types").map { |type| type.upcase.to_sym }
|
|
369
328
|
}
|
|
370
329
|
end
|
|
371
330
|
|
|
372
331
|
def validate_resource_name!(name, property_id, collection)
|
|
373
|
-
|
|
374
|
-
|
|
332
|
+
pattern = %r{\A#{Regexp.escape(property_name(property_id))}/#{collection}/[A-Za-z0-9_-]+\z}
|
|
333
|
+
return if name.is_a?(String) && pattern.match?(name)
|
|
334
|
+
|
|
335
|
+
raise InvalidPlanError, "Plan resource belongs to a different property"
|
|
375
336
|
end
|
|
376
337
|
|
|
377
338
|
def property_name(property_id)
|
|
378
|
-
|
|
379
|
-
|
|
339
|
+
unless property_id.is_a?(String) && property_id.match?(/\A\d{1,50}\z/)
|
|
340
|
+
raise ConfigurationError, "Invalid property ID; expected a numeric string"
|
|
341
|
+
end
|
|
380
342
|
|
|
381
|
-
"properties/#{
|
|
343
|
+
"properties/#{property_id}"
|
|
382
344
|
end
|
|
383
345
|
|
|
384
346
|
def google_retention(value)
|
|
@@ -415,19 +377,44 @@ module AnalyticsOps
|
|
|
415
377
|
Array(field(value, name))
|
|
416
378
|
end
|
|
417
379
|
|
|
418
|
-
def
|
|
419
|
-
|
|
380
|
+
def remote_boolean(value)
|
|
381
|
+
return false if value.nil?
|
|
382
|
+
return value if [true, false].include?(value)
|
|
383
|
+
|
|
384
|
+
raise RemoteError, "Google Admin API returned an invalid boolean"
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def optional_boolean(value)
|
|
388
|
+
return nil if value.nil?
|
|
389
|
+
|
|
390
|
+
remote_boolean(value)
|
|
420
391
|
end
|
|
421
392
|
|
|
422
393
|
def resource_id(name)
|
|
423
|
-
name.
|
|
394
|
+
id = remote_string(name, "resource name").split("/").last
|
|
395
|
+
return id unless id.nil? || id.empty?
|
|
396
|
+
|
|
397
|
+
raise RemoteError, "Google Admin API returned an invalid resource name"
|
|
424
398
|
end
|
|
425
399
|
|
|
426
|
-
def optional_string(value)
|
|
427
|
-
|
|
400
|
+
def optional_string(value, label)
|
|
401
|
+
return nil if value.nil?
|
|
402
|
+
|
|
403
|
+
string = remote_string(value, label)
|
|
428
404
|
string.empty? ? nil : string
|
|
429
405
|
end
|
|
430
406
|
|
|
407
|
+
def remote_string(value, label)
|
|
408
|
+
raise RemoteError, "Google Admin API returned an invalid #{label}" unless value.is_a?(String)
|
|
409
|
+
|
|
410
|
+
string = value.encode(Encoding::UTF_8)
|
|
411
|
+
raise EncodingError unless string.valid_encoding?
|
|
412
|
+
|
|
413
|
+
string
|
|
414
|
+
rescue EncodingError
|
|
415
|
+
raise RemoteError, "Google Admin API returned an invalid #{label}"
|
|
416
|
+
end
|
|
417
|
+
|
|
431
418
|
def normalize_enum(value, prefix: nil)
|
|
432
419
|
name = enum_name(value)
|
|
433
420
|
name = name.delete_prefix(prefix) if prefix
|
|
@@ -441,6 +428,20 @@ module AnalyticsOps
|
|
|
441
428
|
def enum_symbol(value)
|
|
442
429
|
enum_name(value).upcase.to_sym
|
|
443
430
|
end
|
|
431
|
+
|
|
432
|
+
def validate_transport(value)
|
|
433
|
+
transport = value.to_sym if value.respond_to?(:to_sym)
|
|
434
|
+
return transport if %i[grpc rest].include?(transport)
|
|
435
|
+
|
|
436
|
+
raise ConfigurationError, "transport must be grpc or rest"
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def validate_timeout(value)
|
|
440
|
+
return nil if value.nil?
|
|
441
|
+
return value if [Integer, Float].any? { |type| value.is_a?(type) } && value.finite? && value.positive?
|
|
442
|
+
|
|
443
|
+
raise ConfigurationError, "timeout must be a finite positive number"
|
|
444
|
+
end
|
|
444
445
|
end
|
|
445
446
|
end
|
|
446
447
|
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnalyticsOps
|
|
4
|
+
module Clients
|
|
5
|
+
# Internal normalization of generated Admin API responses into gem-owned values.
|
|
6
|
+
module AdminNormalization
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def normalize_property(value, can_edit:)
|
|
10
|
+
name = remote_string(field(value, :property) || field(value, :name), "property name")
|
|
11
|
+
Resources::Property.new(
|
|
12
|
+
id: resource_id(name),
|
|
13
|
+
name:,
|
|
14
|
+
display_name: remote_string(field(value, :display_name), "property display name"),
|
|
15
|
+
parent: optional_string(field(value, :parent), "property parent"),
|
|
16
|
+
property_type: normalize_enum(field(value, :property_type), prefix: "PROPERTY_TYPE_"),
|
|
17
|
+
can_edit: optional_boolean(can_edit)
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def normalize_stream(value)
|
|
22
|
+
name = remote_string(field(value, :name), "data stream name")
|
|
23
|
+
type = Admin::STREAM_TYPES.fetch(enum_name(field(value, :type)), "unspecified")
|
|
24
|
+
web_data = field(value, :web_stream_data)
|
|
25
|
+
Resources::DataStream.new(
|
|
26
|
+
id: resource_id(name),
|
|
27
|
+
name:,
|
|
28
|
+
display_name: remote_string(field(value, :display_name), "data stream display name"),
|
|
29
|
+
type:,
|
|
30
|
+
default_uri: optional_string(field(web_data, :default_uri), "data stream default URI"),
|
|
31
|
+
measurement_id: optional_string(field(web_data, :measurement_id), "measurement ID")
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def normalize_retention(value)
|
|
36
|
+
Resources::Retention.new(
|
|
37
|
+
name: remote_string(field(value, :name), "retention resource name"),
|
|
38
|
+
event_data: normalize_retention_value(field(value, :event_data_retention)),
|
|
39
|
+
user_data: normalize_retention_value(field(value, :user_data_retention)),
|
|
40
|
+
reset_on_new_activity: remote_boolean(field(value, :reset_user_data_on_new_activity))
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def normalize_key_event(value)
|
|
45
|
+
Resources::KeyEvent.new(
|
|
46
|
+
name: remote_string(field(value, :name), "key event resource name"),
|
|
47
|
+
event_name: remote_string(field(value, :event_name), "key event name"),
|
|
48
|
+
counting_method: normalize_enum(field(value, :counting_method))
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def normalize_custom_dimension(value)
|
|
53
|
+
Resources::CustomDimension.new(
|
|
54
|
+
name: remote_string(field(value, :name), "custom dimension resource name"),
|
|
55
|
+
parameter_name: remote_string(field(value, :parameter_name), "custom dimension parameter name"),
|
|
56
|
+
display_name: remote_string(field(value, :display_name), "custom dimension display name"),
|
|
57
|
+
description: remote_string(field(value, :description), "custom dimension description"),
|
|
58
|
+
scope: normalize_enum(field(value, :scope), prefix: "DIMENSION_SCOPE_"),
|
|
59
|
+
disallow_ads_personalization: remote_boolean(field(value, :disallow_ads_personalization))
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def normalize_custom_metric(value)
|
|
64
|
+
Resources::CustomMetric.new(
|
|
65
|
+
name: remote_string(field(value, :name), "custom metric resource name"),
|
|
66
|
+
parameter_name: remote_string(field(value, :parameter_name), "custom metric parameter name"),
|
|
67
|
+
display_name: remote_string(field(value, :display_name), "custom metric display name"),
|
|
68
|
+
description: remote_string(field(value, :description), "custom metric description"),
|
|
69
|
+
scope: normalize_enum(field(value, :scope), prefix: "METRIC_SCOPE_"),
|
|
70
|
+
measurement_unit: normalize_enum(field(value, :measurement_unit), prefix: "MEASUREMENT_UNIT_"),
|
|
71
|
+
restricted_metric_types: array_field(value, :restricted_metric_type).map do |type|
|
|
72
|
+
normalize_enum(type, prefix: "RESTRICTED_METRIC_TYPE_")
|
|
73
|
+
end.sort
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def validate_snapshot_names!(property:, streams:, retention:, key_events:, dimensions:, metrics:)
|
|
78
|
+
valid = retention.name == "#{property}/dataRetentionSettings" &&
|
|
79
|
+
resource_collection?(streams, property, "dataStreams") &&
|
|
80
|
+
resource_collection?(key_events, property, "keyEvents") &&
|
|
81
|
+
resource_collection?(dimensions, property, "customDimensions") &&
|
|
82
|
+
resource_collection?(metrics, property, "customMetrics")
|
|
83
|
+
return if valid
|
|
84
|
+
|
|
85
|
+
raise RemoteError, "Google Admin API returned a resource belonging to a different property"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def resource_collection?(values, property, collection)
|
|
89
|
+
pattern = %r{\A#{Regexp.escape(property)}/#{collection}/[A-Za-z0-9_-]+\z}
|
|
90
|
+
values.all? { |value| pattern.match?(value.name) }
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
private_constant :AdminNormalization
|
|
94
|
+
end
|
|
95
|
+
end
|