内部事件跟踪快速入门
为了提供更高效、可扩展且统一的跟踪 API,GitLab 正在逐步弃用现有的 RedisHLL 和 Snowplow 跟踪方式。取而代之的是,我们正在实现新的 track_event(后端)和 trackEvent(前端)方法。
通过这种方式,我们可以在不关心底层实现的情况下,同时更新 RedisHLL 计数器并发送 Snowplow 事件。
要在您的代码中集成内部事件跟踪,您需要做三件事:
- 定义一个事件
- 定义一个或多个指标
- 触发事件
定义事件和指标
要创建事件和/或指标定义,请使用 gitlab 目录下的 internal_events 生成器:
scripts/internal_events/cli.rb这个 CLI 工具将帮助您根据具体用例创建正确的定义文件,然后提供用于集成和测试的代码示例。
事件命名应遵循 <action>_<target_of_action>_<where/when> 的格式,有效示例包括 create_ci_build 或 click_previous_blame_on_blob_page。
触发事件
在后端和前端触发事件并更新指标的方式略有不同。请参考下面的相关部分。
后端跟踪
要触发事件,请从 Gitlab::InternalEventsTracking 模块调用 track_internal_event 方法,并传入所需的参数:
include Gitlab::InternalEventsTracking
track_internal_event(
"create_ci_build",
user: user,
namespace: namespace,
project: project
)此方法会自动递增与事件 create_ci_build 相关的所有 RedisHLL 指标,并发送一个包含所有命名参数和标准上下文(仅限 SaaS)的相应 Snowplow 事件。
此外,触发事件的类名会保存在 Snowplow 事件的 category 属性中。
如果您定义了一个具有 unique 属性的指标(如 unique: project.id),则必须提供 project 参数。
建议尽可能多地填写 user、namespace 和 project,因为这可以提高数据质量,并使将来定义指标更加容易。
如果提供了 project 但没有提供 namespace,则使用 project.namespace 作为事件的 namespace。
在某些情况下,您可能需要手动指定 category 或完全不提供。为此,您可以直接调用 InternalEvents.track_event 方法,而不是使用模块。
当一个功能通过多个命名空间启用,并且需要跟踪启用该功能的原因时,
可以传递一个可选的 feature_enabled_by_namespace_ids 参数,其中包含命名空间 ID 数组。
track_internal_event(
...
feature_enabled_by_namespace_ids: [namespace_one.id, namespace_two.id]
)额外属性
在跟踪事件时可以传递额外属性。它们可用于保存与给定事件相关的额外数据。
跟踪类已经内置了三个属性:
label(字符串)property(字符串)value(数字)
这三个属性的任意命名和类型是由于数据提取过程的限制所致。
建议首先使用这些属性,即使它们的名称与您要跟踪的数据不匹配。您可以通过在事件的 YAML 定义中使用 description 属性来进一步描述实际跟踪的数据。示例请参见
create_ci_internal_pipeline.yml:
additional_properties:
label:
description: 管道来源,例如推送、计划任务等。
property:
description: 配置来源,例如仓库、auto_devops 等。通过在 #track_event 调用中包含 additional_properties 哈希来传递额外属性:
track_internal_event(
"create_ci_build",
user: user,
additional_properties: {
label: source, # label 跟踪管道的来源
property: config_source # property 跟踪配置的来源
}
)如果需要传递超过三个内置的额外属性,可以使用带有自定义键的 additional_properties 哈希:
track_internal_event(
"code_suggestion_accepted",
user: user,
additional_properties: {
# 内置属性
label: editor_name,
property: suggestion_type,
value: suggestion_shown_duration,
# 您的自定义属性
lang: 'ruby',
custom_key: 'custom_value'
}
)仅在添加内置属性的基础上添加自定义属性。额外属性只能具有字符串或数字值。
确保额外属性不包含任何敏感信息。更多信息请参阅 数据分类标准。
Controller 和 API 助手
有一个 ProductAnalyticsTracking 助手模块供控制器使用,您可以通过调用 #track_internal_event 来跟踪特定控制器操作的内部事件:
class Projects::PipelinesController < Projects::ApplicationController
include ProductAnalyticsTracking
track_internal_event :charts, name: 'visit_charts_on_ci_cd_pipelines', conditions: -> { should_track_ci_cd_pipelines? }
def charts
...
end
private
def should_track_ci_cd_pipelines?
params[:chart].blank? || params[:chart] == 'pipelines'
end
end您需要在控制器主体中添加这两个方法,以便助手可以获取事件的当前项目和命名空间:
private
def tracking_namespace_source
project.namespace
end
def tracking_project_source
project
end此外,还有一个 API 助手:
track_event(
event_name,
user: current_user,
namespace_id: namespace_id,
project_id: project_id
)批处理
当同时发出多个事件时,使用 with_batched_redis_writes 将它们批量处理到单个 Redis 调用中。
Gitlab::InternalEvents.with_batched_redis_writes do
incr.times { Gitlab::InternalEvents.track_event(event) }
end请注意,只有对总计数器的更新会被批处理。如果定义了 n 个唯一指标和 m 个总计数器指标,将导致 incr * n + m 次 Redis 写入。
后端测试
在测试触发内部事件或递增指标的代码时,可以在块参数上使用 trigger_internal_events 和 increment_usage_metrics 匹配器。
expect { subject }
.to trigger_internal_events('web_ide_viewed')
.with(user: user, project: project, namespace: namespace)
.and increment_usage_metrics('counts.web_views')trigger_internal_events 匹配器接受与 receive 匹配器相同的链式方法(#once、#at_most 等)。默认情况下,它期望提供的事件只被触发一次。
链式方法 #with 接受以下参数:
user- 用户对象project- 项目对象namespace- 命名空间对象。如果未提供,将设置为project.namespaceadditional_properties- 哈希。要随事件发送的额外属性。例如:{ label: 'scheduled', value: 20 }category- 字符串。如果未提供,将设置为触发事件的对象的类名
increment_usage_metrics 匹配器接受与 change 匹配器相同的链式方法(#by、#from、#to 等)。默认情况下,它期望提供的指标递增一。
expect { subject }
.to trigger_internal_events('web_ide_viewed')
.with(user: user, project: project, namespace: namespace)
.exactly(3).times这两个匹配器都可以与其他作用于块的匹配器(如 change 匹配器)组合使用。
expect { subject }
.to trigger_internal_events('mr_created')
.with(user: user, project: project, category: category, additional_properties: { label: label } )
.and increment_usage_metrics('counts.deployments')
.at_least(:once)
.and change { mr.notes.count }.by(1)调试提示:如果您的测试因指标未按预期递增而失败,
您可能需要应用 :clean_gitlab_redis_shared_state 特性来清除示例之间的 Redis 缓存。
要测试事件未被触发,可以使用 not_trigger_internal_events 匹配器。它不接受消息链。
expect { subject }.to trigger_internal_events('mr_created')
.with(user: user, project: project, namespace: namespace)
.and increment_usage_metrics('counts.deployments')
.and not_trigger_internal_events('pipeline_started')或者您可以使用 not_to 语法:
expect { subject }.not_to trigger_internal_events('mr_created', 'member_role_created')trigger_internal_events 匹配器也可用于测试 Haml with data attributes。
前端跟踪
任何前端跟踪调用都会自动从当前页面的上下文中传递 user.id、namespace.id 和 project.id 的值。
Vue 组件
在 Vue 组件中,可以使用 Vue mixin 进行跟踪。
要实现 Vue 组件跟踪:
-
导入
InternalEvents库并调用mixin方法:import { InternalEvents } from '~/tracking'; const trackingMixin = InternalEvents.mixin(); -
在组件中使用该 mixin:
export default { mixins: [trackingMixin], data() { return { expanded: false, }; }, }; -
调用
trackEvent方法。跟踪选项可以作为第二个参数传递:this.trackEvent('click_previous_blame_on_blob_page');或者在模板中使用
trackEvent方法:<template> <div> <button data-testid="toggle" @click="toggle">Toggle</button> <div v-if="expanded"> <p>Hello world!</p> <button @click="trackEvent('click_previous_blame_on_blob_page')">Track another event</button> </div> </div> </template>
原生 JavaScript
对于从任意前端 JavaScript 代码直接跟踪事件,提供了原生 JavaScript 模块。这可以在无法使用 Mixin 的组件上下文之外使用。
import { InternalEvents } from '~/tracking';
InternalEvents.trackEvent('click_previous_blame_on_blob_page');data-event 属性
此属性确保如果我们想要为按钮跟踪 GitLab 内部事件,不需要在 Click 处理程序中编写 JavaScript 代码。相反,我们只需添加一个带有事件值的 data-event-tracking 属性,它就应该可以工作。这也可以与 HAML 视图一起使用。
<gl-button
data-event-tracking="click_previous_blame_on_blob_page"
>
Click Me
</gl-button>Haml
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle', data: { event_tracking: 'click_previous_blame_on_blob_page' }}) do渲染时的内部事件
有时我们希望在组件渲染或加载时发送内部事件。在这些情况下,我们可以添加 data-event-tracking-load="true" 属性:
= render Pajamas::ButtonComponent.new(button_options: { data: { event_tracking_load: 'true', event_tracking: 'click_previous_blame_on_blob_page' } }) do
= _("New project")额外属性
您可以包含额外属性与事件一起保存额外数据。包含时,您必须在 additional_properties 字段中定义每个额外属性。可以发送三个内置的额外属性,键为 label(字符串)、property(字符串)和 value(数字),以及自定义额外属性,如果内置属性不够用的话。
不要将页面 URL 或页面路径作为额外属性传递,因为我们已经为每个事件跟踪了匿名化的页面 URL。
从 window.location 获取 URL 不会对项目和命名空间信息进行匿名化处理,如文档所述。
对于 Vue Mixin:
this.trackEvent('click_view_runners_button', {
label: 'group_runner_form',
property: dynamicPropertyVar,
value: 20
});对于原生 JavaScript:
InternalEvents.trackEvent('click_view_runners_button', {
label: 'group_runner_form',
property: dynamicPropertyVar,
value: 20
});对于 data-event 属性:
<gl-button
data-event-tracking="click_view_runners_button"
data-event-label="group_runner_form"
:data-event-property=dynamicPropertyVar
data-event-additional='{"key1": "value1", "key2": "value2"}'
>
Click Me
</gl-button>对于 Haml:
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle', data: { event_tracking: 'action', event_label: 'group_runner_form', event_property: dynamic_property_var, event_value: 2, event_additional: '{"key1": "value1", "key2": "value2"}' }}) do前端测试
JavaScript/Vue
如果您在任何代码中使用 trackEvent 方法,无论是在原生 JavaScript 中还是在 Vue 组件中,都可以使用 useMockInternalEventsTracking 助手方法来断言 trackEvent 是否被调用。
例如,如果我们需要测试下面的 Vue 组件,
<script>
import { GlButton } from '@gitlab/ui';
import { InternalEvents } from '~/tracking';
import { __ } from '~/locale';
export default {
components: {
GlButton,
},
mixins: [InternalEvents.mixin()],
methods: {
handleButtonClick() {
// 一些应用逻辑
// 当某些事件发生时触发跟踪调用
this.trackEvent('click_view_runners_button', {
label: 'group_runner_form',
property: 'property_value',
value: 3,
});
},
},
i18n: {
button1: __('Sample Button'),
},
};
</script>
<template>
<div style="display: flex; height: 90vh; align-items: center; justify-content: center">
<gl-button class="sample-button" @click="handleButtonClick">
{{ $options.i18n.button1 }}
</gl-button>
</div>
</template>以下是该组件的测试用例。
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import DeleteApplication from '~/admin/applications/components/delete_application.vue';
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
describe('DeleteApplication', () => {
/** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */
let wrapper;
const createComponent = () => {
wrapper = shallowMountExtended(DeleteApplication);
};
beforeEach(() => {
createComponent();
});
describe('sample button 1', () => {
const { bindInternalEventDocument } = useMockInternalEventsTracking();
it('should call trackEvent method when clicked on sample button', async () => {
const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
await wrapper.find('.sample-button').vm.$emit('click');
expect(trackEventSpy).toHaveBeenCalledWith(
'click_view_runners_button',
{
label: 'group_runner_form',
property: 'property_value',
value: 3,
},
undefined,
);
});
});
});如果您在 Vue/View 模板中使用跟踪属性,如下所示,
<script>
import { GlButton } from '@gitlab/ui';
import { InternalEvents } from '~/tracking';
import { __ } from '~/locale';
export default {
components: {
GlButton,
},
mixins: [InternalEvents.mixin()],
i18n: {
button1: __('Sample Button'),
},
};
</script>
<template>
<div style="display: flex; height: 90vh; align-items: center; justify-content: center">
<gl-button
class="sample-button"
data-event-tracking="click_view_runners_button"
data-event-label="group_runner_form"
>
{{ $options.i18n.button1 }}
</gl-button>
</div>
</template>以下是该组件的测试用例。
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import DeleteApplication from '~/admin/applications/components/delete_application.vue';
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
describe('DeleteApplication', () => {
/** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */
let wrapper;
const createComponent = () => {
wrapper = shallowMountExtended(DeleteApplication);
};
beforeEach(() => {
createComponent();
});
describe('sample button', () => {
const { bindInternalEventDocument } = useMockInternalEventsTracking();
it('should call trackEvent method when clicked on sample button', () => {
const { triggerEvent, trackEventSpy } = bindInternalEventDocument(wrapper.element);
triggerEvent('.sample-button');
expect(trackEventSpy).toHaveBeenCalledWith('click_view_runners_button', {
label: 'group_runner_form',
});
});
});
});带数据属性的 Haml
如果您在 Haml 层使用 data attributes 来跟踪内部事件,
可以使用 trigger_internal_events matcher 来断言预期的属性是否存在。
例如,如果您需要测试下面的 Haml,
%div{ data: { testid: '_testid_', event_tracking: 'some_event', event_label: 'some_label' } }您可以在任何与 have_css 匹配器兼容的渲染 HTML 上调用断言。
使用 :on_click 和 :on_load 链式方法来指示您期望事件何时触发。
以下是该 haml 的测试用例。
- 渲染的 HTML 是一个
String(RSpec views)
it 'assigns the tracking items' do
render
expect(rendered).to trigger_internal_events('some_event').on_click
.with(additional_properties: { label: 'some_label' })
end- 渲染的 HTML 是一个
Capybara::Node::Simple(ViewComponent)
it 'assigns the tracking items' do
render_inline(component)
expect(page.find_by_testid('_testid_'))
.to trigger_internal_events('some_event').on_click
.with(additional_properties: { label: 'some_label' })
end- 渲染的 HTML 是一个
Nokogiri::HTML4::DocumentFragment(ViewComponent)
it 'assigns the tracking items' do
expect(render_inline(component))
.to trigger_internal_events('some_event').on_click
.with(additional_properties: { label: 'some_label' })
end或者您可以使用 not_to 语法:
it 'assigns the tracking items' do
render_inline(component)
expect(page).not_to trigger_internal_events
end当否定时,匹配器不接受额外的链式方法或参数。 这断言没有使用任何跟踪属性。
使用内部事件 API
您还可以使用我们的 API 来跟踪连接到 GitLab 实例的其他系统的事件。 更多信息请参阅 Usage Data API documentation。
其他系统上的内部事件
除了 GitLab 代码库,我们还在以下系统中使用内部事件。