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
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnalyticsOps
|
|
4
|
+
# Interactive or automated property selection after service-account loading.
|
|
5
|
+
class Setup
|
|
6
|
+
PROPERTY_ID = /\A\d{1,50}\z/
|
|
7
|
+
PROFILE = /\A[A-Za-z][A-Za-z0-9_]{0,63}\z/
|
|
8
|
+
|
|
9
|
+
# Immutable setup outcome returned to the CLI and Ruby callers.
|
|
10
|
+
class Result
|
|
11
|
+
attr_reader :config_path, :profile, :property
|
|
12
|
+
|
|
13
|
+
def initialize(config_path:, profile:, property:, created:)
|
|
14
|
+
unless property.is_a?(Resources::Property)
|
|
15
|
+
raise ArgumentError, "property must be an AnalyticsOps::Resources::Property"
|
|
16
|
+
end
|
|
17
|
+
raise ArgumentError, "created must be true or false" unless [true, false].include?(created)
|
|
18
|
+
|
|
19
|
+
@config_path = config_path.to_s.dup.freeze
|
|
20
|
+
@profile = profile.to_s.dup.freeze
|
|
21
|
+
@property = property
|
|
22
|
+
@created = created
|
|
23
|
+
freeze
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def created?
|
|
27
|
+
@created
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def status
|
|
31
|
+
created? ? "configured" : "already_configured"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_h
|
|
35
|
+
{
|
|
36
|
+
"status" => status,
|
|
37
|
+
"config" => config_path,
|
|
38
|
+
"profile" => profile,
|
|
39
|
+
"property" => property.to_h
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def initialize(connection:, config:, profile:, property_id: nil, noninteractive: false,
|
|
45
|
+
input: $stdin, out: $stdout)
|
|
46
|
+
validate_options!(
|
|
47
|
+
config:,
|
|
48
|
+
profile:,
|
|
49
|
+
property_id:,
|
|
50
|
+
noninteractive:
|
|
51
|
+
)
|
|
52
|
+
@connection = connection
|
|
53
|
+
@config = config
|
|
54
|
+
@profile = profile.to_s
|
|
55
|
+
@property_id = property_id
|
|
56
|
+
@noninteractive = noninteractive
|
|
57
|
+
@input = input
|
|
58
|
+
@out = out
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def call
|
|
62
|
+
selection = select_property(google_call { @connection.properties })
|
|
63
|
+
verification = google_call { @connection.verify(selection.fetch("id")) }
|
|
64
|
+
write = Configuration::Writer.new.write_minimal(
|
|
65
|
+
@config,
|
|
66
|
+
profile: @profile,
|
|
67
|
+
property_id: verification.property.id
|
|
68
|
+
)
|
|
69
|
+
Result.new(
|
|
70
|
+
config_path: write.path,
|
|
71
|
+
profile: @profile,
|
|
72
|
+
property: verification.property,
|
|
73
|
+
created: write.created?
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def validate_options!(config:, profile:, property_id:, noninteractive:)
|
|
80
|
+
validate_config_path!(config)
|
|
81
|
+
validate_profile!(profile)
|
|
82
|
+
validate_property_id!(property_id)
|
|
83
|
+
validate_boolean_option!(noninteractive)
|
|
84
|
+
raise ConfigurationError, "Non-interactive setup requires a property ID" if noninteractive && !property_id
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def validate_config_path!(config)
|
|
88
|
+
valid = config.is_a?(String) && !config.empty? && !config.match?(/[\u0000-\u001f\u007f]/)
|
|
89
|
+
return if valid
|
|
90
|
+
|
|
91
|
+
raise ConfigurationError, "Setup configuration path is invalid"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def validate_profile!(profile)
|
|
95
|
+
raise ConfigurationError, "Setup profile is invalid" unless PROFILE.match?(profile.to_s)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def validate_property_id!(property_id)
|
|
99
|
+
return if property_id.nil? || (property_id.is_a?(String) && PROPERTY_ID.match?(property_id))
|
|
100
|
+
|
|
101
|
+
raise ConfigurationError, "Setup property must be a numeric GA4 property ID"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def validate_boolean_option!(noninteractive)
|
|
105
|
+
return if [true, false].include?(noninteractive)
|
|
106
|
+
|
|
107
|
+
raise ConfigurationError, "Setup boolean options must be true or false"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def google_call
|
|
111
|
+
yield
|
|
112
|
+
rescue AuthorizationError, InvalidRequestError, RemoteError => error
|
|
113
|
+
raise_disabled_api! if api_disabled?(error)
|
|
114
|
+
|
|
115
|
+
raise
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def api_disabled?(error)
|
|
119
|
+
error.message.match?(/SERVICE_DISABLED|API.*(?:disabled|not been used)|serviceusage/i)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def raise_disabled_api!
|
|
123
|
+
raise RemoteError,
|
|
124
|
+
"Google Analytics APIs appear disabled. Enable Google Analytics Admin API and " \
|
|
125
|
+
"Google Analytics Data API in the Google Cloud project that owns the service account."
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def select_property(accounts)
|
|
129
|
+
choices = property_choices(accounts)
|
|
130
|
+
raise AuthorizationError, "No accessible Google Analytics properties found" if choices.empty?
|
|
131
|
+
|
|
132
|
+
return explicit_property(choices) if @property_id
|
|
133
|
+
return choices.first if choices.one?
|
|
134
|
+
|
|
135
|
+
prompt_for_property(choices)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def explicit_property(choices)
|
|
139
|
+
choices.find { |choice| choice.fetch("id") == @property_id } ||
|
|
140
|
+
raise(AuthorizationError, "Property #{@property_id} is not accessible")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def prompt_for_property(choices)
|
|
144
|
+
@out.puts "Choose a Google Analytics property:"
|
|
145
|
+
choices.each.with_index(1) do |choice, index|
|
|
146
|
+
@out.puts " #{index}. #{Redaction.message(choice.fetch("display_name"))} — " \
|
|
147
|
+
"#{Redaction.message(choice.fetch("account_display_name"))} " \
|
|
148
|
+
"(property #{Redaction.message(choice.fetch("id"))})"
|
|
149
|
+
end
|
|
150
|
+
@out.print "Property number: "
|
|
151
|
+
@out.flush
|
|
152
|
+
index = Integer(@input.gets&.strip, exception: false)
|
|
153
|
+
unless index&.between?(1, choices.length)
|
|
154
|
+
raise ConfigurationError, "A property number from 1 to #{choices.length} is required"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
choices.fetch(index - 1)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def property_choices(accounts)
|
|
161
|
+
choices = accounts.flat_map do |account|
|
|
162
|
+
account.properties.map do |property|
|
|
163
|
+
property.merge("account_id" => account.id, "account_display_name" => account.display_name)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
choices.sort_by do |property|
|
|
167
|
+
[property.fetch("account_display_name"), property.fetch("display_name"), property.fetch("id")]
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
@@ -8,12 +8,23 @@ module AnalyticsOps
|
|
|
8
8
|
attr_reader :property, :streams, :retention, :key_events, :custom_dimensions, :custom_metrics
|
|
9
9
|
|
|
10
10
|
def initialize(property:, streams:, retention:, key_events:, custom_dimensions:, custom_metrics:)
|
|
11
|
+
unless property.is_a?(Resources::Property)
|
|
12
|
+
raise ArgumentError, "property must be an AnalyticsOps::Resources::Property"
|
|
13
|
+
end
|
|
14
|
+
unless retention.nil? || retention.is_a?(Resources::Retention)
|
|
15
|
+
raise ArgumentError, "retention must be an AnalyticsOps::Resources::Retention or nil"
|
|
16
|
+
end
|
|
17
|
+
|
|
11
18
|
@property = property
|
|
12
|
-
@streams =
|
|
19
|
+
@streams = sorted_resources(streams, Resources::DataStream, "streams", &:id)
|
|
13
20
|
@retention = retention
|
|
14
|
-
@key_events =
|
|
15
|
-
@custom_dimensions =
|
|
16
|
-
|
|
21
|
+
@key_events = sorted_resources(key_events, Resources::KeyEvent, "key_events", &:event_name)
|
|
22
|
+
@custom_dimensions = sorted_resources(
|
|
23
|
+
custom_dimensions, Resources::CustomDimension, "custom_dimensions"
|
|
24
|
+
) do |item|
|
|
25
|
+
[item.scope, item.parameter_name]
|
|
26
|
+
end
|
|
27
|
+
@custom_metrics = sorted_resources(custom_metrics, Resources::CustomMetric, "custom_metrics", &:parameter_name)
|
|
17
28
|
freeze
|
|
18
29
|
end
|
|
19
30
|
|
|
@@ -38,7 +49,11 @@ module AnalyticsOps
|
|
|
38
49
|
|
|
39
50
|
private
|
|
40
51
|
|
|
41
|
-
def
|
|
52
|
+
def sorted_resources(values, type, label, &)
|
|
53
|
+
unless values.is_a?(Array) && values.all?(type)
|
|
54
|
+
raise ArgumentError, "#{label} must contain only #{type.name} values"
|
|
55
|
+
end
|
|
56
|
+
|
|
42
57
|
Canonical.deep_freeze(values.sort_by(&))
|
|
43
58
|
end
|
|
44
59
|
end
|
|
@@ -27,7 +27,7 @@ module AnalyticsOps
|
|
|
27
27
|
attr_reader :checks
|
|
28
28
|
|
|
29
29
|
def initialize(checks:)
|
|
30
|
-
@checks = Canonical.
|
|
30
|
+
@checks = Canonical.immutable(checks)
|
|
31
31
|
freeze
|
|
32
32
|
end
|
|
33
33
|
|
|
@@ -42,17 +42,23 @@ module AnalyticsOps
|
|
|
42
42
|
|
|
43
43
|
attr_reader :desired_state
|
|
44
44
|
|
|
45
|
-
def self.load(path, profile:, environment: ENV, admin: nil, data: nil,
|
|
45
|
+
def self.load(path, profile:, environment: ENV, admin: nil, data: nil, service_account: nil,
|
|
46
46
|
transport: :grpc, timeout: nil, logger: nil)
|
|
47
47
|
document = Configuration.load(path, environment:)
|
|
48
|
-
new(desired_state: document.profile(profile), admin:, data:,
|
|
48
|
+
new(desired_state: document.profile(profile), admin:, data:, service_account:, transport:, timeout:, logger:)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
def initialize(desired_state:, admin: nil, data: nil,
|
|
51
|
+
def initialize(desired_state:, admin: nil, data: nil, service_account: nil,
|
|
52
|
+
transport: :grpc, timeout: nil, logger: nil)
|
|
53
|
+
unless service_account.nil? || service_account.is_a?(ServiceAccount)
|
|
54
|
+
raise ConfigurationError, "service_account must be an AnalyticsOps::ServiceAccount"
|
|
55
|
+
end
|
|
56
|
+
|
|
52
57
|
@desired_state = desired_state
|
|
53
58
|
@admin = admin
|
|
59
|
+
@injected_admin = admin
|
|
54
60
|
@data = data
|
|
55
|
-
@
|
|
61
|
+
@service_account = service_account
|
|
56
62
|
@transport = transport
|
|
57
63
|
@timeout = timeout
|
|
58
64
|
@logger = logger
|
|
@@ -77,7 +83,7 @@ module AnalyticsOps
|
|
|
77
83
|
def apply(plan_or_path, confirm: false)
|
|
78
84
|
saved_plan = plan_or_path.is_a?(Plan) ? plan_or_path : Plan.load(plan_or_path)
|
|
79
85
|
validate_plan_target!(saved_plan)
|
|
80
|
-
Applier.new(admin:).call(saved_plan, confirm:)
|
|
86
|
+
Applier.new(admin: edit_admin).call(saved_plan, confirm:)
|
|
81
87
|
end
|
|
82
88
|
|
|
83
89
|
def verify
|
|
@@ -94,11 +100,17 @@ module AnalyticsOps
|
|
|
94
100
|
data.run(desired_state.property_id, definition)
|
|
95
101
|
end
|
|
96
102
|
|
|
103
|
+
def overview
|
|
104
|
+
reports = data.batch(desired_state.property_id, Reports::Catalog.overview)
|
|
105
|
+
Reports::OverviewResult.new(property_id: desired_state.property_id, reports:)
|
|
106
|
+
end
|
|
107
|
+
|
|
97
108
|
def doctor
|
|
98
109
|
remote = snapshot
|
|
99
110
|
checks = [
|
|
100
111
|
{ "name" => "configuration", "status" => "ok", "detail" => "Profile #{desired_state.profile} is valid" },
|
|
101
|
-
{ "name" => "credentials", "status" => "ok",
|
|
112
|
+
{ "name" => "credentials", "status" => "ok",
|
|
113
|
+
"detail" => "Google accepted the configured service-account credentials" },
|
|
102
114
|
{ "name" => "admin_api", "status" => "ok", "detail" => "Admin API read succeeded" },
|
|
103
115
|
{ "name" => "property_access", "status" => "ok", "detail" => "Read properties/#{remote.property_id}" }
|
|
104
116
|
]
|
|
@@ -108,9 +120,9 @@ module AnalyticsOps
|
|
|
108
120
|
data.run(desired_state.property_id, doctor_report_definition)
|
|
109
121
|
checks << { "name" => "data_api", "status" => "ok", "detail" => "Data API read succeeded" }
|
|
110
122
|
checks << {
|
|
111
|
-
"name" => "
|
|
112
|
-
"status" => "
|
|
113
|
-
"detail" => "
|
|
123
|
+
"name" => "credential_scope",
|
|
124
|
+
"status" => "ok",
|
|
125
|
+
"detail" => "This read-only command uses the Analytics read-only scope"
|
|
114
126
|
}
|
|
115
127
|
admin.capabilities.each do |name, available|
|
|
116
128
|
checks << {
|
|
@@ -126,7 +138,8 @@ module AnalyticsOps
|
|
|
126
138
|
|
|
127
139
|
def admin
|
|
128
140
|
@admin ||= Clients::Admin.new(
|
|
129
|
-
|
|
141
|
+
service_account:,
|
|
142
|
+
access: :read,
|
|
130
143
|
transport: @transport,
|
|
131
144
|
timeout: @timeout,
|
|
132
145
|
logger: @logger
|
|
@@ -135,13 +148,29 @@ module AnalyticsOps
|
|
|
135
148
|
|
|
136
149
|
def data
|
|
137
150
|
@data ||= Clients::Data.new(
|
|
138
|
-
|
|
151
|
+
service_account:,
|
|
139
152
|
transport: @transport,
|
|
140
153
|
timeout: @timeout,
|
|
141
154
|
logger: @logger
|
|
142
155
|
)
|
|
143
156
|
end
|
|
144
157
|
|
|
158
|
+
def edit_admin
|
|
159
|
+
return @injected_admin if @injected_admin
|
|
160
|
+
|
|
161
|
+
@edit_admin ||= Clients::Admin.new(
|
|
162
|
+
service_account:,
|
|
163
|
+
access: :edit,
|
|
164
|
+
transport: @transport,
|
|
165
|
+
timeout: @timeout,
|
|
166
|
+
logger: @logger
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def service_account
|
|
171
|
+
@service_account ||= ServiceAccount.load
|
|
172
|
+
end
|
|
173
|
+
|
|
145
174
|
def compatibility_checks
|
|
146
175
|
[admin.compatibility, data.compatibility].map do |details|
|
|
147
176
|
{
|
data/lib/analytics_ops.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "analytics_ops/version"
|
|
4
4
|
require_relative "analytics_ops/errors"
|
|
5
5
|
require_relative "analytics_ops/redaction"
|
|
6
|
+
require_relative "analytics_ops/service_account"
|
|
6
7
|
require_relative "analytics_ops/canonical"
|
|
7
8
|
require_relative "analytics_ops/resources"
|
|
8
9
|
require_relative "analytics_ops/desired_state"
|
|
@@ -10,9 +11,12 @@ require_relative "analytics_ops/configuration"
|
|
|
10
11
|
require_relative "analytics_ops/snapshot"
|
|
11
12
|
require_relative "analytics_ops/plan"
|
|
12
13
|
require_relative "analytics_ops/planner"
|
|
14
|
+
require_relative "analytics_ops/clients/error_translation"
|
|
13
15
|
require_relative "analytics_ops/clients/admin"
|
|
14
16
|
require_relative "analytics_ops/reports"
|
|
15
17
|
require_relative "analytics_ops/clients/data"
|
|
18
|
+
require_relative "analytics_ops/connection"
|
|
19
|
+
require_relative "analytics_ops/setup"
|
|
16
20
|
require_relative "analytics_ops/applier"
|
|
17
21
|
require_relative "analytics_ops/workspace"
|
|
18
22
|
|
|
@@ -11,6 +11,8 @@ module AnalyticsOps
|
|
|
11
11
|
desc "Create config/analytics_ops.yml and bin/analytics-ops"
|
|
12
12
|
|
|
13
13
|
def copy_configuration
|
|
14
|
+
return if File.exist?(File.join(destination_root, "config/analytics_ops.yml"))
|
|
15
|
+
|
|
14
16
|
template "analytics_ops.yml", "config/analytics_ops.yml"
|
|
15
17
|
end
|
|
16
18
|
|
|
@@ -6,18 +6,5 @@ profiles:
|
|
|
6
6
|
# GA4 property ID (numeric), not an account ID, stream ID, or measurement ID.
|
|
7
7
|
property_id: "${GA4_PROPERTY_ID}"
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# Numeric stream ID from Admin > Data streams, not the G- measurement ID.
|
|
12
|
-
stream_id: "${GA4_STREAM_ID}"
|
|
13
|
-
default_uri: "https://example.test"
|
|
14
|
-
|
|
15
|
-
retention:
|
|
16
|
-
event_data: 14_months
|
|
17
|
-
user_data: 14_months
|
|
18
|
-
reset_on_new_activity: false
|
|
19
|
-
|
|
20
|
-
key_events: []
|
|
21
|
-
custom_dimensions: []
|
|
22
|
-
custom_metrics: []
|
|
23
|
-
manual_requirements: []
|
|
9
|
+
# Add managed settings only after reviewing the Analytics Ops configuration guide.
|
|
10
|
+
# This generated starting point produces no mutation plan by itself.
|
data/sig/analytics_ops.rbs
CHANGED
|
@@ -54,6 +54,30 @@ module AnalyticsOps
|
|
|
54
54
|
def initialize: (String message, result: Applier::Result) -> void
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
class ServiceAccount
|
|
58
|
+
MAX_KEY_BYTES: Integer
|
|
59
|
+
READ_SCOPE: String
|
|
60
|
+
EDIT_SCOPE: String
|
|
61
|
+
ACCESS_SCOPES: Hash[Symbol, Array[String]]
|
|
62
|
+
|
|
63
|
+
attr_reader path: String
|
|
64
|
+
|
|
65
|
+
def self.load: (?path: String?, ?store: Store) -> ServiceAccount
|
|
66
|
+
def initialize: (String path) -> void
|
|
67
|
+
|
|
68
|
+
class Store
|
|
69
|
+
VERSION: Integer
|
|
70
|
+
MAX_BYTES: Integer
|
|
71
|
+
|
|
72
|
+
attr_reader path: String
|
|
73
|
+
|
|
74
|
+
def self.default_path: () -> String
|
|
75
|
+
def initialize: (?path: String?) -> void
|
|
76
|
+
def read: () -> String
|
|
77
|
+
def write: (String service_account_path) -> String
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
57
81
|
module Resources
|
|
58
82
|
class Value
|
|
59
83
|
def to_h: () -> record
|
|
@@ -116,6 +140,7 @@ module AnalyticsOps
|
|
|
116
140
|
attr_reader description: String
|
|
117
141
|
attr_reader scope: String
|
|
118
142
|
attr_reader measurement_unit: String
|
|
143
|
+
attr_reader restricted_metric_types: Array[String]
|
|
119
144
|
end
|
|
120
145
|
end
|
|
121
146
|
|
|
@@ -131,6 +156,18 @@ module AnalyticsOps
|
|
|
131
156
|
def initialize: (version: Integer, profiles: Hash[String, DesiredState]) -> void
|
|
132
157
|
def profile: (String | Symbol name) -> DesiredState
|
|
133
158
|
end
|
|
159
|
+
|
|
160
|
+
class Writer
|
|
161
|
+
class Result
|
|
162
|
+
attr_reader path: String
|
|
163
|
+
|
|
164
|
+
def initialize: (path: String, created: bool) -> void
|
|
165
|
+
def created?: () -> bool
|
|
166
|
+
def to_h: () -> record
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def write_minimal: (String path, profile: String | Symbol, property_id: String) -> Result
|
|
170
|
+
end
|
|
134
171
|
end
|
|
135
172
|
|
|
136
173
|
class DesiredState
|
|
@@ -277,6 +314,8 @@ module AnalyticsOps
|
|
|
277
314
|
module Catalog
|
|
278
315
|
def self.fetch: (String | Symbol name, ?kind: String?) -> Definition
|
|
279
316
|
def self.names: () -> Array[String]
|
|
317
|
+
def self.aliases: () -> Hash[String, String]
|
|
318
|
+
def self.overview: () -> Array[Definition]
|
|
280
319
|
end
|
|
281
320
|
|
|
282
321
|
class Result
|
|
@@ -300,18 +339,31 @@ module AnalyticsOps
|
|
|
300
339
|
def headers: () -> Array[String]
|
|
301
340
|
def to_h: () -> record
|
|
302
341
|
end
|
|
342
|
+
|
|
343
|
+
class OverviewResult
|
|
344
|
+
MAX_REPORTS: Integer
|
|
345
|
+
|
|
346
|
+
attr_reader property_id: String
|
|
347
|
+
attr_reader reports: Array[Result]
|
|
348
|
+
attr_reader property_quota: record
|
|
349
|
+
|
|
350
|
+
def initialize: (property_id: String, reports: Array[Result]) -> void
|
|
351
|
+
def report: (String | Symbol name) -> Result
|
|
352
|
+
def to_h: () -> record
|
|
353
|
+
end
|
|
303
354
|
end
|
|
304
355
|
|
|
305
356
|
module Clients
|
|
306
357
|
class Admin
|
|
307
358
|
def initialize: (
|
|
308
359
|
?client: untyped,
|
|
309
|
-
?
|
|
360
|
+
?service_account: ServiceAccount?,
|
|
361
|
+
?access: :read | :edit,
|
|
310
362
|
?transport: Symbol,
|
|
311
|
-
?timeout: Float?,
|
|
363
|
+
?timeout: (Integer | Float)?,
|
|
312
364
|
?logger: untyped
|
|
313
365
|
) -> void
|
|
314
|
-
def discover: () -> Array[Resources::Account]
|
|
366
|
+
def discover: (?include_streams: bool) -> Array[Resources::Account]
|
|
315
367
|
def property_access: (String property_id) -> Resources::Property
|
|
316
368
|
def snapshot: (String property_id) -> Snapshot
|
|
317
369
|
def capabilities: () -> Hash[String, bool]
|
|
@@ -322,17 +374,68 @@ module AnalyticsOps
|
|
|
322
374
|
class Data
|
|
323
375
|
def initialize: (
|
|
324
376
|
?client: untyped,
|
|
325
|
-
?
|
|
377
|
+
?service_account: ServiceAccount?,
|
|
326
378
|
?transport: Symbol,
|
|
327
|
-
?timeout: Float?,
|
|
379
|
+
?timeout: (Integer | Float)?,
|
|
328
380
|
?logger: untyped
|
|
329
381
|
) -> void
|
|
330
382
|
def run: (String property_id, Reports::Definition definition) -> Reports::Result
|
|
383
|
+
def batch: (String property_id, Array[Reports::Definition] definitions) -> Array[Reports::Result]
|
|
331
384
|
def available?: () -> bool
|
|
332
385
|
def compatibility: () -> record
|
|
333
386
|
end
|
|
334
387
|
end
|
|
335
388
|
|
|
389
|
+
class Connection
|
|
390
|
+
class Verification
|
|
391
|
+
attr_reader property: Resources::Property
|
|
392
|
+
|
|
393
|
+
def initialize: (property: Resources::Property) -> void
|
|
394
|
+
def to_h: () -> record
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def initialize: (
|
|
398
|
+
?admin: Clients::Admin?,
|
|
399
|
+
?data: Clients::Data?,
|
|
400
|
+
?service_account: ServiceAccount?,
|
|
401
|
+
?transport: Symbol,
|
|
402
|
+
?timeout: (Integer | Float)?,
|
|
403
|
+
?logger: untyped
|
|
404
|
+
) -> void
|
|
405
|
+
def discover: () -> Array[Resources::Account]
|
|
406
|
+
def properties: () -> Array[Resources::Account]
|
|
407
|
+
def verify: (String property_id) -> Verification
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
class Setup
|
|
411
|
+
class Result
|
|
412
|
+
attr_reader config_path: String
|
|
413
|
+
attr_reader profile: String
|
|
414
|
+
attr_reader property: Resources::Property
|
|
415
|
+
|
|
416
|
+
def initialize: (
|
|
417
|
+
config_path: String,
|
|
418
|
+
profile: String | Symbol,
|
|
419
|
+
property: Resources::Property,
|
|
420
|
+
created: bool
|
|
421
|
+
) -> void
|
|
422
|
+
def created?: () -> bool
|
|
423
|
+
def status: () -> String
|
|
424
|
+
def to_h: () -> record
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def initialize: (
|
|
428
|
+
connection: Connection,
|
|
429
|
+
config: String,
|
|
430
|
+
profile: String | Symbol,
|
|
431
|
+
?property_id: String?,
|
|
432
|
+
?noninteractive: bool,
|
|
433
|
+
?input: untyped,
|
|
434
|
+
?out: untyped
|
|
435
|
+
) -> void
|
|
436
|
+
def call: () -> Result
|
|
437
|
+
end
|
|
438
|
+
|
|
336
439
|
class Workspace
|
|
337
440
|
class Verification
|
|
338
441
|
attr_reader converged: bool
|
|
@@ -358,18 +461,18 @@ module AnalyticsOps
|
|
|
358
461
|
?environment: Hash[String, String],
|
|
359
462
|
?admin: Clients::Admin?,
|
|
360
463
|
?data: Clients::Data?,
|
|
361
|
-
?
|
|
464
|
+
?service_account: ServiceAccount?,
|
|
362
465
|
?transport: Symbol,
|
|
363
|
-
?timeout: Float?,
|
|
466
|
+
?timeout: (Integer | Float)?,
|
|
364
467
|
?logger: untyped
|
|
365
468
|
) -> Workspace
|
|
366
469
|
def initialize: (
|
|
367
470
|
desired_state: DesiredState,
|
|
368
471
|
?admin: Clients::Admin?,
|
|
369
472
|
?data: Clients::Data?,
|
|
370
|
-
?
|
|
473
|
+
?service_account: ServiceAccount?,
|
|
371
474
|
?transport: Symbol,
|
|
372
|
-
?timeout: Float?,
|
|
475
|
+
?timeout: (Integer | Float)?,
|
|
373
476
|
?logger: untyped
|
|
374
477
|
) -> void
|
|
375
478
|
def discover: () -> Array[Resources::Account]
|
|
@@ -380,6 +483,7 @@ module AnalyticsOps
|
|
|
380
483
|
def verify: () -> Verification
|
|
381
484
|
def report: (String | Reports::Definition name_or_definition) -> Reports::Result
|
|
382
485
|
def realtime: (?String | Reports::Definition name_or_definition) -> Reports::Result
|
|
486
|
+
def overview: () -> Reports::OverviewResult
|
|
383
487
|
def doctor: () -> DoctorResult
|
|
384
488
|
end
|
|
385
489
|
|
|
@@ -389,7 +493,10 @@ module AnalyticsOps
|
|
|
389
493
|
?out: untyped,
|
|
390
494
|
?err: untyped,
|
|
391
495
|
?input: untyped,
|
|
392
|
-
?workspace_loader: untyped
|
|
496
|
+
?workspace_loader: untyped,
|
|
497
|
+
?connection_loader: untyped,
|
|
498
|
+
?service_account_loader: untyped,
|
|
499
|
+
?service_account_store: ServiceAccount::Store?
|
|
393
500
|
) -> Integer
|
|
394
501
|
end
|
|
395
502
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: analytics_ops
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nelson Chaves
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: 0.9.0
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: googleauth
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.12'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.12'
|
|
40
54
|
description: |-
|
|
41
55
|
Analytics Ops provides configuration-as-code, drift detection, explicit
|
|
42
56
|
plans, safe application, and reporting for Google Analytics 4 properties.
|
|
@@ -62,6 +76,7 @@ files:
|
|
|
62
76
|
- docs/configuration-schema-v1.json
|
|
63
77
|
- docs/configuration.md
|
|
64
78
|
- docs/google-client-compatibility.md
|
|
79
|
+
- docs/live-smoke-test.md
|
|
65
80
|
- docs/plan-format.md
|
|
66
81
|
- docs/plan-schema-v1.json
|
|
67
82
|
- docs/rails.md
|
|
@@ -73,13 +88,19 @@ files:
|
|
|
73
88
|
- lib/analytics_ops/applier.rb
|
|
74
89
|
- lib/analytics_ops/canonical.rb
|
|
75
90
|
- lib/analytics_ops/cli.rb
|
|
91
|
+
- lib/analytics_ops/cli/options.rb
|
|
92
|
+
- lib/analytics_ops/cli/presenter.rb
|
|
76
93
|
- lib/analytics_ops/clients/admin.rb
|
|
94
|
+
- lib/analytics_ops/clients/admin_normalization.rb
|
|
77
95
|
- lib/analytics_ops/clients/data.rb
|
|
96
|
+
- lib/analytics_ops/clients/error_translation.rb
|
|
78
97
|
- lib/analytics_ops/configuration.rb
|
|
79
98
|
- lib/analytics_ops/configuration/document.rb
|
|
80
99
|
- lib/analytics_ops/configuration/loader.rb
|
|
81
100
|
- lib/analytics_ops/configuration/schema.rb
|
|
82
101
|
- lib/analytics_ops/configuration/validator.rb
|
|
102
|
+
- lib/analytics_ops/configuration/writer.rb
|
|
103
|
+
- lib/analytics_ops/connection.rb
|
|
83
104
|
- lib/analytics_ops/desired_state.rb
|
|
84
105
|
- lib/analytics_ops/errors.rb
|
|
85
106
|
- lib/analytics_ops/plan.rb
|
|
@@ -90,8 +111,12 @@ files:
|
|
|
90
111
|
- lib/analytics_ops/reports.rb
|
|
91
112
|
- lib/analytics_ops/reports/catalog.rb
|
|
92
113
|
- lib/analytics_ops/reports/definition.rb
|
|
114
|
+
- lib/analytics_ops/reports/overview.rb
|
|
115
|
+
- lib/analytics_ops/reports/overview_result.rb
|
|
93
116
|
- lib/analytics_ops/reports/result.rb
|
|
94
117
|
- lib/analytics_ops/resources.rb
|
|
118
|
+
- lib/analytics_ops/service_account.rb
|
|
119
|
+
- lib/analytics_ops/setup.rb
|
|
95
120
|
- lib/analytics_ops/snapshot.rb
|
|
96
121
|
- lib/analytics_ops/version.rb
|
|
97
122
|
- lib/analytics_ops/workspace.rb
|