Commit e8c94c61 authored by Mikołaj Wawrzyniak's avatar Mikołaj Wawrzyniak Committed by Rémy Coutable

Include constraints in suggested metrics name

Enchance automated metric names suggestion with inclusion of
variable parts based on underlaying query constraints.
parent a378e3b9
...@@ -15,11 +15,11 @@ module Gitlab ...@@ -15,11 +15,11 @@ module Gitlab
private private
def count(relation, column = nil, batch: true, batch_size: nil, start: nil, finish: nil) def count(relation, column = nil, batch: true, batch_size: nil, start: nil, finish: nil)
"count_#{parse_target_and_source(column, relation)}" name_suggestion(column: column, relation: relation, prefix: 'count')
end end
def distinct_count(relation, column = nil, batch: true, batch_size: nil, start: nil, finish: nil) def distinct_count(relation, column = nil, batch: true, batch_size: nil, start: nil, finish: nil)
"count_distinct_#{parse_target_and_source(column, relation)}" name_suggestion(column: column, relation: relation, prefix: 'count_distinct', distinct: :distinct)
end end
def redis_usage_counter def redis_usage_counter
...@@ -35,22 +35,67 @@ module Gitlab ...@@ -35,22 +35,67 @@ module Gitlab
end end
def sum(relation, column, *rest) def sum(relation, column, *rest)
"sum_#{parse_target_and_source(column, relation)}" name_suggestion(column: column, relation: relation, prefix: 'sum')
end end
def estimate_batch_distinct_count(relation, column = nil, *rest) def estimate_batch_distinct_count(relation, column = nil, *rest)
"estimate_distinct_#{parse_target_and_source(column, relation)}" name_suggestion(column: column, relation: relation, prefix: 'estimate_distinct_count')
end end
def add(*args) def add(*args)
"add_#{args.join('_and_')}" "add_#{args.join('_and_')}"
end end
def parse_target_and_source(column, relation) def name_suggestion(relation:, column: nil, prefix: nil, distinct: nil)
parts = [prefix]
if column if column
"#{column}_from_#{relation.table_name}" parts << parse_target(column)
parts << 'from'
end
source = parse_source(relation)
constraints = parse_constraints(relation: relation, column: column, distinct: distinct)
if constraints.include?(source)
parts << "<adjective describing: '#{constraints}'>"
end
parts << source
parts.compact.join('_')
end
def parse_constraints(relation:, column: nil, distinct: nil)
connection = relation.connection
::Gitlab::Usage::Metrics::NamesSuggestions::RelationParsers::Constraints
.new(connection)
.accept(arel(relation: relation, column: column, distinct: distinct), collector(connection))
.value
end
def parse_target(column)
if column.is_a?(Arel::Attribute)
"#{column.relation.name}.#{column.name}"
else
column
end
end
def parse_source(relation)
relation.table_name
end
def collector(connection)
Arel::Collectors::SubstituteBinds.new(connection, Arel::Collectors::SQLString.new)
end
def arel(relation:, column: nil, distinct: nil)
column ||= relation.primary_key
if column.is_a?(Arel::Attribute)
relation.select(column.count(distinct)).arel
else else
relation.table_name relation.select(relation.all.table[column].count(distinct)).arel
end end
end end
end end
......
# frozen_string_literal: true
module Gitlab
module Usage
module Metrics
module NamesSuggestions
module RelationParsers
class Constraints < ::Arel::Visitors::PostgreSQL
# rubocop:disable Naming/MethodName
def visit_Arel_Nodes_SelectCore(object, collector)
collect_nodes_for(object.wheres, collector, "") || collector
end
# rubocop:enable Naming/MethodName
def quote(value)
"#{value}"
end
def quote_table_name(name)
"#{name}"
end
def quote_column_name(name)
"#{name}"
end
end
end
end
end
end
end
...@@ -10,39 +10,57 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do ...@@ -10,39 +10,57 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do
end end
describe '#generate' do describe '#generate' do
context 'for count metrics' do shared_examples 'name suggestion' do
it 'return correct name' do it 'return correct name' do
expect(described_class.generate('counts.boards')).to eq 'count_boards' expect(described_class.generate(key_path)).to eq name_suggestion
end end
end end
context 'for count distinct metrics' do context 'for count with default column metrics' do
it 'return correct name' do it_behaves_like 'name suggestion' do
expect(described_class.generate('counts.issues_using_zoom_quick_actions')).to eq 'count_distinct_issue_id_from_zoom_meetings' # corresponding metric is collected with count(Board)
let(:key_path) { 'counts.boards' }
let(:name_suggestion) { 'count_boards' }
end
end
context 'for count distinct with column defined metrics' do
it_behaves_like 'name suggestion' do
# corresponding metric is collected with distinct_count(ZoomMeeting, :issue_id)
let(:key_path) { 'counts.issues_using_zoom_quick_actions' }
let(:name_suggestion) { 'count_distinct_issue_id_from_zoom_meetings' }
end end
end end
context 'for sum metrics' do context 'for sum metrics' do
it 'return correct name' do it_behaves_like 'name suggestion' do
expect(described_class.generate('counts.jira_imports_total_imported_issues_count')).to eq 'sum_imported_issues_count_from_jira_imports' # corresponding metric is collected with sum(JiraImportState.finished, :imported_issues_count)
let(:key_path) { 'counts.jira_imports_total_imported_issues_count' }
let(:name_suggestion) { "sum_imported_issues_count_from_<adjective describing: '(jira_imports.status = 4)'>_jira_imports" }
end end
end end
context 'for add metrics' do context 'for add metrics' do
it 'return correct name' do it_behaves_like 'name suggestion' do
expect(described_class.generate('counts.snippets')).to eq 'add_count_snippets_and_count_snippets' # corresponding metric is collected with add(data[:personal_snippets], data[:project_snippets])
let(:key_path) { 'counts.snippets' }
let(:name_suggestion) { "add_count_<adjective describing: '(snippets.type = 'PersonalSnippet')'>_snippets_and_count_<adjective describing: '(snippets.type = 'ProjectSnippet')'>_snippets" }
end end
end end
context 'for redis metrics' do context 'for redis metrics' do
it 'return correct name' do it_behaves_like 'name suggestion' do
expect(described_class.generate('analytics_unique_visits.analytics_unique_visits_for_any_target')).to eq '<please fill metric name>' # corresponding metric is collected with redis_usage_data { unique_visit_service.unique_visits_for(targets: :analytics) }
let(:key_path) { 'analytics_unique_visits.analytics_unique_visits_for_any_target' }
let(:name_suggestion) { '<please fill metric name>' }
end end
end end
context 'for alt_usage_data metrics' do context 'for alt_usage_data metrics' do
it 'return correct name' do it_behaves_like 'name suggestion' do
expect(described_class.generate('settings.operating_system')).to eq '<please fill metric name>' # corresponding metric is collected with alt_usage_data(fallback: nil) { operating_system }
let(:key_path) { 'settings.operating_system' }
let(:name_suggestion) { '<please fill metric name>' }
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::RelationParsers::Constraints do
describe '#accept' do
let(:collector) { Arel::Collectors::SubstituteBinds.new(ActiveRecord::Base.connection, Arel::Collectors::SQLString.new) }
it 'builds correct constraints description' do
table = Arel::Table.new('records')
arel = table.from.project(table['id'].count).where(table[:attribute].eq(true).and(table[:some_value].gt(5)))
described_class.new(ApplicationRecord.connection).accept(arel, collector)
expect(collector.value).to eql '(records.attribute = true AND records.some_value > 5)'
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment