Commit 05db84d4 authored by Alishan Ladhani's avatar Alishan Ladhani Committed by Alper Akgun

Generate Snowplow event dictionary

parent 8dce6828
---
stage: Growth
group: Product Intelligence
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
<!---
This documentation is auto generated by a script.
Please do not edit this file directly, check generate_metrics_dictionary task on lib/tasks/gitlab/usage_data.rake.
--->
<!-- vale gitlab.Spelling = NO -->
# Event Dictionary
This file is autogenerated, please do not edit it directly.
To generate these files from the GitLab repository, run:
```shell
bundle exec rake gitlab:snowplow:generate_event_dictionary
```
The Event Dictionary is based on the following event definition YAML files:
- [`config/events`](https://gitlab.com/gitlab-org/gitlab/-/tree/f9a404301ca22d038e7b9a9eb08d9c1bbd6c4d84/config/events)
- [`ee/config/events`](https://gitlab.com/gitlab-org/gitlab/-/tree/f9a404301ca22d038e7b9a9eb08d9c1bbd6c4d84/ee/config/events)
## Event definitions
### `epics promote`
| category | action | label | property | value |
|---|---|---|---|---|
| `epics` | `promote` | `` | `The string "issue_id"` | `ID of the issue` |
Issue promoted to epic
YAML definition: `/ee/config/events/epics_promote.yml`
Owner: `group::product planning`
Tiers: `premium`, `ultimate`
# frozen_string_literal: true
module Gitlab
module Tracking
module Docs
# Helper with functions to be used by HAML templates
module Helper
def auto_generated_comment
<<-MARKDOWN.strip_heredoc
---
stage: Growth
group: Product Intelligence
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
<!---
This documentation is auto generated by a script.
Please do not edit this file directly, check generate_metrics_dictionary task on lib/tasks/gitlab/usage_data.rake.
--->
<!-- vale gitlab.Spelling = NO -->
MARKDOWN
end
def render_description(object)
return 'Missing description' unless object.description.present?
object.description
end
def render_event_taxonomy(object)
headers = %w[category action label property value]
values = %i[category action label property_description value_description]
values = values.map { |key| backtick(object.attributes[key]) }
values = values.join(" | ")
[
"| #{headers.join(" | ")} |",
"#{'|---' * headers.size}|",
"| #{values} |"
].join("\n")
end
def md_link_to(anchor_text, url)
"[#{anchor_text}](#{url})"
end
def render_owner(object)
"Owner: #{backtick(object.product_group)}"
end
def render_tiers(object)
"Tiers: #{object.tiers.map(&method(:backtick)).join(', ')}"
end
def render_yaml_definition_path(object)
"YAML definition: #{backtick(object.yaml_path)}"
end
def backtick(string)
"`#{string}`"
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Tracking
module Docs
class Renderer
include Gitlab::Tracking::Docs::Helper
DICTIONARY_PATH = Rails.root.join('doc', 'development', 'snowplow')
TEMPLATE_PATH = Rails.root.join('lib', 'gitlab', 'tracking', 'docs', 'templates', 'default.md.haml')
def initialize(event_definitions)
@layout = Haml::Engine.new(File.read(TEMPLATE_PATH))
@event_definitions = event_definitions.sort
end
def contents
# Render and remove an extra trailing new line
@contents ||= @layout.render(self, event_definitions: @event_definitions).sub!(/\n(?=\Z)/, '')
end
def write
filename = DICTIONARY_PATH.join('dictionary.md').to_s
FileUtils.mkdir_p(DICTIONARY_PATH)
File.write(filename, contents)
filename
end
end
end
end
end
= auto_generated_comment
:plain
# Event Dictionary
This file is autogenerated, please do not edit it directly.
To generate these files from the GitLab repository, run:
```shell
bundle exec rake gitlab:snowplow:generate_event_dictionary
```
The Event Dictionary is based on the following event definition YAML files:
- [`config/events`](https://gitlab.com/gitlab-org/gitlab/-/tree/f9a404301ca22d038e7b9a9eb08d9c1bbd6c4d84/config/events)
- [`ee/config/events`](https://gitlab.com/gitlab-org/gitlab/-/tree/f9a404301ca22d038e7b9a9eb08d9c1bbd6c4d84/ee/config/events)
## Event definitions
\
- event_definitions.each do |_path, object|
= "### `#{object.category} #{object.action}`"
\
= render_event_taxonomy(object)
\
= render_description(object)
\
= render_yaml_definition_path(object)
\
= render_owner(object)
\
= render_tiers(object)
\
# frozen_string_literal: true
module Gitlab
module Tracking
InvalidEventError = Class.new(RuntimeError)
class EventDefinition
EVENT_SCHEMA_PATH = Rails.root.join('config', 'events', 'schema.json')
BASE_REPO_PATH = 'https://gitlab.com/gitlab-org/gitlab/-/blob/master'
SCHEMA = ::JSONSchemer.schema(Pathname.new(EVENT_SCHEMA_PATH))
attr_reader :path
attr_reader :attributes
class << self
def paths
@paths ||= [Rails.root.join('config', 'events', '*.yml'), Rails.root.join('ee', 'config', 'events', '*.yml')]
end
def definitions
paths.each_with_object({}) do |glob_path, definitions|
load_all_from_path!(definitions, glob_path)
end
end
private
def load_from_file(path)
definition = File.read(path)
definition = YAML.safe_load(definition)
definition.deep_symbolize_keys!
self.new(path, definition).tap(&:validate!)
rescue StandardError => e
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(Gitlab::Tracking::InvalidEventError.new(e.message))
end
def load_all_from_path!(definitions, glob_path)
Dir.glob(glob_path).each do |path|
definition = load_from_file(path)
definitions[definition.path] = definition
end
end
end
def initialize(path, opts = {})
@path = path
@attributes = opts
end
def to_h
attributes
end
alias_method :to_dictionary, :to_h
def yaml_path
path.delete_prefix(Rails.root.to_s)
end
def validate!
SCHEMA.validate(attributes.stringify_keys).each do |error|
error_message = <<~ERROR_MSG
Error type: #{error['type']}
Data: #{error['data']}
Path: #{error['data_pointer']}
Details: #{error['details']}
Definition file: #{path}
ERROR_MSG
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(Gitlab::Tracking::InvalidEventError.new(error_message))
end
end
private
def method_missing(method, *args)
attributes[method] || super
end
end
end
end
# frozen_string_literal: true
namespace :gitlab do
namespace :snowplow do
desc 'GitLab | Snowplow | Generate event dictionary'
task generate_event_dictionary: :environment do
items = Gitlab::Tracking::EventDefinition.definitions
Gitlab::Tracking::Docs::Renderer.new(items).write
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Tracking::Docs::Helper do
let_it_be(:klass) do
Class.new do
include Gitlab::Tracking::Docs::Helper
end
end
describe '#auto_generated_comment' do
it 'renders information about missing description' do
expect(klass.new.auto_generated_comment).to match /This documentation is auto generated by a script/
end
end
describe '#render_description' do
context 'description is empty' do
it 'renders information about missing description' do
object = double(description: '')
expect(klass.new.render_description(object)).to eq('Missing description')
end
end
context 'description is present' do
it 'render description' do
object = double(description: 'some description')
expect(klass.new.render_description(object)).to eq('some description')
end
end
end
describe '#render_event_taxonomy' do
it 'render table with event taxonomy' do
attributes = {
category: 'epics',
action: 'promote',
label: nil,
property_description: 'String with issue id',
value_description: 'Integer issue id'
}
object = double(attributes: attributes)
event_taxonomy = <<~MD.chomp
| category | action | label | property | value |
|---|---|---|---|---|
| `epics` | `promote` | `` | `String with issue id` | `Integer issue id` |
MD
expect(klass.new.render_event_taxonomy(object)).to eq(event_taxonomy)
end
end
describe '#md_link_to' do
it 'render link in md format' do
expect(klass.new.md_link_to('zelda', 'link')).to eq('[zelda](link)')
end
end
describe '#render_owner' do
it 'render information about group owning event' do
object = double(product_group: "group::product intelligence")
expect(klass.new.render_owner(object)).to eq("Owner: `group::product intelligence`")
end
end
describe '#render_tiers' do
it 'render information about tiers' do
object = double(tiers: %w[bronze silver gold])
expect(klass.new.render_tiers(object)).to eq("Tiers: `bronze`, `silver`, `gold`")
end
end
describe '#render_yaml_definition_path' do
it 'render relative location of yaml definition' do
object = double(yaml_path: 'config/events/button_click.yaml')
expect(klass.new.render_yaml_definition_path(object)).to eq("YAML definition: `config/events/button_click.yaml`")
end
end
describe '#backtick' do
it 'wraps string in backticks chars' do
expect(klass.new.backtick('test')).to eql("`test`")
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Tracking::Docs::Renderer do
describe 'contents' do
let(:dictionary_path) { described_class::DICTIONARY_PATH }
let(:items) { Gitlab::Tracking::EventDefinition.definitions.first(10).to_h }
it 'generates dictionary for given items' do
generated_dictionary = described_class.new(items).contents
table_of_contents_items = items.values.map { |item| "#{item.category} #{item.action}"}
generated_dictionary_keys = RDoc::Markdown
.parse(generated_dictionary)
.table_of_contents
.select { |metric_doc| metric_doc.level == 3 }
.map { |item| item.text.match(%r{<code>(.*)</code>})&.captures&.first }
expect(generated_dictionary_keys).to match_array(table_of_contents_items)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Tracking::EventDefinition do
let(:attributes) do
{
description: 'Created issues',
category: 'issues',
action: 'create',
label_description: 'API',
property_description: 'The string "issue_id"',
value_description: 'ID of the issue',
extra_properties: { confidential: false },
product_category: 'collection',
product_stage: 'growth',
product_section: 'dev',
product_group: 'group::product analytics',
distribution: %w(ee ce),
tier: %w(free premium ultimate)
}
end
let(:path) { File.join('events', 'issues_create.yml') }
let(:definition) { described_class.new(path, attributes) }
let(:yaml_content) { attributes.deep_stringify_keys.to_yaml }
def write_metric(metric, path, content)
path = File.join(metric, path)
dir = File.dirname(path)
FileUtils.mkdir_p(dir)
File.write(path, content)
end
it 'has all definitions valid' do
expect { described_class.definitions }.not_to raise_error(Gitlab::Tracking::InvalidEventError)
end
describe '#validate' do
using RSpec::Parameterized::TableSyntax
where(:attribute, :value) do
:description | 1
:category | nil
:action | nil
:label_description | 1
:property_description | 1
:value_description | 1
:extra_properties | 'smth'
:product_category | 1
:product_stage | 1
:product_section | nil
:product_group | nil
:distributions | %[be eb]
:tiers | %[pro]
end
with_them do
before do
attributes[attribute] = value
end
it 'raise exception' do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Tracking::InvalidEventError))
described_class.new(path, attributes).validate!
end
end
end
describe '.definitions' do
let(:metric1) { Dir.mktmpdir('metric1') }
let(:metric2) { Dir.mktmpdir('metric2') }
before do
allow(described_class).to receive(:paths).and_return(
[
File.join(metric1, '**', '*.yml'),
File.join(metric2, '**', '*.yml')
]
)
end
subject { described_class.definitions }
it 'has empty list when there are no definition files' do
is_expected.to be_empty
end
it 'has one metric when there is one file' do
write_metric(metric1, path, yaml_content)
is_expected.to be_one
end
after do
FileUtils.rm_rf(metric1)
FileUtils.rm_rf(metric2)
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