diff options
24 files changed, 239 insertions, 89 deletions
diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml index 880283839fe..af8f5a62980 100644 --- a/.rubocop_todo/layout/argument_alignment.yml +++ b/.rubocop_todo/layout/argument_alignment.yml @@ -1256,31 +1256,6 @@ Layout/ArgumentAlignment: - 'ee/lib/slack/block_kit/app_home_opened.rb' - 'ee/spec/components/billing/plan_component_spec.rb' - 'ee/spec/components/namespaces/storage/pre_enforcement_alert_component_spec.rb' - - 'ee/spec/controllers/ee/admin/sessions_controller_spec.rb' - - 'ee/spec/controllers/ee/search_controller_spec.rb' - - 'ee/spec/controllers/groups/analytics/cycle_analytics/stages_controller_spec.rb' - - 'ee/spec/controllers/groups/epic_boards_controller_spec.rb' - - 'ee/spec/controllers/groups/group_members_controller_spec.rb' - - 'ee/spec/controllers/groups/omniauth_callbacks_controller_spec.rb' - - 'ee/spec/controllers/groups/sso_controller_spec.rb' - - 'ee/spec/controllers/operations_controller_spec.rb' - - 'ee/spec/controllers/projects/approver_groups_controller_spec.rb' - - 'ee/spec/controllers/projects/approvers_controller_spec.rb' - - 'ee/spec/controllers/projects/branches_controller_spec.rb' - - 'ee/spec/controllers/projects/environments_controller_spec.rb' - - 'ee/spec/controllers/projects/imports_controller_spec.rb' - - 'ee/spec/controllers/projects/iterations_controller_spec.rb' - - 'ee/spec/controllers/projects/merge_requests/creations_controller_spec.rb' - - 'ee/spec/controllers/projects/protected_environments_controller_spec.rb' - - 'ee/spec/controllers/projects/repositories_controller_spec.rb' - - 'ee/spec/controllers/projects/security/sast_configuration_controller_spec.rb' - - 'ee/spec/controllers/projects/security/scanned_resources_controller_spec.rb' - - 'ee/spec/controllers/projects/settings/ci_cd_controller_spec.rb' - - 'ee/spec/controllers/projects/settings/merge_requests_controller_spec.rb' - - 'ee/spec/controllers/projects/settings/operations_controller_spec.rb' - - 'ee/spec/controllers/projects/vulnerability_feedback_controller_spec.rb' - - 'ee/spec/controllers/projects_controller_spec.rb' - - 'ee/spec/controllers/trials_controller_spec.rb' - 'ee/spec/elastic/migrate/20220119120500_populate_commit_permissions_in_main_index_spec.rb' - 'ee/spec/elastic/migrate/20221124090600_add_namespace_ancestry_ids_to_original_index_mapping_spec.rb' - 'ee/spec/elastic/migrate/20221221110300_backfill_traversal_ids_to_blobs_and_wiki_blobs_spec.rb' diff --git a/GITLAB_KAS_VERSION b/GITLAB_KAS_VERSION index 332da5068c6..85ad38c582c 100644 --- a/GITLAB_KAS_VERSION +++ b/GITLAB_KAS_VERSION @@ -1 +1 @@ -v15.11.0-rc1 +v15.11.0-rc2 diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue index 418a22af406..ee2c0a771cf 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue @@ -9,6 +9,7 @@ import { ENVIRONMENT_QUERY_LIMIT, SORT_DIRECTIONS, UPDATE_MUTATION_ACTION, + mapMutationActionToToast, environmentFetchErrorText, genericMutationErrorText, variableFetchErrorText, @@ -268,11 +269,15 @@ export default { if (data.ciVariableMutation?.errors?.length) { const { errors } = data.ciVariableMutation; createAlert({ message: errors[0] }); - } else if (this.refetchAfterMutation) { - // The writing to cache for admin variable is not working - // because there is no ID in the cache at the top level. - // We therefore need to manually refetch. - this.$apollo.queries.ciVariables.refetch(); + } else { + this.$toast.show(mapMutationActionToToast[mutationAction](variable.key)); + + if (this.refetchAfterMutation) { + // The writing to cache for admin variable is not working + // because there is no ID in the cache at the top level. + // We therefore need to manually refetch. + this.$apollo.queries.ciVariables.refetch(); + } } } catch (e) { createAlert({ message: genericMutationErrorText }); diff --git a/app/assets/javascripts/ci/ci_variable_list/constants.js b/app/assets/javascripts/ci/ci_variable_list/constants.js index 03374162b79..c8f67bd3436 100644 --- a/app/assets/javascripts/ci/ci_variable_list/constants.js +++ b/app/assets/javascripts/ci/ci_variable_list/constants.js @@ -1,4 +1,4 @@ -import { __, s__ } from '~/locale'; +import { __, s__, sprintf } from '~/locale'; export const ADD_CI_VARIABLE_MODAL_ID = 'add-ci-variable'; export const ENVIRONMENT_QUERY_LIMIT = 30; @@ -98,6 +98,19 @@ export const ADD_MUTATION_ACTION = 'add'; export const UPDATE_MUTATION_ACTION = 'update'; export const DELETE_MUTATION_ACTION = 'delete'; +export const ADD_VARIABLE_TOAST = (key) => + sprintf(s__('CiVariable|Variable %{key} has been successfully added.'), { key }); +export const UPDATE_VARIABLE_TOAST = (key) => + sprintf(s__('CiVariable|Variable %{key} has been updated.'), { key }); +export const DELETE_VARIABLE_TOAST = (key) => + sprintf(s__('CiVariable|Variable %{key} has been deleted.'), { key }); + +export const mapMutationActionToToast = { + [ADD_MUTATION_ACTION]: ADD_VARIABLE_TOAST, + [UPDATE_MUTATION_ACTION]: UPDATE_VARIABLE_TOAST, + [DELETE_MUTATION_ACTION]: DELETE_VARIABLE_TOAST, +}; + export const EXPANDED_VARIABLES_NOTE = __( '%{codeStart}$%{codeEnd} will be treated as the start of a reference to another variable.', ); diff --git a/app/assets/javascripts/projects/commits/index.js b/app/assets/javascripts/projects/commits/index.js index f56884f605f..3179fcb14fd 100644 --- a/app/assets/javascripts/projects/commits/index.js +++ b/app/assets/javascripts/projects/commits/index.js @@ -35,6 +35,10 @@ export const initCommitsRefSwitcher = () => { const { projectId, ref, commitsPath, refType } = el.dataset; const commitsPathPrefix = commitsPath.match(COMMITS_PATH_REGEX)?.[0]; + const generateRefDestinationUrl = (selectedRef, selectedRefType) => { + const commitsPathSuffix = selectedRefType ? `?ref_type=${selectedRefType}` : ''; + return `${commitsPathPrefix}/${encodeURIComponent(selectedRef)}${commitsPathSuffix}`; + }; const useSymbolicRefNames = Boolean(refType); return new Vue({ el, @@ -48,15 +52,11 @@ export const initCommitsRefSwitcher = () => { }, on: { input(selected) { - if (useSymbolicRefNames) { - const matches = selected.match(/refs\/(heads|tags)\/(.+)/); - if (matches) { - visitUrl(`${commitsPathPrefix}/${matches[2]}?ref_type=${matches[1]}`); - } else { - visitUrl(`${commitsPathPrefix}/${selected}`); - } + const matches = selected.match(/refs\/(heads|tags)\/(.+)/); + if (useSymbolicRefNames && matches) { + visitUrl(generateRefDestinationUrl(matches[2], matches[1])); } else { - visitUrl(`${commitsPathPrefix}/${selected}`); + visitUrl(generateRefDestinationUrl(selected)); } }, }, diff --git a/app/assets/javascripts/super_sidebar/components/user_bar.vue b/app/assets/javascripts/super_sidebar/components/user_bar.vue index 62161f2846a..750ec0aa0c6 100644 --- a/app/assets/javascripts/super_sidebar/components/user_bar.vue +++ b/app/assets/javascripts/super_sidebar/components/user_bar.vue @@ -70,6 +70,9 @@ export default { v-gl-tooltip:super-sidebar.hover.bottom="$options.i18n.homepage" :href="rootPath" :title="$options.i18n.homepage" + data-track-action="click_link" + data-track-label="gitlab_logo_link" + data-track-property="nav_core_menu" > <img v-if="sidebarData.logo_url" @@ -120,6 +123,9 @@ export default { :count="sidebarData.assigned_open_issues_count" :href="sidebarData.issues_dashboard_path" :label="$options.i18n.issues" + data-track-action="click_link" + data-track-label="issues_link" + data-track-property="nav_core_menu" /> <merge-request-menu class="gl-flex-basis-third gl-display-block!" @@ -131,6 +137,9 @@ export default { icon="merge-request-open" :count="sidebarData.total_merge_requests_count" :label="$options.i18n.mergeRequests" + data-track-action="click_dropdown" + data-track-label="merge_requests_menu" + data-track-property="nav_core_menu" /> </merge-request-menu> <counter @@ -141,6 +150,9 @@ export default { href="/dashboard/todos" :label="$options.i18n.todoList" data-qa-selector="todos_shortcut_button" + data-track-action="click_link" + data-track-label="todos_link" + data-track-property="nav_core_menu" /> </div> </div> diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb index 3769f03feb0..6a19943afa6 100644 --- a/app/helpers/sidebars_helper.rb +++ b/app/helpers/sidebars_helper.rb @@ -149,7 +149,12 @@ module SidebarsHelper items: section[:menu_items].map do |item| { text: item[:title], - href: item[:href] + href: item[:href], + extraAttrs: { + 'data-track-label': item[:id], + 'data-track-action': 'click_link', + 'data-track-property': 'nav_create_menu' + } } end } @@ -164,12 +169,22 @@ module SidebarsHelper { text: _('Assigned'), href: merge_requests_dashboard_path(assignee_username: user.username), - count: user_merge_requests_counts[:assigned] + count: user_merge_requests_counts[:assigned], + extraAttrs: { + 'data-track-action': 'click_link', + 'data-track-label': 'merge_requests_assigned', + 'data-track-property': 'nav_core_menu' + } }, { text: _('Review requests'), href: merge_requests_dashboard_path(reviewer_username: user.username), - count: user_merge_requests_counts[:review_requested] + count: user_merge_requests_counts[:review_requested], + extraAttrs: { + 'data-track-action': 'click_link', + 'data-track-label': 'merge_requests_to_review', + 'data-track-property': 'nav_core_menu' + } } ] } diff --git a/app/workers/database/batched_background_migration/execution_worker.rb b/app/workers/database/batched_background_migration/execution_worker.rb index 37b40c73ca6..53c92ab8969 100644 --- a/app/workers/database/batched_background_migration/execution_worker.rb +++ b/app/workers/database/batched_background_migration/execution_worker.rb @@ -11,7 +11,6 @@ module Database INTERVAL_VARIANCE = 5.seconds.freeze LEASE_TIMEOUT_MULTIPLIER = 3 - MAX_RUNNING_MIGRATIONS = 4 included do data_consistency :always @@ -21,7 +20,7 @@ module Database class_methods do def max_running_jobs - MAX_RUNNING_MIGRATIONS + Gitlab::CurrentSettings.database_max_running_batched_background_migrations end # We have to overirde this one, as we want diff --git a/db/migrate/20230403023440_add_database_max_running_batched_background_migrations_to_application_settings.rb b/db/migrate/20230403023440_add_database_max_running_batched_background_migrations_to_application_settings.rb new file mode 100644 index 00000000000..136855403f9 --- /dev/null +++ b/db/migrate/20230403023440_add_database_max_running_batched_background_migrations_to_application_settings.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class AddDatabaseMaxRunningBatchedBackgroundMigrationsToApplicationSettings < Gitlab::Database::Migration[2.1] + def change + add_column :application_settings, :database_max_running_batched_background_migrations, + :integer, null: false, default: 2 + end +end diff --git a/db/migrate/20230403023441_set_max_running_batched_background_migrations_for_gitlab_com.rb b/db/migrate/20230403023441_set_max_running_batched_background_migrations_for_gitlab_com.rb new file mode 100644 index 00000000000..6e8d423e679 --- /dev/null +++ b/db/migrate/20230403023441_set_max_running_batched_background_migrations_for_gitlab_com.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class SetMaxRunningBatchedBackgroundMigrationsForGitlabCom < Gitlab::Database::Migration[2.1] + restrict_gitlab_migration gitlab_schema: :gitlab_main + + def up + return unless Gitlab.com? && !Gitlab.jh? + + execute 'UPDATE application_settings SET database_max_running_batched_background_migrations = 4' + end + + def down + return unless Gitlab.com? && !Gitlab.jh? + + execute 'UPDATE application_settings SET database_max_running_batched_background_migrations = 2' + end +end diff --git a/db/schema_migrations/20230403023440 b/db/schema_migrations/20230403023440 new file mode 100644 index 00000000000..2705965c70c --- /dev/null +++ b/db/schema_migrations/20230403023440 @@ -0,0 +1 @@ +0cd1bcb2a2a02c09e7fd9271f815522adc1c20a1b39953349cfe2fff4541ed66
\ No newline at end of file diff --git a/db/schema_migrations/20230403023441 b/db/schema_migrations/20230403023441 new file mode 100644 index 00000000000..6e259200d0e --- /dev/null +++ b/db/schema_migrations/20230403023441 @@ -0,0 +1 @@ +e9684618df40260a7bae8b3dc217f3dd662e1cbc5db8b8a6d0334716e034c77b
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 98b006c0ac3..89de87610b7 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11756,6 +11756,7 @@ CREATE TABLE application_settings ( database_apdex_settings jsonb, encrypted_openai_api_key bytea, encrypted_openai_api_key_iv bytea, + database_max_running_batched_background_migrations integer DEFAULT 2 NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), diff --git a/doc/architecture/blueprints/cells/index.md b/doc/architecture/blueprints/cells/index.md index 1fa53222827..516860cd6d6 100644 --- a/doc/architecture/blueprints/cells/index.md +++ b/doc/architecture/blueprints/cells/index.md @@ -75,7 +75,6 @@ Organizations work under the following assumptions: #### Organization properties - Top-level namespaces belong to organizations -- Users can be members of different organizations - Organizations are isolated from each other by default meaning that cross-namespace features will only work for namespaces that exist within a single organization - User namespaces must not belong to an organization @@ -83,9 +82,7 @@ Discouraged synonyms: Billable entities, customers ### Top-Level namespace -A top-level namespace is the logical object container in the code that represents all groups, subgroups and projects that belong to an organization. - -A top-level namespace is the root of nested collection namespaces and projects. The namespace and its related entities form a tree-like hierarchy: Namespaces are the nodes of the tree, projects are the leaves. +Top-level namespace is the name given to the top most group of all other groups. Groups and projects are nested underneath the top-level namespace. Example: @@ -94,7 +91,9 @@ Example: - `gitlab-org` is a `top-level namespace`; the root for all groups and projects of an organization - `gitlab` is a `project`; a project of the organization. -Top-level namespaces may [be replaced by organizations](https://gitlab.com/gitlab-org/gitlab/-/issues/368237#high-level-goals). This proposal only uses the term top-level namespaces as the organization definition is ongoing. +The top-level namespace has served as the defacto Organization entity. With the creation of Organization, top-level namespaces will be [nested underneath Organizations](https://gitlab.com/gitlab-org/gitlab/-/issues/394796). + +Over time there won't be a distinction between a top level namespace and a group. All features that make Top-level namespaces different from groups will move to Organization. Discouraged synonyms: Root-level namespace @@ -107,14 +106,15 @@ Discouraged synonyms: Root-level namespace ### Users -Users are available globally and not restricted to a single Cell. Users can be members of many different organizations with varying permissions. Inside organizations, users can create multiple top-level namespaces. User activity is not limited to a single organization but their contributions (for example TODOs) are only aggregated within an organization. This avoids the need for aggregating across cells. +Users are available globally and not restricted to a single Cell. Users belong to a single organization, but can participate in many organizations through group and project membership with varying permissions. Inside organizations, users can create multiple top-level namespaces. User activity is not limited to a single organization but their contributions (for example TODOs) are only aggregated within an organization. This avoids the need for aggregating across cells. #### User properties - Users are shared globally across all Cells - Users can create multiple top-level namespaces - Users can be a member of multiple top-level namespaces -- Users can be a member of multiple organizations +- Users belong to one organization. See [!395736](https://gitlab.com/gitlab-org/gitlab/-/issues/395736) +- Users can be members of groups and projects in different organizations - Users can administer organizations - User activity is aggregated in an organization - Every user has one personal namespace diff --git a/doc/user/admin_area/reporting/git_abuse_rate_limit.md b/doc/user/admin_area/reporting/git_abuse_rate_limit.md index a6c3ff3ae32..83b28404714 100644 --- a/doc/user/admin_area/reporting/git_abuse_rate_limit.md +++ b/doc/user/admin_area/reporting/git_abuse_rate_limit.md @@ -11,7 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w This is the administration documentation. For information about Git abuse rate limiting at the group level, see the [group-level documentation](../../group/reporting/git_abuse_rate_limit.md). -Git abuse rate limiting is a feature to automatically [ban users](../moderate_users.md#ban-and-unban-users) who download or clone more than a specified number of repositories in any project in the instance in a given time frame. Banned users cannot sign in to the instance and cannot access any non-public group via HTTP or SSH. The rate limit also applies to users who authenticate with a [personal](../../../user/profile/personal_access_tokens.md) or [group access token](../../../user/group/settings/group_access_tokens.md). +Git abuse rate limiting is a feature to automatically [ban users](../moderate_users.md#ban-and-unban-users) who download, clone, or fork more than a specified number of repositories in any project in the instance in a given time frame. Banned users cannot sign in to the instance and cannot access any non-public group via HTTP or SSH. The rate limit also applies to users who authenticate with a [personal](../../../user/profile/personal_access_tokens.md) or [group access token](../../../user/group/settings/group_access_tokens.md). Git abuse rate limiting does not apply to instance administrators, [deploy tokens](../../../user/project/deploy_tokens/index.md), or [deploy keys](../../../user/project/deploy_keys/index.md). diff --git a/doc/user/group/reporting/git_abuse_rate_limit.md b/doc/user/group/reporting/git_abuse_rate_limit.md index 8ccaea2b614..d5c44f4e245 100644 --- a/doc/user/group/reporting/git_abuse_rate_limit.md +++ b/doc/user/group/reporting/git_abuse_rate_limit.md @@ -13,7 +13,7 @@ On self-managed GitLab, by default this feature is not available. To make it ava This is the group-level documentation. For self-managed instances, see the [administration documentation](../../admin_area/reporting/git_abuse_rate_limit.md). -Git abuse rate limiting is a feature to automatically ban users who download or clone more than a specified number of repositories of a group in a given time frame. Banned users cannot access the top-level group or any of its non-public subgroups via HTTP or SSH. The rate limit also applies to users who authenticate with a [personal](../../../user/profile/personal_access_tokens.md) or [group access token](../../../user/group/settings/group_access_tokens.md). Access to unrelated groups is unaffected. +Git abuse rate limiting is a feature to automatically ban users who download, clone, or fork more than a specified number of repositories of a group in a given time frame. Banned users cannot access the top-level group or any of its non-public subgroups via HTTP or SSH. The rate limit also applies to users who authenticate with a [personal](../../../user/profile/personal_access_tokens.md) or [group access token](../../../user/group/settings/group_access_tokens.md). Access to unrelated groups is unaffected. Git abuse rate limiting does not apply to top-level group owners, [deploy tokens](../../../user/project/deploy_tokens/index.md), or [deploy keys](../../../user/project/deploy_keys/index.md). diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f3efaded685..1aaaf6c9ab5 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2886,7 +2886,7 @@ msgstr "" msgid "AdminSettings|Configure Let's Encrypt" msgstr "" -msgid "AdminSettings|Configure limits on the number of repositories users can download in a given time." +msgid "AdminSettings|Configure limits on the number of repositories users can download, clone, or fork in a given time." msgstr "" msgid "AdminSettings|Configure product analytics to track events within your project applications." @@ -9203,6 +9203,15 @@ msgstr "" msgid "CiVariable|Search environments" msgstr "" +msgid "CiVariable|Variable %{key} has been deleted." +msgstr "" + +msgid "CiVariable|Variable %{key} has been successfully added." +msgstr "" + +msgid "CiVariable|Variable %{key} has been updated." +msgstr "" + msgid "Classification Label (optional)" msgstr "" @@ -20643,7 +20652,7 @@ msgstr "" msgid "GroupSettings|Configure compliance frameworks to make them available to projects in this group. %{linkStart}What are compliance frameworks?%{linkEnd}" msgstr "" -msgid "GroupSettings|Configure limits on the number of repositories users can download in a given time." +msgid "GroupSettings|Configure limits on the number of repositories users can download, clone, or fork in a given time." msgstr "" msgid "GroupSettings|Custom project templates" diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index eafe74f4b0b..aacba6d2af8 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -165,10 +165,24 @@ RSpec.describe 'Commits', feature_category: :source_code_management do context 'viewing commits for a branch' do let(:branch_name) { 'master' } + let(:ref_selector) { '.ref-selector' } + let(:ref_with_hash) { 'ref-#-hash' } + + def switch_ref_to(ref_name) + first(ref_selector).click + wait_for_requests + + page.within ref_selector do + fill_in 'Search by Git revision', with: ref_name + wait_for_requests + find('li', text: ref_name, match: :prefer_exact).click + end + end before do project.add_maintainer(user) sign_in(user) + project.repository.create_branch(ref_with_hash, branch_name) visit project_commits_path(project, branch_name) end @@ -180,11 +194,17 @@ RSpec.describe 'Commits', feature_category: :source_code_management do end end + it 'switches ref to ref containing a hash', :js do + switch_ref_to(ref_with_hash) + + expect(page).to have_selector ref_selector, text: ref_with_hash + end + it 'shows the ref switcher with the multi-file editor enabled', :js do set_cookie('new_repo', 'true') visit project_commits_path(project, branch_name) - expect(find('.ref-selector')).to have_content branch_name + expect(find(ref_selector)).to have_content branch_name end end diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js index da67bcf786d..66b41223b33 100644 --- a/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js +++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js @@ -25,6 +25,7 @@ import { environmentFetchErrorText, genericMutationErrorText, variableFetchErrorText, + mapMutationActionToToast, } from '~/ci/ci_variable_list/constants'; import { @@ -66,6 +67,8 @@ describe('Ci Variable Shared Component', () => { let mockEnvironments; let mockVariables; + const mockToastShow = jest.fn(); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findCiTable = () => wrapper.findComponent(GlTable); const findCiSettings = () => wrapper.findComponent(ciVariableSettings); @@ -95,6 +98,11 @@ describe('Ci Variable Shared Component', () => { }, apolloProvider: mockApollo, stubs: { ciVariableSettings, ciVariableTable }, + mocks: { + $toast: { + show: mockToastShow, + }, + }, }); if (!isLoading) { @@ -295,9 +303,9 @@ describe('Ci Variable Shared Component', () => { ${'update'} | ${groupProps.mutationData[UPDATE_MUTATION_ACTION]} | ${'update-variable'} ${'delete'} | ${groupProps.mutationData[DELETE_MUTATION_ACTION]} | ${'delete-variable'} `( - 'calls the right mutation from propsData when user performs $actionName variable', - async ({ event, mutation }) => { - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(); + 'calls the mutation from propsData and shows a toast when user performs $actionName variable', + async ({ event, mutation, actionName }) => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({ data: {} }); await findCiSettings().vm.$emit(event, newVariable); @@ -310,6 +318,12 @@ describe('Ci Variable Shared Component', () => { variable: newVariable, }, }); + + await nextTick(); + + expect(mockToastShow).toHaveBeenCalledWith( + mapMutationActionToToast[actionName](newVariable.key), + ); }, ); diff --git a/spec/frontend/super_sidebar/components/merge_request_menu_spec.js b/spec/frontend/super_sidebar/components/merge_request_menu_spec.js index fe87c4be9c3..75998ee6c55 100644 --- a/spec/frontend/super_sidebar/components/merge_request_menu_spec.js +++ b/spec/frontend/super_sidebar/components/merge_request_menu_spec.js @@ -8,7 +8,7 @@ describe('MergeRequestMenu component', () => { const findGlBadge = (at) => wrapper.findAllComponents(GlBadge).at(at); const findGlDisclosureDropdown = () => wrapper.findComponent(GlDisclosureDropdown); - const findLink = () => wrapper.findByRole('link'); + const findLink = (name) => wrapper.findByRole('link', { name }); const createWrapper = () => { wrapper = mountExtended(MergeRequestMenu, { @@ -27,11 +27,17 @@ describe('MergeRequestMenu component', () => { expect(findGlDisclosureDropdown().props('items')).toBe(mergeRequestMenuGroup); }); - it('renders item text and count in link', () => { - const { text, href, count } = mergeRequestMenuGroup[0].items[0]; - expect(findLink().text()).toContain(text); - expect(findLink().text()).toContain(String(count)); - expect(findLink().attributes('href')).toBe(href); + it.each(mergeRequestMenuGroup[0].items)('renders item text and count in link', (item) => { + const index = mergeRequestMenuGroup[0].items.indexOf(item); + const { text, href, count, extraAttrs } = mergeRequestMenuGroup[0].items[index]; + const link = findLink(new RegExp(text)); + + expect(link.text()).toContain(text); + expect(link.text()).toContain(String(count)); + expect(link.attributes('href')).toBe(href); + expect(link.attributes('data-track-action')).toBe(extraAttrs['data-track-action']); + expect(link.attributes('data-track-label')).toBe(extraAttrs['data-track-label']); + expect(link.attributes('data-track-property')).toBe(extraAttrs['data-track-property']); }); it('renders item count string in badge', () => { diff --git a/spec/frontend/super_sidebar/components/user_bar_spec.js b/spec/frontend/super_sidebar/components/user_bar_spec.js index 48e62c3564d..e078d5a943c 100644 --- a/spec/frontend/super_sidebar/components/user_bar_spec.js +++ b/spec/frontend/super_sidebar/components/user_bar_spec.js @@ -18,6 +18,9 @@ describe('UserBar component', () => { const findCreateMenu = () => wrapper.findComponent(CreateMenu); const findCounter = (at) => wrapper.findAllComponents(Counter).at(at); + const findIssuesCounter = () => findCounter(0); + const findMRsCounter = () => findCounter(1); + const findTodosCounter = () => findCounter(2); const findMergeRequestMenu = () => wrapper.findComponent(MergeRequestMenu); const findBrandLogo = () => wrapper.findByTestId('brand-header-custom-logo'); const findSearchButton = () => wrapper.findByTestId('super-sidebar-search-button'); @@ -60,20 +63,32 @@ describe('UserBar component', () => { }); it('renders issues counter', () => { - expect(findCounter(0).props('count')).toBe(sidebarData.assigned_open_issues_count); - expect(findCounter(0).props('href')).toBe(sidebarData.issues_dashboard_path); - expect(findCounter(0).props('label')).toBe(__('Issues')); + const isuesCounter = findIssuesCounter(); + expect(isuesCounter.props('count')).toBe(sidebarData.assigned_open_issues_count); + expect(isuesCounter.props('href')).toBe(sidebarData.issues_dashboard_path); + expect(isuesCounter.props('label')).toBe(__('Issues')); + expect(isuesCounter.attributes('data-track-action')).toBe('click_link'); + expect(isuesCounter.attributes('data-track-label')).toBe('issues_link'); + expect(isuesCounter.attributes('data-track-property')).toBe('nav_core_menu'); }); it('renders merge requests counter', () => { - expect(findCounter(1).props('count')).toBe(sidebarData.total_merge_requests_count); - expect(findCounter(1).props('label')).toBe(__('Merge requests')); + const mrsCounter = findMRsCounter(); + expect(mrsCounter.props('count')).toBe(sidebarData.total_merge_requests_count); + expect(mrsCounter.props('label')).toBe(__('Merge requests')); + expect(mrsCounter.attributes('data-track-action')).toBe('click_dropdown'); + expect(mrsCounter.attributes('data-track-label')).toBe('merge_requests_menu'); + expect(mrsCounter.attributes('data-track-property')).toBe('nav_core_menu'); }); it('renders todos counter', () => { - expect(findCounter(2).props('count')).toBe(sidebarData.todos_pending_count); - expect(findCounter(2).props('href')).toBe('/dashboard/todos'); - expect(findCounter(2).props('label')).toBe(__('To-Do list')); + const todosCounter = findTodosCounter(); + expect(todosCounter.props('count')).toBe(sidebarData.todos_pending_count); + expect(todosCounter.props('href')).toBe('/dashboard/todos'); + expect(todosCounter.props('label')).toBe(__('To-Do list')); + expect(todosCounter.attributes('data-track-action')).toBe('click_link'); + expect(todosCounter.attributes('data-track-label')).toBe('todos_link'); + expect(todosCounter.attributes('data-track-property')).toBe('nav_core_menu'); }); it('renders branding logo', () => { diff --git a/spec/frontend/super_sidebar/mock_data.js b/spec/frontend/super_sidebar/mock_data.js index 0a2dc5216d6..fdf88e910e3 100644 --- a/spec/frontend/super_sidebar/mock_data.js +++ b/spec/frontend/super_sidebar/mock_data.js @@ -49,11 +49,21 @@ export const mergeRequestMenuGroup = [ text: 'Assigned', href: '/dashboard/merge_requests?assignee_username=root', count: 4, + extraAttrs: { + 'data-track-action': 'click_link', + 'data-track-label': 'merge_requests_assigned', + 'data-track-property': 'nav_core_menu', + }, }, { text: 'Review requests', href: '/dashboard/merge_requests?reviewer_username=root', count: 0, + extraAttrs: { + 'data-track-action': 'click_link', + 'data-track-label': 'merge_requests_to_review', + 'data-track-property': 'nav_core_menu', + }, }, ], }, diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb index f7260f14927..76e87667e25 100644 --- a/spec/helpers/sidebars_helper_spec.rb +++ b/spec/helpers/sidebars_helper_spec.rb @@ -151,12 +151,22 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do { text: _('Assigned'), href: merge_requests_dashboard_path(assignee_username: user.username), - count: 4 + count: 4, + extraAttrs: { + 'data-track-action': 'click_link', + 'data-track-label': 'merge_requests_assigned', + 'data-track-property': 'nav_core_menu' + } }, { text: _('Review requests'), href: merge_requests_dashboard_path(reviewer_username: user.username), - count: 0 + count: 0, + extraAttrs: { + 'data-track-action': 'click_link', + 'data-track-label': 'merge_requests_to_review', + 'data-track-property': 'nav_core_menu' + } } ] } @@ -164,19 +174,26 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do end it 'returns "Create new" menu groups without headers', :use_clean_rails_memory_store_caching do + extra_attrs = { "data-track-action": "click_link", "data-track-property": "nav_create_menu" } + expect(subject[:create_new_menu_groups]).to eq([ { name: "", items: [ - { href: "/projects/new", text: "New project/repository" }, - { href: "/groups/new", text: "New group" }, - { href: "/-/snippets/new", text: "New snippet" } + { href: "/projects/new", text: "New project/repository", + extraAttrs: extra_attrs.merge("data-track-label": "general_new_project") }, + { href: "/groups/new", text: "New group", + extraAttrs: extra_attrs.merge("data-track-label": "general_new_group") }, + { href: "/-/snippets/new", text: "New snippet", + extraAttrs: extra_attrs.merge("data-track-label": "general_new_snippet") } ] } ]) end it 'returns "Create new" menu groups with headers', :use_clean_rails_memory_store_caching do + extra_attrs = { "data-track-action": "click_link", "data-track-property": "nav_create_menu" } + allow(group).to receive(:persisted?).and_return(true) allow(helper).to receive(:can?).and_return(true) @@ -184,17 +201,23 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do a_hash_including( name: "In this group", items: array_including( - { href: "/projects/new", text: "New project/repository" }, - { href: "/groups/new#create-group-pane", text: "New subgroup" }, - { href: '', text: "Invite members" } + { href: "/projects/new", text: "New project/repository", + extraAttrs: extra_attrs.merge("data-track-label": "new_project") }, + { href: "/groups/new#create-group-pane", text: "New subgroup", + extraAttrs: extra_attrs.merge("data-track-label": "new_subgroup") }, + { href: "", text: "Invite members", + extraAttrs: extra_attrs.merge("data-track-label": "invite") } ) ), a_hash_including( name: "In GitLab", items: array_including( - { href: "/projects/new", text: "New project/repository" }, - { href: "/groups/new", text: "New group" }, - { href: "/-/snippets/new", text: "New snippet" } + { href: "/projects/new", text: "New project/repository", + extraAttrs: extra_attrs.merge("data-track-label": "general_new_project") }, + { href: "/groups/new", text: "New group", + extraAttrs: extra_attrs.merge("data-track-label": "general_new_group") }, + { href: "/-/snippets/new", text: "New snippet", + extraAttrs: extra_attrs.merge("data-track-label": "general_new_snippet") } ) ) ) diff --git a/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb b/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb index e224b71da91..095c32c3136 100644 --- a/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb +++ b/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb @@ -50,14 +50,20 @@ RSpec.shared_examples 'batched background migrations execution worker' do end describe '.max_running_jobs' do - it 'returns MAX_RUNNING_MIGRATIONS' do - expect(described_class.max_running_jobs).to eq(described_class::MAX_RUNNING_MIGRATIONS) + it 'returns database_max_running_batched_background_migrations application setting' do + stub_application_setting(database_max_running_batched_background_migrations: 3) + + expect(described_class.max_running_jobs) + .to eq(Gitlab::CurrentSettings.database_max_running_batched_background_migrations) end end describe '#max_running_jobs' do - it 'returns MAX_RUNNING_MIGRATIONS' do - expect(described_class.new.max_running_jobs).to eq(described_class::MAX_RUNNING_MIGRATIONS) + it 'returns database_max_running_batched_background_migrations application setting' do + stub_application_setting(database_max_running_batched_background_migrations: 3) + + expect(described_class.new.max_running_jobs) + .to eq(Gitlab::CurrentSettings.database_max_running_batched_background_migrations) end end |