Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot
2024-11-01 09:14:54 +00:00
parent d744e314a5
commit 87a71c09b2
29 changed files with 2292 additions and 1736 deletions

View File

@ -1,6 +1,6 @@
<script>
import VueRouter from 'vue-router';
import { GlTab, GlTabs, GlButton, GlSprintf, GlIcon, GlLink } from '@gitlab/ui';
import { GlAvatar, GlTab, GlTabs, GlButton, GlSprintf, GlIcon, GlLink } from '@gitlab/ui';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { createAlert, VARIANT_DANGER } from '~/alert';
@ -35,6 +35,7 @@ const routes = [
export default {
name: 'ShowMlModelVersionApp',
components: {
GlAvatar,
GlButton,
LoadOrErrorOrShow,
ModelVersionActionsDropdown,
@ -166,6 +167,12 @@ export default {
tabIndex() {
return routes.findIndex(({ name }) => name === this.$route.name);
},
showAuthor() {
return Boolean(this.modelVersion?.author);
},
author() {
return this.modelVersion?.author;
},
},
methods: {
handleError(error) {
@ -216,6 +223,7 @@ export default {
},
i18n: {
editModelVersionButtonLabel: s__('MlModelRegistry|Edit model version'),
authorTitle: s__('MlModelRegistry|Publisher'),
},
ROUTE_DETAILS,
ROUTE_PERFORMANCE,
@ -241,10 +249,10 @@ export default {
<template #author>
<gl-link
class="js-user-link gl-font-bold !gl-text-gray-500"
:href="modelVersion.author.webUrl"
:href="author.webUrl"
:data-user-id="authorId"
>
<span class="sm:gl-inline">{{ modelVersion.author.name }}</span>
<span class="sm:gl-inline">{{ author.name }}</span>
</gl-link>
</template>
</gl-sprintf>
@ -264,23 +272,41 @@ export default {
</div>
</div>
<load-or-error-or-show :is-loading="isLoading" :error-message="errorMessage">
<gl-tabs class="gl-mt-4" :value="tabIndex">
<gl-tab
:title="s__('MlModelRegistry|Version card')"
@click="goTo($options.ROUTE_DETAILS)"
/>
<gl-tab
:title="s__('MlModelRegistry|Performance')"
@click="goTo($options.ROUTE_PERFORMANCE)"
/>
</gl-tabs>
<router-view
:model-version="modelVersion"
can-write-model-registry
import-path
allow-artifact-import
/>
</load-or-error-or-show>
<div class="gl-grid gl-gap-3 md:gl-grid-cols-4">
<div class="md:gl-col-span-3 md:gl-pr-8">
<load-or-error-or-show :is-loading="isLoading" :error-message="errorMessage">
<gl-tabs class="gl-mt-4" :value="tabIndex">
<gl-tab
:title="s__('MlModelRegistry|Version card')"
@click="goTo($options.ROUTE_DETAILS)"
/>
<gl-tab
:title="s__('MlModelRegistry|Performance')"
@click="goTo($options.ROUTE_PERFORMANCE)"
/>
</gl-tabs>
<router-view
:model-version="modelVersion"
can-write-model-registry
import-path
allow-artifact-import
/>
</load-or-error-or-show>
</div>
<div class="gl-pt-6 md:gl-col-span-1">
<div class="gl-text-lg gl-font-bold">{{ $options.i18n.authorTitle }}</div>
<div v-if="showAuthor" class="gl-mt-3 gl-text-gray-500">
<gl-link
data-testid="sidebar-author-link"
class="js-user-link gl-font-bold !gl-text-gray-500"
:href="author.webUrl"
>
<gl-avatar :label="author.name" :src="author.avatarUrl" :size="24" />
{{ author.name }}
</gl-link>
</div>
</div>
</div>
</div>
</template>

View File

@ -22,6 +22,7 @@ class WorkItem < Issue
foreign_key: 'issue_id',
inverse_of: :work_item,
autosave: true
has_one :weights_source, class_name: 'WorkItems::WeightsSource'
has_many :child_links, class_name: '::WorkItems::ParentLink', foreign_key: :work_item_parent_id
has_many :work_item_children, through: :child_links, class_name: 'WorkItem',

View File

@ -1,8 +1,9 @@
---
migration_job_name: BackfillDastScannerProfilesBuildsProjectId
description: Backfills sharding key `dast_scanner_profiles_builds.project_id` from `dast_scanner_profiles`.
description: Backfills sharding key `dast_scanner_profiles_builds.project_id` from
`dast_scanner_profiles`.
feature_category: dynamic_application_security_testing
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167626
milestone: '17.5'
queued_migration_version: 20240930135301
finalized_by: # version of the migration that finalized this BBM
finalized_by: '20241027232438'

View File

@ -0,0 +1,12 @@
---
table_name: work_item_weights_sources
classes:
- WorkItems::WeightsSource
feature_categories:
- team_planning
description: Weight data associated to work items.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/170371
milestone: '17.6'
gitlab_schema: gitlab_main_cell
sharding_key:
namespace_id: namespaces

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class CreateWorkItemWeightsSourcesTable < Gitlab::Database::Migration[2.2]
milestone '17.6'
def change
create_table :work_item_weights_sources, id: false do |t| # rubocop:disable Migration/EnsureFactoryForTable -- factory is in ee/spec/factories/work_items/weights_sources.rb
t.references :work_item,
primary_key: true,
default: nil,
foreign_key: { on_delete: :cascade, to_table: :issues }
t.bigint :namespace_id, null: false
t.bigint :rolled_up_weight
t.bigint :rolled_up_completed_weight
t.timestamps_with_timezone null: false
t.index :namespace_id
end
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class AddWorkItemWeightsSourcesNamespaceFk < Gitlab::Database::Migration[2.2]
milestone '17.6'
disable_ddl_transaction!
def up
add_concurrent_foreign_key :work_item_weights_sources, :namespaces,
column: :namespace_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :work_item_weights_sources, column: :namespace_id
end
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FinalizeBackfillDastScannerProfilesBuildsProjectId < Gitlab::Database::Migration[2.2]
milestone '17.6'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_sec
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillDastScannerProfilesBuildsProjectId',
table_name: :dast_scanner_profiles_builds,
column_name: :ci_build_id,
job_arguments: [:project_id, :dast_scanner_profiles, :project_id, :dast_scanner_profile_id],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1 @@
be5034e209a4f141ef3dd8530aee4337b4cce96010fc1132a661e930cf9bad2f

View File

@ -0,0 +1 @@
3092506e75b1aebe2f9e6047629316331eaf22e080043567c1c492b91aed9e86

View File

@ -0,0 +1 @@
bf583f1f7f53e25e861091a8afdf962d2fcc701f294620bbd9d4c3c9b8c12ae1

View File

@ -21674,6 +21674,15 @@ CREATE SEQUENCE work_item_types_id_seq
ALTER SEQUENCE work_item_types_id_seq OWNED BY work_item_types.id;
CREATE TABLE work_item_weights_sources (
work_item_id bigint NOT NULL,
namespace_id bigint NOT NULL,
rolled_up_weight bigint,
rolled_up_completed_weight bigint,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
CREATE TABLE work_item_widget_definitions (
id bigint NOT NULL,
work_item_type_id bigint NOT NULL,
@ -26525,6 +26534,9 @@ ALTER TABLE ONLY work_item_type_custom_fields
ALTER TABLE ONLY work_item_types
ADD CONSTRAINT work_item_types_pkey PRIMARY KEY (id);
ALTER TABLE ONLY work_item_weights_sources
ADD CONSTRAINT work_item_weights_sources_pkey PRIMARY KEY (work_item_id);
ALTER TABLE ONLY work_item_widget_definitions
ADD CONSTRAINT work_item_widget_definitions_pkey PRIMARY KEY (id);
@ -32342,6 +32354,10 @@ CREATE UNIQUE INDEX index_work_item_types_on_correct_id_unique ON work_item_type
CREATE UNIQUE INDEX index_work_item_types_on_name_unique ON work_item_types USING btree (TRIM(BOTH FROM lower(name)));
CREATE INDEX index_work_item_weights_sources_on_namespace_id ON work_item_weights_sources USING btree (namespace_id);
CREATE INDEX index_work_item_weights_sources_on_work_item_id ON work_item_weights_sources USING btree (work_item_id);
CREATE UNIQUE INDEX index_work_item_widget_definitions_on_type_id_and_name ON work_item_widget_definitions USING btree (work_item_type_id, TRIM(BOTH FROM lower(name)));
CREATE INDEX index_work_item_widget_definitions_on_work_item_type_id ON work_item_widget_definitions USING btree (work_item_type_id);
@ -35619,6 +35635,9 @@ ALTER TABLE ONLY sprints
ALTER TABLE ONLY alert_management_alert_metric_images
ADD CONSTRAINT fk_80b75a6094 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY work_item_weights_sources
ADD CONSTRAINT fk_815ba3b395 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY alert_management_alert_user_mentions
ADD CONSTRAINT fk_8175238264 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
@ -37278,6 +37297,9 @@ ALTER TABLE ONLY ci_cost_settings
ALTER TABLE ONLY operations_feature_flags_issues
ADD CONSTRAINT fk_rails_6a8856ca4f FOREIGN KEY (feature_flag_id) REFERENCES operations_feature_flags(id) ON DELETE CASCADE;
ALTER TABLE ONLY work_item_weights_sources
ADD CONSTRAINT fk_rails_6ac227847a FOREIGN KEY (work_item_id) REFERENCES issues(id) ON DELETE CASCADE;
ALTER TABLE ONLY import_source_users
ADD CONSTRAINT fk_rails_6aee6cd676 FOREIGN KEY (placeholder_user_id) REFERENCES users(id) ON DELETE SET NULL;

View File

@ -349,6 +349,9 @@ To migrate back to local storage:
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/11872) in GitLab 17.2.
> - [Introduced](https://gitlab.com/gitlab-org/charts/gitlab/-/merge_requests/3845) for Helm chart (Kubernetes) in GitLab 17.3.
WARNING:
This feature is affected by [a known issue](https://github.com/git-lfs/git-lfs/issues/5880). If you clone a repository with multiple Git LFS objects using the pure SSH protocol, the client might crash due to a `nil` pointer reference.
[`git-lfs` 3.0.0](https://github.com/git-lfs/git-lfs/blob/main/CHANGELOG.md#300-24-sep-2021)
released support for using SSH as the transfer protocol instead of HTTP.
SSH is handled transparently by the `git-lfs` command line tool.

View File

@ -848,8 +848,8 @@ The script does not print job templates that:
```shell
export GL_GROUP_ID=56595735
# Script requires pyyaml too.
pip3 install python-gitlab pyyaml
# Install script dependencies
python3 -m pip install 'python-gitlab[yaml]'
python3 get_all_cicd_config_artifacts_expiry.py

View File

@ -1,251 +1,249 @@
{
"qa/specs/features/api/10_govern/group_access_token_spec.rb": 22.368781745999968,
"qa/specs/features/api/10_govern/project_access_token_spec.rb": 23.633304253000006,
"qa/specs/features/api/1_manage/rate_limits_spec.rb": 3.1467832400003317,
"qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb": 7.066938178999862,
"qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb": 8.327959042000202,
"qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb": 8.461132950999854,
"qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb": 26.816687255000033,
"qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb": 8.936014013000204,
"qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb": 22.04809043399996,
"qa/specs/features/api/3_create/merge_request/view_merge_requests_spec.rb": 0.07425793599986719,
"qa/specs/features/api/3_create/repository/add_list_delete_branches_spec.rb": 5.418077632999939,
"qa/specs/features/api/3_create/repository/commit_to_templated_project_spec.rb": 9.446346610999626,
"qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb": 8.764005738999913,
"qa/specs/features/api/3_create/repository/files_spec.rb": 4.811035699000058,
"qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb": 4.926575522999883,
"qa/specs/features/api/3_create/repository/push_postreceive_idempotent_spec.rb": 6.722170169000037,
"qa/specs/features/api/3_create/repository/storage_size_spec.rb": 15.720025386000088,
"qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb": 2.1978715340001145,
"qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb": 45.16411672999993,
"qa/specs/features/api/4_verify/cancel_pipeline_when_block_user_spec.rb": 10.504823287000363,
"qa/specs/features/api/4_verify/file_variable_spec.rb": 34.60797590999982,
"qa/specs/features/api/4_verify/job_downloads_artifacts_spec.rb": 25.90519738099988,
"qa/specs/features/api/9_data_stores/user_inherited_access_spec.rb": 72.58973470299998,
"qa/specs/features/api/9_data_stores/users_spec.rb": 0.46943177399998604,
"qa/specs/features/browser_ui/10_govern/group/group_access_token_spec.rb": 10.867392854000173,
"qa/specs/features/browser_ui/10_govern/login/2fa_recovery_spec.rb": 44.48518719200001,
"qa/specs/features/browser_ui/10_govern/login/2fa_ssh_recovery_spec.rb": 45.29727074599987,
"qa/specs/features/browser_ui/10_govern/login/log_in_spec.rb": 9.24467735899998,
"qa/specs/features/browser_ui/10_govern/login/log_in_with_2fa_spec.rb": 71.21219507099977,
"qa/specs/features/browser_ui/10_govern/login/login_via_oauth_and_oidc_with_gitlab_as_idp_spec.rb": 434.290598865,
"qa/specs/features/browser_ui/10_govern/login/register_spec.rb": 167.21751289999997,
"qa/specs/features/browser_ui/10_govern/project/project_access_token_spec.rb": 15.002181600999847,
"qa/specs/features/browser_ui/10_govern/user/impersonation_token_spec.rb": 34.3730335500004,
"qa/specs/features/browser_ui/10_govern/user/user_access_termination_spec.rb": 42.11820167299993,
"qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb": 18.42159530700019,
"qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb": 12.865294517000166,
"qa/specs/features/browser_ui/1_manage/integrations/jenkins/jenkins_build_status_spec.rb": 64.23631289100013,
"qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb": 13.557514046000051,
"qa/specs/features/browser_ui/2_plan/design_management/archive_design_content_spec.rb": 15.577577431000009,
"qa/specs/features/browser_ui/2_plan/design_management/modify_design_content_spec.rb": 16.93965877000005,
"qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb": 15.712710897999841,
"qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 11.492959395999605,
"qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb": 14.383327321999786,
"qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 47.38104187599993,
"qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb": 18.069448444000045,
"qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb": 20.876328290999936,
"qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 21.686897792999844,
"qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 17.499067247999847,
"qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb": 20.052594967999994,
"qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb": 15.281954894000137,
"qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb": 9.612242246999813,
"qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb": 58.40833293900005,
"qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb": 12.194349004999822,
"qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb": 16.66858077699999,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_creation_spec.rb": 57.8606801320002,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_manipulation_spec.rb": 36.7572706560004,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_directory_management_spec.rb": 13.130093501000374,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_file_upload_spec.rb": 26.96827703400004,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_list_spec.rb": 47.06445215099984,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_page_deletion_spec.rb": 32.69071444200017,
"qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb": 12.757383355999991,
"qa/specs/features/browser_ui/2_plan/transient/comment_on_discussion_spec.rb": 94.82951411399995,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_a_merge_spec.rb": 28.79146358800017,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_commit_spec.rb": 15.203331360999982,
"qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 34.60106437700006,
"qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb": 16.876695956000276,
"qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 20.24183390600001,
"qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb": 39.35212773400008,
"qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb": 16.664726423000047,
"qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb": 29.294655512999725,
"qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb": 29.818330108000055,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb": 49.977204337999865,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb": 74.05581426499998,
"qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 25.886177607000036,
"qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 58.32857243300032,
"qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb": 19.121554731999822,
"qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb": 12.009725652999805,
"qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 14.347447782000017,
"qa/specs/features/browser_ui/3_create/repository/file/create_file_via_web_spec.rb": 16.534865420999722,
"qa/specs/features/browser_ui/3_create/repository/file/delete_file_via_web_spec.rb": 13.119983175000016,
"qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb": 15.378666402999897,
"qa/specs/features/browser_ui/3_create/repository/file/file_with_unusual_name_spec.rb": 15.938765588000024,
"qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb": 21.63542792399994,
"qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb": 79.99040074799996,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb": 12.59656118699968,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb": 14.377605238999877,
"qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 12.259645183999965,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb": 61.57848500299997,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 58.59291245999975,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 40.70022904899997,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 12.597955936999824,
"qa/specs/features/browser_ui/3_create/repository/push_over_ssh_spec.rb": 23.335798235999846,
"qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 11.16296970899998,
"qa/specs/features/browser_ui/3_create/repository/push_to_canary_gitaly_spec.rb": 19.33630398599996,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_create_spec.rb": 15.139340509000021,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_delete_spec.rb": 15.598802374000115,
"qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 33.16634144599993,
"qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb": 43.07075570100005,
"qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb": 32.04896657299969,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb": 44.12173341700009,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb": 45.1201057369999,
"qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb": 35.046242132000316,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb": 11.228186354000172,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb": 11.679584620000014,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb": 17.142693512999813,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb": 25.128736303000096,
"qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb": 22.43362897299994,
"qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb": 20.63210230699997,
"qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb": 51.18759698200006,
"qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb": 18.490141503999894,
"qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb": 47.41684779299976,
"qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb": 55.95471532700003,
"qa/specs/features/browser_ui/3_create/web_ide/closing_web_ide_with_unsaved_changes_spec.rb": 17.89687930900027,
"qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb": 127.97824422899998,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/ci_catalog_sorting_spec.rb": 92.95931840900016,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/run_component_in_project_pipeline_spec.rb": 35.63051462899966,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/expose_job_artifacts_in_mr_spec.rb": 28.20214109099993,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/job_artifacts_access_keyword_spec.rb": 110.66918478300022,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/unlocking_job_artifacts_across_pipelines_spec.rb": 217.47925253999983,
"qa/specs/features/browser_ui/4_verify/ci_project_artifacts/user_can_bulk_delete_artifacts_spec.rb": 26.754828502000237,
"qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb": 31.085558825000135,
"qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb": 44.06894120200013,
"qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb": 35.78982095099991,
"qa/specs/features/browser_ui/4_verify/ci_variable/raw_variables_defined_in_yaml_spec.rb": 29.708999760000097,
"qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb": 50.36197774100037,
"qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb": 13.341353649999746,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb": 42.411010002999774,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb": 29.5737167530001,
"qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb": 48.13610776800033,
"qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb": 28.524868175999927,
"qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb": 106.57642176700028,
"qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb": 43.70788082299987,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb": 36.24359708399993,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb": 34.50332342399997,
"qa/specs/features/browser_ui/4_verify/pipeline/update_ci_file_with_pipeline_editor_spec.rb": 24.002371265999955,
"qa/specs/features/browser_ui/4_verify/runner/fleet_management/group_runner_counts_spec.rb": 10.757894206999936,
"qa/specs/features/browser_ui/4_verify/runner/fleet_management/group_runner_status_counts_spec.rb": 10.301866036000092,
"qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb": 12.12438019199999,
"qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb": 15.005740251000134,
"qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb": 29.49941567299993,
"qa/specs/features/browser_ui/5_package/infrastructure_registry/terraform_module_registry_spec.rb": 51.479649964000146,
"qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb": 27.352062939999996,
"qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb": 81.84952514700012,
"qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb": 28.553728266000235,
"qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb": 168.17629702600016,
"qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb": 371.8446552810001,
"qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb": 147.34913712900016,
"qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb": 143.3438839700002,
"qa/specs/features/browser_ui/5_package/package_registry/npm/npm_group_level_spec.rb": 212.5769153199999,
"qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb": 154.38543904599965,
"qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb": 65.49403680400019,
"qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 14.485062738000124,
"qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 95.86104218500031,
"qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 7.291155398999763,
"qa/specs/features/browser_ui/8_monitor/alert_management/alert_settings_create_new_alerts_spec.rb": 28.596902782000143,
"qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb": 51.41727816599996,
"qa/specs/features/browser_ui/8_monitor/alert_management/create_alert_using_authorization_key_spec.rb": 30.579300772999886,
"qa/specs/features/browser_ui/8_monitor/alert_management/recovery_alert_resolves_correct_alert_spec.rb": 17.057656027999656,
"qa/specs/features/browser_ui/9_data_stores/group/group_member_access_request_spec.rb": 62.32897362400013,
"qa/specs/features/browser_ui/9_data_stores/group/transfer_project_spec.rb": 18.02293271999997,
"qa/specs/features/browser_ui/9_data_stores/project/add_project_member_spec.rb": 17.59251898699995,
"qa/specs/features/browser_ui/9_data_stores/project/create_project_badge_spec.rb": 15.446313674000066,
"qa/specs/features/browser_ui/9_data_stores/project/create_project_spec.rb": 18.019042763000016,
"qa/specs/features/browser_ui/9_data_stores/project/dashboard_images_spec.rb": 14.63526036400026,
"qa/specs/features/browser_ui/9_data_stores/project/invite_group_to_project_spec.rb": 50.74090813900011,
"qa/specs/features/browser_ui/9_data_stores/project/project_owner_permissions_spec.rb": 44.86465373500005,
"qa/specs/features/browser_ui/9_data_stores/project/view_project_activity_spec.rb": 10.57122799900003,
"qa/specs/features/browser_ui/9_data_stores/user/follow_user_activity_spec.rb": 17.56207801699975,
"qa/specs/features/browser_ui/9_data_stores/user/parent_group_access_termination_spec.rb": 27.12611723300006,
"qa/specs/features/browser_ui/9_data_stores/user/user_inherited_access_spec.rb": 22.777019481000025,
"qa/specs/features/ee/api/10_govern/compliance_pipeline_spec.rb": 9.899398154999744,
"qa/specs/features/ee/api/10_govern/instance_audit_event_streaming_spec.rb": 13.41729055199994,
"qa/specs/features/ee/api/10_govern/user/minimal_access_user_spec.rb": 52.863767513999846,
"qa/specs/features/ee/api/2_plan/epics_milestone_dates_spec.rb": 24.66561908299991,
"qa/specs/features/ee/api/3_create/code_suggestions_spec.rb": 9.734530240000026,
"qa/specs/features/ee/browser_ui/10_govern/change_vulnerability_status_spec.rb": 78.78103058800025,
"qa/specs/features/ee/browser_ui/10_govern/create_merge_request_with_secure_spec.rb": 41.02528116299982,
"qa/specs/features/ee/browser_ui/10_govern/dismissed_vulnerabilities_in_security_widget_spec.rb": 75.75522501800015,
"qa/specs/features/ee/browser_ui/10_govern/export_vulnerability_report_spec.rb": 21.53629811100018,
"qa/specs/features/ee/browser_ui/10_govern/fix_vulnerability_workflow_spec.rb": 87.46915877299989,
"qa/specs/features/ee/browser_ui/10_govern/group/group_audit_event_streaming_spec.rb": 19.81644356400011,
"qa/specs/features/ee/browser_ui/10_govern/group/group_audit_logs_1_spec.rb": 94.59999707999987,
"qa/specs/features/ee/browser_ui/10_govern/group/restrict_by_ip_address_spec.rb": 244.94209053500003,
"qa/specs/features/ee/browser_ui/10_govern/instance/instance_audit_logs_spec.rb": 149.11489667600017,
"qa/specs/features/ee/browser_ui/10_govern/policies_list_spec.rb": 23.17459256999996,
"qa/specs/features/ee/browser_ui/10_govern/project/project_audit_logs_spec.rb": 142.88678456800017,
"qa/specs/features/ee/browser_ui/10_govern/project_security_dashboard_spec.rb": 32.639712987000166,
"qa/specs/features/ee/browser_ui/10_govern/scan_execution_policy_vulnerabilities_spec.rb": 87.48531807299992,
"qa/specs/features/ee/browser_ui/10_govern/scan_result_policy_vulnerabilities_spec.rb": 100.16423248799993,
"qa/specs/features/ee/browser_ui/10_govern/security_reports_spec.rb": 219.48511522400008,
"qa/specs/features/ee/browser_ui/10_govern/user/minimal_access_user_spec.rb": 10.48683282300044,
"qa/specs/features/ee/browser_ui/10_govern/vulnerability_management_spec.rb": 223.67916750899985,
"qa/specs/features/ee/browser_ui/10_govern/vulnerability_security_training_spec.rb": 90.11761338999986,
"qa/specs/features/ee/browser_ui/11_fulfillment/license/license_spec.rb": 6.416189227000359,
"qa/specs/features/ee/browser_ui/11_fulfillment/utilization/user_registration_billing_spec.rb": 25.087748015999978,
"qa/specs/features/ee/browser_ui/13_secure/enable_advanced_sast_spec.rb": 98.2445041179999,
"qa/specs/features/ee/browser_ui/13_secure/enable_scanning_from_configuration_spec.rb": 50.96775453700002,
"qa/specs/features/ee/browser_ui/13_secure/on_demand_dast_spec.rb": 95.48979141200016,
"qa/specs/features/ee/browser_ui/2_plan/analytics/contribution_analytics_spec.rb": 31.527241697000136,
"qa/specs/features/ee/browser_ui/2_plan/analytics/mr_analytics_spec.rb": 25.38604634799981,
"qa/specs/features/ee/browser_ui/2_plan/analytics/value_stream_analytics_spec.rb": 23.01250367600005,
"qa/specs/features/ee/browser_ui/2_plan/burndown_chart/burndown_chart_spec.rb": 10.041722617999994,
"qa/specs/features/ee/browser_ui/2_plan/custom_email/custom_email_spec.rb": 12.056798871999945,
"qa/specs/features/ee/browser_ui/2_plan/epic/epics_management_spec.rb": 134.200256243,
"qa/specs/features/ee/browser_ui/2_plan/epic/promote_issue_to_epic_spec.rb": 28.479518154999823,
"qa/specs/features/ee/browser_ui/2_plan/epic/roadmap_spec.rb": 10.634338394000224,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/create_group_wiki_page_spec.rb": 29.492960455999764,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/delete_group_wiki_page_spec.rb": 9.413657116000195,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/file_upload_group_wiki_page_spec.rb": 14.95102097400013,
"qa/specs/features/ee/browser_ui/2_plan/insights/default_insights_spec.rb": 24.128866205000122,
"qa/specs/features/ee/browser_ui/2_plan/issue/default_issue_template_spec.rb": 15.553754882000248,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configurable_issue_board_spec.rb": 11.234492763999697,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configure_issue_board_by_label_spec.rb": 18.468924121000327,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/create_group_issue_board_spec.rb": 17.354496787000244,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/group_issue_boards_spec.rb": 18.92236214400009,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/project_issue_boards_spec.rb": 39.630014759000005,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/read_only_board_configuration_spec.rb": 24.101322554000035,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/sum_of_issues_weights_spec.rb": 12.440506615000004,
"qa/specs/features/ee/browser_ui/2_plan/issues_analytics/issues_analytics_spec.rb": 21.37253022599998,
"qa/specs/features/ee/browser_ui/2_plan/issues_weight/issue_weight_visualization_spec.rb": 12.460709697999846,
"qa/specs/features/ee/browser_ui/2_plan/iterations/assign_group_iteration_spec.rb": 13.901354654999977,
"qa/specs/features/ee/browser_ui/2_plan/iterations/create_group_iteration_spec.rb": 33.006181079000044,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/four_assignees_spec.rb": 12.56144702499978,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/more_than_four_assignees_spec.rb": 32.16122686400013,
"qa/specs/features/ee/browser_ui/2_plan/scoped_labels/editing_scoped_labels_spec.rb": 13.211152739000227,
"qa/specs/features/ee/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb": 57.75658679099979,
"qa/specs/features/ee/browser_ui/3_create/merge_request/approval_rules_spec.rb": 96.94270416100017,
"qa/specs/features/ee/browser_ui/3_create/merge_request/default_merge_request_template_spec.rb": 33.12321659999998,
"qa/specs/features/ee/browser_ui/3_create/repository/assign_code_owners_spec.rb": 28.152587653999944,
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_spec.rb": 16.502650385000152,
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_with_protected_branch_and_squashed_commits_spec.rb": 25.722663802999932,
"qa/specs/features/ee/browser_ui/3_create/repository/file_locking_spec.rb": 200.75515444999974,
"qa/specs/features/ee/browser_ui/3_create/repository/group_file_template_spec.rb": 92.8232680189999,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_root_group_spec.rb": 81.4973934489999,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_subgroup_spec.rb": 141.39839299200003,
"qa/specs/features/ee/browser_ui/3_create/repository/project_templates_spec.rb": 72.26127357299993,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb": 29.386763341999995,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_ssh_with_key_spec.rb": 45.32656895399987,
"qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb": 244.24686534299963,
"qa/specs/features/ee/browser_ui/3_create/repository/restrict_push_protected_branch_spec.rb": 224.68295732599972,
"qa/specs/features/ee/browser_ui/3_create/web_ide/code_suggestions_in_web_ide_spec.rb": 70.40918833,
"qa/specs/features/ee/browser_ui/4_verify/multi-project_pipelines_spec.rb": 16.11996115000011,
"qa/specs/features/ee/browser_ui/4_verify/parent_child_pipelines_dependent_relationship_spec.rb": 46.88418564700032,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_subscription_with_group_owned_project_spec.rb": 36.758229692000214,
"qa/specs/features/ee/browser_ui/4_verify/pipelines_for_merged_results_and_merge_trains_spec.rb": 79.00419656800022,
"qa/specs/features/ee/browser_ui/4_verify/transient/merge_trains_transient_bug_spec.rb": 102.89802900000018,
"qa/specs/features/ee/browser_ui/8_monitor/incident_management/incident_quick_action_spec.rb": 14.259584643000153,
"qa/specs/features/ee/browser_ui/9_data_stores/group/prevent_forking_outside_group_spec.rb": 38.22584831700033,
"qa/specs/features/ee/browser_ui/9_data_stores/group/share_group_with_group_spec.rb": 24.902903569000046
"qa/specs/features/api/10_govern/group_access_token_spec.rb": 12.751388505000023,
"qa/specs/features/api/10_govern/project_access_token_spec.rb": 32.89926324599992,
"qa/specs/features/api/1_manage/rate_limits_spec.rb": 29.133628446999865,
"qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb": 13.026944022999942,
"qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb": 11.490352364000046,
"qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb": 6.069066840000005,
"qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb": 24.042001749000065,
"qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb": 24.645955939000032,
"qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb": 24.601507323999954,
"qa/specs/features/api/3_create/merge_request/view_merge_requests_spec.rb": 0.01137709700014966,
"qa/specs/features/api/3_create/repository/add_list_delete_branches_spec.rb": 6.916183379000017,
"qa/specs/features/api/3_create/repository/commit_to_templated_project_spec.rb": 14.377443990999836,
"qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb": 5.819943247000083,
"qa/specs/features/api/3_create/repository/files_spec.rb": 5.173241866999888,
"qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb": 4.929177706000246,
"qa/specs/features/api/3_create/repository/push_postreceive_idempotent_spec.rb": 9.340088966999929,
"qa/specs/features/api/3_create/repository/storage_size_spec.rb": 11.24625387900005,
"qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb": 4.532630837999932,
"qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb": 46.805881252999825,
"qa/specs/features/api/4_verify/cancel_pipeline_when_block_user_spec.rb": 8.743665168999996,
"qa/specs/features/api/4_verify/file_variable_spec.rb": 24.386426346999997,
"qa/specs/features/api/4_verify/job_downloads_artifacts_spec.rb": 16.590649373000133,
"qa/specs/features/api/9_data_stores/user_inherited_access_spec.rb": 71.2855975110001,
"qa/specs/features/api/9_data_stores/users_spec.rb": 0.4440915310001401,
"qa/specs/features/browser_ui/10_govern/group/group_access_token_spec.rb": 12.470258933999958,
"qa/specs/features/browser_ui/10_govern/login/2fa_recovery_spec.rb": 36.39139395800021,
"qa/specs/features/browser_ui/10_govern/login/2fa_ssh_recovery_spec.rb": 41.08415781800022,
"qa/specs/features/browser_ui/10_govern/login/log_in_spec.rb": 8.281860885000015,
"qa/specs/features/browser_ui/10_govern/login/log_in_with_2fa_spec.rb": 86.3487416539997,
"qa/specs/features/browser_ui/10_govern/login/login_via_oauth_and_oidc_with_gitlab_as_idp_spec.rb": 499.21237998800007,
"qa/specs/features/browser_ui/10_govern/login/register_spec.rb": 200.75919483400003,
"qa/specs/features/browser_ui/10_govern/project/project_access_token_spec.rb": 11.893035009999949,
"qa/specs/features/browser_ui/10_govern/user/impersonation_token_spec.rb": 29.84007964699981,
"qa/specs/features/browser_ui/10_govern/user/user_access_termination_spec.rb": 40.08391615200003,
"qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb": 21.119296476000272,
"qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb": 10.500143610000123,
"qa/specs/features/browser_ui/1_manage/integrations/jenkins/jenkins_build_status_spec.rb": 65.06015029199989,
"qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb": 17.374500398000237,
"qa/specs/features/browser_ui/2_plan/design_management/archive_design_content_spec.rb": 16.848764783999968,
"qa/specs/features/browser_ui/2_plan/design_management/modify_design_content_spec.rb": 15.021202979999998,
"qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb": 18.450994444000116,
"qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 17.622998689000042,
"qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb": 14.794653964999952,
"qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 41.69908395199991,
"qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb": 20.513510600000018,
"qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb": 18.107505599999968,
"qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 16.460536250999894,
"qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 15.750375492000103,
"qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb": 17.857268017000024,
"qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb": 13.975939827000275,
"qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb": 10.05644193199987,
"qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb": 67.57972961699988,
"qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb": 13.48369254499994,
"qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb": 20.18888547100005,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_creation_spec.rb": 71.71342247399991,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_manipulation_spec.rb": 37.97556997300012,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_directory_management_spec.rb": 13.762443853000036,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_file_upload_spec.rb": 29.93055537700002,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_list_spec.rb": 41.261750471999676,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_page_deletion_spec.rb": 38.11781402800011,
"qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb": 12.23750995599994,
"qa/specs/features/browser_ui/2_plan/transient/comment_on_discussion_spec.rb": 133.01163271799987,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_a_merge_spec.rb": 23.313274683000145,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_commit_spec.rb": 14.570134598999857,
"qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 40.756809996000015,
"qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb": 29.887464119000015,
"qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 20.59689329899993,
"qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb": 62.44206307600007,
"qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb": 17.132142855999973,
"qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb": 36.95017465400019,
"qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb": 22.052299305999895,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb": 49.84356878200015,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb": 33.78413305000004,
"qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 44.768547936000004,
"qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 63.99649399300006,
"qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb": 20.69057171999998,
"qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb": 25.212562218000016,
"qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 7.695580884999913,
"qa/specs/features/browser_ui/3_create/repository/file/create_file_via_web_spec.rb": 14.542094647999875,
"qa/specs/features/browser_ui/3_create/repository/file/delete_file_via_web_spec.rb": 16.766271739999866,
"qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb": 17.48617004699986,
"qa/specs/features/browser_ui/3_create/repository/file/file_with_unusual_name_spec.rb": 15.972208421000005,
"qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb": 32.49958670500018,
"qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb": 82.62286975099994,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb": 11.582223320000026,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb": 17.065084866000007,
"qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 12.421405112000002,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb": 50.74149139600013,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 56.192172966000044,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 43.43877877099999,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 12.306967781000026,
"qa/specs/features/browser_ui/3_create/repository/push_over_ssh_spec.rb": 22.123440633999962,
"qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 11.930819230999987,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_create_spec.rb": 14.466449840999985,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_delete_spec.rb": 13.404772087999845,
"qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 28.492135840000174,
"qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb": 40.902203811999925,
"qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb": 28.4565982299996,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb": 44.05035067500012,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb": 52.8828863990002,
"qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb": 32.79950173500015,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb": 10.373941559000059,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb": 9.907039061999967,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb": 14.810275069,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb": 18.689862891999837,
"qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb": 25.652545791000193,
"qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb": 22.147358860000168,
"qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb": 48.514524907999885,
"qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb": 16.087555477000024,
"qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb": 40.66486228200006,
"qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb": 62.267841525999984,
"qa/specs/features/browser_ui/3_create/web_ide/closing_web_ide_with_unsaved_changes_spec.rb": 17.63566870900013,
"qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb": 133.5650063160001,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/ci_catalog_sorting_spec.rb": 68.8284702850001,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/run_component_in_project_pipeline_spec.rb": 40.266767914999946,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/expose_job_artifacts_in_mr_spec.rb": 36.87972815300009,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/job_artifacts_access_keyword_spec.rb": 154.66687935100026,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/unlocking_job_artifacts_across_pipelines_spec.rb": 266.6912154249999,
"qa/specs/features/browser_ui/4_verify/ci_project_artifacts/user_can_bulk_delete_artifacts_spec.rb": 24.51923005200001,
"qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb": 32.529648410999926,
"qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb": 47.52443747999996,
"qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb": 33.065717594000034,
"qa/specs/features/browser_ui/4_verify/ci_variable/raw_variables_defined_in_yaml_spec.rb": 20.569682358000136,
"qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb": 49.354740160999995,
"qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb": 23.049214602999882,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb": 46.987583461999975,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb": 36.45673636599986,
"qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb": 58.82185278499992,
"qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb": 42.403081290000046,
"qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb": 149.85470755100005,
"qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb": 50.89883141200016,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb": 44.78671075300008,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb": 32.12375235100001,
"qa/specs/features/browser_ui/4_verify/pipeline/update_ci_file_with_pipeline_editor_spec.rb": 20.63341818699996,
"qa/specs/features/browser_ui/4_verify/runner/fleet_management/group_runner_counts_spec.rb": 10.157025357999828,
"qa/specs/features/browser_ui/4_verify/runner/fleet_management/group_runner_status_counts_spec.rb": 14.983221184000058,
"qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb": 10.514455229000077,
"qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb": 18.13358168099967,
"qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb": 35.76304038599983,
"qa/specs/features/browser_ui/5_package/infrastructure_registry/terraform_module_registry_spec.rb": 42.05816078700013,
"qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb": 31.686695261000068,
"qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb": 49.87885218400004,
"qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb": 34.36321494200001,
"qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb": 144.980374235,
"qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb": 360.92151482300005,
"qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb": 150.15723534799986,
"qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb": 136.13819214399996,
"qa/specs/features/browser_ui/5_package/package_registry/npm/npm_group_level_spec.rb": 192.52999090000003,
"qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb": 183.16166350100002,
"qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb": 50.5702839329997,
"qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 14.971665118000033,
"qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 110.33423504300026,
"qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 10.932732566999903,
"qa/specs/features/browser_ui/8_monitor/alert_management/alert_settings_create_new_alerts_spec.rb": 28.898326267000357,
"qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb": 32.88440417899983,
"qa/specs/features/browser_ui/8_monitor/alert_management/create_alert_using_authorization_key_spec.rb": 30.428553857000225,
"qa/specs/features/browser_ui/8_monitor/alert_management/recovery_alert_resolves_correct_alert_spec.rb": 40.65821105299983,
"qa/specs/features/browser_ui/9_data_stores/group/group_member_access_request_spec.rb": 69.0673923659997,
"qa/specs/features/browser_ui/9_data_stores/group/transfer_project_spec.rb": 22.13317842100014,
"qa/specs/features/browser_ui/9_data_stores/project/add_project_member_spec.rb": 14.512742338999942,
"qa/specs/features/browser_ui/9_data_stores/project/create_project_badge_spec.rb": 16.419005202000335,
"qa/specs/features/browser_ui/9_data_stores/project/create_project_spec.rb": 23.331705375999718,
"qa/specs/features/browser_ui/9_data_stores/project/dashboard_images_spec.rb": 16.30081602300004,
"qa/specs/features/browser_ui/9_data_stores/project/invite_group_to_project_spec.rb": 58.298898994999945,
"qa/specs/features/browser_ui/9_data_stores/project/project_owner_permissions_spec.rb": 51.102430441000024,
"qa/specs/features/browser_ui/9_data_stores/project/view_project_activity_spec.rb": 14.038119108000046,
"qa/specs/features/browser_ui/9_data_stores/user/follow_user_activity_spec.rb": 18.661561350000056,
"qa/specs/features/browser_ui/9_data_stores/user/parent_group_access_termination_spec.rb": 26.20017358599989,
"qa/specs/features/browser_ui/9_data_stores/user/user_inherited_access_spec.rb": 23.727169318999586,
"qa/specs/features/ee/api/10_govern/compliance_pipeline_spec.rb": 14.230563544999768,
"qa/specs/features/ee/api/10_govern/instance_audit_event_streaming_spec.rb": 14.22414006200006,
"qa/specs/features/ee/api/10_govern/user/minimal_access_user_spec.rb": 48.28243208599997,
"qa/specs/features/ee/api/2_plan/epics_milestone_dates_spec.rb": 24.16354903199999,
"qa/specs/features/ee/api/3_create/code_suggestions_spec.rb": 12.572733440000036,
"qa/specs/features/ee/browser_ui/10_govern/change_vulnerability_status_spec.rb": 68.61389828400024,
"qa/specs/features/ee/browser_ui/10_govern/create_merge_request_with_secure_spec.rb": 50.011880293999866,
"qa/specs/features/ee/browser_ui/10_govern/export_vulnerability_report_spec.rb": 19.24138949500002,
"qa/specs/features/ee/browser_ui/10_govern/fix_vulnerability_workflow_spec.rb": 85.92190458799996,
"qa/specs/features/ee/browser_ui/10_govern/group/group_audit_event_streaming_spec.rb": 25.921691762000137,
"qa/specs/features/ee/browser_ui/10_govern/group/group_audit_logs_1_spec.rb": 109.84475951299999,
"qa/specs/features/ee/browser_ui/10_govern/group/restrict_by_ip_address_spec.rb": 209.49803926899995,
"qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb": 127.72439350100012,
"qa/specs/features/ee/browser_ui/10_govern/instance/instance_audit_logs_spec.rb": 132.9845808350001,
"qa/specs/features/ee/browser_ui/10_govern/project/project_audit_logs_spec.rb": 124.79136410800015,
"qa/specs/features/ee/browser_ui/10_govern/project_security_dashboard_spec.rb": 32.404924101000006,
"qa/specs/features/ee/browser_ui/10_govern/scan_execution_policy_vulnerabilities_spec.rb": 90.34270750299993,
"qa/specs/features/ee/browser_ui/10_govern/scan_result_policy_vulnerabilities_spec.rb": 73.92341833399996,
"qa/specs/features/ee/browser_ui/10_govern/security_policies_spec.rb": 49.21381796800006,
"qa/specs/features/ee/browser_ui/10_govern/security_reports_spec.rb": 172.6255000389997,
"qa/specs/features/ee/browser_ui/10_govern/user/minimal_access_user_spec.rb": 20.043817780999916,
"qa/specs/features/ee/browser_ui/10_govern/vulnerability_management_spec.rb": 261.28464171700034,
"qa/specs/features/ee/browser_ui/11_fulfillment/license/license_spec.rb": 7.501284284999883,
"qa/specs/features/ee/browser_ui/11_fulfillment/utilization/user_registration_billing_spec.rb": 15.795260276999898,
"qa/specs/features/ee/browser_ui/13_secure/enable_advanced_sast_spec.rb": 74.9842102069997,
"qa/specs/features/ee/browser_ui/13_secure/enable_scanning_from_configuration_spec.rb": 50.713501423000025,
"qa/specs/features/ee/browser_ui/13_secure/on_demand_dast_spec.rb": 94.28897494800003,
"qa/specs/features/ee/browser_ui/2_plan/analytics/contribution_analytics_spec.rb": 28.131016550000027,
"qa/specs/features/ee/browser_ui/2_plan/analytics/mr_analytics_spec.rb": 43.30277173699983,
"qa/specs/features/ee/browser_ui/2_plan/analytics/value_stream_analytics_spec.rb": 33.68210793000026,
"qa/specs/features/ee/browser_ui/2_plan/burndown_chart/burndown_chart_spec.rb": 12.955446931000097,
"qa/specs/features/ee/browser_ui/2_plan/custom_email/custom_email_spec.rb": 12.208983900000021,
"qa/specs/features/ee/browser_ui/2_plan/epic/epics_management_spec.rb": 158.4215048420001,
"qa/specs/features/ee/browser_ui/2_plan/epic/promote_issue_to_epic_spec.rb": 33.01069102800011,
"qa/specs/features/ee/browser_ui/2_plan/epic/roadmap_spec.rb": 8.259949558000017,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/create_group_wiki_page_spec.rb": 25.861441487999855,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/delete_group_wiki_page_spec.rb": 10.608916837999914,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/file_upload_group_wiki_page_spec.rb": 28.67545881199976,
"qa/specs/features/ee/browser_ui/2_plan/insights/default_insights_spec.rb": 27.49661202900006,
"qa/specs/features/ee/browser_ui/2_plan/issue/default_issue_template_spec.rb": 17.888848881000058,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configurable_issue_board_spec.rb": 11.857604426999842,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configure_issue_board_by_label_spec.rb": 13.318511218000367,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/create_group_issue_board_spec.rb": 18.71652893999999,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/group_issue_boards_spec.rb": 19.94479222999962,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/project_issue_boards_spec.rb": 47.67097838999962,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/read_only_board_configuration_spec.rb": 20.099411936000024,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/sum_of_issues_weights_spec.rb": 12.640246061000084,
"qa/specs/features/ee/browser_ui/2_plan/issues_analytics/issues_analytics_spec.rb": 30.00575052099998,
"qa/specs/features/ee/browser_ui/2_plan/issues_weight/issue_weight_visualization_spec.rb": 12.941392978000067,
"qa/specs/features/ee/browser_ui/2_plan/iterations/assign_group_iteration_spec.rb": 16.032257479000236,
"qa/specs/features/ee/browser_ui/2_plan/iterations/create_group_iteration_spec.rb": 38.549332582000034,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/four_assignees_spec.rb": 14.220769124999833,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/more_than_four_assignees_spec.rb": 37.273302462999936,
"qa/specs/features/ee/browser_ui/2_plan/scoped_labels/editing_scoped_labels_spec.rb": 12.753879795000103,
"qa/specs/features/ee/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb": 49.9373758429997,
"qa/specs/features/ee/browser_ui/3_create/merge_request/approval_rules_spec.rb": 83.34420719600007,
"qa/specs/features/ee/browser_ui/3_create/merge_request/default_merge_request_template_spec.rb": 35.74193098000001,
"qa/specs/features/ee/browser_ui/3_create/repository/assign_code_owners_spec.rb": 31.49351207000018,
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_spec.rb": 20.113167300999976,
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_with_protected_branch_and_squashed_commits_spec.rb": 22.566818372999933,
"qa/specs/features/ee/browser_ui/3_create/repository/file_locking_spec.rb": 148.86333409300005,
"qa/specs/features/ee/browser_ui/3_create/repository/group_file_template_spec.rb": 96.78819865899993,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_root_group_spec.rb": 91.5671666899998,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_subgroup_spec.rb": 126.88817487399979,
"qa/specs/features/ee/browser_ui/3_create/repository/project_templates_spec.rb": 63.82498676599971,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb": 30.712793918999978,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_ssh_with_key_spec.rb": 37.274831491999976,
"qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb": 305.9995597960001,
"qa/specs/features/ee/browser_ui/3_create/repository/restrict_push_protected_branch_spec.rb": 266.46528617599984,
"qa/specs/features/ee/browser_ui/3_create/web_ide/code_suggestions_in_web_ide_spec.rb": 57.34347684400018,
"qa/specs/features/ee/browser_ui/4_verify/multi-project_pipelines_spec.rb": 32.21118624199994,
"qa/specs/features/ee/browser_ui/4_verify/parent_child_pipelines_dependent_relationship_spec.rb": 75.41926421400012,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_subscription_with_group_owned_project_spec.rb": 44.61706870600028,
"qa/specs/features/ee/browser_ui/4_verify/pipelines_for_merged_results_and_merge_trains_spec.rb": 73.5016436740002,
"qa/specs/features/ee/browser_ui/4_verify/transient/merge_trains_transient_bug_spec.rb": 93.12980337499994,
"qa/specs/features/ee/browser_ui/8_monitor/incident_management/incident_quick_action_spec.rb": 11.34621108899978,
"qa/specs/features/ee/browser_ui/9_data_stores/group/prevent_forking_outside_group_spec.rb": 32.30694202300003,
"qa/specs/features/ee/browser_ui/9_data_stores/group/share_group_with_group_spec.rb": 25.535255447000054
}

166
qa/qa/runtime/user_store.rb Normal file
View File

@ -0,0 +1,166 @@
# frozen_string_literal: true
module QA
module Runtime
# Helper class to create and store globally accessible test users
#
class UserStore
InvalidTokenError = Class.new(StandardError)
ExpiredAdminPasswordError = Class.new(StandardError)
# @return [String] default admin api token pre-seeded on ephemeral test environments
DEFAULT_ADMIN_API_TOKEN = "ypCa3Dzb23o5nvsixwPA" # gitleaks:allow
# @return [String] default username for admin user
DEFAULT_ADMIN_USERNAME = "root"
# @return [String] default password for admin user
DEFAULT_ADMIN_PASSWORD = "5iveL!fe"
class << self
# Global admin client
#
# @return [QA::Runtime::API::Client]
def admin_api_client
return @admin_api_client if @admin_api_client
info("Creating admin api client for api fabrications")
if Env.admin_personal_access_token
info("Admin api token variable is set, using it for default admin api fabrications")
@admin_api_client = API::Client
.new(personal_access_token: Env.admin_personal_access_token)
.tap { |client| validate_admin_client!(client) }
elsif default_admin_token_valid?
info("Admin api token variable is not set, using default - '#{DEFAULT_ADMIN_API_TOKEN}'")
@admin_api_client = API::Client.new(personal_access_token: DEFAULT_ADMIN_API_TOKEN)
else
@admin_api_client = create_admin_api_client(admin_user)
end
info("Admin token set up successfully")
@admin_api_client
end
alias_method :initialize_admin_api_client, :admin_api_client
# Global admin user
#
# @return [QA::Resource::User]
def admin_user
return @admin_user if @admin_user
@admin_user = Resource::User.init do |user|
user.username = if Env.admin_username
Env.admin_username
else
debug("Admin username variable not set, using default - '#{DEFAULT_ADMIN_USERNAME}'")
DEFAULT_ADMIN_USERNAME
end
user.password = if Env.admin_password
Env.admin_password
else
debug("Admin password variable not set, using default - '#{DEFAULT_ADMIN_PASSWORD}'")
DEFAULT_ADMIN_PASSWORD
end
end
if @admin_api_client && client_belongs_to_user?(@admin_api_client, @admin_user)
@admin_user.api_client = @admin_api_client
@admin_user.reload!
elsif @admin_api_client
warn(<<~WARN)
Configured global admin token does not belong to configured admin user
Please check values for GITLAB_QA_ADMIN_ACCESS_TOKEN, GITLAB_ADMIN_USERNAME and GITLAB_ADMIN_PASSWORD variables
WARN
end
@admin_user
end
alias_method :initialize_admin_user, :admin_user
private
delegate :debug, :info, :warn, :error, to: Logger
# Check if default admin token is present in environment and valid
#
# @return [Boolean]
def default_admin_token_valid?
debug("Validating presence of default admin api token in environment")
validate_admin_client!(API::Client.new(personal_access_token: DEFAULT_ADMIN_API_TOKEN))
debug("Default admin token is present in environment and is valid")
true
rescue InvalidTokenError
debug("Default admin token is not valid or present in environment, skipping...")
false
end
# Create admin access client and validate it
#
# @param [QA::Resource::User] user
# @return [QA::Runtime::API::Client]
def create_admin_api_client(user)
info("Creating admin token via ui")
admin_token = Flow::Login.while_signed_in(as: user) do
Resource::PersonalAccessToken.fabricate_via_browser_ui! { |pat| pat.user = user }.token
end
API::Client.new(:gitlab, personal_access_token: admin_token).tap do |client|
validate_admin_client!(client)
user.api_client = client
user.reload!
end
end
# Validate if client belongs to an admin user
#
# @param [QA::Runtime::API::Client] client
# @return [void]
def validate_admin_client!(client)
debug("Validating admin access token")
resp = fetch_user_details(client)
if resp.code == 403 && resp.body.include?("Your password expired")
raise ExpiredAdminPasswordError, "Admin password has expired and must be reset"
elsif !status_ok?(resp)
raise InvalidTokenError, "Admin token validation failed! Code: #{resp.code}, Err: '#{resp.body}'"
end
is_admin = Support::API.parse_body(resp)[:is_admin]
raise InvalidTokenError, "Admin token does not belong to admin user" unless is_admin
debug("Admin token is valid")
end
# Check if token belongs to specific user
#
# @param [QA::Runtime::API::Client] client
# @param [QA::Resource::User] user
# @return [Boolean]
def client_belongs_to_user?(client, user)
resp = fetch_user_details(client)
unless status_ok?(resp)
raise InvalidTokenError, "Token validation failed! Code: #{resp.code}, Err: '#{resp.body}'"
end
Support::API.parse_body(resp)[:username] == user.username
end
# Fetch user details of given api client
#
# @param [QA::Runtime::API::Client] client
# @return [RestClient::Response]
def fetch_user_details(client)
Support::API.get(API::Request.new(client, "/user").url)
end
# Validate 200 HTTP status code of response
#
# @param [RestClient::Response] resp
# @return [Boolean]
def status_ok?(resp)
resp.code == Support::API::HTTP_STATUS_OK
end
end
end
end
end

View File

@ -0,0 +1,189 @@
# frozen_string_literal: true
module QA
RSpec.describe Runtime::UserStore do
let(:default_admin_token) { "ypCa3Dzb23o5nvsixwPA" }
before do
allow(Runtime::Scenario).to receive(:send).with("gitlab_address").and_return("https://example.com")
allow(Runtime::Logger).to receive_messages({
debug: nil,
info: nil,
warn: nil,
error: nil
})
allow(Runtime::Env).to receive_messages({
admin_username: nil,
admin_password: nil,
admin_personal_access_token: nil
})
described_class.instance_variable_set(:@admin_api_client, nil)
described_class.instance_variable_set(:@admin_user, nil)
end
def mock_user_get(token:, code: 200, body: { is_admin: true, id: 1, username: "root" }.to_json)
allow(Support::API).to receive(:get).with("https://example.com/api/v4/user?private_token=#{token}").and_return(
instance_double(RestClient::Response, code: code, body: body)
)
end
describe "#admin_api_client" do
let(:admin_token) { nil }
before do
allow(Runtime::Env).to receive(:admin_personal_access_token).and_return(admin_token)
end
context "when admin token variable is set" do
let(:admin_token) { "admin-token" }
before do
mock_user_get(token: admin_token)
end
it "creates admin api client with configured token" do
expect(described_class.admin_api_client.personal_access_token).to eq(admin_token)
end
end
context "with valid default admin token and no token configured" do
before do
mock_user_get(token: default_admin_token)
end
it "creates admin api client with default admin token" do
expect(described_class.admin_api_client.personal_access_token).to eq(default_admin_token)
end
end
context "with invalid token set via environment variable" do
let(:admin_token) { "admin-token" }
before do
mock_user_get(token: admin_token, code: 401, body: "401 Unauthorized")
end
it "raises InvalidTokenError" do
expect { described_class.admin_api_client }.to raise_error(
described_class::InvalidTokenError, "Admin token validation failed! Code: 401, Err: '401 Unauthorized'"
)
end
end
context "with expired admin password" do
let(:admin_token) { "admin-token" }
before do
mock_user_get(token: admin_token, code: 403, body: "Your password expired")
end
it "raises ExpiredAdminPasswordError" do
expect { described_class.admin_api_client }.to raise_error(
described_class::ExpiredAdminPasswordError, "Admin password has expired and must be reset"
)
end
end
context "with token creation via UI" do
let(:admin_user) { Resource::User.new }
let(:pat) { Resource::PersonalAccessToken.init { |pat| pat.token = "test" } }
before do
allow(Resource::User).to receive(:init).and_yield(admin_user).and_return(admin_user)
allow(Resource::PersonalAccessToken).to receive(:fabricate_via_browser_ui!).and_yield(pat).and_return(pat)
allow(Flow::Login).to receive(:while_signed_in).with(as: admin_user).and_yield
allow(admin_user).to receive(:reload!)
mock_user_get(token: default_admin_token, code: 401)
mock_user_get(token: pat.token)
end
it "creates admin api client with token created from UI" do
expect(described_class.admin_api_client.personal_access_token).to eq(pat.token)
expect(admin_user.username).to eq("root")
expect(admin_user.password).to eq("5iveL!fe")
expect(admin_user).to have_received(:reload!)
end
end
end
describe "#admin_user" do
context "when admin client has not been initialized" do
context "with admin user variables set" do
let(:username) { "admin-username" }
let(:password) { "admin-password" }
before do
allow(Runtime::Env).to receive_messages({ admin_username: username, admin_password: password })
end
it "returns admin user with configured credentials" do
expect(described_class.admin_user.username).to eq(username)
expect(described_class.admin_user.password).to eq(password)
end
end
context "without admin user variables set" do
let(:username) { "root" }
let(:password) { "5iveL!fe" }
it "returns admin user with default credentials" do
expect(described_class.admin_user.username).to eq(username)
expect(described_class.admin_user.password).to eq(password)
end
end
end
context "when admin client has been initialized" do
let(:admin_user) { Resource::User.new }
let(:admin_client) { Runtime::API::Client.new(personal_access_token: default_admin_token) }
before do
allow(Resource::User).to receive(:init).and_yield(admin_user).and_return(admin_user)
allow(admin_user).to receive(:reload!)
described_class.instance_variable_set(:@admin_api_client, admin_client)
end
context "with valid admin client belonging to user" do
before do
mock_user_get(token: default_admin_token)
end
it "sets api client on admin user and reloads it" do
expect(described_class.admin_user.instance_variable_get(:@api_client)).to eq(admin_client)
expect(admin_user).to have_received(:reload!)
end
end
context "with valid admin client not belonging to user" do
before do
mock_user_get(token: default_admin_token, body: { username: "test" }.to_json)
end
it "prints warning message" do
described_class.initialize_admin_user
expect(Runtime::Logger).to have_received(:warn).with(<<~WARN)
Configured global admin token does not belong to configured admin user
Please check values for GITLAB_QA_ADMIN_ACCESS_TOKEN, GITLAB_ADMIN_USERNAME and GITLAB_ADMIN_PASSWORD variables
WARN
end
end
context "with invalid admin client" do
before do
mock_user_get(token: default_admin_token, code: 403, body: "Unauthorized")
end
it "raises invalid token error" do
expect { described_class.admin_user }.to raise_error(
described_class::InvalidTokenError, "Token validation failed! Code: 403, Err: 'Unauthorized'"
)
end
end
end
end
end
end

View File

@ -10,14 +10,13 @@ require 'json_schemer'
require 'delegate'
require_relative './cli/helpers'
require_relative './cli/flows/usage_viewer'
require_relative './cli/flows/metric_definer'
require_relative './cli/flows/event_definer'
require_relative './cli/flows/flow_advisor'
require_relative './cli/flows/metric_definer'
require_relative './cli/flows/usage_viewer'
require_relative './cli/global_state'
require_relative './cli/metric'
require_relative './cli/event'
require_relative './cli/text'
class Cli
include ::InternalEventsCli::Helpers
@ -29,8 +28,8 @@ class Cli
end
def run
cli.say InternalEventsCli::Text::FEEDBACK_NOTICE
cli.say InternalEventsCli::Text::CLI_INSTRUCTIONS
cli.say feedback_notice
cli.say instructions
task = cli.select("What would you like to do?", **select_opts) do |menu|
menu.enum "."
@ -54,6 +53,23 @@ class Cli
InternalEventsCli::Flows::FlowAdvisor.new(cli).run
end
end
def instructions
cli.say <<~TEXT.freeze
#{format_info('INSTRUCTIONS:')}
To start tracking usage of a feature...
1) Define event (using CLI)
2) Trigger event (from code)
3) Define metric (using CLI)
4) View data in Tableau (after merge & deploy)
This CLI will help you create the correct defintion files, then provide code examples for instrumentation and testing.
Learn more: https://docs.gitlab.com/ee/development/internal_analytics/#fundamental-concepts
TEXT
end
end
class GitlabPrompt < SimpleDelegator

View File

@ -1,12 +1,14 @@
# frozen_string_literal: true
require_relative '../helpers'
require_relative '../text/event_definer'
# Entrypoint for flow to create an event definition file
module InternalEventsCli
module Flows
class EventDefiner
include Helpers
include Text::EventDefiner
SCHEMA = ::JSONSchemer.schema(Pathname('config/events/schema.json'))
STEPS = [
@ -20,23 +22,6 @@ module InternalEventsCli
'Save files'
].freeze
IDENTIFIER_OPTIONS = {
%w[project namespace user] =>
'Use case: For project-level user actions (ex - issue_assignee_changed) [MOST COMMON]',
%w[namespace user] =>
'Use case: For namespace-level user actions (ex - epic_assigned_to_milestone)',
%w[user] =>
'Use case: For user-only actions (ex - admin_impersonated_user)',
%w[project namespace] =>
'Use case: For project-level events without user interaction (ex - service_desk_request_received)',
%w[namespace] =>
'Use case: For namespace-level events without user interaction (ex - stale_runners_cleaned_up)',
%w[feature_enabled_by_namespace_ids user] =>
'Use case: For user actions attributable to multiple namespaces (ex - Code-Suggestions / Duo Pro)',
%w[] =>
'Use case: For instance-level events without user interaction [LEAST COMMON]'
}.freeze
IDENTIFIER_FORMATTING_BUFFER = "[#{IDENTIFIER_OPTIONS.keys.map { |k| k.join(', ') }.max_by(&:length)}]".length
attr_reader :cli, :event
@ -64,18 +49,18 @@ module InternalEventsCli
def prompt_for_description
new_page!(1, 7, STEPS)
cli.say Text::EVENT_DESCRIPTION_INTRO
cli.say DESCRIPTION_INTRO
event.description = cli.ask("Describe what the event tracks: #{input_required_text}", **input_opts) do |q|
q.required true
q.modify :trim
q.messages[:required?] = Text::EVENT_DESCRIPTION_HELP
q.messages[:required?] = DESCRIPTION_HELP
end
end
def prompt_for_action
new_page!(2, 7, STEPS)
cli.say Text::EVENT_ACTION_INTRO
cli.say ACTION_INTRO
event.action = cli.ask("Define the event name: #{input_required_text}", **input_opts) do |q|
q.required true
@ -84,7 +69,7 @@ module InternalEventsCli
q.messages[:valid?] = format_warning(
"Invalid event name. Only lowercase/numbers/underscores allowed. " \
"Ensure %{value} is not an existing event.")
q.messages[:required?] = Text::EVENT_ACTION_HELP
q.messages[:required?] = ACTION_HELP
end
end
@ -99,7 +84,7 @@ module InternalEventsCli
end
def prompt_for_identifiers
cli.say Text::EVENT_IDENTIFIERS_INTRO % event.action
cli.say IDENTIFIERS_INTRO % event.action
identifiers = prompt_for_array_selection(
'Which identifiers are available when the event occurs?',
@ -118,7 +103,7 @@ module InternalEventsCli
end
def prompt_for_additional_properties
cli.say Text::ADDITIONAL_PROPERTIES_INTRO
cli.say ADDITIONAL_PROPERTIES_INTRO
available_props = [:label, :property, :value, :add_extra_prop]
@ -191,7 +176,7 @@ module InternalEventsCli
q.required true
q.validate ->(input) { input =~ NAME_REGEX && primary_props.none?(input) }
q.modify :trim
q.messages[:required?] = Text::ADDITIONAL_PROPERTIES_ADD_MORE_HELP
q.messages[:required?] = ADDITIONAL_PROPERTIES_ADD_MORE_HELP
q.messages[:valid?] = format_warning(
"Invalid property name. Only lowercase/numbers/underscores allowed. " \
"Ensure %{value} is not one of `property, label, value`.")
@ -280,7 +265,7 @@ module InternalEventsCli
when :view_usage
UsageViewer.new(cli, event.file_path, event).run
when :exit
cli.say Text::FEEDBACK_NOTICE
cli.say feedback_notice
end
end
end

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative '../helpers'
require_relative '../text/flow_advisor'
# Entrypoint for help flow, which directs the user to the
# correct flow or documentation based on their goal
@ -8,6 +9,7 @@ module InternalEventsCli
module Flows
class FlowAdvisor
include Helpers
include Text::FlowAdvisor
attr_reader :cli
@ -36,7 +38,7 @@ module InternalEventsCli
new_page!
cli.say format_info("Excellent! Let's check that this tool will fit your needs.\n")
cli.say Text::EVENT_TRACKING_EXAMPLES
cli.say EVENT_TRACKING_EXAMPLES
cli.yes?(
'Can usage for the feature be measured with a count of specific user actions or events? ' \
@ -49,7 +51,7 @@ module InternalEventsCli
new_page!
cli.say format_info("Super! Let's figure out if the event is already tracked & usable.\n")
cli.say Text::EVENT_EXISTENCE_CHECK_INSTRUCTIONS
cli.say EVENT_EXISTENCE_CHECK_INSTRUCTIONS
cli.yes?('Is the event already tracked?', **yes_no_opts)
end
@ -58,8 +60,8 @@ module InternalEventsCli
new_page!
cli.error("Oh no! This probably isn't the tool you need!\n")
cli.say Text::ALTERNATE_RESOURCES_NOTICE
cli.say Text::FEEDBACK_NOTICE
cli.say ALTERNATE_RESOURCES_NOTICE
cli.say feedback_notice
end
def proceed_to_metric_definition
@ -84,7 +86,7 @@ module InternalEventsCli
def not_ready_error(description)
cli.say "\nNo problem! When you're ready, run the CLI & select '#{description}'\n"
cli.say Text::FEEDBACK_NOTICE
cli.say feedback_notice
end
end
end

View File

@ -1,13 +1,14 @@
# frozen_string_literal: true
require_relative '../helpers'
require_relative '../text'
require_relative '../text/metric_definer'
# Entrypoint for flow to create an metric definition file
module InternalEventsCli
module Flows
class MetricDefiner
include Helpers
include Text::MetricDefiner
SCHEMA = ::JSONSchemer.schema(Pathname('config/metrics/schema/base.json'))
STEPS = [
@ -23,21 +24,6 @@ module InternalEventsCli
'Save files'
].freeze
NAME_REQUIREMENT_REASONS = {
filters: {
text: 'Metrics using filters are too complex for default naming.',
help: Text::METRIC_NAME_FILTER_HELP
},
length: {
text: 'The default filename will be too long.',
help: Text::METRIC_NAME_LENGTH_HELP
},
conflict: {
text: 'The default key path is already in use.',
help: Text::METRIC_NAME_CONFLICT_HELP
}
}.freeze
attr_reader :cli
def initialize(cli, starting_event = nil)
@ -123,8 +109,8 @@ module InternalEventsCli
**filter_opts(header_size: 7)
)
when :database_metric
cli.error Text::DATABASE_METRIC_NOTICE
cli.say Text::FEEDBACK_NOTICE
cli.error DATABASE_METRIC_NOTICE
cli.say feedback_notice
end
end
@ -132,8 +118,8 @@ module InternalEventsCli
eligible_metrics = get_metric_options(selected_events)
if eligible_metrics.all? { |metric| metric[:disabled] }
cli.error Text::ALL_METRICS_EXIST_NOTICE
cli.say Text::FEEDBACK_NOTICE
cli.error ALL_METRICS_EXIST_NOTICE
cli.say feedback_notice
return
end
@ -206,7 +192,7 @@ module InternalEventsCli
if idx == 0 || separate_page_per_metric
new_page!(4, 9, STEPS)
cli.say Text::METRIC_DESCRIPTION_INTRO
cli.say DESCRIPTION_INTRO
cli.say selected_event_descriptions.join
end
@ -359,7 +345,7 @@ module InternalEventsCli
when :view_usage
UsageViewer.new(cli, @selected_event_paths.first, selected_events.first).run
when :exit
cli.say Text::FEEDBACK_NOTICE
cli.say feedback_notice
end
end
@ -487,7 +473,7 @@ module InternalEventsCli
prompt_for_text(" Finish the description: #{description_start}", default, multiline: true) do |q|
q.required true
q.modify :trim
q.messages[:required?] = Text::METRIC_DESCRIPTION_HELP
q.messages[:required?] = DESCRIPTION_HELP
end
end
@ -515,7 +501,7 @@ module InternalEventsCli
prompt_for_text(' Replace with: ', default, multiline: true) do |q|
q.required true
q.messages[:required?] = name_reason[:help] % help_tokens
q.messages[:valid?] = Text::METRIC_NAME_ERROR % help_tokens
q.messages[:valid?] = NAME_ERROR % help_tokens
q.validate ->(input) do
input.length <= max_length &&
input.match?(NAME_REGEX) &&

View File

@ -96,7 +96,7 @@ module InternalEventsCli
when :other_event
self.class.new(cli).run
when :exit
cli.say(Text::FEEDBACK_NOTICE)
cli.say(feedback_notice)
end
end

View File

@ -26,5 +26,18 @@ module InternalEventsCli
cli.say TTY::Cursor.move_to(0, 0)
cli.say "#{progress_bar(page, total, steps)}\n" if page && total
end
def feedback_notice
format_heading <<~TEXT.chomp
Thanks for using the Internal Events CLI!
Please reach out with any feedback!
About Internal Events: https://gitlab.com/gitlab-org/analytics-section/analytics-instrumentation/internal/-/issues/687
About CLI: https://gitlab.com/gitlab-org/gitlab/-/issues/434038
In Slack: #g_analyze_analytics_instrumentation
Let us know that you used the CLI! React with 👍 on the feedback issue or post in Slack!
TEXT
end
end
end

View File

@ -1,274 +0,0 @@
# frozen_string_literal: true
# Blocks of text rendered in CLI
module InternalEventsCli
module Text
extend Helpers
CLI_INSTRUCTIONS = <<~TEXT.freeze
#{format_info('INSTRUCTIONS:')}
To start tracking usage of a feature...
1) Define event (using CLI)
2) Trigger event (from code)
3) Define metric (using CLI)
4) View data in Tableau (after merge & deploy)
This CLI will help you create the correct defintion files, then provide code examples for instrumentation and testing.
Learn more: https://docs.gitlab.com/ee/development/internal_analytics/#fundamental-concepts
TEXT
FEEDBACK_NOTICE = format_heading <<~TEXT.chomp
Thanks for using the Internal Events CLI!
Please reach out with any feedback!
About Internal Events: https://gitlab.com/gitlab-org/analytics-section/analytics-instrumentation/internal/-/issues/687
About CLI: https://gitlab.com/gitlab-org/gitlab/-/issues/434038
In Slack: #g_analyze_analytics_instrumentation
Let us know that you used the CLI! React with 👍 on the feedback issue or post in Slack!
TEXT
ALTERNATE_RESOURCES_NOTICE = <<~TEXT.freeze
Other resources:
#{format_warning('Tracking GitLab feature usage from database info:')}
https://docs.gitlab.com/ee/development/internal_analytics/metrics/metrics_instrumentation.html#database-metrics
#{format_warning('Migrating existing metrics to use Internal Events:')}
https://docs.gitlab.com/ee/development/internal_analytics/internal_event_instrumentation/migration.html
#{format_warning('Remove an existing metric:')}
https://docs.gitlab.com/ee/development/internal_analytics/metrics/metrics_lifecycle.html
#{format_warning('Finding existing usage data for GitLab features:')}
https://metrics.gitlab.com/ (Customize Table > Snowflake query)
https://10az.online.tableau.com/#/site/gitlab/views/SnowplowEventExplorationLast30Days/SnowplowEventExplorationLast30D
https://10az.online.tableau.com/#/site/gitlab/views/PDServicePingExplorationDashboard/MetricsExploration
#{format_warning('Customer wants usage data for their own GitLab instance:')}
https://docs.gitlab.com/ee/user/analytics/
#{format_warning('Customer wants usage data for their own products:')}
https://docs.gitlab.com/ee/operations/product_analytics/
TEXT
EVENT_TRACKING_EXAMPLES = <<~TEXT
Product usage can be tracked in several ways.
By tracking events: ex) a user changes the assignee on an issue
ex) a user uploads a CI template
ex) a service desk request is received
ex) all stale runners are cleaned up
ex) a user copies code to the clipboard from markdown
ex) a user uploads an issue template OR a user uploads an MR template
From database data: ex) track whether each gitlab instance allows signups
ex) query how many projects are on each gitlab instance
TEXT
EVENT_EXISTENCE_CHECK_INSTRUCTIONS = <<~TEXT.freeze
To determine what to do next, let's figure out if the event is already tracked & usable.
If you're unsure whether an event exists, you can check the existing defintions.
#{format_info('FROM GDK')}: Check `config/events/` or `ee/config/events`
#{format_info('FROM BROWSER')}: Check https://metrics.gitlab.com/snowplow
Find one? Create a new metric for the event.
Otherwise? Create a new event.
If you find a relevant event that does not have the property `internal_events: true`, it can be migrated to
Internal Events. See https://docs.gitlab.com/ee/development/internal_analytics/internal_event_instrumentation/migration.html
TEXT
EVENT_DESCRIPTION_INTRO = <<~TEXT.freeze
#{format_info('EVENT DESCRIPTION')}
Include what the event is supposed to track, where, and when.
The description field helps others find & reuse this event. This will be used by Engineering, Product, Data team, Support -- and also GitLab customers directly. Be specific and explicit.
ex - Debian package published to the registry using a deploy token
ex - Issue confidentiality was changed
TEXT
EVENT_DESCRIPTION_HELP = <<~TEXT.freeze
#{format_warning('Required. 10+ words likely, but length may vary.')}
#{format_info('GOOD EXAMPLES:')}
- Pipeline is created with a CI Template file included in its configuration
- Quick action `/assign @user1` used to assign a single individual to an issuable
- Quick action `/target_branch` used on a Merge Request
- Quick actions `/unlabel` or `/remove_label` used to remove one or more specific labels
- User edits file using the single file editor
- User edits file using the Web IDE
- User removed issue link between issue and incident
- Debian package published to the registry using a deploy token
#{format_info('GUT CHECK:')}
For your description...
1. Would two different engineers likely instrument the event from the same code locations?
2. Would a new GitLab user find where the event is triggered in the product?
3. Would a GitLab customer understand what the description says?
TEXT
EVENT_ACTION_INTRO = <<~TEXT.freeze
#{format_info('EVENT NAME')}
The event name is a unique identifier used from both a) app code and b) metric definitions.
The name should concisely communicate the same information as the event description.
ex - change_time_estimate_on_issue
ex - push_package_to_repository
ex - publish_go_module_to_the_registry_from_pipeline
ex - admin_user_comments_on_issue_while_impersonating_blocked_user
#{format_info('EXPECTED FORMAT:')} #{format_selection('<action>_<target_of_action>_<where/when>')}
ex) click_save_button_in_issue_description_within_15s_of_page_load
- ACTION: click
- TARGET: save button
- WHERE: in issue description
- WHEN: within 15s of page load
TEXT
EVENT_ACTION_HELP = <<~TEXT.freeze
#{format_warning('Required. Must be globally unique. Must use only letters/numbers/underscores.')}
#{format_info('FAQs:')}
- Q: Present tense or past tense?
A: Prefer present tense! But it's up to you.
- Q: Other event names have prefixes like `i_` or the `g_group_name`. Why?
A: Those are leftovers from legacy naming schemes. Changing the names of old events/metrics can break dashboards, so stability is better than uniformity.
TEXT
EVENT_IDENTIFIERS_INTRO = <<~TEXT.freeze
#{format_info('KEY IDENTIFIERS')}
Indicates the attributes recorded when the event occurs. Generally, we want to include every identifier available to us when the event is triggered.
#{format_info('BACKEND')}: Attributes must be specified when the event is triggered
ex) User, project, and namespace are the identifiers available for backend instrumentation:
track_internal_event(
'%s',
user: user,
project: project,
namespace: project.namespace
)
#{format_info('FRONTEND')}: Attributes are automatically included from the URL
ex) When a user takes an action on the MR list page, the URL is https://gitlab.com/gitlab-org/gitlab/-/merge_requests
Because this URL is for a project, we know that all of user/project/namespace are available for the event
#{format_info('NOTE')}: If you're planning to instrument a unique-by-user metric, you should still include project & namespace when possible. This is especially helpful in the data warehouse, where namespace and project can make events relevant for CSM use-cases.
TEXT
ADDITIONAL_PROPERTIES_INTRO = <<~TEXT.freeze
#{format_info('ADDITIONAL PROPERTIES')}
Describe any related attributes or information which should be tracked when the event occurs. This enables extra capabilities:
- Service Ping: define metrics filtered to a specific subset of events (built-in properties only)
- Snowflake: view/sort/group individual events from GitLab.com
BUILT-IN PROPERTIES (recommended)
For the best performance and flexibility, provide event context using:
property (string), label (string), value (numeric)
These attribute names correspond to repurposed fields in Snowflake. They have no special meaning other than data type.
ex) To add a metric like "Monthly count of unique users who changed an MR status to closed" using a 'change_merge_request_status' event, define an additional property like:
Attribute: label (string)
Description: Status of merge request after update (one of opened, merged, closed)
CUSTOM PROPERTIES (as-needed)
When the built-in properties are insufficient, properties of any name can be provided.
This option becomes available after both property and label are defined, or after value is defined.
TEXT
ADDITIONAL_PROPERTIES_ADD_MORE_HELP = <<~TEXT.freeze
#{format_warning('Required. Must be unique within the event context. Must use only letters/numbers/underscores.')}
#{format_info('It should not be named any of the following:')}
- property#{' '}
- label
- value
TEXT
DATABASE_METRIC_NOTICE = <<~TEXT
For right now, this script can only define metrics for internal events.
For more info on instrumenting database-backed metrics, see https://docs.gitlab.com/ee/development/internal_analytics/metrics/metrics_instrumentation.html
TEXT
ALL_METRICS_EXIST_NOTICE = <<~TEXT
Looks like the potential metrics for this event either already exist or are unsupported.
Check out https://metrics.gitlab.com/ for improved event/metric search capabilities.
TEXT
METRIC_DESCRIPTION_INTRO = <<~TEXT.freeze
#{format_info('METRIC DESCRIPTION')}
Describes which occurrences of an event are tracked in the metric and how they're grouped.
The description field is critical for helping others find & reuse this event. This will be used by Engineering, Product, Data team, Support -- and also GitLab customers directly. Be specific and explicit.
#{format_info('GOOD EXAMPLES:')}
- Total count of analytics dashboard list views
- Weekly count of unique users who viewed the analytics dashboard list
- Monthly count of unique projects where the analytics dashboard list was viewed
- Total count of issue updates
#{format_info('SELECTED EVENT(S):')}
TEXT
METRIC_DESCRIPTION_HELP = <<~TEXT.chomp.freeze
#{format_warning('Required. 10+ words likely, but length may vary.')}
An event description can often be rearranged to work as a metric description.
ex) Event description: A merge request was created
Metric description: Total count of merge requests created
Metric description: Weekly count of unqiue users who created merge requests
Look at the event descriptions above to get ideas!
TEXT
METRIC_NAME_FILTER_HELP = <<~TEXT.freeze
#{format_warning('Required. Max %{count} characters. Only lowercase/numbers/underscores allowed.')}
Metrics with filters must manually define this portion of their key path.
Auto-generated key paths for metrics filters results in long & confusing naming. By defining them manually, clarity and discoverability should be better.
TEXT
METRIC_NAME_CONFLICT_HELP = <<~TEXT.freeze
#{format_warning('Required. Max %{count} characters. Only lowercase/numbers/underscores allowed.')}
Conflict! A metric with the same name already exists: %{name}
TEXT
METRIC_NAME_LENGTH_HELP = <<~TEXT.freeze
#{format_warning('Required. Max %{count} characters. Only lowercase/numbers/underscores allowed.')}
Filenames cannot exceed 100 characters. The key path (ID) is not restricted, but keeping them aligned is recommended.
If needed, you can modify the key path and filename further after saving.
TEXT
METRIC_NAME_ERROR = <<~TEXT.freeze
#{format_warning('Input is invalid. Max %{count} characters. Only lowercase/numbers/underscores allowed. Ensure this key path (ID) is not already in use.')}
TEXT
end
end

View File

@ -0,0 +1,144 @@
# frozen_string_literal: true
module InternalEventsCli
module Text
module EventDefiner
extend Helpers
DESCRIPTION_INTRO = <<~TEXT.freeze
#{format_info('EVENT DESCRIPTION')}
Include what the event is supposed to track, where, and when.
The description field helps others find & reuse this event. This will be used by Engineering, Product, Data team, Support -- and also GitLab customers directly. Be specific and explicit.
ex - Debian package published to the registry using a deploy token
ex - Issue confidentiality was changed
TEXT
DESCRIPTION_HELP = <<~TEXT.freeze
#{format_warning('Required. 10+ words likely, but length may vary.')}
#{format_info('GOOD EXAMPLES:')}
- Pipeline is created with a CI Template file included in its configuration
- Quick action `/assign @user1` used to assign a single individual to an issuable
- Quick action `/target_branch` used on a Merge Request
- Quick actions `/unlabel` or `/remove_label` used to remove one or more specific labels
- User edits file using the single file editor
- User edits file using the Web IDE
- User removed issue link between issue and incident
- Debian package published to the registry using a deploy token
#{format_info('GUT CHECK:')}
For your description...
1. Would two different engineers likely instrument the event from the same code locations?
2. Would a new GitLab user find where the event is triggered in the product?
3. Would a GitLab customer understand what the description says?
TEXT
ACTION_INTRO = <<~TEXT.freeze
#{format_info('EVENT NAME')}
The event name is a unique identifier used from both a) app code and b) metric definitions.
The name should concisely communicate the same information as the event description.
ex - change_time_estimate_on_issue
ex - push_package_to_repository
ex - publish_go_module_to_the_registry_from_pipeline
ex - admin_user_comments_on_issue_while_impersonating_blocked_user
#{format_info('EXPECTED FORMAT:')} #{format_selection('<action>_<target_of_action>_<where/when>')}
ex) click_save_button_in_issue_description_within_15s_of_page_load
- ACTION: click
- TARGET: save button
- WHERE: in issue description
- WHEN: within 15s of page load
TEXT
ACTION_HELP = <<~TEXT.freeze
#{format_warning('Required. Must be globally unique. Must use only letters/numbers/underscores.')}
#{format_info('FAQs:')}
- Q: Present tense or past tense?
A: Prefer present tense! But it's up to you.
- Q: Other event names have prefixes like `i_` or the `g_group_name`. Why?
A: Those are leftovers from legacy naming schemes. Changing the names of old events/metrics can break dashboards, so stability is better than uniformity.
TEXT
IDENTIFIERS_INTRO = <<~TEXT.freeze
#{format_info('KEY IDENTIFIERS')}
Indicates the attributes recorded when the event occurs. Generally, we want to include every identifier available to us when the event is triggered.
#{format_info('BACKEND')}: Attributes must be specified when the event is triggered
ex) User, project, and namespace are the identifiers available for backend instrumentation:
track_internal_event(
'%s',
user: user,
project: project,
namespace: project.namespace
)
#{format_info('FRONTEND')}: Attributes are automatically included from the URL
ex) When a user takes an action on the MR list page, the URL is https://gitlab.com/gitlab-org/gitlab/-/merge_requests
Because this URL is for a project, we know that all of user/project/namespace are available for the event
#{format_info('NOTE')}: If you're planning to instrument a unique-by-user metric, you should still include project & namespace when possible. This is especially helpful in the data warehouse, where namespace and project can make events relevant for CSM use-cases.
TEXT
IDENTIFIER_OPTIONS = {
%w[project namespace user] =>
'Use case: For project-level user actions (ex - issue_assignee_changed) [MOST COMMON]',
%w[namespace user] =>
'Use case: For namespace-level user actions (ex - epic_assigned_to_milestone)',
%w[user] =>
'Use case: For user-only actions (ex - admin_impersonated_user)',
%w[project namespace] =>
'Use case: For project-level events without user interaction (ex - service_desk_request_received)',
%w[namespace] =>
'Use case: For namespace-level events without user interaction (ex - stale_runners_cleaned_up)',
%w[feature_enabled_by_namespace_ids user] =>
'Use case: For user actions attributable to multiple namespaces (ex - Code-Suggestions / Duo Pro)',
%w[] =>
'Use case: For instance-level events without user interaction [LEAST COMMON]'
}.freeze
ADDITIONAL_PROPERTIES_INTRO = <<~TEXT.freeze
#{format_info('ADDITIONAL PROPERTIES')}
Describe any related attributes or information which should be tracked when the event occurs. This enables extra capabilities:
- Service Ping: define metrics filtered to a specific subset of events (built-in properties only)
- Snowflake: view/sort/group individual events from GitLab.com
BUILT-IN PROPERTIES (recommended)
For the best performance and flexibility, provide event context using:
property (string), label (string), value (numeric)
These attribute names correspond to repurposed fields in Snowflake. They have no special meaning other than data type.
ex) To add a metric like "Monthly count of unique users who changed an MR status to closed" using a 'change_merge_request_status' event, define an additional property like:
Attribute: label (string)
Description: Status of merge request after update (one of opened, merged, closed)
CUSTOM PROPERTIES (as-needed)
When the built-in properties are insufficient, properties of any name can be provided.
This option becomes available after both property and label are defined, or after value is defined.
TEXT
ADDITIONAL_PROPERTIES_ADD_MORE_HELP = <<~TEXT.freeze
#{format_warning('Required. Must be unique within the event context. Must use only letters/numbers/underscores.')}
#{format_info('It should not be named any of the following:')}
- property#{' '}
- label
- value
TEXT
end
end
end

View File

@ -0,0 +1,65 @@
# frozen_string_literal: true
# Blocks of text rendered in CLI
module InternalEventsCli
module Text
module FlowAdvisor
extend Helpers
ALTERNATE_RESOURCES_NOTICE = <<~TEXT.freeze
Other resources:
#{format_warning('Tracking GitLab feature usage from database info:')}
https://docs.gitlab.com/ee/development/internal_analytics/metrics/metrics_instrumentation.html#database-metrics
#{format_warning('Migrating existing metrics to use Internal Events:')}
https://docs.gitlab.com/ee/development/internal_analytics/internal_event_instrumentation/migration.html
#{format_warning('Remove an existing metric:')}
https://docs.gitlab.com/ee/development/internal_analytics/metrics/metrics_lifecycle.html
#{format_warning('Finding existing usage data for GitLab features:')}
https://metrics.gitlab.com/ (Customize Table > Snowflake query)
https://10az.online.tableau.com/#/site/gitlab/views/SnowplowEventExplorationLast30Days/SnowplowEventExplorationLast30D
https://10az.online.tableau.com/#/site/gitlab/views/PDServicePingExplorationDashboard/MetricsExploration
#{format_warning('Customer wants usage data for their own GitLab instance:')}
https://docs.gitlab.com/ee/user/analytics/
#{format_warning('Customer wants usage data for their own products:')}
https://docs.gitlab.com/ee/operations/product_analytics/
TEXT
EVENT_TRACKING_EXAMPLES = <<~TEXT
Product usage can be tracked in several ways.
By tracking events: ex) a user changes the assignee on an issue
ex) a user uploads a CI template
ex) a service desk request is received
ex) all stale runners are cleaned up
ex) a user copies code to the clipboard from markdown
ex) a user uploads an issue template OR a user uploads an MR template
From database data: ex) track whether each gitlab instance allows signups
ex) query how many projects are on each gitlab instance
TEXT
EVENT_EXISTENCE_CHECK_INSTRUCTIONS = <<~TEXT.freeze
To determine what to do next, let's figure out if the event is already tracked & usable.
If you're unsure whether an event exists, you can check the existing defintions.
#{format_info('FROM GDK')}: Check `config/events/` or `ee/config/events`
#{format_info('FROM BROWSER')}: Check https://metrics.gitlab.com/snowplow
Find one? Create a new metric for the event.
Otherwise? Create a new event.
If you find a relevant event that does not have the property `internal_events: true`, it can be migrated to
Internal Events. See https://docs.gitlab.com/ee/development/internal_analytics/internal_event_instrumentation/migration.html
TEXT
end
end
end

View File

@ -0,0 +1,92 @@
# frozen_string_literal: true
# Blocks of text rendered in CLI
module InternalEventsCli
module Text
module MetricDefiner
extend Helpers
DATABASE_METRIC_NOTICE = <<~TEXT
For right now, this script can only define metrics for internal events.
For more info on instrumenting database-backed metrics, see https://docs.gitlab.com/ee/development/internal_analytics/metrics/metrics_instrumentation.html
TEXT
ALL_METRICS_EXIST_NOTICE = <<~TEXT
Looks like the potential metrics for this event either already exist or are unsupported.
Check out https://metrics.gitlab.com/ for improved event/metric search capabilities.
TEXT
DESCRIPTION_INTRO = <<~TEXT.freeze
#{format_info('METRIC DESCRIPTION')}
Describes which occurrences of an event are tracked in the metric and how they're grouped.
The description field is critical for helping others find & reuse this event. This will be used by Engineering, Product, Data team, Support -- and also GitLab customers directly. Be specific and explicit.
#{format_info('GOOD EXAMPLES:')}
- Total count of analytics dashboard list views
- Weekly count of unique users who viewed the analytics dashboard list
- Monthly count of unique projects where the analytics dashboard list was viewed
- Total count of issue updates
#{format_info('SELECTED EVENT(S):')}
TEXT
DESCRIPTION_HELP = <<~TEXT.freeze
#{format_warning('Required. 10+ words likely, but length may vary.')}
An event description can often be rearranged to work as a metric description.
ex) Event description: A merge request was created
Metric description: Total count of merge requests created
Metric description: Weekly count of unqiue users who created merge requests
Look at the event descriptions above to get ideas!
TEXT
NAME_FILTER_HELP = <<~TEXT.freeze
#{format_warning('Required. Max %{count} characters. Only lowercase/numbers/underscores allowed.')}
Metrics with filters must manually define this portion of their key path.
Auto-generated key paths for metrics filters results in long & confusing naming. By defining them manually, clarity and discoverability should be better.
TEXT
NAME_CONFLICT_HELP = <<~TEXT.freeze
#{format_warning('Required. Max %{count} characters. Only lowercase/numbers/underscores allowed.')}
Conflict! A metric with the same name already exists: %{name}
TEXT
NAME_LENGTH_HELP = <<~TEXT.freeze
#{format_warning('Required. Max %{count} characters. Only lowercase/numbers/underscores allowed.')}
Filenames cannot exceed 100 characters. The key path (ID) is not restricted, but keeping them aligned is recommended.
If needed, you can modify the key path and filename further after saving.
TEXT
NAME_REQUIREMENT_REASONS = {
filters: {
text: 'Metrics using filters are too complex for default naming.',
help: NAME_FILTER_HELP
},
length: {
text: 'The default filename will be too long.',
help: NAME_LENGTH_HELP
},
conflict: {
text: 'The default key path is already in use.',
help: NAME_CONFLICT_HELP
}
}.freeze
NAME_ERROR = <<~TEXT.freeze
#{format_warning('Input is invalid. Max %{count} characters. Only lowercase/numbers/underscores allowed. Ensure this key path (ID) is not already in use.')}
TEXT
end
end
end

View File

@ -1,4 +1,4 @@
import { GlBadge, GlTab, GlTabs, GlIcon, GlSprintf, GlLink } from '@gitlab/ui';
import { GlAvatar, GlBadge, GlTab, GlTabs, GlIcon, GlSprintf, GlLink } from '@gitlab/ui';
import VueRouter from 'vue-router';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
@ -140,6 +140,31 @@ describe('ml/model_registry/apps/show_model_version.vue', () => {
});
});
describe('Sidebar', () => {
const findSidebarAuthorLink = () => wrapper.findByTestId('sidebar-author-link');
const findAvatar = () => wrapper.findComponent(GlAvatar);
it('displays sidebar author link', async () => {
const resolver = jest.fn().mockResolvedValue(modelVersionQueryWithAuthor);
createWrapper({ resolver });
await waitForPromises();
expect(findSidebarAuthorLink().attributes('href')).toBe('path/to/user');
expect(findSidebarAuthorLink().text()).toBe('Root');
expect(findAvatar().props('src')).toBe('path/to/avatar');
});
describe('when model does not get loaded', () => {
it('does not displays sidebar author link', async () => {
createWrapper({ resolver: jest.fn().mockRejectedValue(new Error('Failure!')) });
await waitForPromises();
expect(findSidebarAuthorLink().exists()).toBe(false);
});
});
});
it('Requests data with the right parameters', async () => {
const resolver = jest.fn().mockResolvedValue(modelVersionQueryWithAuthor);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff