Class: GitlabInternalEventsCli::NewMetric

Inherits:
Struct
  • Object
show all
Defined in:
lib/gitlab_internal_events_cli/metric.rb

Overview

NOTE: :instrumentation_operation is kept on the struct but intentionally excluded from NEW_METRIC_FIELDS so it does NOT leak into the YAML output. It is only used to generate the Ruby instrumentation class file for database metrics.

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#actionsObject

Returns the value of attribute actions

Returns:

  • (Object)

    the current value of actions



86
87
88
# File 'lib/gitlab_internal_events_cli/metric.rb', line 86

def actions
  @actions
end

#filtersObject

Returns the value of attribute filters

Returns:

  • (Object)

    the current value of filters



86
87
88
# File 'lib/gitlab_internal_events_cli/metric.rb', line 86

def filters
  @filters
end

#identifierObject

Returns the value of attribute identifier

Returns:

  • (Object)

    the current value of identifier



86
87
88
# File 'lib/gitlab_internal_events_cli/metric.rb', line 86

def identifier
  @identifier
end

#instrumentation_operationObject

Returns the value of attribute instrumentation_operation

Returns:

  • (Object)

    the current value of instrumentation_operation



86
87
88
# File 'lib/gitlab_internal_events_cli/metric.rb', line 86

def instrumentation_operation
  @instrumentation_operation
end

#keyObject

Returns the value of attribute key

Returns:

  • (Object)

    the current value of key



86
87
88
# File 'lib/gitlab_internal_events_cli/metric.rb', line 86

def key
  @key
end

#operatorObject

Returns the value of attribute operator

Returns:

  • (Object)

    the current value of operator



86
87
88
# File 'lib/gitlab_internal_events_cli/metric.rb', line 86

def operator
  @operator
end

Instance Method Details

#assign_time_frameObject

Maintain current functionality of string time_frame for backward compatibility TODO: Remove once we can deduplicate and merge metric files



321
322
323
# File 'lib/gitlab_internal_events_cli/metric.rb', line 321

def assign_time_frame
  time_frame.single? ? time_frame.value.first : time_frame.value
end

#bulk_assign(key_value_pairs) ⇒ Object



315
316
317
# File 'lib/gitlab_internal_events_cli/metric.rb', line 315

def bulk_assign(key_value_pairs)
  key_value_pairs.each { |key, value| self[key] = value }
end

#database_metric?Boolean

Returns:

  • (Boolean)


196
197
198
# File 'lib/gitlab_internal_events_cli/metric.rb', line 196

def database_metric?
  data_source == 'database'
end

#default_name_from_instrumentation_classObject

Derives a snake_case metric-name suggestion from the instrumentation_class so database metrics can show a sensible default in the “How should we reference this metric?” prompt.

Trailing ‘_metric` is stripped because the metric’s key_path / filename should not end in ‘_metric` (the class name does, but the key path describes the value, not the Ruby class).

Examples:

"CountIssuesMetric"          -> "count_issues"
"CountStuffINTheDBMetric"    -> "count_stuff_in_the_db"
nil                          -> nil


354
355
356
357
358
# File 'lib/gitlab_internal_events_cli/metric.rb', line 354

def default_name_from_instrumentation_class
  return unless instrumentation_class && !instrumentation_class.empty?

  instrumentation_class_underscored.sub(/_metric\z/, '')
end

#description_prefixObject

Automatically prepended to all new descriptions ex) Total count of ex) Weekly/Monthly count of unique ex) Count of



291
292
293
294
295
296
297
298
299
# File 'lib/gitlab_internal_events_cli/metric.rb', line 291

def description_prefix
  return unless event_metric?

  [
    (time_frame.description if time_frame.single?),
    operator.description,
    *(identifier.plural if identifier.default?)
  ].compact.join(' ').capitalize
end

#distribution_pathObject



