From a26438c5473f93cedaf9fe7f1a3cd61dc6f9a7e6 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 12 Feb 2025 21:11:54 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/ci/frontend.gitlab-ci.yml | 60 ++++-- .rubocop_todo/layout/line_length.yml | 1 - .rubocop_todo/rspec/context_wording.yml | 1 - .rubocop_todo/rspec/return_from_stub.yml | 1 - .rubocop_todo/style/format_string.yml | 1 - .../style/inline_disable_annotation.yml | 1 - GITALY_SERVER_VERSION | 2 +- .../components/access_token_table_app.vue | 2 +- .../inactive_access_token_table_app.vue | 2 +- .../behaviors/copy_to_clipboard.js | 76 ++++---- .../components/environment_stop.vue | 40 ++-- .../components/new_environment_item.vue | 5 +- .../reviewers/reviewer_dropdown.vue | 59 ++++-- .../javascripts/merge_requests/constants.js | 3 + .../branch_rules/components/view/constants.js | 8 +- .../branch_rules/components/view/index.vue | 46 ++++- .../view/squash_settings_drawer.vue | 8 +- .../branch_rules/components/view/utils.js | 4 + .../branch_rules/mount_branch_rules.js | 3 +- .../edit_squash_option.mutation.graphql | 9 + .../components/clipboard_button.vue | 2 +- .../branch_rules/squash_options/update.rb | 55 ++++++ app/graphql/types/mutation_type.rb | 1 + .../types/permission_types/work_item.rb | 2 +- .../squash_option_setting_enum.rb | 17 ++ app/models/concerns/has_user_type.rb | 4 +- app/models/concerns/projects/squash_option.rb | 8 +- .../squash_options/update_service.rb | 62 ++++++ .../user_selects_reviewer_from_mr_sidebar.yml | 21 ++ ..._reviewer_from_mr_sidebar_after_search.yml | 21 ++ ..._user_selects_reviewer_from_mr_sidebar.yml | 22 +++ ..._reviewer_from_mr_sidebar_after_search.yml | 22 +++ config/sidekiq_queues.yml | 2 + .../merge_requests_approval_rules_groups.yml | 12 ++ ...requests_approval_rules_merge_requests.yml | 12 ++ ...merge_requests_approval_rules_projects.yml | 12 ++ ...te_merge_requests_approval_rules_groups.rb | 21 ++ ..._requests_approval_rules_merge_requests.rb | 23 +++ ..._merge_requests_approval_rules_projects.rb | 21 ++ ..._approval_rules_groups_approval_rule_fk.rb | 17 ++ ...requests_approval_rules_groups_group_fk.rb | 17 ++ ...pproval_rules_projects_approval_rule_fk.rb | 17 ++ ...ests_approval_rules_projects_project_fk.rb | 17 ++ ...sts_approval_rules_mrs_approval_rule_fk.rb | 17 ++ ...merge_requests_approval_rules_mrs_mr_fk.rb | 18 ++ ..._requests_approval_rules_mrs_project_fk.rb | 18 ++ db/schema_migrations/20250123151708 | 1 + db/schema_migrations/20250123151726 | 1 + db/schema_migrations/20250123151745 | 1 + db/schema_migrations/20250206143903 | 1 + db/schema_migrations/20250206143924 | 1 + db/schema_migrations/20250206144004 | 1 + db/schema_migrations/20250206144027 | 1 + db/schema_migrations/20250206144049 | 1 + db/schema_migrations/20250206144110 | 1 + db/schema_migrations/20250211141110 | 1 + db/structure.sql | 102 ++++++++++ doc/api/graphql/reference/_index.md | 2 + .../ai_features/evaluation_runner/_index.md | 50 +++++ .../agent/managed_kubernetes_resources.md | 181 ++++++++++++++++++ doc/user/clusters/agent/user_access.md | 2 +- doc/user/compliance/audit_event_types.md | 1 + .../clusters/migrate_to_gitlab_agent.md | 142 ++++++++++++-- doc/user/profile/personal_access_tokens.md | 2 +- lib/gitlab/auth.rb | 2 +- locale/gitlab.pot | 40 +++- qa/qa/page/group/runners/index.rb | 4 +- .../deprecated_unregister_runner_spec.rb | 3 +- .../import/github_controller_spec.rb | 2 +- spec/factories/users.rb | 4 + .../environments/environments_spec.rb | 2 +- .../components/access_token_table_app_spec.js | 4 +- .../inactive_access_token_table_app_spec.js | 2 +- .../environments/environment_stop_spec.js | 72 +++++-- .../environments/new_environment_item_spec.js | 9 + .../reviewers/reviewer_dropdown_spec.js | 67 +++++++ .../components/view/index_spec.js | 90 +++++++++ .../view/squash_settings_drawer_spec.js | 17 +- .../components/view/utils_spec.js | 24 +++ .../types/permission_types/work_item_spec.rb | 2 +- spec/lib/gitlab/import_export/all_models.yml | 6 + spec/models/concerns/has_user_type_spec.rb | 2 +- spec/policies/work_item_policy_spec.rb | 20 +- spec/requests/api/commit_statuses_spec.rb | 33 ++-- .../api/conan/v1/instance_packages_spec.rb | 8 +- .../api/conan/v1/project_packages_spec.rb | 8 +- .../api/conan/v2/project_packages_spec.rb | 4 +- .../squash_options/update_spec.rb | 71 +++++++ spec/requests/api/graphql/work_item_spec.rb | 3 +- .../squash_options/update_service_spec.rb | 48 +++++ .../api/conan_packages_shared_context.rb | 8 +- ...roject_level_work_items_shared_examples.rb | 22 ++- .../api/conan_packages_shared_examples.rb | 59 +++--- 93 files changed, 1688 insertions(+), 233 deletions(-) create mode 100644 app/assets/javascripts/projects/settings/branch_rules/components/view/utils.js create mode 100644 app/assets/javascripts/projects/settings/branch_rules/mutations/edit_squash_option.mutation.graphql create mode 100644 app/graphql/mutations/projects/branch_rules/squash_options/update.rb create mode 100644 app/graphql/types/projects/branch_rules/squash_option_setting_enum.rb create mode 100644 app/services/projects/branch_rules/squash_options/update_service.rb create mode 100644 config/events/user_selects_reviewer_from_mr_sidebar.yml create mode 100644 config/events/user_selects_reviewer_from_mr_sidebar_after_search.yml create mode 100644 config/metrics/counts_all/count_distinct_user_id_from_user_selects_reviewer_from_mr_sidebar.yml create mode 100644 config/metrics/counts_all/count_distinct_user_id_from_user_selects_reviewer_from_mr_sidebar_after_search.yml create mode 100644 db/docs/merge_requests_approval_rules_groups.yml create mode 100644 db/docs/merge_requests_approval_rules_merge_requests.yml create mode 100644 db/docs/merge_requests_approval_rules_projects.yml create mode 100644 db/migrate/20250123151708_create_merge_requests_approval_rules_groups.rb create mode 100644 db/migrate/20250123151726_create_merge_requests_approval_rules_merge_requests.rb create mode 100644 db/migrate/20250123151745_create_merge_requests_approval_rules_projects.rb create mode 100644 db/migrate/20250206143903_add_merge_requests_approval_rules_groups_approval_rule_fk.rb create mode 100644 db/migrate/20250206143924_add_merge_requests_approval_rules_groups_group_fk.rb create mode 100644 db/migrate/20250206144004_add_merge_requests_approval_rules_projects_approval_rule_fk.rb create mode 100644 db/migrate/20250206144027_add_merge_requests_approval_rules_projects_project_fk.rb create mode 100644 db/migrate/20250206144049_add_merge_requests_approval_rules_mrs_approval_rule_fk.rb create mode 100644 db/migrate/20250206144110_add_merge_requests_approval_rules_mrs_mr_fk.rb create mode 100644 db/migrate/20250211141110_add_merge_requests_approval_rules_mrs_project_fk.rb create mode 100644 db/schema_migrations/20250123151708 create mode 100644 db/schema_migrations/20250123151726 create mode 100644 db/schema_migrations/20250123151745 create mode 100644 db/schema_migrations/20250206143903 create mode 100644 db/schema_migrations/20250206143924 create mode 100644 db/schema_migrations/20250206144004 create mode 100644 db/schema_migrations/20250206144027 create mode 100644 db/schema_migrations/20250206144049 create mode 100644 db/schema_migrations/20250206144110 create mode 100644 db/schema_migrations/20250211141110 create mode 100644 doc/development/ai_features/evaluation_runner/_index.md create mode 100644 doc/user/clusters/agent/managed_kubernetes_resources.md create mode 100644 spec/frontend/projects/settings/branch_rules/components/view/utils_spec.js create mode 100644 spec/requests/api/graphql/mutations/projects/branch_rules/squash_options/update_spec.rb create mode 100644 spec/services/projects/branch_rules/squash_options/update_service_spec.rb diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 199808ddf50..27b35cbe6fd 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -22,22 +22,29 @@ stage: prepare needs: [] script: - - yarn_install_script - - export GITLAB_ASSETS_HASH=$(bin/rake gitlab:assets:hash_sum) - - 'echo "CACHE_ASSETS_AS_PACKAGE: ${CACHE_ASSETS_AS_PACKAGE}"' - # The new strategy to cache assets as generic packages is experimental and can be disabled by removing the `CACHE_ASSETS_AS_PACKAGE` variable - | - if [[ "${CACHE_ASSETS_AS_PACKAGE}" == "true" ]]; then - source scripts/gitlab_component_helpers.sh + function compile_assets() { + yarn_install_script + export GITLAB_ASSETS_HASH=$(bin/rake gitlab:assets:hash_sum) + echo "CACHE_ASSETS_AS_PACKAGE: ${CACHE_ASSETS_AS_PACKAGE}" - if ! gitlab_assets_archive_doesnt_exist; then - # We remove all assets from the native cache as they could pollute the fresh assets from the package - rm -rf public/assets/ app/assets/javascripts/locale/**/app.js - run_timed_command "retry download_and_extract_gitlab_assets" + # The new strategy to cache assets as generic packages is experimental and + # can be disabled by removing the `CACHE_ASSETS_AS_PACKAGE` variable + if [[ "${CACHE_ASSETS_AS_PACKAGE}" == "true" ]]; then + source scripts/gitlab_component_helpers.sh + + if ! gitlab_assets_archive_doesnt_exist; then + # We remove all assets from the native cache as they could pollute the fresh assets from the package + rm -rf public/assets/ app/assets/javascripts/locale/**/app.js + run_timed_command "retry download_and_extract_gitlab_assets" + fi fi - fi - - assets_compile_script - - echo -n "${GITLAB_ASSETS_HASH}" > "cached-assets-hash.txt" + + assets_compile_script + echo -n "${GITLAB_ASSETS_HASH}" > "cached-assets-hash.txt" + } + + run_with_custom_exit_code compile_assets .update-cache-base: after_script: @@ -123,12 +130,18 @@ retrieve-frontend-fixtures: script: - source scripts/utils.sh - source scripts/gitlab_component_helpers.sh - - install_gitlab_gem - - export_fixtures_sha_for_download - | - if check_fixtures_download; then - run_timed_command "download_and_extract_fixtures" - fi + function retrieve_frontend_fixtures() { + install_gitlab_gem + export_fixtures_sha_for_download + + if check_fixtures_download; then + run_timed_command "download_and_extract_fixtures" + fi + } + + run_with_custom_exit_code retrieve_frontend_fixtures + artifacts: expire_in: 30 days paths: @@ -203,9 +216,14 @@ upload-frontend-fixtures: script: - source scripts/gitlab_component_helpers.sh - export_fixtures_sha_for_upload - - 'fixtures_archive_doesnt_exist || { echoinfo "INFO: Exiting early as package exists."; exit 0; }' - - run_timed_command "create_fixtures_package" - - run_timed_command "upload_fixtures_package" + - | + function upload_frontend_fixtures() { + fixtures_archive_doesnt_exist || { echoinfo "INFO: Exiting early as package exists."; exit 0; } + run_timed_command "create_fixtures_package" + run_timed_command "upload_fixtures_package" + } + + run_with_custom_exit_code upload_frontend_fixtures graphql-schema-dump: variables: diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index dd2095491f5..fe7b3e6d591 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -4129,7 +4129,6 @@ Layout/LineLength: - 'spec/support/shared_examples/quick_actions/issue/clone_quick_action_shared_examples.rb' - 'spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb' - 'spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb' - - 'spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb' - 'spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb' - 'spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb' - 'spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb' diff --git a/.rubocop_todo/rspec/context_wording.yml b/.rubocop_todo/rspec/context_wording.yml index 1da097f48f6..f442491a32b 100644 --- a/.rubocop_todo/rspec/context_wording.yml +++ b/.rubocop_todo/rspec/context_wording.yml @@ -2611,7 +2611,6 @@ RSpec/ContextWording: - 'spec/support/shared_contexts/prometheus/alert_shared_context.rb' - 'spec/support/shared_contexts/rack_attack_shared_context.rb' - 'spec/support/shared_contexts/read_ci_configuration_shared_context.rb' - - 'spec/support/shared_contexts/requests/api/conan_packages_shared_context.rb' - 'spec/support/shared_contexts/requests/api/debian_repository_shared_context.rb' - 'spec/support/shared_contexts/requests/api/go_modules_shared_context.rb' - 'spec/support/shared_contexts/requests/api/graphql/group_and_project_boards_query_shared_context.rb' diff --git a/.rubocop_todo/rspec/return_from_stub.yml b/.rubocop_todo/rspec/return_from_stub.yml index 3af4879b098..0f26a92fff0 100644 --- a/.rubocop_todo/rspec/return_from_stub.yml +++ b/.rubocop_todo/rspec/return_from_stub.yml @@ -176,7 +176,6 @@ RSpec/ReturnFromStub: - 'spec/support/shared_examples/lib/gitlab/middleware/read_only_gitlab_instance_shared_examples.rb' - 'spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb' - 'spec/support/shared_examples/models/concerns/can_move_repository_storage_shared_examples.rb' - - 'spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb' - 'spec/support/shared_examples/services/boards/boards_create_service_shared_examples.rb' - 'spec/support/shared_examples/services/boards/create_service_shared_examples.rb' - 'spec/support/shared_examples/uploaders/object_storage_shared_examples.rb' diff --git a/.rubocop_todo/style/format_string.yml b/.rubocop_todo/style/format_string.yml index ec29a5bee09..ad98b870c24 100644 --- a/.rubocop_todo/style/format_string.yml +++ b/.rubocop_todo/style/format_string.yml @@ -232,7 +232,6 @@ Style/FormatString: - 'spec/support/helpers/javascript_fixtures_helpers.rb' - 'spec/support/shared_contexts/bulk_imports_requests_shared_context.rb' - 'spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb' - - 'spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb' - 'spec/support/shared_examples/services/jira/requests/base_shared_examples.rb' - 'spec/support/shared_examples/views/registration_features_prompt_shared_examples.rb' - 'spec/validators/any_field_validator_spec.rb' diff --git a/.rubocop_todo/style/inline_disable_annotation.yml b/.rubocop_todo/style/inline_disable_annotation.yml index 45d4e345b4f..3378f647299 100644 --- a/.rubocop_todo/style/inline_disable_annotation.yml +++ b/.rubocop_todo/style/inline_disable_annotation.yml @@ -2405,7 +2405,6 @@ Style/InlineDisableAnnotation: - 'spec/support/shared_examples/models/member_shared_examples.rb' - 'spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb' - 'spec/support/shared_examples/requests/api/award_emoji_todo_shared_examples.rb' - - 'spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb' - 'spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb' - 'spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb' - 'spec/support/shared_examples/requests/api/ml_model_packages_shared_examples.rb' diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 90adf7c4bae..491a933497e 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -9673bce13f9be045d82ba58ebbb53daf0d5fa804 +af8ae81032144f6b873ec403ad397a195d08c3ae diff --git a/app/assets/javascripts/access_tokens/components/access_token_table_app.vue b/app/assets/javascripts/access_tokens/components/access_token_table_app.vue index d0b8328e724..ed30900d388 100644 --- a/app/assets/javascripts/access_tokens/components/access_token_table_app.vue +++ b/app/assets/javascripts/access_tokens/components/access_token_table_app.vue @@ -43,7 +43,7 @@ export default { }, mixins: [glFeatureFlagsMixin()], lastUsedHelpLink: helpPagePath('/user/profile/personal_access_tokens.md', { - anchor: 'view-the-time-at-and-ips-where-a-token-was-last-used', + anchor: 'view-token-usage-information', }), i18n: { button: { diff --git a/app/assets/javascripts/access_tokens/components/inactive_access_token_table_app.vue b/app/assets/javascripts/access_tokens/components/inactive_access_token_table_app.vue index 35a9cc0a667..5344c2b3cd8 100644 --- a/app/assets/javascripts/access_tokens/components/inactive_access_token_table_app.vue +++ b/app/assets/javascripts/access_tokens/components/inactive_access_token_table_app.vue @@ -27,7 +27,7 @@ export default { GlTooltip: GlTooltipDirective, }, lastUsedHelpLink: helpPagePath('/user/profile/personal_access_tokens.md', { - anchor: 'view-the-time-at-and-ips-where-a-token-was-last-used', + anchor: 'view-token-usage-information', }), i18n: { emptyField: __('Never'), diff --git a/app/assets/javascripts/behaviors/copy_to_clipboard.js b/app/assets/javascripts/behaviors/copy_to_clipboard.js index 834defe336b..4d9c91fbe21 100644 --- a/app/assets/javascripts/behaviors/copy_to_clipboard.js +++ b/app/assets/javascripts/behaviors/copy_to_clipboard.js @@ -1,6 +1,4 @@ -import ClipboardJS from 'clipboard'; -import $ from 'jquery'; - +import Clipboard from 'clipboard'; import { parseBoolean } from '~/lib/utils/common_utils'; import { __ } from '~/locale'; import { fixTitle, add, show, hide, once } from '~/tooltips'; @@ -55,41 +53,51 @@ function genericError(e) { } } +/** + * This a workaround around Clipboard limitations to allow the context-specific copy/pasting + * of plain text or GFM. The Ruby `clipboard_button` helper sneaks a JSON hash with `text` and + * `gfm` keys into the `data-clipboard-text` attribute that Clipboard reads from. + * When Clipboard creates a new `textarea` (directly inside `body`, with a `readonly` + * attribute`), sets its value to the value of this data attribute, focusses on it, and finally + * programmatically issues the 'Copy' command, this code intercepts the copy command/event at + * the last minute to deconstruct this JSON hash and set the `text/plain` and `text/x-gfm` copy + * data types to the intended values. + */ +const handleCopyEvent = (e) => { + const { target } = e; + + if (target !== document.querySelector('body > textarea[readonly]')) { + return; + } + + const { clipboardData } = e; + if (!clipboardData) return; + + const text = target.value; + + let json; + + try { + json = JSON.parse(text); + } catch { + return; + } + + if (!json.text || !json.gfm) return; + + e.preventDefault(); + + clipboardData.setData('text/plain', json.text); + clipboardData.setData('text/x-gfm', json.gfm); +}; + export default function initCopyToClipboard() { - const clipboard = new ClipboardJS('[data-clipboard-target], [data-clipboard-text]'); + const clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]'); + clipboard.on('success', genericSuccess); clipboard.on('error', genericError); - /** - * This a workaround around ClipboardJS limitations to allow the context-specific copy/pasting - * of plain text or GFM. The Ruby `clipboard_button` helper sneaks a JSON hash with `text` and - * `gfm` keys into the `data-clipboard-text` attribute that ClipboardJS reads from. - * When ClipboardJS creates a new `textarea` (directly inside `body`, with a `readonly` - * attribute`), sets its value to the value of this data attribute, focusses on it, and finally - * programmatically issues the 'Copy' command, this code intercepts the copy command/event at - * the last minute to deconstruct this JSON hash and set the `text/plain` and `text/x-gfm` copy - * data types to the intended values. - */ - $(document).on('copy', 'body > textarea[readonly]', (e) => { - const { clipboardData } = e.originalEvent; - if (!clipboardData) return; - - const text = e.target.value; - - let json; - try { - json = JSON.parse(text); - } catch (ex) { - return; - } - - if (!json.text || !json.gfm) return; - - e.preventDefault(); - - clipboardData.setData('text/plain', json.text); - clipboardData.setData('text/x-gfm', json.gfm); - }); + document.addEventListener('copy', handleCopyEvent); return clipboard; } diff --git a/app/assets/javascripts/environments/components/environment_stop.vue b/app/assets/javascripts/environments/components/environment_stop.vue index ba81cab2256..d608eebd1ae 100644 --- a/app/assets/javascripts/environments/components/environment_stop.vue +++ b/app/assets/javascripts/environments/components/environment_stop.vue @@ -39,7 +39,8 @@ export default { }, }, i18n: { - title: s__('Environments|Stop environment'), + stopTitle: s__('Environments|Stop environment'), + stoppingTitle: s__('Environments|Stopping environment'), }, data() { return { @@ -47,6 +48,14 @@ export default { isEnvironmentStopping: false, }; }, + computed: { + isLoadingState() { + return this.environment.state === 'stopping' || this.isEnvironmentStopping || this.isLoading; + }, + title() { + return this.isLoadingState ? this.$options.i18n.stoppingTitle : this.$options.i18n.stopTitle; + }, + }, mounted() { eventHub.$on('stopEnvironment', this.onStopEnvironment); }, @@ -75,16 +84,23 @@ export default { }; diff --git a/app/assets/javascripts/environments/components/new_environment_item.vue b/app/assets/javascripts/environments/components/new_environment_item.vue index 12b2aec47b5..72946320ada 100644 --- a/app/assets/javascripts/environments/components/new_environment_item.vue +++ b/app/assets/javascripts/environments/components/new_environment_item.vue @@ -110,6 +110,9 @@ export default { ...action, })); }, + isEnvironmentStopping() { + return this.environment?.state === 'stopping'; + }, canStop() { return this.environment?.canStop; }, @@ -233,7 +236,7 @@ export default { /> -import { debounce } from 'lodash'; +import { debounce, difference } from 'lodash'; import { GlCollapsibleListbox, GlButton, GlAvatar, GlIcon } from '@gitlab/ui'; import { __ } from '~/locale'; +import { InternalEvents } from '~/tracking'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import { TYPENAME_MERGE_REQUEST } from '~/graphql_shared/constants'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import userAutocompleteWithMRPermissionsQuery from '~/graphql_shared/queries/project_autocomplete_users_with_mr_permissions.query.graphql'; import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue'; + +import { SEARCH_SELECT_REVIEWER_EVENT, SELECT_REVIEWER_EVENT } from '../../constants'; + import UpdateReviewers from './update_reviewers.vue'; import userPermissionsQuery from './queries/user_permissions.query.graphql'; +function toUsernames(reviewers) { + return reviewers.map((reviewer) => reviewer.username); +} + export default { apollo: { userPermissions: { @@ -31,6 +39,7 @@ export default { UpdateReviewers, InviteMembersTrigger, }, + mixins: [InternalEvents.mixin()], inject: ['projectPath', 'issuableId', 'issuableIid', 'directlyInviteMembers'], props: { users: { @@ -54,14 +63,25 @@ export default { search: '', searching: false, fetchedUsers: [], - currentSelectedReviewers: this.selectedReviewers.map((r) => r.username), + currentSelectedReviewers: toUsernames(this.selectedReviewers), userPermissions: {}, }; }, computed: { + usersForList() { + let users; + + if (this.fetchedUsers.length) { + users = this.fetchedUsers; + } else { + users = this.users; + } + + return users; + }, mappedUsers() { const items = []; - let users; + const users = this.usersForList; if (this.selectedReviewersToShow.length && !this.search) { items.push({ @@ -70,12 +90,6 @@ export default { }); } - if (this.fetchedUsers.length) { - users = this.fetchedUsers; - } else { - users = this.users; - } - items.push({ textSrOnly: true, text: __('Users'), @@ -96,7 +110,7 @@ export default { }, watch: { selectedReviewers(newVal) { - this.currentSelectedReviewers = newVal.map((r) => r.username); + this.currentSelectedReviewers = toUsernames(newVal); }, }, created() { @@ -142,6 +156,29 @@ export default { removeAllReviewers() { this.currentSelectedReviewers = []; }, + trackReviewersSelectEvent() { + const telemetryEvent = this.search ? SEARCH_SELECT_REVIEWER_EVENT : SELECT_REVIEWER_EVENT; + const previousUsernames = toUsernames(this.selectedReviewers); + const listUsernames = toUsernames(this.usersForList); + // Reviewers are always shown first if they are in the list, + // so we should exclude them for when we check the position + const selectableList = difference(listUsernames, previousUsernames); + const additions = difference(this.currentSelectedReviewers, previousUsernames); + + additions.forEach((added) => { + // Convert from 0- to 1-index + const listPosition = selectableList.findIndex((user) => user === added) + 1; + + this.trackEvent(telemetryEvent, { + value: listPosition, + selectable_reviewers_count: selectableList.length, + }); + }); + }, + processReviewers(updateReviewers) { + this.trackReviewersSelectEvent(); + updateReviewers(); + }, }, i18n: { selectReviewer: __('Select reviewer'), @@ -170,7 +207,7 @@ export default { :searching="searching" @search="debouncedFetchAutocompleteUsers" @shown="shownDropdown" - @hidden="updateReviewers" + @hidden="processReviewers(updateReviewers)" @reset="removeAllReviewers" >