diff --git a/.gitpod.yml b/.gitpod.yml index 6f00147ae6b..b47ede87c0a 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -57,7 +57,11 @@ tasks: # Waiting for GitLab ... gp await-port 3000 printf "Waiting for GitLab at $(gp url 3000) ..." - until $(curl -sNL $(gp url 3000) | grep -q "GitLab"); do printf '.'; sleep 5; done && echo "" + # Check /-/readiness which returns JSON, but we're only interested in the exit code + # + # We use http://localhost:3000 instead of the public hostname because + # it's no longer possible to access as specific cookies are required + until curl --silent --no-buffer --fail http://localhost:3000/-/readiness > /dev/null 2>&1; do printf '.'; sleep 5; done && echo "" # Give Gitpod a few more seconds to set up everything ... sleep 5 printf "$(date) – GitLab is up (took ~%.1f minutes)\n" "$((10*$SECONDS/60))e-1" | tee -a /workspace/startup.log diff --git a/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue b/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue index e2fd608d9db..a97af5367fb 100644 --- a/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue +++ b/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue @@ -103,6 +103,7 @@ export default { - + diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue index 4b287d31786..65547af3913 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue @@ -1,5 +1,5 @@ diff --git a/app/assets/javascripts/search/topbar/components/group_filter.vue b/app/assets/javascripts/search/topbar/components/group_filter.vue index 45a6ae73fac..e5edb21792a 100644 --- a/app/assets/javascripts/search/topbar/components/group_filter.vue +++ b/app/assets/javascripts/search/topbar/components/group_filter.vue @@ -18,12 +18,18 @@ export default { }, }, computed: { - ...mapState(['groups', 'fetchingGroups']), + ...mapState(['query', 'groups', 'fetchingGroups']), ...mapGetters(['frequentGroups']), selectedGroup() { return isEmpty(this.initialData) ? ANY_OPTION : this.initialData; }, }, + created() { + // This tracks groups searched via the top nav search bar + if (this.query.nav_source === 'navbar' && this.initialData?.id) { + this.setFrequentGroup(this.initialData); + } + }, methods: { ...mapActions(['fetchGroups', 'setFrequentGroup', 'loadFrequentGroups']), handleGroupChange(group) { @@ -33,7 +39,11 @@ export default { } visitUrl( - setUrlParams({ [GROUP_DATA.queryParam]: group.id, [PROJECT_DATA.queryParam]: null }), + setUrlParams({ + [GROUP_DATA.queryParam]: group.id, + [PROJECT_DATA.queryParam]: null, + nav_source: null, + }), ); }, }, diff --git a/app/assets/javascripts/search/topbar/components/project_filter.vue b/app/assets/javascripts/search/topbar/components/project_filter.vue index 1ca31db61e5..85cf2ddbbff 100644 --- a/app/assets/javascripts/search/topbar/components/project_filter.vue +++ b/app/assets/javascripts/search/topbar/components/project_filter.vue @@ -17,12 +17,18 @@ export default { }, }, computed: { - ...mapState(['projects', 'fetchingProjects']), + ...mapState(['query', 'projects', 'fetchingProjects']), ...mapGetters(['frequentProjects']), selectedProject() { return this.initialData ? this.initialData : ANY_OPTION; }, }, + created() { + // This tracks projects searched via the top nav search bar + if (this.query.nav_source === 'navbar' && this.initialData?.id) { + this.setFrequentProject(this.initialData); + } + }, methods: { ...mapActions(['fetchProjects', 'setFrequentProject', 'loadFrequentProjects']), handleProjectChange(project) { @@ -35,6 +41,7 @@ export default { const queryParams = { ...(project.namespace?.id && { [GROUP_DATA.queryParam]: project.namespace.id }), [PROJECT_DATA.queryParam]: project.id, + nav_source: null, }; visitUrl(setUrlParams(queryParams)); diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 4f278677c5f..b2bf913fe45 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -284,8 +284,8 @@ export class SearchAutocomplete { if (projectId) { const projectOptions = gl.projectOptions[getProjectSlug()]; const url = groupId - ? `${gon.relative_url_root}/search?search=${term}&project_id=${projectId}&group_id=${groupId}` - : `${gon.relative_url_root}/search?search=${term}&project_id=${projectId}`; + ? `${gon.relative_url_root}/search?search=${term}&project_id=${projectId}&group_id=${groupId}&nav_source=navbar` + : `${gon.relative_url_root}/search?search=${term}&project_id=${projectId}&nav_source=navbar`; options.push({ icon, @@ -313,7 +313,7 @@ export class SearchAutocomplete { }, false, ), - url: `${gon.relative_url_root}/search?search=${term}&group_id=${groupId}`, + url: `${gon.relative_url_root}/search?search=${term}&group_id=${groupId}&nav_source=navbar`, }); } @@ -321,7 +321,7 @@ export class SearchAutocomplete { icon, text: term, template: s__('SearchAutocomplete|in all GitLab'), - url: `${gon.relative_url_root}/search?search=${term}`, + url: `${gon.relative_url_root}/search?search=${term}&nav_source=navbar`, }); return options; diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 18f3f153aee..45f21d4d082 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -143,7 +143,7 @@ module GroupsHelper end def remove_group_message(group) - _("You are going to remove %{group_name}, this will also delete all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") % + _("You are going to remove %{group_name}. This will also delete all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") % { group_name: group.name } end diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 261c82df02d..a810993f622 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -101,6 +101,8 @@ module Projects @project.track_project_repository @project.create_project_setting unless @project.project_setting + yield if block_given? + event_service.create_project(@project, current_user) system_hook_service.execute_hooks_for(@project, :create) diff --git a/app/views/groups/settings/_advanced.html.haml b/app/views/groups/settings/_advanced.html.haml index e1ffefa8031..cdff533e3c7 100644 --- a/app/views/groups/settings/_advanced.html.haml +++ b/app/views/groups/settings/_advanced.html.haml @@ -28,3 +28,4 @@ = render 'groups/settings/transfer', group: @group = render 'groups/settings/remove', group: @group = render_if_exists 'groups/settings/restore', group: @group += render_if_exists 'groups/settings/immediately_remove', group: @group diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index d54310bfa82..5c78f9bcc8c 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -5,6 +5,7 @@ = hidden_field_tag :group_id, params[:group_id] - if params[:project_id].present? = hidden_field_tag :project_id, params[:project_id] +- group_attributes = @group&.attributes&.slice('id', 'name')&.merge(full_name: @group&.full_name) - project_attributes = @project&.attributes&.slice('id', 'namespace_id', 'name')&.merge(name_with_namespace: @project&.name_with_namespace) - if @search_results @@ -16,7 +17,7 @@ = render_if_exists 'search/form_elasticsearch', attrs: { class: 'mb-2 mb-sm-0 align-self-center' } .gl-mt-3 - #js-search-topbar{ data: { "group-initial-data": @group.to_json, "project-initial-data": project_attributes.to_json } } + #js-search-topbar{ data: { "group-initial-data": group_attributes.to_json, "project-initial-data": project_attributes.to_json } } - if @search_term = render 'search/category' = render 'search/results' diff --git a/config/feature_flags/development/allow_archive_as_web_access_format.yml b/config/feature_flags/development/allow_archive_as_web_access_format.yml deleted file mode 100644 index 8f5d8101316..00000000000 --- a/config/feature_flags/development/allow_archive_as_web_access_format.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: allow_archive_as_web_access_format -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64471 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334944 -milestone: '14.1' -type: development -group: group::release -default_enabled: false diff --git a/config/metrics/counts_28d/20210216181923_successful_deployments.yml b/config/metrics/counts_28d/20210216181923_successful_deployments.yml index de1b4a6a583..8a8137fb6e5 100644 --- a/config/metrics/counts_28d/20210216181923_successful_deployments.yml +++ b/config/metrics/counts_28d/20210216181923_successful_deployments.yml @@ -3,15 +3,17 @@ data_category: Optional key_path: counts_monthly.successful_deployments description: Total successful deployments product_section: ops -product_stage: +product_stage: release product_group: group::release -product_category: +product_category: continuous_delivery value_type: number status: data_available time_frame: 28d data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216181924_failed_deployments.yml b/config/metrics/counts_28d/20210216181924_failed_deployments.yml index 92a7dc95186..0b483d91f5f 100644 --- a/config/metrics/counts_28d/20210216181924_failed_deployments.yml +++ b/config/metrics/counts_28d/20210216181924_failed_deployments.yml @@ -3,15 +3,17 @@ data_category: Optional key_path: counts_monthly.failed_deployments description: Total failed deployments product_section: ops -product_stage: +product_stage: release product_group: group::release -product_category: +product_category: continuous_delivery value_type: number status: data_available time_frame: 28d data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216181935_deployments.yml b/config/metrics/counts_28d/20210216181935_deployments.yml index 21dcf0a3884..6c38d6cd234 100644 --- a/config/metrics/counts_28d/20210216181935_deployments.yml +++ b/config/metrics/counts_28d/20210216181935_deployments.yml @@ -3,15 +3,16 @@ data_category: Optional key_path: usage_activity_by_stage_monthly.release.deployments description: Unique users triggering deployments product_section: ops -product_stage: +product_stage: release product_group: group::release -product_category: +product_category: continuous_delivery value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216181937_failed_deployments.yml b/config/metrics/counts_28d/20210216181937_failed_deployments.yml index 75496173175..c38d518eef4 100644 --- a/config/metrics/counts_28d/20210216181937_failed_deployments.yml +++ b/config/metrics/counts_28d/20210216181937_failed_deployments.yml @@ -3,15 +3,17 @@ data_category: Optional key_path: usage_activity_by_stage_monthly.release.failed_deployments description: Total failed deployments product_section: ops -product_stage: +product_stage: release product_group: group::release -product_category: +product_category: continuous_delivery value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216181939_releases.yml b/config/metrics/counts_28d/20210216181939_releases.yml index 232a8c12873..ff7864d2ea0 100644 --- a/config/metrics/counts_28d/20210216181939_releases.yml +++ b/config/metrics/counts_28d/20210216181939_releases.yml @@ -3,15 +3,17 @@ data_category: Optional key_path: usage_activity_by_stage_monthly.release.releases description: Unique users creating release tags product_section: ops -product_stage: +product_stage: release product_group: group::release -product_category: +product_category: continuous_delivery value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210216181941_successful_deployments.yml b/config/metrics/counts_28d/20210216181941_successful_deployments.yml index a3c5e8f7374..5e4ca7cfd50 100644 --- a/config/metrics/counts_28d/20210216181941_successful_deployments.yml +++ b/config/metrics/counts_28d/20210216181941_successful_deployments.yml @@ -3,15 +3,17 @@ data_category: Optional key_path: usage_activity_by_stage_monthly.release.successful_deployments description: Total successful deployments product_section: ops -product_stage: +product_stage: release product_group: group::release -product_category: +product_category: continuous_delivery value_type: number status: data_available time_frame: 28d -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210201124934_deployments.yml b/config/metrics/counts_all/20210201124934_deployments.yml index c4be64fb54c..af73e53be5f 100644 --- a/config/metrics/counts_all/20210201124934_deployments.yml +++ b/config/metrics/counts_all/20210201124934_deployments.yml @@ -4,7 +4,7 @@ key_path: counts.deployments description: Total deployments count product_section: ops product_stage: release -product_group: group::ops release +product_group: group::release value_type: number status: data_available milestone: "8.12" diff --git a/config/metrics/counts_all/20210216181249_feature_flags.yml b/config/metrics/counts_all/20210216181249_feature_flags.yml index 9b424c243be..03b732e9ae7 100644 --- a/config/metrics/counts_all/20210216181249_feature_flags.yml +++ b/config/metrics/counts_all/20210216181249_feature_flags.yml @@ -4,14 +4,16 @@ key_path: counts.feature_flags description: Number of feature flag toggles product_section: ops product_stage: release -product_group: group::progressive delivery -product_category: +product_group: group::release +product_category: feature_flags value_type: number status: data_available time_frame: all data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216181908_deploy_keys.yml b/config/metrics/counts_all/20210216181908_deploy_keys.yml index fcd6a193ca2..461ae55e57f 100644 --- a/config/metrics/counts_all/20210216181908_deploy_keys.yml +++ b/config/metrics/counts_all/20210216181908_deploy_keys.yml @@ -1,17 +1,19 @@ --- data_category: Optional key_path: counts.deploy_keys -description: +description: Count of deploy keys product_section: ops -product_stage: +product_stage: release product_group: group::release -product_category: +product_category: continuous_delivery value_type: number status: data_available time_frame: all data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216181911_successful_deployments.yml b/config/metrics/counts_all/20210216181911_successful_deployments.yml index 7ce7f26a17c..b0a93989471 100644 --- a/config/metrics/counts_all/20210216181911_successful_deployments.yml +++ b/config/metrics/counts_all/20210216181911_successful_deployments.yml @@ -3,15 +3,17 @@ data_category: Optional key_path: counts.successful_deployments description: Total successful deployments product_section: ops -product_stage: +product_stage: release product_group: group::release -product_category: +product_category: continuous_delivery value_type: number status: data_available time_frame: all data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216181912_failed_deployments.yml b/config/metrics/counts_all/20210216181912_failed_deployments.yml index 83cce6392b5..628dd890fe0 100644 --- a/config/metrics/counts_all/20210216181912_failed_deployments.yml +++ b/config/metrics/counts_all/20210216181912_failed_deployments.yml @@ -3,15 +3,17 @@ data_category: Optional key_path: counts.failed_deployments description: Total failed deployments product_section: ops -product_stage: +product_stage: release product_group: group::release -product_category: +product_category: continuous_delivery value_type: number status: data_available time_frame: all data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216181914_environments.yml b/config/metrics/counts_all/20210216181914_environments.yml index efb8570a3e6..a4d96d09316 100644 --- a/config/metrics/counts_all/20210216181914_environments.yml +++ b/config/metrics/counts_all/20210216181914_environments.yml @@ -3,15 +3,17 @@ data_category: Optional key_path: counts.environments description: Total available and stopped environments product_section: ops -product_stage: +product_stage: release product_group: group::release -product_category: +product_category: continuous_delivery value_type: number status: data_available time_frame: all data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216181916_in_review_folder.yml b/config/metrics/counts_all/20210216181916_in_review_folder.yml index 09a8f842a06..07e9ec8c58f 100644 --- a/config/metrics/counts_all/20210216181916_in_review_folder.yml +++ b/config/metrics/counts_all/20210216181916_in_review_folder.yml @@ -1,17 +1,19 @@ --- data_category: Optional key_path: counts.in_review_folder -description: +description: A number of environments with name review/* product_section: ops -product_stage: +product_stage: release product_group: group::release -product_category: +product_category: continuous_delivery value_type: number status: data_available time_frame: all data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216181918_releases.yml b/config/metrics/counts_all/20210216181918_releases.yml index 5ce068be420..fc9cb9aaf4b 100644 --- a/config/metrics/counts_all/20210216181918_releases.yml +++ b/config/metrics/counts_all/20210216181918_releases.yml @@ -1,17 +1,19 @@ --- data_category: Optional key_path: counts.releases -description: Unique release tags +description: Count of releases product_section: ops -product_stage: +product_stage: releases product_group: group::release -product_category: +product_category: release_orchestration value_type: number status: data_available time_frame: all data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216181926_deployments.yml b/config/metrics/counts_all/20210216181926_deployments.yml index 6cc7e7d7575..38bc758f078 100644 --- a/config/metrics/counts_all/20210216181926_deployments.yml +++ b/config/metrics/counts_all/20210216181926_deployments.yml @@ -3,15 +3,17 @@ data_category: Optional key_path: usage_activity_by_stage.release.deployments description: Unique users triggering deployments product_section: ops -product_stage: +product_stage: release product_group: group::release -product_category: +product_category: continuous_delivery value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216181928_failed_deployments.yml b/config/metrics/counts_all/20210216181928_failed_deployments.yml index 7cd10c9540a..ffba6d91a28 100644 --- a/config/metrics/counts_all/20210216181928_failed_deployments.yml +++ b/config/metrics/counts_all/20210216181928_failed_deployments.yml @@ -3,15 +3,17 @@ data_category: Optional key_path: usage_activity_by_stage.release.failed_deployments description: Total failed deployments product_section: ops -product_stage: +product_stage: release product_group: group::release -product_category: +product_category: continuous_delivery value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216181930_releases.yml b/config/metrics/counts_all/20210216181930_releases.yml index bcb81558888..dbaca9f247f 100644 --- a/config/metrics/counts_all/20210216181930_releases.yml +++ b/config/metrics/counts_all/20210216181930_releases.yml @@ -3,15 +3,17 @@ data_category: Optional key_path: usage_activity_by_stage.release.releases description: Unique users creating release tags product_section: ops -product_stage: +product_stage: release product_group: group::release -product_category: +product_category: release_orchestration value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216181932_successful_deployments.yml b/config/metrics/counts_all/20210216181932_successful_deployments.yml index 4f59431b5ab..f178023b199 100644 --- a/config/metrics/counts_all/20210216181932_successful_deployments.yml +++ b/config/metrics/counts_all/20210216181932_successful_deployments.yml @@ -3,15 +3,17 @@ data_category: Optional key_path: usage_activity_by_stage.release.successful_deployments description: Total successful deployments product_section: ops -product_stage: +product_stage: release product_group: group::release -product_category: +product_category: continuous_delivery value_type: number status: data_available time_frame: all -data_source: +data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_all/20210216181946_pages_domains.yml b/config/metrics/counts_all/20210216181946_pages_domains.yml index 4aaccd68c87..9151cfdd0a5 100644 --- a/config/metrics/counts_all/20210216181946_pages_domains.yml +++ b/config/metrics/counts_all/20210216181946_pages_domains.yml @@ -4,14 +4,16 @@ key_path: counts.pages_domains description: Total GitLab Pages domains product_section: ops product_stage: release -product_group: group::release management -product_category: +product_group: group::release +product_category: pages value_type: number status: data_available time_frame: all data_source: database distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/license/20210204124936_version.yml b/config/metrics/license/20210204124936_pages_version.yml similarity index 60% rename from config/metrics/license/20210204124936_version.yml rename to config/metrics/license/20210204124936_pages_version.yml index 2fe130ff780..d589255399c 100644 --- a/config/metrics/license/20210204124936_version.yml +++ b/config/metrics/license/20210204124936_pages_version.yml @@ -2,16 +2,18 @@ data_category: Optional key_path: gitlab_pages.version description: The version number of GitLab Pages -product_section: growth -product_stage: growth -product_group: group::product intelligence -product_category: collection +product_section: ops +product_stage: release +product_group: group::release +product_category: pages value_type: string status: data_available time_frame: none data_source: system distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/settings/20210204124934_enabled.yml b/config/metrics/settings/20210204124934_pages_enabled.yml similarity index 68% rename from config/metrics/settings/20210204124934_enabled.yml rename to config/metrics/settings/20210204124934_pages_enabled.yml index 24ba7606e08..14a2ea4bc76 100644 --- a/config/metrics/settings/20210204124934_enabled.yml +++ b/config/metrics/settings/20210204124934_pages_enabled.yml @@ -2,9 +2,9 @@ data_category: Optional key_path: gitlab_pages.enabled description: Whether GitLab Pages is enabled -product_section: growth -product_stage: growth -product_group: group::product intelligence +product_section: ops +product_stage: release +product_group: group::release product_category: collection value_type: boolean status: data_available @@ -12,6 +12,8 @@ time_frame: none data_source: system distribution: - ce +- ee tier: - free -skip_validation: true +- premium +- ultimate diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md index 2a4db4102f1..3af806d8f57 100644 --- a/doc/development/testing_guide/frontend_testing.md +++ b/doc/development/testing_guide/frontend_testing.md @@ -425,6 +425,10 @@ it('does something', () => { ### Mocking the current location in Jest +NOTE: +The value of `window.location.href` is reset before every test to avoid earlier +tests affecting later ones. + If your tests require `window.location.href` to take a particular value, use the `setWindowLocation` helper: @@ -442,7 +446,6 @@ it('passes', () => { }); ``` -NOTE: To modify only the hash, use either the `setWindowLocation` helper, or assign directly to `window.location.hash`, e.g.: diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md index 4e59a6a957d..8bb4d4f61a2 100644 --- a/doc/development/usage_ping/dictionary.md +++ b/doc/development/usage_ping/dictionary.md @@ -1156,7 +1156,7 @@ Tiers: `ultimate` ### `counts.deploy_keys` -Missing description +Count of deploy keys [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216181908_deploy_keys.yml) @@ -1166,7 +1166,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts.deployments` @@ -1174,7 +1174,7 @@ Total deployments count [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210201124934_deployments.yml) -Group: `group::ops release` +Group: `group::release` Data Category: `Optional` @@ -1224,6 +1224,20 @@ Status: `data_available` Tiers: `free`, `premium`, `ultimate` +### `counts.diff_searches` + +Total count of merge request diff searches + +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210723075525_diff_searches.yml) + +Group: `group::code review` + +Data Category: `Optional` + +Status: `implemented` + +Tiers: `free`, `premium`, `ultimate` + ### `counts.environments` Total available and stopped environments @@ -1236,7 +1250,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts.epic_issues` @@ -1292,7 +1306,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts.feature_flags` @@ -1300,13 +1314,13 @@ Number of feature flag toggles [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216181249_feature_flags.yml) -Group: `group::progressive delivery` +Group: `group::release` Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts.geo_event_log_max_id` @@ -2864,7 +2878,7 @@ Tiers: `free`, `premium`, `ultimate` ### `counts.in_review_folder` -Missing description +A number of environments with name review/* [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216181916_in_review_folder.yml) @@ -2874,7 +2888,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts.incident_issues` @@ -4988,7 +5002,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts.personal_snippets` @@ -6728,7 +6742,7 @@ Tiers: `free`, `premium`, `ultimate` ### `counts.releases` -Unique release tags +Count of releases [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216181918_releases.yml) @@ -6738,7 +6752,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts.remote_mirrors` @@ -7046,7 +7060,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts.suggestions` @@ -7706,20 +7720,6 @@ Status: `data_available` Tiers: `free`, `premium`, `ultimate` -### `counts.user_searches_diffs` - -Count of users who search merge request diffs - -[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210723075525_user_searches_diffs.yml) - -Group: `group::code review` - -Data Category: `Optional` - -Status: `implemented` - -Tiers: `free`, `premium`, `ultimate` - ### `counts.web_hooks` Missing description @@ -8026,7 +8026,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts_monthly.packages` @@ -8124,7 +8124,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `counts_weekly.aggregated_metrics.code_review_category_monthly_active_users` @@ -8424,29 +8424,29 @@ Tiers: `free` Whether GitLab Pages is enabled -[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/settings/20210204124934_enabled.yml) +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/settings/20210204124934_pages_enabled.yml) -Group: `group::product intelligence` +Group: `group::release` Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `gitlab_pages.version` The version number of GitLab Pages -[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/license/20210204124936_version.yml) +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/license/20210204124936_pages_version.yml) -Group: `group::product intelligence` +Group: `group::release` Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `gitlab_shared_runners_enabled` @@ -10716,20 +10716,6 @@ Status: `data_available` Tiers: `free`, `premium`, `ultimate` -### `redis_hll_counters.code_review.i_code_review_searches_in_diff_monthly` - -Count of searches in merge request diffs - -[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210722132444_i_code_review_searches_in_diff_monthly.yml) - -Group: `group::code review` - -Data Category: `Optional` - -Status: `implemented` - -Tiers: `free`, `premium`, `ultimate` - ### `redis_hll_counters.code_review.i_code_review_user_add_suggestion_monthly` Count of unique users per month who added a suggestion @@ -11612,6 +11598,20 @@ Status: `implemented` Tiers: `free`, `premium`, `ultimate` +### `redis_hll_counters.code_review.i_code_review_user_searches_diff_weekly` + +Count of users who search merge request diffs + +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_7d/20210720144005_i_code_review_user_searches_diff_weekly.yml) + +Group: `group::code review` + +Data Category: `Optional` + +Status: `implemented` + +Tiers: `free`, `premium`, `ultimate` + ### `redis_hll_counters.code_review.i_code_review_user_single_file_diffs_monthly` Count of unique users per month with diffs viewed file by file @@ -20422,7 +20422,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.release.failed_deployments` @@ -20436,7 +20436,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.release.projects_mirrored_with_pipelines_enabled` @@ -20464,7 +20464,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.release.successful_deployments` @@ -20478,7 +20478,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage.secure.api_fuzzing_scans` @@ -22830,7 +22830,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.release.failed_deployments` @@ -22844,7 +22844,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.release.projects_mirrored_with_pipelines_enabled` @@ -22872,7 +22872,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.release.successful_deployments` @@ -22886,7 +22886,7 @@ Data Category: `Optional` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `usage_activity_by_stage_monthly.secure.api_fuzzing_pipeline` diff --git a/doc/user/group/index.md b/doc/user/group/index.md index 99bdbc27981..c9a05e2f67f 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -430,6 +430,28 @@ Specifically: - In [GitLab 13.6 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/39504), if the user who sets up the deletion is removed from the group before the deletion happens, the job is cancelled, and the group is no longer scheduled for deletion. +## Remove a group immediately **(PREMIUM)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/336985) in GitLab 14.2. + +If you don't want to wait, you can remove a group immediately. + +Prerequisites: + +- You must have at least the Owner role for a group. +- You have [marked the group for deletion](#remove-a-group). + +To immediately remove a group marked for deletion: + +1. On the top bar, select **Menu > Groups** and find your group. +1. On the left sidebar, select **Settings > General**. +1. Expand **Advanced**. +1. In the "Permanently remove group" section, select **Remove group**. +1. Confirm the action when asked to. + +Your group, its subgroups, projects, and all related resources, including issues and merge requests, +are deleted. + ## Restore a group **(PREMIUM)** > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33257) in GitLab 12.8. diff --git a/lib/gitlab/auth/auth_finders.rb b/lib/gitlab/auth/auth_finders.rb index f54fa7504a3..a7312ac759a 100644 --- a/lib/gitlab/auth/auth_finders.rb +++ b/lib/gitlab/auth/auth_finders.rb @@ -298,7 +298,7 @@ module Gitlab when :api api_request? when :archive - archive_request? if Feature.enabled?(:allow_archive_as_web_access_format, default_enabled: :yaml) + archive_request? end end diff --git a/lib/gitlab/kas.rb b/lib/gitlab/kas.rb index 06ab0e78d9b..45582f19214 100644 --- a/lib/gitlab/kas.rb +++ b/lib/gitlab/kas.rb @@ -41,7 +41,9 @@ module Gitlab end def tunnel_url - URI.join(external_url, K8S_PROXY_PATH).to_s + uri = URI.join(external_url, K8S_PROXY_PATH) + uri.scheme = uri.scheme.in?(%w(grpcs wss)) ? 'https' : 'http' + uri.to_s end # Return GitLab KAS internal_url diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d57ab576118..57d5e3171de 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4317,6 +4317,9 @@ msgstr "" msgid "Are you ABSOLUTELY SURE you wish to delete this project?" msgstr "" +msgid "Are you ABSOLUTELY SURE you wish to remove this group?" +msgstr "" + msgid "Are you sure that you want to archive this project?" msgstr "" @@ -24072,6 +24075,9 @@ msgstr "" msgid "Permanently delete project" msgstr "" +msgid "Permanently remove group" +msgstr "" + msgid "Permissions" msgstr "" @@ -33688,6 +33694,9 @@ msgstr "" msgid "This action will %{strongOpen}permanently delete%{strongClose} %{codeOpen}%{project}%{codeClose} %{strongOpen}on %{date}%{strongClose}, including its repositories and all related resources, including issues and merge requests." msgstr "" +msgid "This action will %{strongOpen}permanently remove%{strongClose} %{codeOpen}%{group}%{codeClose} %{strongOpen}immediately%{strongClose}." +msgstr "" + msgid "This also resolves all related threads" msgstr "" @@ -37633,7 +37642,7 @@ msgstr "" msgid "You are going to delete %{project_full_name}. Deleted projects CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "" -msgid "You are going to remove %{group_name}, this will also delete all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" +msgid "You are going to remove %{group_name}. This will also delete all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "" msgid "You are going to remove the fork relationship from %{project_full_name}. Are you ABSOLUTELY sure?" diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 6b54d8ab1ac..9ed9cb1634d 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -21,7 +21,7 @@ module QA end def to_s - <<~MSG.strip % { page: @page_class } + format(<<~MSG.strip, page: @page_class) %{page} has no required elements. See https://docs.gitlab.com/ee/development/testing_guide/end_to_end/dynamic_element_validation.html#required-elements MSG @@ -232,7 +232,7 @@ module QA visible = kwargs.delete(:visible) visible = visible.nil? && true - try_find_element = ->(wait) do + try_find_element = lambda do |wait| if disabled.nil? has_css?(element_selector_css(name, kwargs), text: text, wait: wait, class: klass, visible: visible) else @@ -322,13 +322,13 @@ module QA # It would be ideal if we could detect when the animation is complete # but in some cases there's nothing we can easily access via capybara # so instead we wait for the element, and then we wait a little longer - raise ElementNotFound, %Q(Couldn't find element named "#{name}") unless has_element?(name) + raise ElementNotFound, %(Couldn't find element named "#{name}") unless has_element?(name) sleep 1 end def within_element(name, **kwargs) - wait_for_requests + wait_for_requests(skip_finished_loading_check: kwargs.delete(:skip_finished_loading_check)) text = kwargs.delete(:text) page.within(element_selector_css(name, kwargs), text: text) do @@ -386,9 +386,7 @@ module QA end def self.errors - if views.empty? - return ["Page class does not have views / elements defined!"] - end + return ["Page class does not have views / elements defined!"] if views.empty? views.flat_map(&:errors) end diff --git a/qa/qa/page/project/import/github.rb b/qa/qa/page/project/import/github.rb index 74bc4cec467..bb35c5eb17c 100644 --- a/qa/qa/page/project/import/github.rb +++ b/qa/qa/page/project/import/github.rb @@ -18,12 +18,17 @@ module QA element :import_button element :project_path_content element :go_to_project_button + element :import_status_indicator end view "app/assets/javascripts/import_entities/components/group_dropdown.vue" do element :target_namespace_selector_dropdown end + # Add personal access token + # + # @param [String] personal_access_token + # @return [void] def add_personal_access_token(personal_access_token) # If for some reasons this process is retried, user cannot re-enter github token in the same group # In this case skip this step and proceed to import project row @@ -34,71 +39,50 @@ module QA finished_loading? end - def import!(full_path, name) - return if already_imported(full_path) - - choose_test_namespace(full_path) - set_path(full_path, name) - import_project(full_path) - - wait_for_success - end - - # TODO: refactor to use 'go to project' button instead of generic main menu - def go_to_project(name) - Page::Main::Menu.perform(&:go_to_projects) - Page::Dashboard::Projects.perform do |dashboard| - dashboard.go_to_project(name) - end - end - - private - - def within_repo_path(full_path, &block) - project_import_row = find_element(:project_import_row, text: full_path) - - within(project_import_row, &block) - end - - def choose_test_namespace(full_path) - within_repo_path(full_path) do - within_element(:target_namespace_selector_dropdown) { click_button(class: 'dropdown-toggle') } - click_element(:target_group_dropdown_item, group_name: Runtime::Namespace.path) - end - end - - def set_path(full_path, name) - within_repo_path(full_path) do - fill_element(:project_path_field, name) - end - end - - def import_project(full_path) - within_repo_path(full_path) do + # Import project + # + # @param [String] source_project_name + # @param [String] target_group_path + # @return [void] + def import!(gh_project_name, target_group_path, project_name) + within_element(:project_import_row, source_project: gh_project_name) do + click_element(:target_namespace_selector_dropdown) + click_element(:target_group_dropdown_item, group_name: target_group_path) + fill_element(:project_path_field, project_name) click_element(:import_button) end end - def wait_for_success - # TODO: set reload:false and remove skip_finished_loading_check_on_refresh when - # https://gitlab.com/gitlab-org/gitlab/-/issues/292861 is fixed - wait_until( - max_duration: 90, - sleep_interval: 5.0, - reload: true, - skip_finished_loading_check_on_refresh: true - ) do - # TODO: Refactor to explicitly wait for specific project import successful status - # This check can create false positive if main importing message appears with delay and check exits early - page.has_no_content?('Importing 1 repository', wait: 3) + # Check Go to project button present + # + # @param [String] gh_project_name + # @return [Boolean] + def has_go_to_project_button?(gh_project_name) + within_element(:project_import_row, source_project: gh_project_name) do + has_element?(:go_to_project_button) end end - def already_imported(full_path) - within_repo_path(full_path) do - has_element?(:project_path_content) && has_element?(:go_to_project_button) + # Check if import page has a successfully imported project + # + # @param [String] source_project_name + # @param [Integer] wait + # @return [Boolean] + def has_imported_project?(gh_project_name, wait: QA::Support::WaitForRequests::DEFAULT_MAX_WAIT_TIME) + within_element(:project_import_row, source_project: gh_project_name, skip_finished_loading_check: true) do + # TODO: remove retrier with reload:true once https://gitlab.com/gitlab-org/gitlab/-/issues/292861 is fixed + wait_until( + max_duration: wait, + sleep_interval: 5, + reload: true, + skip_finished_loading_check_on_refresh: true + ) do + has_element?(:import_status_indicator, text: "Complete") + end end end + + alias_method :wait_for_success, :has_imported_project? end end end diff --git a/qa/qa/resource/project_imported_from_github.rb b/qa/qa/resource/project_imported_from_github.rb index 214e8f517bb..8aa19555d50 100644 --- a/qa/qa/resource/project_imported_from_github.rb +++ b/qa/qa/resource/project_imported_from_github.rb @@ -18,9 +18,12 @@ module QA Page::Project::Import::Github.perform do |import_page| import_page.add_personal_access_token(github_personal_access_token) - import_page.import!(github_repository_path, name) - import_page.go_to_project(name) + import_page.import!(github_repository_path, group.full_path, name) + import_page.wait_for_success(github_repository_path) end + + reload! + visit! end def go_to_import_page diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb index 4f85fa257a2..c55ecb28361 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb @@ -3,9 +3,11 @@ module QA RSpec.describe 'Manage', :github, :requires_admin do describe 'Project import' do - let!(:api_client) { Runtime::API::Client.as_admin } - let!(:group) { Resource::Group.fabricate_via_api! { |resource| resource.api_client = api_client } } - let!(:user) do + let(:github_repo) { 'gitlab-qa-github/test-project' } + let(:imported_project_name) { 'imported-project' } + let(:api_client) { Runtime::API::Client.as_admin } + let(:group) { Resource::Group.fabricate_via_api! { |resource| resource.api_client = api_client } } + let(:user) do Resource::User.fabricate_via_api! do |resource| resource.api_client = api_client resource.hard_delete_on_api_removal = true @@ -13,16 +15,25 @@ module QA end let(:imported_project) do - Resource::ProjectImportedFromGithub.fabricate_via_browser_ui! do |project| - project.name = 'imported-project' + Resource::ProjectImportedFromGithub.init do |project| + project.import = true + project.add_name_uuid = false + project.name = imported_project_name project.group = group project.github_personal_access_token = Runtime::Env.github_access_token - project.github_repository_path = 'gitlab-qa-github/test-project' + project.github_repository_path = github_repo end end before do group.add_member(user, Resource::Members::AccessLevel::MAINTAINER) + + Flow::Login.sign_in(as: user) + Page::Main::Menu.perform(&:go_to_create_project) + Page::Project::New.perform do |project_page| + project_page.click_import_project + project_page.click_github_link + end end after do @@ -30,13 +41,24 @@ module QA end it 'imports a GitHub repo', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1762' do - Flow::Login.sign_in(as: user) + Page::Project::Import::Github.perform do |import_page| + import_page.add_personal_access_token(Runtime::Env.github_access_token) + import_page.import!(github_repo, group.full_path, imported_project_name) - imported_project # import the project + aggregate_failures do + expect(import_page).to have_imported_project(github_repo) + # validate button is present instead of navigating to avoid dealing with multiple tabs + # which makes the test more complicated + expect(import_page).to have_go_to_project_button(github_repo) + end + end + imported_project.reload!.visit! Page::Project::Show.perform do |project| - expect(project).to have_content(imported_project.name) - expect(project).to have_content('This test project is used for automated GitHub import by GitLab QA.') + aggregate_failures do + expect(project).to have_content(imported_project_name) + expect(project).to have_content('This test project is used for automated GitHub import by GitLab QA.') + end end end end diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb index c002d199b01..8736f16b991 100644 --- a/spec/features/search/user_uses_header_search_field_spec.rb +++ b/spec/features/search/user_uses_header_search_field_spec.rb @@ -254,6 +254,7 @@ RSpec.describe 'User uses header search field', :js do href = search_path(search: term) href.concat("&project_id=#{project_id}") if project_id href.concat("&group_id=#{group_id}") if group_id + href.concat("&nav_source=navbar") ".dropdown a[href='#{href}']" end diff --git a/spec/frontend/__helpers__/mock_window_location_helper.js b/spec/frontend/__helpers__/mock_window_location_helper.js index 08a28fbbbd6..3755778e5c1 100644 --- a/spec/frontend/__helpers__/mock_window_location_helper.js +++ b/spec/frontend/__helpers__/mock_window_location_helper.js @@ -10,7 +10,7 @@ */ const useMockLocation = (fn) => { const origWindowLocation = window.location; - let currentWindowLocation; + let currentWindowLocation = origWindowLocation; Object.defineProperty(window, 'location', { get: () => currentWindowLocation, diff --git a/spec/frontend/__helpers__/set_window_location_helper_spec.js b/spec/frontend/__helpers__/set_window_location_helper_spec.js index b9c1d6f5e99..c0f3debddbc 100644 --- a/spec/frontend/__helpers__/set_window_location_helper_spec.js +++ b/spec/frontend/__helpers__/set_window_location_helper_spec.js @@ -1,4 +1,5 @@ import setWindowLocation from './set_window_location_helper'; +import { TEST_HOST } from './test_constants'; describe('helpers/set_window_location_helper', () => { const originalLocation = window.location.href; @@ -103,4 +104,30 @@ describe('helpers/set_window_location_helper', () => { ); }); }); + + // This set of tests relies on Jest executing tests in source order, which is + // at the time of writing the only order they will execute, by design. + // See https://github.com/facebook/jest/issues/4386 for more details. + describe('window.location resetting by global beforeEach', () => { + const overridden = 'https://gdk.test:1234/'; + const initial = `${TEST_HOST}/`; + + it('works before an override', () => { + expect(window.location.href).toBe(initial); + }); + + describe('overriding', () => { + beforeEach(() => { + setWindowLocation(overridden); + }); + + it('works', () => { + expect(window.location.href).toBe(overridden); + }); + }); + + it('works after an override', () => { + expect(window.location.href).toBe(initial); + }); + }); }); diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js index bc06990e03a..d0ad4eb7b05 100644 --- a/spec/frontend/diffs/components/app_spec.js +++ b/spec/frontend/diffs/components/app_spec.js @@ -259,6 +259,8 @@ describe('diffs/components/app', () => { }); it('marks current diff file based on currently highlighted row', async () => { + window.location.hash = 'ABC_123'; + createComponent({ shouldShow: true, }); @@ -429,9 +431,8 @@ describe('diffs/components/app', () => { jest.spyOn(wrapper.vm, 'refetchDiffData').mockImplementation(() => {}); jest.spyOn(wrapper.vm, 'adjustView').mockImplementation(() => {}); }; - const location = window.location.href; - beforeAll(() => { + beforeEach(() => { setWindowLocation(COMMIT_URL); document.title = 'My Title'; }); @@ -440,10 +441,6 @@ describe('diffs/components/app', () => { jest.spyOn(urlUtils, 'updateHistory'); }); - afterAll(() => { - setWindowLocation(location); - }); - it('when the commit changes and the app is not loading it should update the history, refetch the diff data, and update the view', async () => { createComponent({}, ({ state }) => { state.diffs.commit = { ...state.diffs.commit, id: 'OLD' }; diff --git a/spec/frontend/monitoring/utils_spec.js b/spec/frontend/monitoring/utils_spec.js index 25ae4dcd702..31975052077 100644 --- a/spec/frontend/monitoring/utils_spec.js +++ b/spec/frontend/monitoring/utils_spec.js @@ -448,7 +448,7 @@ describe('monitoring/utils', () => { input | urlParams ${[]} | ${''} ${[{ name: 'env', value: 'prod' }]} | ${'?var-env=prod'} - ${[{ name: 'env1', value: 'prod' }, { name: 'env2', value: null }]} | ${'?var-env=prod&var-env1=prod'} + ${[{ name: 'env1', value: 'prod' }, { name: 'env2', value: null }]} | ${'?var-env1=prod'} `( 'setCustomVariablesFromUrl updates history with query "$urlParams" with input $input', ({ input, urlParams }) => { diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/package_title_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/package_title_spec.js.snap index 848fe90cf5c..45d261625b4 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/package_title_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/package_title_spec.js.snap @@ -35,9 +35,19 @@ exports[`PackageTitle renders with tags 1`] = ` size="16" /> - + + v + 1.0.0 + published + + @@ -123,9 +133,19 @@ exports[`PackageTitle renders without tags 1`] = ` size="16" /> - + + v + 1.0.0 + published + + diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_title_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_title_spec.js index 511d5987e39..327f6d81905 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/package_title_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_title_spec.js @@ -1,3 +1,4 @@ +import { GlIcon, GlSprintf } from '@gitlab/ui'; import { GlBreakpointInstance } from '@gitlab/ui/dist/utils'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import PackageTags from '~/packages/shared/components/package_tags.vue'; @@ -9,6 +10,7 @@ import { PACKAGE_TYPE_NUGET, } from '~/packages_and_registries/package_registry/constants'; import TitleArea from '~/vue_shared/components/registry/title_area.vue'; +import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import { packageData, packageFiles, packageTags, packagePipelines } from '../../mock_data'; @@ -26,6 +28,7 @@ describe('PackageTitle', () => { propsData: { packageEntity }, stubs: { TitleArea, + GlSprintf, }, }); return wrapper.vm.$nextTick(); @@ -38,6 +41,9 @@ describe('PackageTitle', () => { const findPackageRef = () => wrapper.findByTestId('package-ref'); const findPackageTags = () => wrapper.findComponent(PackageTags); const findPackageBadges = () => wrapper.findAllByTestId('tag-badge'); + const findSubHeaderIcon = () => wrapper.findComponent(GlIcon); + const findSubHeaderText = () => wrapper.findByTestId('sub-header'); + const findSubHeaderTimeAgo = () => wrapper.findComponent(TimeAgoTooltip); afterEach(() => { wrapper.destroy(); @@ -59,6 +65,7 @@ describe('PackageTitle', () => { it('with tags on mobile', async () => { jest.spyOn(GlBreakpointInstance, 'isDesktop').mockReturnValue(false); await createComponent(); + await wrapper.vm.$nextTick(); expect(findPackageBadges()).toHaveLength(packageTags().length); @@ -93,6 +100,25 @@ describe('PackageTitle', () => { }); }); + describe('sub-header', () => { + it('has the eye icon', async () => { + await createComponent(); + + expect(findSubHeaderIcon().props('name')).toBe('eye'); + }); + + it('has a text showing version', async () => { + await createComponent(); + + expect(findSubHeaderText().text()).toMatchInterpolatedText('v 1.0.0 published'); + }); + + it('has a time ago tooltip component', async () => { + await createComponent(); + expect(findSubHeaderTimeAgo().props('time')).toBe(packageWithTags.createdAt); + }); + }); + describe.each` packageType | text ${PACKAGE_TYPE_CONAN} | ${'Conan'} diff --git a/spec/frontend/search/mock_data.js b/spec/frontend/search/mock_data.js index 24ce45e8a09..0542e96c77c 100644 --- a/spec/frontend/search/mock_data.js +++ b/spec/frontend/search/mock_data.js @@ -86,19 +86,22 @@ export const STALE_STORED_DATA = [ export const MOCK_FRESH_DATA_RES = { name: 'fresh' }; -export const PROMISE_ALL_EXPECTED_MUTATIONS = { - initGroups: { +export const PRELOAD_EXPECTED_MUTATIONS = [ + { type: types.LOAD_FREQUENT_ITEMS, payload: { key: GROUPS_LOCAL_STORAGE_KEY, data: FRESH_STORED_DATA }, }, + { + type: types.LOAD_FREQUENT_ITEMS, + payload: { key: PROJECTS_LOCAL_STORAGE_KEY, data: FRESH_STORED_DATA }, + }, +]; + +export const PROMISE_ALL_EXPECTED_MUTATIONS = { resGroups: { type: types.LOAD_FREQUENT_ITEMS, payload: { key: GROUPS_LOCAL_STORAGE_KEY, data: [MOCK_FRESH_DATA_RES, MOCK_FRESH_DATA_RES] }, }, - initProjects: { - type: types.LOAD_FREQUENT_ITEMS, - payload: { key: PROJECTS_LOCAL_STORAGE_KEY, data: FRESH_STORED_DATA }, - }, resProjects: { type: types.LOAD_FREQUENT_ITEMS, payload: { key: PROJECTS_LOCAL_STORAGE_KEY, data: [MOCK_FRESH_DATA_RES, MOCK_FRESH_DATA_RES] }, diff --git a/spec/frontend/search/store/actions_spec.js b/spec/frontend/search/store/actions_spec.js index 3755f8ffae7..9f8c83f2873 100644 --- a/spec/frontend/search/store/actions_spec.js +++ b/spec/frontend/search/store/actions_spec.js @@ -17,6 +17,7 @@ import { MOCK_GROUP, FRESH_STORED_DATA, MOCK_FRESH_DATA_RES, + PRELOAD_EXPECTED_MUTATIONS, PROMISE_ALL_EXPECTED_MUTATIONS, } from '../mock_data'; @@ -68,31 +69,31 @@ describe('Global Search Store Actions', () => { }); describe.each` - action | axiosMock | type | expectedMutations | flashCallCount | lsKey - ${actions.loadFrequentGroups} | ${{ method: 'onGet', code: 200 }} | ${'success'} | ${[PROMISE_ALL_EXPECTED_MUTATIONS.initGroups, PROMISE_ALL_EXPECTED_MUTATIONS.resGroups]} | ${0} | ${GROUPS_LOCAL_STORAGE_KEY} - ${actions.loadFrequentGroups} | ${{ method: 'onGet', code: 500 }} | ${'error'} | ${[PROMISE_ALL_EXPECTED_MUTATIONS.initGroups]} | ${1} | ${GROUPS_LOCAL_STORAGE_KEY} - ${actions.loadFrequentProjects} | ${{ method: 'onGet', code: 200 }} | ${'success'} | ${[PROMISE_ALL_EXPECTED_MUTATIONS.initProjects, PROMISE_ALL_EXPECTED_MUTATIONS.resProjects]} | ${0} | ${PROJECTS_LOCAL_STORAGE_KEY} - ${actions.loadFrequentProjects} | ${{ method: 'onGet', code: 500 }} | ${'error'} | ${[PROMISE_ALL_EXPECTED_MUTATIONS.initProjects]} | ${1} | ${PROJECTS_LOCAL_STORAGE_KEY} - `( - 'Promise.all calls', - ({ action, axiosMock, type, expectedMutations, flashCallCount, lsKey }) => { - describe(action.name, () => { - describe(`on ${type}`, () => { - beforeEach(() => { - storeUtils.loadDataFromLS = jest.fn().mockReturnValue(FRESH_STORED_DATA); - mock[axiosMock.method]().reply(axiosMock.code, MOCK_FRESH_DATA_RES); - }); + action | axiosMock | type | expectedMutations | flashCallCount + ${actions.loadFrequentGroups} | ${{ method: 'onGet', code: 200 }} | ${'success'} | ${[PROMISE_ALL_EXPECTED_MUTATIONS.resGroups]} | ${0} + ${actions.loadFrequentGroups} | ${{ method: 'onGet', code: 500 }} | ${'error'} | ${[]} | ${1} + ${actions.loadFrequentProjects} | ${{ method: 'onGet', code: 200 }} | ${'success'} | ${[PROMISE_ALL_EXPECTED_MUTATIONS.resProjects]} | ${0} + ${actions.loadFrequentProjects} | ${{ method: 'onGet', code: 500 }} | ${'error'} | ${[]} | ${1} + `('Promise.all calls', ({ action, axiosMock, type, expectedMutations, flashCallCount }) => { + describe(action.name, () => { + describe(`on ${type}`, () => { + beforeEach(() => { + state.frequentItems = { + [GROUPS_LOCAL_STORAGE_KEY]: FRESH_STORED_DATA, + [PROJECTS_LOCAL_STORAGE_KEY]: FRESH_STORED_DATA, + }; - it(`should dispatch the correct mutations`, () => { - return testAction({ action, state, expectedMutations }).then(() => { - expect(storeUtils.loadDataFromLS).toHaveBeenCalledWith(lsKey); - flashCallback(flashCallCount); - }); + mock[axiosMock.method]().reply(axiosMock.code, MOCK_FRESH_DATA_RES); + }); + + it(`should dispatch the correct mutations`, () => { + return testAction({ action, state, expectedMutations }).then(() => { + flashCallback(flashCallCount); }); }); }); - }, - ); + }); + }); describe('getGroupsData', () => { const mockCommit = () => {}; @@ -182,14 +183,38 @@ describe('Global Search Store Actions', () => { }); }); - describe('setFrequentGroup', () => { + describe('preloadStoredFrequentItems', () => { beforeEach(() => { - storeUtils.setFrequentItemToLS = jest.fn(); + storeUtils.loadDataFromLS = jest.fn().mockReturnValue(FRESH_STORED_DATA); }); - it(`calls setFrequentItemToLS with ${GROUPS_LOCAL_STORAGE_KEY} and item data`, async () => { + it('calls preloadStoredFrequentItems for both groups and projects and commits LOAD_FREQUENT_ITEMS', async () => { + await testAction({ + action: actions.preloadStoredFrequentItems, + state, + expectedMutations: PRELOAD_EXPECTED_MUTATIONS, + }); + + expect(storeUtils.loadDataFromLS).toHaveBeenCalledTimes(2); + expect(storeUtils.loadDataFromLS).toHaveBeenCalledWith(GROUPS_LOCAL_STORAGE_KEY); + expect(storeUtils.loadDataFromLS).toHaveBeenCalledWith(PROJECTS_LOCAL_STORAGE_KEY); + }); + }); + + describe('setFrequentGroup', () => { + beforeEach(() => { + storeUtils.setFrequentItemToLS = jest.fn().mockReturnValue(FRESH_STORED_DATA); + }); + + it(`calls setFrequentItemToLS with ${GROUPS_LOCAL_STORAGE_KEY} and item data then commits LOAD_FREQUENT_ITEMS`, async () => { await testAction({ action: actions.setFrequentGroup, + expectedMutations: [ + { + type: types.LOAD_FREQUENT_ITEMS, + payload: { key: GROUPS_LOCAL_STORAGE_KEY, data: FRESH_STORED_DATA }, + }, + ], payload: MOCK_GROUP, state, }); @@ -204,12 +229,18 @@ describe('Global Search Store Actions', () => { describe('setFrequentProject', () => { beforeEach(() => { - storeUtils.setFrequentItemToLS = jest.fn(); + storeUtils.setFrequentItemToLS = jest.fn().mockReturnValue(FRESH_STORED_DATA); }); it(`calls setFrequentItemToLS with ${PROJECTS_LOCAL_STORAGE_KEY} and item data`, async () => { await testAction({ action: actions.setFrequentProject, + expectedMutations: [ + { + type: types.LOAD_FREQUENT_ITEMS, + payload: { key: PROJECTS_LOCAL_STORAGE_KEY, data: FRESH_STORED_DATA }, + }, + ], payload: MOCK_PROJECT, state, }); diff --git a/spec/frontend/search/store/utils_spec.js b/spec/frontend/search/store/utils_spec.js index 5055fa2cc3d..cd7f7dc3b5f 100644 --- a/spec/frontend/search/store/utils_spec.js +++ b/spec/frontend/search/store/utils_spec.js @@ -51,19 +51,25 @@ describe('Global Search Store Utils', () => { describe('setFrequentItemToLS', () => { const frequentItems = {}; + let res; describe('with existing data', () => { describe(`when frequency is less than ${MAX_FREQUENCY}`, () => { beforeEach(() => { frequentItems[MOCK_LS_KEY] = [{ ...MOCK_GROUPS[0], frequency: 1, lastUsed: PREV_TIME }]; - setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_GROUPS[0]); + res = setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_GROUPS[0]); }); - it('adds 1 to the frequency, tracks lastUsed, and calls localStorage.setItem', () => { + it('adds 1 to the frequency, tracks lastUsed, calls localStorage.setItem and returns the array', () => { + const updatedFrequentItems = [ + { ...MOCK_GROUPS[0], frequency: 2, lastUsed: CURRENT_TIME }, + ]; + expect(localStorage.setItem).toHaveBeenCalledWith( MOCK_LS_KEY, - JSON.stringify([{ ...MOCK_GROUPS[0], frequency: 2, lastUsed: CURRENT_TIME }]), + JSON.stringify(updatedFrequentItems), ); + expect(res).toEqual(updatedFrequentItems); }); }); @@ -72,16 +78,19 @@ describe('Global Search Store Utils', () => { frequentItems[MOCK_LS_KEY] = [ { ...MOCK_GROUPS[0], frequency: MAX_FREQUENCY, lastUsed: PREV_TIME }, ]; - setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_GROUPS[0]); + res = setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_GROUPS[0]); }); - it(`does not further increase frequency past ${MAX_FREQUENCY}, tracks lastUsed, and calls localStorage.setItem`, () => { + it(`does not further increase frequency past ${MAX_FREQUENCY}, tracks lastUsed, calls localStorage.setItem, and returns the array`, () => { + const updatedFrequentItems = [ + { ...MOCK_GROUPS[0], frequency: MAX_FREQUENCY, lastUsed: CURRENT_TIME }, + ]; + expect(localStorage.setItem).toHaveBeenCalledWith( MOCK_LS_KEY, - JSON.stringify([ - { ...MOCK_GROUPS[0], frequency: MAX_FREQUENCY, lastUsed: CURRENT_TIME }, - ]), + JSON.stringify(updatedFrequentItems), ); + expect(res).toEqual(updatedFrequentItems); }); }); }); @@ -89,14 +98,17 @@ describe('Global Search Store Utils', () => { describe('with no existing data', () => { beforeEach(() => { frequentItems[MOCK_LS_KEY] = []; - setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_GROUPS[0]); + res = setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_GROUPS[0]); }); - it('adds a new entry with frequency 1, tracks lastUsed, and calls localStorage.setItem', () => { + it('adds a new entry with frequency 1, tracks lastUsed, calls localStorage.setItem, and returns the array', () => { + const updatedFrequentItems = [{ ...MOCK_GROUPS[0], frequency: 1, lastUsed: CURRENT_TIME }]; + expect(localStorage.setItem).toHaveBeenCalledWith( MOCK_LS_KEY, - JSON.stringify([{ ...MOCK_GROUPS[0], frequency: 1, lastUsed: CURRENT_TIME }]), + JSON.stringify(updatedFrequentItems), ); + expect(res).toEqual(updatedFrequentItems); }); }); @@ -107,18 +119,21 @@ describe('Global Search Store Utils', () => { { id: 2, frequency: 1, lastUsed: PREV_TIME }, { id: 3, frequency: 1, lastUsed: PREV_TIME }, ]; - setFrequentItemToLS(MOCK_LS_KEY, frequentItems, { id: 3 }); + res = setFrequentItemToLS(MOCK_LS_KEY, frequentItems, { id: 3 }); }); - it('sorts the array by most frequent and lastUsed', () => { + it('sorts the array by most frequent and lastUsed and returns the array', () => { + const updatedFrequentItems = [ + { id: 3, frequency: 2, lastUsed: CURRENT_TIME }, + { id: 1, frequency: 2, lastUsed: PREV_TIME }, + { id: 2, frequency: 1, lastUsed: PREV_TIME }, + ]; + expect(localStorage.setItem).toHaveBeenCalledWith( MOCK_LS_KEY, - JSON.stringify([ - { id: 3, frequency: 2, lastUsed: CURRENT_TIME }, - { id: 1, frequency: 2, lastUsed: PREV_TIME }, - { id: 2, frequency: 1, lastUsed: PREV_TIME }, - ]), + JSON.stringify(updatedFrequentItems), ); + expect(res).toEqual(updatedFrequentItems); }); }); @@ -131,31 +146,35 @@ describe('Global Search Store Utils', () => { { id: 4, frequency: 2, lastUsed: PREV_TIME }, { id: 5, frequency: 1, lastUsed: PREV_TIME }, ]; - setFrequentItemToLS(MOCK_LS_KEY, frequentItems, { id: 6 }); + res = setFrequentItemToLS(MOCK_LS_KEY, frequentItems, { id: 6 }); }); - it('removes the last item in the array', () => { + it('removes the last item in the array and returns the array', () => { + const updatedFrequentItems = [ + { id: 1, frequency: 5, lastUsed: PREV_TIME }, + { id: 2, frequency: 4, lastUsed: PREV_TIME }, + { id: 3, frequency: 3, lastUsed: PREV_TIME }, + { id: 4, frequency: 2, lastUsed: PREV_TIME }, + { id: 6, frequency: 1, lastUsed: CURRENT_TIME }, + ]; + expect(localStorage.setItem).toHaveBeenCalledWith( MOCK_LS_KEY, - JSON.stringify([ - { id: 1, frequency: 5, lastUsed: PREV_TIME }, - { id: 2, frequency: 4, lastUsed: PREV_TIME }, - { id: 3, frequency: 3, lastUsed: PREV_TIME }, - { id: 4, frequency: 2, lastUsed: PREV_TIME }, - { id: 6, frequency: 1, lastUsed: CURRENT_TIME }, - ]), + JSON.stringify(updatedFrequentItems), ); + expect(res).toEqual(updatedFrequentItems); }); }); describe('with null data loaded in', () => { beforeEach(() => { frequentItems[MOCK_LS_KEY] = null; - setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_GROUPS[0]); + res = setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_GROUPS[0]); }); - it('wipes local storage', () => { + it('wipes local storage and returns empty array', () => { expect(localStorage.removeItem).toHaveBeenCalledWith(MOCK_LS_KEY); + expect(res).toEqual([]); }); }); @@ -163,14 +182,17 @@ describe('Global Search Store Utils', () => { beforeEach(() => { const MOCK_ADDITIONAL_DATA_GROUP = { ...MOCK_GROUPS[0], extraData: 'test' }; frequentItems[MOCK_LS_KEY] = []; - setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_ADDITIONAL_DATA_GROUP); + res = setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_ADDITIONAL_DATA_GROUP); }); - it('parses out extra data for LS', () => { + it('parses out extra data for LS and returns the array', () => { + const updatedFrequentItems = [{ ...MOCK_GROUPS[0], frequency: 1, lastUsed: CURRENT_TIME }]; + expect(localStorage.setItem).toHaveBeenCalledWith( MOCK_LS_KEY, - JSON.stringify([{ ...MOCK_GROUPS[0], frequency: 1, lastUsed: CURRENT_TIME }]), + JSON.stringify(updatedFrequentItems), ); + expect(res).toEqual(updatedFrequentItems); }); }); }); diff --git a/spec/frontend/search/topbar/components/app_spec.js b/spec/frontend/search/topbar/components/app_spec.js index fb953f2ed1b..7ce5efb3c52 100644 --- a/spec/frontend/search/topbar/components/app_spec.js +++ b/spec/frontend/search/topbar/components/app_spec.js @@ -1,13 +1,13 @@ import { GlForm, GlSearchBoxByType, GlButton } from '@gitlab/ui'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import { MOCK_QUERY } from 'jest/search/mock_data'; import GlobalSearchTopbar from '~/search/topbar/components/app.vue'; import GroupFilter from '~/search/topbar/components/group_filter.vue'; import ProjectFilter from '~/search/topbar/components/project_filter.vue'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('GlobalSearchTopbar', () => { let wrapper; @@ -15,6 +15,7 @@ describe('GlobalSearchTopbar', () => { const actionSpies = { applyQuery: jest.fn(), setQuery: jest.fn(), + preloadStoredFrequentItems: jest.fn(), }; const createComponent = (initialState) => { @@ -27,14 +28,12 @@ describe('GlobalSearchTopbar', () => { }); wrapper = shallowMount(GlobalSearchTopbar, { - localVue, store, }); }; afterEach(() => { wrapper.destroy(); - wrapper = null; }); const findTopbarForm = () => wrapper.find(GlForm); @@ -110,4 +109,14 @@ describe('GlobalSearchTopbar', () => { expect(actionSpies.applyQuery).toHaveBeenCalled(); }); }); + + describe('onCreate', () => { + beforeEach(() => { + createComponent(); + }); + + it('calls preloadStoredFrequentItems', () => { + expect(actionSpies.preloadStoredFrequentItems).toHaveBeenCalled(); + }); + }); }); diff --git a/spec/frontend/search/topbar/components/group_filter_spec.js b/spec/frontend/search/topbar/components/group_filter_spec.js index fbd7ad6bb57..bd173791fee 100644 --- a/spec/frontend/search/topbar/components/group_filter_spec.js +++ b/spec/frontend/search/topbar/components/group_filter_spec.js @@ -51,7 +51,6 @@ describe('GroupFilter', () => { afterEach(() => { wrapper.destroy(); - wrapper = null; }); const findSearchableDropdown = () => wrapper.find(SearchableDropdown); @@ -89,10 +88,11 @@ describe('GroupFilter', () => { findSearchableDropdown().vm.$emit('change', ANY_OPTION); }); - it('calls setUrlParams with group null, project id null, and then calls visitUrl', () => { + it('calls setUrlParams with group null, project id null, nav_source null, and then calls visitUrl', () => { expect(setUrlParams).toHaveBeenCalledWith({ [GROUP_DATA.queryParam]: null, [PROJECT_DATA.queryParam]: null, + nav_source: null, }); expect(visitUrl).toHaveBeenCalled(); @@ -108,10 +108,11 @@ describe('GroupFilter', () => { findSearchableDropdown().vm.$emit('change', MOCK_GROUP); }); - it('calls setUrlParams with group id, project id null, and then calls visitUrl', () => { + it('calls setUrlParams with group id, project id null, nav_source null, and then calls visitUrl', () => { expect(setUrlParams).toHaveBeenCalledWith({ [GROUP_DATA.queryParam]: MOCK_GROUP.id, [PROJECT_DATA.queryParam]: null, + nav_source: null, }); expect(visitUrl).toHaveBeenCalled(); @@ -156,4 +157,31 @@ describe('GroupFilter', () => { }); }); }); + + describe.each` + navSource | initialData | callMethod + ${null} | ${null} | ${false} + ${null} | ${MOCK_GROUP} | ${false} + ${'navbar'} | ${null} | ${false} + ${'navbar'} | ${MOCK_GROUP} | ${true} + `('onCreate', ({ navSource, initialData, callMethod }) => { + describe(`when nav_source is ${navSource} and ${ + initialData ? 'has' : 'does not have' + } an initial group`, () => { + beforeEach(() => { + createComponent({ query: { ...MOCK_QUERY, nav_source: navSource } }, { initialData }); + }); + + it(`${callMethod ? 'does' : 'does not'} call setFrequentGroup`, () => { + if (callMethod) { + expect(actionSpies.setFrequentGroup).toHaveBeenCalledWith( + expect.any(Object), + initialData, + ); + } else { + expect(actionSpies.setFrequentGroup).not.toHaveBeenCalled(); + } + }); + }); + }); }); diff --git a/spec/frontend/search/topbar/components/project_filter_spec.js b/spec/frontend/search/topbar/components/project_filter_spec.js index 63b0f882ca4..5afcd281d0c 100644 --- a/spec/frontend/search/topbar/components/project_filter_spec.js +++ b/spec/frontend/search/topbar/components/project_filter_spec.js @@ -51,7 +51,6 @@ describe('ProjectFilter', () => { afterEach(() => { wrapper.destroy(); - wrapper = null; }); const findSearchableDropdown = () => wrapper.find(SearchableDropdown); @@ -89,9 +88,10 @@ describe('ProjectFilter', () => { findSearchableDropdown().vm.$emit('change', ANY_OPTION); }); - it('calls setUrlParams with null, no group id, then calls visitUrl', () => { + it('calls setUrlParams with null, no group id, nav_source null, then calls visitUrl', () => { expect(setUrlParams).toHaveBeenCalledWith({ [PROJECT_DATA.queryParam]: null, + nav_source: null, }); expect(visitUrl).toHaveBeenCalled(); }); @@ -106,10 +106,11 @@ describe('ProjectFilter', () => { findSearchableDropdown().vm.$emit('change', MOCK_PROJECT); }); - it('calls setUrlParams with project id, group id, then calls visitUrl', () => { + it('calls setUrlParams with project id, group id, nav_source null, then calls visitUrl', () => { expect(setUrlParams).toHaveBeenCalledWith({ [GROUP_DATA.queryParam]: MOCK_PROJECT.namespace.id, [PROJECT_DATA.queryParam]: MOCK_PROJECT.id, + nav_source: null, }); expect(visitUrl).toHaveBeenCalled(); }); @@ -157,4 +158,31 @@ describe('ProjectFilter', () => { }); }); }); + + describe.each` + navSource | initialData | callMethod + ${null} | ${null} | ${false} + ${null} | ${MOCK_PROJECT} | ${false} + ${'navbar'} | ${null} | ${false} + ${'navbar'} | ${MOCK_PROJECT} | ${true} + `('onCreate', ({ navSource, initialData, callMethod }) => { + describe(`when nav_source is ${navSource} and ${ + initialData ? 'has' : 'does not have' + } an initial project`, () => { + beforeEach(() => { + createComponent({ query: { ...MOCK_QUERY, nav_source: navSource } }, { initialData }); + }); + + it(`${callMethod ? 'does' : 'does not'} call setFrequentProject`, () => { + if (callMethod) { + expect(actionSpies.setFrequentProject).toHaveBeenCalledWith( + expect.any(Object), + initialData, + ); + } else { + expect(actionSpies.setFrequentProject).not.toHaveBeenCalled(); + } + }); + }); + }); }); diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index 9b95ed6b816..4d1b0f54e42 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -3,6 +3,8 @@ import * as jqueryMatchers from 'custom-jquery-matchers'; import Vue from 'vue'; import 'jquery'; import { setGlobalDateToFakeDate } from 'helpers/fake_date'; +import setWindowLocation from 'helpers/set_window_location_helper'; +import { TEST_HOST } from 'helpers/test_constants'; import Translate from '~/vue_shared/translate'; import { getJSONFixture, loadHTMLFixture, setHTMLFixture } from './__helpers__/fixtures'; import { initializeTestTimeout } from './__helpers__/timeout'; @@ -88,8 +90,13 @@ Object.assign(global, { }, }); -// make sure that each test actually tests something -// see https://jestjs.io/docs/en/expect#expecthasassertions beforeEach(() => { + // make sure that each test actually tests something + // see https://jestjs.io/docs/en/expect#expecthasassertions expect.hasAssertions(); + + // Reset the mocked window.location. This ensures tests don't interfere with + // each other, and removes the need to tidy up if it was changed for a given + // test. + setWindowLocation(TEST_HOST); }); diff --git a/spec/frontend/vue_shared/components/url_sync_spec.js b/spec/frontend/vue_shared/components/url_sync_spec.js index 86bbc146c5f..aefe6a5c3e8 100644 --- a/spec/frontend/vue_shared/components/url_sync_spec.js +++ b/spec/frontend/vue_shared/components/url_sync_spec.js @@ -1,5 +1,4 @@ import { shallowMount } from '@vue/test-utils'; -import setWindowLocation from 'helpers/set_window_location_helper'; import { historyPushState } from '~/lib/utils/common_utils'; import { mergeUrlParams } from '~/lib/utils/url_utility'; import UrlSyncComponent from '~/vue_shared/components/url_sync.vue'; @@ -15,9 +14,6 @@ jest.mock('~/lib/utils/common_utils', () => ({ describe('url sync component', () => { let wrapper; const mockQuery = { group_id: '5014437163714', project_ids: ['5014437608314'] }; - const TEST_HOST = 'http://testhost/'; - - setWindowLocation(TEST_HOST); const findButton = () => wrapper.find('button'); @@ -35,7 +31,9 @@ describe('url sync component', () => { const expectUrlSync = (query, times, mergeUrlParamsReturnValue) => { expect(mergeUrlParams).toHaveBeenCalledTimes(times); - expect(mergeUrlParams).toHaveBeenCalledWith(query, TEST_HOST, { spreadArrays: true }); + expect(mergeUrlParams).toHaveBeenCalledWith(query, window.location.href, { + spreadArrays: true, + }); expect(historyPushState).toHaveBeenCalledTimes(times); expect(historyPushState).toHaveBeenCalledWith(mergeUrlParamsReturnValue); diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb index 2d4239eb761..b0522e269e0 100644 --- a/spec/lib/gitlab/auth/auth_finders_spec.rb +++ b/spec/lib/gitlab/auth/auth_finders_spec.rb @@ -496,18 +496,6 @@ RSpec.describe Gitlab::Auth::AuthFinders do expect(find_user_from_web_access_token(:archive)).to eq(user) end - context 'when allow_archive_as_web_access_format feature flag is disabled' do - before do - stub_feature_flags(allow_archive_as_web_access_format: false) - end - - it 'returns nil for ARCHIVE requests' do - set_header('SCRIPT_NAME', '/-/archive/main.zip') - - expect(find_user_from_web_access_token(:archive)).to be_nil - end - end - context 'for API requests' do it 'returns the user' do set_header('SCRIPT_NAME', '/api/endpoint') diff --git a/spec/lib/gitlab/kas_spec.rb b/spec/lib/gitlab/kas_spec.rb index bf70b83fb73..17d038ed16c 100644 --- a/spec/lib/gitlab/kas_spec.rb +++ b/spec/lib/gitlab/kas_spec.rb @@ -66,8 +66,34 @@ RSpec.describe Gitlab::Kas do end describe '.tunnel_url' do - it 'returns gitlab_kas external_url with proxy path appended' do - expect(described_class.tunnel_url).to eq(Gitlab.config.gitlab_kas.external_url + '/k8s-proxy') + before do + stub_config(gitlab_kas: { external_url: external_url }) + end + + subject { described_class.tunnel_url } + + context 'external_url uses wss://' do + let(:external_url) { 'wss://kas.gitlab.example.com' } + + it { is_expected.to eq('https://kas.gitlab.example.com/k8s-proxy') } + end + + context 'external_url uses ws://' do + let(:external_url) { 'ws://kas.gitlab.example.com' } + + it { is_expected.to eq('http://kas.gitlab.example.com/k8s-proxy') } + end + + context 'external_url uses grpcs://' do + let(:external_url) { 'grpcs://kas.gitlab.example.com' } + + it { is_expected.to eq('https://kas.gitlab.example.com/k8s-proxy') } + end + + context 'external_url uses grpc://' do + let(:external_url) { 'grpc://kas.gitlab.example.com' } + + it { is_expected.to eq('http://kas.gitlab.example.com/k8s-proxy') } end end