200
201
202
# File 'lib/gitlab_internal_events_cli/metric.rb', line 200

def distribution_path
  'ee' unless tiers.include?('free')
end

#event_metric?Boolean

Returns:

  • (Boolean)


325
326
327
# File 'lib/gitlab_internal_events_cli/metric.rb', line 325

def event_metric?
  data_source == 'internal_events'
end

#event_params(action, filter = nil) ⇒ Object



257
258
259
260
261
262
263
264
# File 'lib/gitlab_internal_events_cli/metric.rb', line 257

def event_params(action, filter = nil)
  params = { 'name' => action }
  params['unique'] = identifier.reference if operator.value == 'unique_count'
  params['filter'] = filter if filter&.any?
  params['operator'] = operator.reference(identifier) if operator.value == 'sum'

  params
end

#eventsHash

Returns value for the ‘events` key in the metric definition. Requires #actions or #filters to be set by the caller first.

Returns:

  • (Hash)


249
250
251
252
253
254
255
# File 'lib/gitlab_internal_events_cli/metric.rb', line 249

def events
  if filters.assigned?
    self[:filters].map { |(action, filter)| event_params(action, filter) }
  else
    actions.map { |action| event_params(action) }
  end
end

#file_nameObject



204
205
206
207
# File 'lib/gitlab_internal_events_cli/metric.rb', line 204

def file_name
  name = event_metric? ? key.value : key_path
  "#{name}.yml"
end

#file_pathObject



106
107
108
109
110
111
112
113
114
115
116
# File 'lib/gitlab_internal_events_cli/metric.rb', line 106

def file_path
  File.join(
    *[
      distribution_path,
      'config',
      'metrics',
      time_frame.directory_name,
      file_name
    ].compact
  )
end

#filtered?Boolean

How to interpret different values for filters: nil –> not expected, assigned or filtered

(metric not initialized with filters)

–> both expected and filtered

(metric initialized with filters, but not yet assigned by user)
[‘event’, {}]

–> not expected, assigned or filtered

(filters were expected, but then skipped by user)
[‘event’, { ‘label’ => ‘a’ }]

–> both assigned and filtered

(filters exist for any event; user is done assigning)

Returns:

  • (Boolean)


279
280
281
# File 'lib/gitlab_internal_events_cli/metric.rb', line 279

def filtered?
  filters.assigned? || filters.expected?
end

#filters_expected?Boolean

Returns:

  • (Boolean)


283
284
285
# File 'lib/gitlab_internal_events_cli/metric.rb', line 283

def filters_expected?
  filters.expected?
end

#formatted_outputObject



91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/gitlab_internal_events_cli/metric.rb', line 91

def formatted_output
  extra_keys = event_metric? ? { events: events } : {}

  # These keys will always be included in the definition yaml
  METRIC_DEFAULTS
    .merge(to_h.compact)
    .merge(
      time_frame: assign_time_frame,
      key_path: key_path
    ).merge(extra_keys)
    .slice(*NEW_METRIC_FIELDS)
    .transform_keys(&:to_s)
    .to_yaml(line_width: 150)
end

#instrumentation_class_contentObject

Renders the body of the database instrumentation class.

Based on upstream lib/generators/gitlab/usage_metric/templates/database_instrumentation_class.rb.template with one intentional deviation: the upstream Rails generator appends “Metric” to the class name from a separate argument, whereas the CLI asks the user to provide the full class name (e.g. “CountIssuesMetric”) and interpolates it verbatim.



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/gitlab_internal_events_cli/metric.rb', line 150

def instrumentation_class_content
  <<~RUBY
    # frozen_string_literal: true

    module Gitlab
      module Usage
        module Metrics
          module Instrumentations
            class #{instrumentation_class} < DatabaseMetric
              operation :#{instrumentation_operation}

              relation do
                # Insert ActiveRecord relation here
              end
            end
          end
        end
      end
    end
  RUBY
end

#instrumentation_class_file_pathObject

Path to the Ruby instrumentation class file generated for database metrics. Mirrors the layout used by the upstream ‘rails generate gitlab:usage_metric` generator.



