analytics_ops 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 +4 -4
- data/CHANGELOG.md +108 -0
- data/CONTRIBUTING.md +1 -0
- data/README.md +141 -13
- data/SECURITY.md +11 -2
- data/docs/ai-connections.md +192 -0
- data/docs/api-support-matrix.md +25 -8
- data/docs/architecture.md +78 -17
- data/docs/authentication.md +53 -3
- data/docs/bigquery.md +128 -0
- data/docs/commands.md +133 -12
- data/docs/configuration-schema-v1.json +608 -50
- data/docs/configuration.md +118 -5
- data/docs/google-client-compatibility.md +21 -1
- data/docs/governance.md +93 -0
- data/docs/health.md +82 -0
- data/docs/live-smoke-test.md +13 -10
- data/docs/rails.md +25 -9
- data/docs/reports.md +165 -3
- data/docs/safety.md +37 -5
- data/docs/troubleshooting.md +81 -7
- data/lib/analytics_ops/bigquery/definition.rb +173 -0
- data/lib/analytics_ops/bigquery.rb +4 -0
- data/lib/analytics_ops/cli/local_commands.rb +80 -0
- data/lib/analytics_ops/cli/options.rb +282 -9
- data/lib/analytics_ops/cli/presenter.rb +263 -28
- data/lib/analytics_ops/cli/presenter_csv.rb +92 -0
- data/lib/analytics_ops/cli/reporting_commands.rb +129 -0
- data/lib/analytics_ops/cli/setup_commands.rb +91 -0
- data/lib/analytics_ops/cli.rb +121 -44
- data/lib/analytics_ops/clients/admin.rb +28 -2
- data/lib/analytics_ops/clients/admin_governance.rb +95 -0
- data/lib/analytics_ops/clients/admin_governance_normalization.rb +90 -0
- data/lib/analytics_ops/clients/admin_resource_normalization.rb +61 -0
- data/lib/analytics_ops/clients/bigquery.rb +313 -0
- data/lib/analytics_ops/clients/data.rb +147 -54
- data/lib/analytics_ops/clients/data_result_normalization.rb +55 -0
- data/lib/analytics_ops/clients/error_translation.rb +13 -1
- data/lib/analytics_ops/configuration/schema.rb +175 -1
- data/lib/analytics_ops/configuration/validator.rb +45 -7
- data/lib/analytics_ops/configuration/validator_experimental.rb +136 -0
- data/lib/analytics_ops/configuration/validator_reporting.rb +93 -0
- data/lib/analytics_ops/configuration/writer.rb +112 -7
- data/lib/analytics_ops/desired_state.rb +28 -3
- data/lib/analytics_ops/errors.rb +33 -1
- data/lib/analytics_ops/funnels.rb +58 -0
- data/lib/analytics_ops/governance.rb +363 -0
- data/lib/analytics_ops/health.rb +310 -0
- data/lib/analytics_ops/mcp_server/custom_report_tool.rb +101 -0
- data/lib/analytics_ops/mcp_server/discovery_tools.rb +79 -0
- data/lib/analytics_ops/mcp_server/experimental_tools.rb +89 -0
- data/lib/analytics_ops/mcp_server/health_tools.rb +80 -0
- data/lib/analytics_ops/mcp_server/overview_tools.rb +49 -0
- data/lib/analytics_ops/mcp_server/standard_report_tool.rb +53 -0
- data/lib/analytics_ops/mcp_server.rb +402 -0
- data/lib/analytics_ops/portfolio.rb +216 -0
- data/lib/analytics_ops/rails/railtie.rb +19 -8
- data/lib/analytics_ops/redaction.rb +10 -6
- data/lib/analytics_ops/reports/csv_export.rb +91 -0
- data/lib/analytics_ops/reports/definition.rb +152 -14
- data/lib/analytics_ops/reports/metadata.rb +158 -0
- data/lib/analytics_ops/reports/period.rb +104 -0
- data/lib/analytics_ops/reports/result.rb +8 -3
- data/lib/analytics_ops/reports.rb +3 -0
- data/lib/analytics_ops/service_account.rb +321 -29
- data/lib/analytics_ops/setup.rb +31 -7
- data/lib/analytics_ops/version.rb +1 -1
- data/lib/analytics_ops/workspace.rb +222 -10
- data/lib/analytics_ops.rb +7 -2
- data/lib/generators/analytics_ops/templates/analytics_ops.yml +1 -1
- data/sig/analytics_ops.rbs +494 -9
- metadata +60 -1
|
@@ -6,22 +6,33 @@ module AnalyticsOps
|
|
|
6
6
|
class Options
|
|
7
7
|
DEFAULTS = {
|
|
8
8
|
config: "config/analytics_ops.yml",
|
|
9
|
+
config_explicit: false,
|
|
9
10
|
profile: "production",
|
|
11
|
+
profile_explicit: false,
|
|
10
12
|
format: "human",
|
|
11
13
|
log_level: "warn",
|
|
14
|
+
log_level_explicit: false,
|
|
12
15
|
transport: :grpc,
|
|
16
|
+
transport_explicit: false,
|
|
17
|
+
timeout_explicit: false,
|
|
13
18
|
yes: false,
|
|
14
|
-
noninteractive: false
|
|
19
|
+
noninteractive: false,
|
|
20
|
+
compare: false,
|
|
21
|
+
dimensions: [].freeze,
|
|
22
|
+
metrics: [].freeze,
|
|
23
|
+
filters: [].freeze,
|
|
24
|
+
all_rows: false
|
|
15
25
|
}.freeze
|
|
16
26
|
SETUP_OPTIONS = %i[property service_account].freeze
|
|
17
27
|
PROPERTY_ID = /\A\d{1,50}\z/
|
|
18
28
|
PROFILE = /\A[A-Za-z][A-Za-z0-9_]{0,63}\z/
|
|
29
|
+
CONNECTION = /\A[A-Za-z][A-Za-z0-9_-]{0,63}\z/
|
|
19
30
|
|
|
20
31
|
attr_reader :values
|
|
21
32
|
|
|
22
33
|
def initialize(arguments)
|
|
23
34
|
@arguments = arguments
|
|
24
|
-
@values = DEFAULTS.dup
|
|
35
|
+
@values = DEFAULTS.transform_values { |value| value.is_a?(Array) ? value.dup : value }
|
|
25
36
|
@explicit_format = nil
|
|
26
37
|
end
|
|
27
38
|
|
|
@@ -38,15 +49,25 @@ module AnalyticsOps
|
|
|
38
49
|
add_file_options(options)
|
|
39
50
|
add_format_options(options)
|
|
40
51
|
add_setup_options(options)
|
|
52
|
+
add_report_options(options)
|
|
41
53
|
add_client_options(options)
|
|
42
54
|
add_execution_options(options)
|
|
43
55
|
end
|
|
44
56
|
end
|
|
45
57
|
|
|
46
58
|
def add_file_options(options)
|
|
47
|
-
options.on("-c", "--config PATH", "Configuration path")
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
options.on("-c", "--config PATH", "Configuration path") do |path|
|
|
60
|
+
@values[:config] = path
|
|
61
|
+
@values[:config_explicit] = true
|
|
62
|
+
end
|
|
63
|
+
options.on("-p", "--profile NAME", "Configuration profile") do |name|
|
|
64
|
+
@values[:profile] = name
|
|
65
|
+
@values[:profile_explicit] = true
|
|
66
|
+
end
|
|
67
|
+
options.on("--connection NAME", "Saved Google connection") { |name| @values[:connection] = name }
|
|
68
|
+
options.on("-o", "--output PATH", "Write a plan or bounded CSV export to PATH") do |path|
|
|
69
|
+
@values[:output] = path
|
|
70
|
+
end
|
|
50
71
|
end
|
|
51
72
|
|
|
52
73
|
def add_format_options(options)
|
|
@@ -73,12 +94,46 @@ module AnalyticsOps
|
|
|
73
94
|
end
|
|
74
95
|
end
|
|
75
96
|
|
|
97
|
+
def add_report_options(options)
|
|
98
|
+
options.on("--last DAYS", Integer, "Use the previous number of complete days") do |days|
|
|
99
|
+
@values[:last_days] = days
|
|
100
|
+
end
|
|
101
|
+
options.on("--from DATE", "Report start date (YYYY-MM-DD)") { |date| @values[:start_date] = date }
|
|
102
|
+
options.on("--to DATE", "Report end date (YYYY-MM-DD)") { |date| @values[:end_date] = date }
|
|
103
|
+
options.on("--compare", "Also include the equally long preceding period") { @values[:compare] = true }
|
|
104
|
+
options.on("--dimension NAME", "Dimension for `report custom` (repeatable)") do |name|
|
|
105
|
+
@values.fetch(:dimensions) << name
|
|
106
|
+
end
|
|
107
|
+
options.on("--metric NAME", "Metric for `report custom` (repeatable)") do |name|
|
|
108
|
+
@values.fetch(:metrics) << name
|
|
109
|
+
end
|
|
110
|
+
options.on("--where FIELD=VALUE", "Exact dimension filter for `report custom` (repeatable)") do |value|
|
|
111
|
+
@values.fetch(:filters) << value
|
|
112
|
+
end
|
|
113
|
+
options.on("--limit ROWS", Integer, "Maximum rows in one report request") { |value| @values[:limit] = value }
|
|
114
|
+
options.on("--all", "Export every available row, within --max-rows") { @values[:all_rows] = true }
|
|
115
|
+
options.on("--max-rows ROWS", Integer, "Hard limit for --all (default 1000000)") do |value|
|
|
116
|
+
@values[:max_rows] = value
|
|
117
|
+
end
|
|
118
|
+
options.on("--page-size ROWS", Integer, "Rows per --all API request (default 100000)") do |value|
|
|
119
|
+
@values[:page_size] = value
|
|
120
|
+
end
|
|
121
|
+
options.on("--kind KIND", %w[dimension metric], "Limit `fields` to one field kind") do |value|
|
|
122
|
+
@values[:field_kind] = value
|
|
123
|
+
end
|
|
124
|
+
options.on("--concurrency COUNT", Integer, "Portfolio requests at once (1-8; default 4)") do |value|
|
|
125
|
+
@values[:concurrency] = value
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
76
129
|
def add_client_options(options)
|
|
77
130
|
options.on("--log-level LEVEL", CLI::LOG_LEVELS, CLI::LOG_LEVELS.join(", ")) do |level|
|
|
78
131
|
@values[:log_level] = level
|
|
132
|
+
@values[:log_level_explicit] = true
|
|
79
133
|
end
|
|
80
134
|
options.on("--transport TRANSPORT", %w[grpc rest], "grpc or rest") do |transport|
|
|
81
135
|
@values[:transport] = transport.to_sym
|
|
136
|
+
@values[:transport_explicit] = true
|
|
82
137
|
end
|
|
83
138
|
options.on("--timeout SECONDS", Float, "Google API timeout") do |seconds|
|
|
84
139
|
unless seconds.finite? && seconds.positive?
|
|
@@ -86,6 +141,7 @@ module AnalyticsOps
|
|
|
86
141
|
end
|
|
87
142
|
|
|
88
143
|
@values[:timeout] = seconds
|
|
144
|
+
@values[:timeout_explicit] = true
|
|
89
145
|
end
|
|
90
146
|
end
|
|
91
147
|
|
|
@@ -97,7 +153,16 @@ module AnalyticsOps
|
|
|
97
153
|
def validate!(command)
|
|
98
154
|
validate_arguments!(command)
|
|
99
155
|
validate_scoped_options!(command)
|
|
156
|
+
validate_local_options!(command)
|
|
157
|
+
validate_connection!
|
|
158
|
+
validate_report_period!(command)
|
|
100
159
|
validate_setup! if command == "setup"
|
|
160
|
+
validate_apply! if command == "apply"
|
|
161
|
+
validate_mcp! if command == "mcp"
|
|
162
|
+
validate_custom_report!(command)
|
|
163
|
+
validate_export!(command)
|
|
164
|
+
validate_field_options!(command)
|
|
165
|
+
validate_portfolio_options!(command)
|
|
101
166
|
validate_format!(command)
|
|
102
167
|
end
|
|
103
168
|
|
|
@@ -108,16 +173,212 @@ module AnalyticsOps
|
|
|
108
173
|
end
|
|
109
174
|
|
|
110
175
|
def validate_scoped_options!(command)
|
|
111
|
-
|
|
176
|
+
validate_plan_scope!(command)
|
|
177
|
+
validate_selection_scope!(command)
|
|
178
|
+
validate_execution_scope!(command)
|
|
179
|
+
validate_setup_scope!(command)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def validate_local_options!(command)
|
|
183
|
+
if %w[connections schema].include?(command) && @values.fetch(:config_explicit)
|
|
184
|
+
raise OptionParser::InvalidArgument, "--config is not valid with #{command}"
|
|
185
|
+
end
|
|
186
|
+
return unless %w[connections profiles schema use].include?(command)
|
|
187
|
+
return unless @values.fetch(:log_level_explicit) ||
|
|
188
|
+
@values.fetch(:transport_explicit) ||
|
|
189
|
+
@values.fetch(:timeout_explicit)
|
|
190
|
+
|
|
191
|
+
raise OptionParser::InvalidArgument,
|
|
192
|
+
"--log-level, --transport, and --timeout are not valid with #{command}"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def validate_plan_scope!(command)
|
|
196
|
+
if @values[:output] && !%w[plan report].include?(command)
|
|
197
|
+
raise OptionParser::InvalidArgument, "--output is only valid with plan or report --all"
|
|
198
|
+
end
|
|
112
199
|
raise OptionParser::InvalidArgument, "--yes is only valid with apply" if @values[:yes] && command != "apply"
|
|
113
|
-
|
|
114
|
-
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def validate_selection_scope!(command)
|
|
203
|
+
if @values[:connection] && %w[connections portfolio profiles schema].include?(command)
|
|
204
|
+
raise OptionParser::InvalidArgument, "--connection is not valid with #{command}"
|
|
115
205
|
end
|
|
206
|
+
return unless @values[:profile_explicit] && %w[connections portfolio profiles schema use].include?(command)
|
|
207
|
+
|
|
208
|
+
raise OptionParser::InvalidArgument, "--profile is not valid with #{command}"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def validate_execution_scope!(command)
|
|
212
|
+
return unless @values[:noninteractive] && !%w[apply setup].include?(command)
|
|
213
|
+
|
|
214
|
+
raise OptionParser::InvalidArgument, "--non-interactive is only valid with apply or setup"
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def validate_setup_scope!(command)
|
|
116
218
|
return if SETUP_OPTIONS.none? { |name| @values[name] } || command == "setup"
|
|
117
219
|
|
|
118
220
|
raise OptionParser::InvalidArgument, "--property and --service-account are setup-only"
|
|
119
221
|
end
|
|
120
222
|
|
|
223
|
+
def validate_connection!
|
|
224
|
+
return unless @values[:connection] && !CONNECTION.match?(@values.fetch(:connection))
|
|
225
|
+
|
|
226
|
+
raise OptionParser::InvalidArgument,
|
|
227
|
+
"--connection must start with a letter and use only letters, numbers, hyphens, or underscores"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def validate_report_period!(command)
|
|
231
|
+
requested = @values[:last_days] || @values[:start_date] || @values[:end_date] || @values[:compare]
|
|
232
|
+
return unless requested
|
|
233
|
+
unless %w[overview portfolio report health raw funnel].include?(command)
|
|
234
|
+
raise OptionParser::InvalidArgument,
|
|
235
|
+
"--last, --from, --to, and --compare are only valid with date-aware read commands"
|
|
236
|
+
end
|
|
237
|
+
if %w[raw funnel].include?(command) && @values.fetch(:compare)
|
|
238
|
+
raise OptionParser::InvalidArgument, "#{command} does not support --compare"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
Reports::Period.resolve(
|
|
242
|
+
last_days: @values[:last_days],
|
|
243
|
+
start_date: @values[:start_date],
|
|
244
|
+
end_date: @values[:end_date],
|
|
245
|
+
compare: @values.fetch(:compare)
|
|
246
|
+
)
|
|
247
|
+
rescue InvalidRequestError => error
|
|
248
|
+
raise OptionParser::InvalidArgument, error.message
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def validate_custom_report!(command)
|
|
252
|
+
return validate_raw_report_options! if command == "raw"
|
|
253
|
+
|
|
254
|
+
custom = command == "report" && @arguments.first == "custom"
|
|
255
|
+
selected = custom_report_options_selected?
|
|
256
|
+
require_custom_metrics! if custom
|
|
257
|
+
return unless selected
|
|
258
|
+
|
|
259
|
+
validate_custom_report_scope!(command)
|
|
260
|
+
require_custom_metrics!
|
|
261
|
+
validate_positive_bound!(:limit, Reports::Definition::MAX_STANDARD_LIMIT)
|
|
262
|
+
filters = parsed_custom_filters
|
|
263
|
+
@values[:custom_definition] = Reports::Definition.new(
|
|
264
|
+
name: "custom_report",
|
|
265
|
+
kind: "standard",
|
|
266
|
+
dimensions: @values.fetch(:dimensions),
|
|
267
|
+
metrics: @values.fetch(:metrics),
|
|
268
|
+
date_ranges: custom_report_date_ranges,
|
|
269
|
+
dimension_filter: combined_filter(filters),
|
|
270
|
+
limit: @values.fetch(:limit, 100)
|
|
271
|
+
)
|
|
272
|
+
rescue InvalidRequestError => error
|
|
273
|
+
raise OptionParser::InvalidArgument, error.message
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def validate_raw_report_options!
|
|
277
|
+
if @values.fetch(:dimensions).any? || @values.fetch(:metrics).any? || @values.fetch(:filters).any?
|
|
278
|
+
raise OptionParser::InvalidArgument, "--dimension, --metric, and --where are not valid with raw"
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
validate_positive_bound!(:limit, 10_000)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def custom_report_options_selected?
|
|
285
|
+
@values.fetch(:dimensions).any? ||
|
|
286
|
+
@values.fetch(:metrics).any? ||
|
|
287
|
+
@values.fetch(:filters).any? ||
|
|
288
|
+
@values[:limit]
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def validate_custom_report_scope!(command)
|
|
292
|
+
unless command == "report"
|
|
293
|
+
raise OptionParser::InvalidArgument, "custom report options are only valid with report"
|
|
294
|
+
end
|
|
295
|
+
return if @arguments.first == "custom"
|
|
296
|
+
|
|
297
|
+
raise OptionParser::InvalidArgument, "--dimension, --metric, --where, and --limit require `report custom`"
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def require_custom_metrics!
|
|
301
|
+
return unless @values.fetch(:metrics).empty?
|
|
302
|
+
|
|
303
|
+
raise OptionParser::MissingArgument, "`report custom` requires at least one --metric"
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def parsed_custom_filters
|
|
307
|
+
@values.fetch(:filters).map do |filter|
|
|
308
|
+
field, value = filter.to_s.split("=", 2)
|
|
309
|
+
unless field && value && !field.empty? && !value.empty?
|
|
310
|
+
raise OptionParser::InvalidArgument, "--where must use FIELD=VALUE"
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
{ "field" => field, "match_type" => "exact", "value" => value }
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def combined_filter(filters)
|
|
318
|
+
filters.length > 1 ? { "and" => filters } : filters.first
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def custom_report_date_ranges
|
|
322
|
+
Reports::Period.resolve(
|
|
323
|
+
last_days: @values[:last_days],
|
|
324
|
+
start_date: @values[:start_date],
|
|
325
|
+
end_date: @values[:end_date],
|
|
326
|
+
compare: @values.fetch(:compare)
|
|
327
|
+
) || Reports::Catalog::STANDARD_DATE_RANGE
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def validate_export!(command)
|
|
331
|
+
validate_export_scope!(command)
|
|
332
|
+
validate_export_dependencies!(command)
|
|
333
|
+
validate_positive_bound!(:max_rows, Workspace::MAX_EXPORT_ROWS)
|
|
334
|
+
validate_positive_bound!(:page_size, Reports::Definition::MAX_STANDARD_LIMIT)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def validate_export_scope!(command)
|
|
338
|
+
requested = @values.fetch(:all_rows) || @values[:max_rows] || @values[:page_size]
|
|
339
|
+
return unless requested && command != "report"
|
|
340
|
+
|
|
341
|
+
raise OptionParser::InvalidArgument, "--all, --max-rows, and --page-size are report-only"
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def validate_export_dependencies!(command)
|
|
345
|
+
if @values.fetch(:all_rows)
|
|
346
|
+
raise OptionParser::MissingArgument, "--all requires --output PATH" unless @values[:output]
|
|
347
|
+
elsif command == "report" && @values[:output]
|
|
348
|
+
raise OptionParser::InvalidArgument, "report --output requires --all"
|
|
349
|
+
end
|
|
350
|
+
return unless (@values[:max_rows] || @values[:page_size]) && !@values.fetch(:all_rows)
|
|
351
|
+
|
|
352
|
+
raise OptionParser::InvalidArgument, "--max-rows and --page-size require --all"
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def validate_positive_bound!(name, maximum)
|
|
356
|
+
value = @values[name]
|
|
357
|
+
return unless value
|
|
358
|
+
return if value.is_a?(Integer) && value.between?(1, maximum)
|
|
359
|
+
|
|
360
|
+
raise OptionParser::InvalidArgument, "--#{name.to_s.tr("_", "-")} must be between 1 and #{maximum}"
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def validate_field_options!(command)
|
|
364
|
+
return unless @values[:field_kind] && command != "fields"
|
|
365
|
+
|
|
366
|
+
raise OptionParser::InvalidArgument, "--kind is only valid with fields"
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def validate_portfolio_options!(command)
|
|
370
|
+
if @values[:concurrency] && command != "portfolio"
|
|
371
|
+
raise OptionParser::InvalidArgument, "--concurrency is only valid with portfolio"
|
|
372
|
+
end
|
|
373
|
+
if @values[:concurrency] && !@values.fetch(:concurrency).between?(1, 8)
|
|
374
|
+
raise OptionParser::InvalidArgument, "--concurrency must be between 1 and 8"
|
|
375
|
+
end
|
|
376
|
+
return unless command == "portfolio" && @values.fetch(:format) == "csv"
|
|
377
|
+
return if @arguments.first == "report" && @arguments.length == 2
|
|
378
|
+
|
|
379
|
+
raise OptionParser::InvalidArgument, "CSV portfolio output requires `portfolio report NAME`"
|
|
380
|
+
end
|
|
381
|
+
|
|
121
382
|
def validate_setup!
|
|
122
383
|
validate_setup_property!
|
|
123
384
|
raise OptionParser::InvalidArgument, "--profile is invalid" unless PROFILE.match?(@values.fetch(:profile))
|
|
@@ -147,8 +408,20 @@ module AnalyticsOps
|
|
|
147
408
|
raise OptionParser::MissingArgument, "JSON setup requires --non-interactive --property ID"
|
|
148
409
|
end
|
|
149
410
|
|
|
411
|
+
def validate_apply!
|
|
412
|
+
return unless @values.fetch(:format) == "json" && (!@values[:noninteractive] || !@values[:yes])
|
|
413
|
+
|
|
414
|
+
raise OptionParser::MissingArgument, "JSON apply requires --non-interactive --yes"
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def validate_mcp!
|
|
418
|
+
return if @values.fetch(:format) == "human"
|
|
419
|
+
|
|
420
|
+
raise OptionParser::InvalidArgument, "mcp uses the MCP protocol and does not accept output formats"
|
|
421
|
+
end
|
|
422
|
+
|
|
150
423
|
def validate_format!(command)
|
|
151
|
-
return unless @values.fetch(:format) == "csv" && !%w[report realtime].include?(command)
|
|
424
|
+
return unless @values.fetch(:format) == "csv" && !%w[report realtime portfolio raw funnel].include?(command)
|
|
152
425
|
|
|
153
426
|
raise OptionParser::InvalidArgument, "CSV output is only valid for report results"
|
|
154
427
|
end
|