121
122
123
124
125
126
127
128
129
# File 'lib/gitlab_internal_events_cli/metric.rb', line 121

def instrumentation_class_file_path
  File.join(
    *[
      distribution_path,
      'lib', 'gitlab', 'usage', 'metrics', 'instrumentations',
      "#{instrumentation_class_underscored}.rb"
    ].compact
  )
end

#instrumentation_class_spec_contentObject

Renders the spec body for the generated instrumentation class.

Differs from the upstream Rails template in two ways:

  1. Uses the combined ‘a correct instrumented metric value and query` shared example (issue gitlab-org/gitlab#569191), where upstream uses `a correct instrumented metric value`.

  2. Adds ‘data_source: ’database’‘ to the shared example params, matching the convention used by recent database metric specs in gitlab-org/gitlab (e.g. `count_admins_metric_spec.rb`).



181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/gitlab_internal_events_cli/metric.rb', line 181

def instrumentation_class_spec_content
  <<~RUBY
    # frozen_string_literal: true

    require 'spec_helper'

    RSpec.describe Gitlab::Usage::Metrics::Instrumentations::#{instrumentation_class}, feature_category: :service_ping do
      let(:expected_value) { 0 }
      let(:expected_query) { '' }

      it_behaves_like 'a correct instrumented metric value and query', { time_frame: 'all', data_source: 'database' }
    end
  RUBY
end

#instrumentation_class_spec_file_pathObject

Path to the RSpec file for the generated instrumentation class.



132
133
134
135
136
137
138
139
140
# File 'lib/gitlab_internal_events_cli/metric.rb', line 132

def instrumentation_class_spec_file_path
  File.join(
    *[
      distribution_path,
      'spec', 'lib', 'gitlab', 'usage', 'metrics', 'instrumentations',
      "#{instrumentation_class_underscored}_spec.rb"
    ].compact
  )
end

#instrumentation_class_underscoredObject

Converts a CamelCase class name like “CountIssuesMetric” to the snake_case file name stem “count_issues_metric”. Implemented without ActiveSupport to keep the gem dependency-free.



332
333
334
335
336
337
338
339
340
# File 'lib/gitlab_internal_events_cli/metric.rb', line 332

def instrumentation_class_underscored
  return '' unless instrumentation_class

  instrumentation_class
    .gsub('::', '/')
    .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
    .gsub(/([a-z\d])([A-Z])/, '\1_\2')
    .downcase
end

#key_pathObject



209
210
211
# File 'lib/gitlab_internal_events_cli/metric.rb', line 209

def key_path
  event_metric? ? key.full_path : self[:key]
end

#technical_descriptionObject

Provides simplified but technically accurate description to be used before the user has provided a description



303
304
305
306
307
308
309
310
311
312
313
# File 'lib/gitlab_internal_events_cli/metric.rb', line 303

def technical_description
  return unless event_metric?

  event_name = actions.first if events.length == 1 && !filtered?
  event_name ||= 'the selected events'
  [
    (time_frame.description if time_frame.single?),
    operator.description,
    (identifier.description % event_name).to_s
  ].compact.join(' ').capitalize
end

#time_frameObject



213
214
215
# File 'lib/gitlab_internal_events_cli/metric.rb', line 213

def time_frame
  Metric::TimeFrames.new(self[:time_frame])
end

#unique_idsObject

Enables comparison with existing metrics



234
235
236
237
238
239
240
241
242
243
# File 'lib/gitlab_internal_events_cli/metric.rb', line 234

def unique_ids
  prefix = [
    operator.reference(identifier),
    actions.sort.join('+'),
    'filter-',
    filtered?
  ].join('_')

  time_frame.value.map { |t| prefix + t }
end