From 2e2cd0ea3e50af738049eccde210713f976a6d7c Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 2 Jun 2020 00:08:07 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- GITALY_SERVER_VERSION | 2 +- .../javascripts/lib/utils/common_utils.js | 2 +- app/assets/javascripts/main.js | 9 +- .../javascripts/persistent_user_callouts.js | 15 ++ app/assets/javascripts/search_autocomplete.js | 122 +++++++------ app/controllers/application_controller.rb | 1 + app/helpers/application_helper.rb | 3 +- app/helpers/page_layout_helper.rb | 10 ++ .../chat_message/merge_message.rb | 2 +- app/views/layouts/_page.html.haml | 1 + app/views/layouts/_search.html.haml | 33 ++-- app/views/layouts/header/_default.html.haml | 8 +- ...e-request-webhook-message-is-malformed.yml | 5 + ...n-to-search-dropdown-where-not-present.yml | 5 + doc/ci/pipelines/job_artifacts.md | 3 +- doc/development/telemetry/usage_ping.md | 162 +++++++++--------- doc/install/installation.md | 2 +- doc/install/requirements.md | 4 +- doc/update/README.md | 10 ++ doc/update/upgrading_from_source.md | 15 +- lib/gitlab/search_context.rb | 162 ++++++++++++++++++ locale/gitlab.pot | 18 +- .../projects/issues_controller_spec.rb | 3 +- .../user_uses_header_search_field_spec.rb | 8 - spec/helpers/application_helper_spec.rb | 25 ++- spec/helpers/page_layout_helper_spec.rb | 15 ++ .../lib/gitlab/search_context/builder_spec.rb | 152 ++++++++++++++++ .../search_context/controller_concern_spec.rb | 82 +++++++++ .../chat_message/merge_message_spec.rb | 8 +- 29 files changed, 685 insertions(+), 202 deletions(-) create mode 100644 app/assets/javascripts/persistent_user_callouts.js create mode 100644 changelogs/unreleased/21002-slack-opened-merge-request-webhook-message-is-malformed.yml create mode 100644 changelogs/unreleased/213597-add-in-this-group-option-to-search-dropdown-where-not-present.yml create mode 100644 lib/gitlab/search_context.rb create mode 100644 spec/lib/gitlab/search_context/builder_spec.rb create mode 100644 spec/lib/gitlab/search_context/controller_concern_spec.rb diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index b3bbf604c0f..68ac810549f 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -d08b7024a0a2882dc55d8a480d891fc0ded5bb9b +13.1.0-rc2 diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 4a48852159a..905a0b9d5ef 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -31,7 +31,7 @@ export const getProjectSlug = () => { }; export const getGroupSlug = () => { - if (isInGroupsPage()) { + if (isInProjectPage() || isInGroupsPage()) { return $('body').data('group'); } return null; diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index dbe445b374d..6e3066f55d5 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -36,7 +36,7 @@ import initSearchAutocomplete from './search_autocomplete'; import GlFieldErrors from './gl_field_errors'; import initUserPopovers from './user_popovers'; import initBroadcastNotifications from './broadcast_notification'; -import PersistentUserCallout from './persistent_user_callout'; +import initPersistentUserCallouts from './persistent_user_callouts'; import { initUserTracking } from './tracking'; import { __ } from './locale'; @@ -108,12 +108,7 @@ function deferredInitialisation() { initUserPopovers(); initBroadcastNotifications(); initFrequentItemDropdowns(); - - const recoverySettingsCallout = document.querySelector('.js-recovery-settings-callout'); - PersistentUserCallout.factory(recoverySettingsCallout); - - const usersOverLicenseCallout = document.querySelector('.js-users-over-license-callout'); - PersistentUserCallout.factory(usersOverLicenseCallout); + initPersistentUserCallouts(); if (document.querySelector('.search')) initSearchAutocomplete(); diff --git a/app/assets/javascripts/persistent_user_callouts.js b/app/assets/javascripts/persistent_user_callouts.js new file mode 100644 index 00000000000..6e292299778 --- /dev/null +++ b/app/assets/javascripts/persistent_user_callouts.js @@ -0,0 +1,15 @@ +import PersistentUserCallout from './persistent_user_callout'; + +const PERSISTENT_USER_CALLOUTS = [ + '.js-recovery-settings-callout', + '.js-users-over-license-callout', + '.js-admin-licensed-user-count-threshold', +]; + +const initCallouts = () => { + PERSISTENT_USER_CALLOUTS.forEach(calloutContainer => + PersistentUserCallout.factory(document.querySelector(calloutContainer)), + ); +}; + +export default initCallouts; diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index d8eb981c106..05e0b9e7089 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import { escape, throttle } from 'lodash'; -import { s__, __ } from '~/locale'; +import { s__, __, sprintf } from '~/locale'; import { getIdenticonBackgroundClass, getIdenticonTitle } from '~/helpers/avatar_helper'; import axios from './lib/utils/axios_utils'; import { @@ -170,33 +170,24 @@ export class SearchAutocomplete { }, }) .then(response => { - // Hide dropdown menu if no suggestions returns - if (!response.data.length) { - this.disableAutocomplete(); - return; - } + const options = this.scopedSearchOptions(term); - const data = []; // List results - let firstCategory = true; - let lastCategory; + let lastCategory = null; for (let i = 0, len = response.data.length; i < len; i += 1) { const suggestion = response.data[i]; // Add group header before list each group if (lastCategory !== suggestion.category) { - if (!firstCategory) { - data.push({ type: 'separator' }); - } - if (firstCategory) { - firstCategory = false; - } - data.push({ + options.push({ type: 'separator' }); + options.push({ type: 'header', content: suggestion.category, }); lastCategory = suggestion.category; } - data.push({ + + // Add the suggestion + options.push({ id: `${suggestion.category.toLowerCase()}-${suggestion.id}`, icon: this.getAvatar(suggestion), category: suggestion.category, @@ -204,39 +195,8 @@ export class SearchAutocomplete { url: suggestion.url, }); } - // Add option to proceed with the search - if (data.length) { - const icon = spriteIcon('search', 's16 inline-search-icon'); - let template; - if (this.projectInputEl.val()) { - template = s__('SearchAutocomplete|in this project'); - } - if (this.groupInputEl.val()) { - template = s__('SearchAutocomplete|in this group'); - } - - data.unshift({ type: 'separator' }); - data.unshift({ - icon, - text: term, - template: s__('SearchAutocomplete|in all GitLab'), - url: `${gon.relative_url_root}/search?search=${term}`, - }); - - if (template) { - data.unshift({ - icon, - text: term, - template, - url: `${ - gon.relative_url_root - }/search?search=${term}&project_id=${this.projectInputEl.val()}&group_id=${this.groupInputEl.val()}`, - }); - } - } - - callback(data); + callback(options); this.loadingSuggestions = false; this.highlightFirstRow(); @@ -253,10 +213,10 @@ export class SearchAutocomplete { // Get options let options; - if (isInGroupsPage() && groupOptions) { - options = groupOptions[getGroupSlug()]; - } else if (isInProjectPage() && projectOptions) { + if (isInProjectPage() && projectOptions) { options = projectOptions[getProjectSlug()]; + } else if (isInGroupsPage() && groupOptions) { + options = groupOptions[getGroupSlug()]; } else if (dashboardOptions) { options = dashboardOptions; } @@ -301,6 +261,64 @@ export class SearchAutocomplete { return items; } + // Add option to proceed with the search for each + // scope that is currently available, namely: + // + // - Search in this project + // - Search in this group (or project's group) + // - Search in all GitLab + scopedSearchOptions(term) { + const icon = spriteIcon('search', 's16 inline-search-icon'); + const projectId = this.projectInputEl.val(); + const groupId = this.groupInputEl.val(); + const options = []; + + 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}`; + + options.push({ + icon, + text: term, + template: sprintf( + s__(`SearchAutocomplete|in project %{projectName}`), + { + projectName: `${projectOptions.name}`, + }, + false, + ), + url, + }); + } + + if (groupId) { + const groupOptions = gl.groupOptions[getGroupSlug()]; + options.push({ + icon, + text: term, + template: sprintf( + s__(`SearchAutocomplete|in group %{groupName}`), + { + groupName: `${groupOptions.name}`, + }, + false, + ), + url: `${gon.relative_url_root}/search?search=${term}&group_id=${groupId}`, + }); + } + + options.push({ + icon, + text: term, + template: s__('SearchAutocomplete|in all GitLab'), + url: `${gon.relative_url_root}/search?search=${term}`, + }); + + return options; + } + serializeState() { return { // Search Criteria diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 54e3275662b..79a164a5574 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -12,6 +12,7 @@ class ApplicationController < ActionController::Base include WorkhorseHelper include EnforcesTwoFactorAuthentication include WithPerformanceBar + include Gitlab::SearchContext::ControllerConcern include SessionlessAuthentication include SessionsHelper include ConfirmEmailWarning diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2df33073a89..bdfdf5a69b3 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -103,7 +103,7 @@ module ApplicationHelper page: body_data_page, page_type_id: controller.params[:id], find_file: find_file_path, - group: "#{@group&.path}" + group: @group&.path }.merge(project_data) end @@ -113,6 +113,7 @@ module ApplicationHelper { project_id: @project.id, project: @project.path, + group: @project.group&.path, namespace_id: @project.namespace&.id } end diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 46e2c9ce56e..a44760e85ca 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -104,6 +104,16 @@ module PageLayoutHelper end end + # This helper ensures there is always a default `Gitlab::SearchContext` available + # to all controller that use the application layout. + def search_context + strong_memoize(:search_context) do + next super if defined?(super) + + Gitlab::SearchContext::Builder.new(controller.view_context).build! + end + end + def fluid_layout current_user && current_user.layout == "fluid" end diff --git a/app/models/project_services/chat_message/merge_message.rb b/app/models/project_services/chat_message/merge_message.rb index 0a2d9120adc..c4fcdff8386 100644 --- a/app/models/project_services/chat_message/merge_message.rb +++ b/app/models/project_services/chat_message/merge_message.rb @@ -48,7 +48,7 @@ module ChatMessage end def merge_request_message - "#{user_combined_name} #{state_or_action_text} #{merge_request_link} in #{project_link}" + "#{user_combined_name} #{state_or_action_text} merge request #{merge_request_link} in #{project_link}" end def merge_request_link diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 3885fa311ba..57445424b26 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -8,6 +8,7 @@ = render_if_exists 'layouts/header/users_over_license_banner' - if Feature.enabled?(:subscribable_banner_license, default_enabled: true) = render_if_exists "layouts/header/ee_subscribable_banner" + = render_if_exists "layouts/header/licensed_user_count_threshold" = render "layouts/broadcast" = render "layouts/header/read_only_banner" = render "layouts/nav/classification_level_banner" diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index ba5cd0fdd41..81fe0798bd1 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -1,7 +1,3 @@ -- if @group && @group.persisted? && @group.path - - group_data_attrs = { group_path: j(@group.path), name: j(@group.name), issues_path: issues_group_path(@group), mr_path: merge_requests_group_path(@group) } -- if @project && @project.persisted? - - project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: project_issues_path(@project), mr_path: project_merge_requests_path(@project), issues_disabled: !@project.issues_enabled? } .search.search-form{ data: { track_label: "navbar_search", track_event: "activate_form_input", track_value: "" } } = form_tag search_path, method: :get, class: 'form-inline' do |f| .search-input-container @@ -27,27 +23,20 @@ = sprite_icon('search', size: 16, css_class: 'search-icon') = sprite_icon('close', size: 16, css_class: 'clear-icon js-clear-input') - = hidden_field_tag :group_id, @group.try(:id), class: 'js-search-group-options', data: group_data_attrs + = hidden_field_tag :group_id, search_context.for_group? ? search_context.group.id : '', class: 'js-search-group-options', data: search_context.group_metadata + = hidden_field_tag :project_id, search_context.for_project? ? search_context.project.id : '', id: 'search_project_id', class: 'js-search-project-options', data: search_context.project_metadata - = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id', class: 'js-search-project-options', data: project_data_attrs + - if search_context.for_project? + = hidden_field_tag :scope, search_context.scope + = hidden_field_tag :search_code, search_context.code_search? - - if @project && @project.persisted? - - if current_controller?(:issues) - = hidden_field_tag :scope, 'issues' - - elsif current_controller?(:merge_requests) - = hidden_field_tag :scope, 'merge_requests' - - elsif current_controller?(:wikis) - = hidden_field_tag :scope, 'wiki_blobs' - - elsif current_controller?(:commits) - = hidden_field_tag :scope, 'commits' - - else - = hidden_field_tag :search_code, true - - - if @snippet || @snippets - = hidden_field_tag :snippets, true - = hidden_field_tag :repository_ref, @ref + = hidden_field_tag :snippets, search_context.for_snippets? + = hidden_field_tag :repository_ref, search_context.ref = hidden_field_tag :nav_source, 'navbar' + -# workaround for non-JS feature specs, see spec/support/helpers/search_helpers.rb - if ENV['RAILS_ENV'] == 'test' %noscript= button_tag 'Search' - .search-autocomplete-opts.hide{ :'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref } + .search-autocomplete-opts.hide{ :'data-autocomplete-path' => search_autocomplete_path, + :'data-autocomplete-project-id' => search_context.project.try(:id), + :'data-autocomplete-project-ref' => search_context.ref } diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index c82fa1021fc..01c34aefd6d 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -1,9 +1,3 @@ -- if project - - search_path_url = search_path(project_id: project.id) -- elsif group - - search_path_url = search_path(group_id: group.id) -- else - - search_path_url = search_path - has_impersonation_link = header_link?(:admin_impersonation) %header.navbar.navbar-gitlab.navbar-expand-sm.js-navbar{ data: { qa_selector: 'navbar' } } @@ -36,7 +30,7 @@ %li.nav-item.d-none.d-lg-block.m-auto = render 'layouts/search' unless current_controller?(:search) %li.nav-item.d-inline-block.d-lg-none - = link_to search_path_url, title: _('Search'), aria: { label: _('Search') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = link_to search_context.search_url, title: _('Search'), aria: { label: _('Search') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = sprite_icon('search', size: 16) - if header_link?(:issues) = nav_link(path: 'dashboard#issues', html_options: { class: "user-counter" }) do diff --git a/changelogs/unreleased/21002-slack-opened-merge-request-webhook-message-is-malformed.yml b/changelogs/unreleased/21002-slack-opened-merge-request-webhook-message-is-malformed.yml new file mode 100644 index 00000000000..840f72be481 --- /dev/null +++ b/changelogs/unreleased/21002-slack-opened-merge-request-webhook-message-is-malformed.yml @@ -0,0 +1,5 @@ +--- +title: Add explicit mention of Merge request in Slack message +merge_request: 33152 +author: +type: changed diff --git a/changelogs/unreleased/213597-add-in-this-group-option-to-search-dropdown-where-not-present.yml b/changelogs/unreleased/213597-add-in-this-group-option-to-search-dropdown-where-not-present.yml new file mode 100644 index 00000000000..c8d405afc01 --- /dev/null +++ b/changelogs/unreleased/213597-add-in-this-group-option-to-search-dropdown-where-not-present.yml @@ -0,0 +1,5 @@ +--- +title: Enable the `in this group` action in the Search dropdown +merge_request: 31939 +author: +type: changed diff --git a/doc/ci/pipelines/job_artifacts.md b/doc/ci/pipelines/job_artifacts.md index 35030194113..f12886d9885 100644 --- a/doc/ci/pipelines/job_artifacts.md +++ b/doc/ci/pipelines/job_artifacts.md @@ -142,7 +142,8 @@ third party ports for other languages like JavaScript, Python, Ruby, and so on. The `terraform` report obtains a Terraform `tfplan.json` file. [JQ processing required to remove creds](../../user/infrastructure/index.md#output-terraform-plan-information-into-a-merge-request). The collected Terraform plan report will be uploaded to GitLab as an artifact and will be automatically shown -in merge requests. +in merge requests. For more information, see +[Output `terraform plan` information into a merge request](../../user/infrastructure/index.md#output-terraform-plan-information-into-a-merge-request). #### `artifacts:reports:codequality` **(STARTER)** diff --git a/doc/development/telemetry/usage_ping.md b/doc/development/telemetry/usage_ping.md index ca0e51b78c8..44abffe376a 100644 --- a/doc/development/telemetry/usage_ping.md +++ b/doc/development/telemetry/usage_ping.md @@ -317,43 +317,43 @@ On GitLab.com, we have DangerBot setup to monitor Telemetry related files and Da | `license_trial` | | | | | `assignee_lists` | `counts` | | | | `boards` | `counts` | | | -| `ci_builds` | `counts` | | | -| `ci_internal_pipelines` | `counts` | | | -| `ci_external_pipelines` | `counts` | | | -| `ci_pipeline_config_auto_devops` | `counts` | | | -| `ci_pipeline_config_repository` | `counts` | | | -| `ci_runners` | `counts` | | | -| `ci_triggers` | `counts` | | | -| `ci_pipeline_schedules` | `counts` | | | -| `auto_devops_enabled` | `counts` | | | -| `auto_devops_disabled` | `counts` | | | +| `ci_builds` | `counts` | `verify` | Unique builds in project | +| `ci_internal_pipelines` | `counts` | `verify` | Total pipelines in GitLab repositories | +| `ci_external_pipelines` | `counts` | `verify` | Total pipelines in external repositories | +| `ci_pipeline_config_auto_devops` | `counts` | `verify` | Total pipelines from an Auto DevOps template | +| `ci_pipeline_config_repository` | `counts` | `verify` | Total Pipelines from templates in repository | +| `ci_runners` | `counts` | `verify` | Total configured Runners in project | +| `ci_triggers` | `counts` | `verify` | Total configured Triggers in project | +| `ci_pipeline_schedules` | `counts` | `verify` | Pipeline schedules in GitLab | +| `auto_devops_enabled` | `counts` |`configure` | Projects with Auto DevOps template enabled | +| `auto_devops_disabled` | `counts` |`configure` | Projects with Auto DevOps template disabled | | `deploy_keys` | `counts` | | | -| `deployments` | `counts` | | | +| `deployments` | `counts` |`release` | Total deployments | | `dast_jobs` | `counts` | | | -| `successful_deployments` | `counts` | | | -| `failed_deployments` | `counts` | | | -| `environments` | `counts` | | | -| `clusters` | `counts` | | | -| `clusters_enabled` | `counts` | | | -| `project_clusters_enabled` | `counts` | | | -| `group_clusters_enabled` | `counts` | | | -| `instance_clusters_enabled` | `counts` | | | -| `clusters_disabled` | `counts` | | | -| `project_clusters_disabled` | `counts` | | | -| `group_clusters_disabled` | `counts` | | | -| `instance_clusters_disabled` | `counts` | | | -| `clusters_platforms_eks` | `counts` | | | -| `clusters_platforms_gke` | `counts` | | | -| `clusters_platforms_user` | `counts` | | | -| `clusters_applications_helm` | `counts` | | | -| `clusters_applications_ingress` | `counts` | | | -| `clusters_applications_cert_managers` | `counts` | | | -| `clusters_applications_crossplane` | `counts` | | | -| `clusters_applications_prometheus` | `counts` | | | -| `clusters_applications_runner` | `counts` | | | -| `clusters_applications_knative` | `counts` | | | -| `clusters_applications_elastic_stack` | `counts` | | | -| `clusters_management_project` | `counts` | | | +| `successful_deployments` | `counts` |`release` | Total successful deployments | +| `failed_deployments` | `counts` |`release` | Total failed deployments | +| `environments` | `counts` |`release` | Total available and stopped environments | +| `clusters` | `counts` |`configure` | Total GitLab Managed clusters both enabled and disabled | +| `clusters_enabled` | `counts` |`configure` | Total GitLab Managed clusters currently enabled | +| `project_clusters_enabled` | `counts` |`configure` | Total GitLab Managed clusters attached to projects| +| `group_clusters_enabled` | `counts` |`configure` | Total GitLab Managed clusters attached to groups | +| `instance_clusters_enabled` | `counts` |`configure` | Total GitLab Managed clusters attached to the instance | +| `clusters_disabled` | `counts` |`configure` | Total GitLab Managed disabled clusters | +| `project_clusters_disabled` | `counts` |`configure` | Total GitLab Managed disabled clusters previously attached to projects | +| `group_clusters_disabled` | `counts` |`configure` | Total GitLab Managed disabled clusters previously attached to groups | +| `instance_clusters_disabled` | `counts` |`configure` | Total GitLab Managed disabled clusters previously attached to the instance | +| `clusters_platforms_eks` | `counts` |`configure` | Total GitLab Managed clusters provisioned with GitLab on AWS EKS | +| `clusters_platforms_gke` | `counts` |`configure` | Total GitLab Managed clusters provisioned with GitLab on GCE GKE | +| `clusters_platforms_user` | `counts` |`configure` | Total GitLab Managed clusters that are user provisioned | +| `clusters_applications_helm` | `counts` |`configure` | Total GitLab Managed clusters with Helm enabled | +| `clusters_applications_ingress` | `counts` |`configure` | Total GitLab Managed clusters with Ingress enabled | +| `clusters_applications_cert_managers` | `counts` |`configure` | Total GitLab Managed clusters with Cert Manager enabled | +| `clusters_applications_crossplane` | `counts` |`configure` | Total GitLab Managed clusters with Crossplane enabled | +| `clusters_applications_prometheus` | `counts` |`configure` | Total GitLab Managed clusters with Prometheus enabled | +| `clusters_applications_runner` | `counts` |`configure` | Total GitLab Managed clusters with Runner enabled | +| `clusters_applications_knative` | `counts` |`configure` | Total GitLab Managed clusters with Knative enabled | +| `clusters_applications_elastic_stack` | `counts` |`configure` | Total GitLab Managed clusters with Elastic Stack enabled | +| `clusters_management_project` | `counts` |`configure` | Total GitLab Managed clusters with defined cluster management project | | `in_review_folder` | `counts` | | | | `grafana_integrated_projects` | `counts` | | | | `groups` | `counts` | | | @@ -368,14 +368,14 @@ On GitLab.com, we have DangerBot setup to monitor Telemetry related files and Da | `lfs_objects` | `counts` | | | | `milestone_lists` | `counts` | | | | `milestones` | `counts` | | | -| `pages_domains` | `counts` | | | +| `pages_domains` | `counts` |`release` | Total GitLab Pages domains | | `pool_repositories` | `counts` | | | | `projects` | `counts` | | | | `projects_imported_from_github` | `counts` | | | | `projects_with_repositories_enabled` | `counts` | | | | `projects_with_error_tracking_enabled` | `counts` | | | | `protected_branches` | `counts` | | | -| `releases` | `counts` | | | +| `releases` | `counts` |`release` | Unique release tags | | `remote_mirrors` | `counts` | | | | `requirements_created` | `counts` | | | | `snippets` | `counts` | | | @@ -466,22 +466,22 @@ On GitLab.com, we have DangerBot setup to monitor Telemetry related files and Da | `ldap_users` | `counts` | | | | `pod_logs_usages_total` | `counts` | | | | `projects_enforcing_code_owner_approval` | `counts` | | | -| `projects_mirrored_with_pipelines_enabled` | `counts` | | | -| `projects_reporting_ci_cd_back_to_github` | `counts` | | | -| `projects_with_packages` | `counts` | | | -| `projects_with_prometheus_alerts` | `counts` | | | -| `projects_with_tracing_enabled` | `counts` | | | -| `projects_with_alerts_service_enabled` | `counts` | | | +| `projects_mirrored_with_pipelines_enabled` | `counts` |`release` | Projects with repository mirroring enabled | +| `projects_reporting_ci_cd_back_to_github` | `counts` |`verify` | Projects with a GitHub service pipeline enabled | +| `projects_with_packages` | `counts` |`package` | Projects with package registry configured | +| `projects_with_prometheus_alerts` | `counts` |`monitor` | Projects with Promethus alerting enabled | +| `projects_with_tracing_enabled` | `counts` |`monitor` | Projects with tracing enabled | +| `projects_with_alerts_service_enabled` | `counts` |`monitor` | Projects with alerting service enabled | | `template_repositories` | `counts` | | | | `container_scanning_jobs` | `counts` | | | | `dependency_scanning_jobs` | `counts` | | | | `license_management_jobs` | `counts` | | | | `sast_jobs` | `counts` | | | -| `status_page_projects` | `counts` | `monitor` | | -| `status_page_issues` | `counts` | `monitor` | | +| `status_page_projects` | `counts` | `monitor` | Projects with status page enabled | +| `status_page_issues` | `counts` | `monitor` | Issues published to a Status Page | | `epics_deepest_relationship_level` | `counts` | | | -| `operations_dashboard_default_dashboard` | `counts` | | | -| `operations_dashboard_users_with_projects_added` | `counts` | | | +| `operations_dashboard_default_dashboard` | `counts` | `monitor` | Active users with enabled operations dashboard | +| `operations_dashboard_users_with_projects_added` | `counts` | `monitor` | Active users with projects on operations dashboard| | `container_registry_enabled` | | | | | `dependency_proxy_enabled` | | | | | `gitlab_shared_runners_enabled` | | | | @@ -507,47 +507,47 @@ On GitLab.com, we have DangerBot setup to monitor Telemetry related files and Da | `sd` | `avg_cycle_analytics - production` | | | | `missing` | `avg_cycle_analytics - production` | | | | `total` | `avg_cycle_analytics` | | | -| `clusters_applications_cert_managers` | `usage_activity_by_stage` | `configure` | | -| `clusters_applications_helm` | `usage_activity_by_stage` | `configure` | | -| `clusters_applications_ingress` | `usage_activity_by_stage` | `configure` | | -| `clusters_applications_knative` | `usage_activity_by_stage` | `configure` | | -| `clusters_management_project` | `usage_activity_by_stage` | `configure` | | -| `clusters_disabled` | `usage_activity_by_stage` | `configure` | | -| `clusters_enabled` | `usage_activity_by_stage` | `configure` | | -| `clusters_platforms_gke` | `usage_activity_by_stage` | `configure` | | -| `clusters_platforms_eks` | `usage_activity_by_stage` | `configure` | | -| `clusters_platforms_user` | `usage_activity_by_stage` | `configure` | | -| `instance_clusters_disabled` | `usage_activity_by_stage` | `configure` | | -| `instance_clusters_enabled` | `usage_activity_by_stage` | `configure` | | -| `group_clusters_disabled` | `usage_activity_by_stage` | `configure` | | -| `group_clusters_enabled` | `usage_activity_by_stage` | `configure` | | -| `project_clusters_disabled` | `usage_activity_by_stage` | `configure` | | -| `project_clusters_enabled` | `usage_activity_by_stage` | `configure` | | -| `projects_slack_notifications_active` | `usage_activity_by_stage` | `configure` | | -| `projects_slack_slash_active` | `usage_activity_by_stage` | `configure` | | -| `projects_with_prometheus_alerts: 0` | `usage_activity_by_stage` | `configure` | | +| `clusters_applications_cert_managers` | `usage_activity_by_stage` | `configure` | Unique clusters with certificate managers enabled | +| `clusters_applications_helm` | `usage_activity_by_stage` | `configure` | Unique clusters with Helm enabled | +| `clusters_applications_ingress` | `usage_activity_by_stage` | `configure` | Unique clusters with Ingress enabled | +| `clusters_applications_knative` | `usage_activity_by_stage` | `configure` | Unique clusters with Knative enabled | +| `clusters_management_project` | `usage_activity_by_stage` | `configure` | Unique clusters with project management enabled | +| `clusters_disabled` | `usage_activity_by_stage` | `configure` | Total non-"GitLab Managed clusters" | +| `clusters_enabled` | `usage_activity_by_stage` | `configure` | Total GitLab Managed clusters | +| `clusters_platforms_gke` | `usage_activity_by_stage` | `configure` | Unique clusters with Google Cloud installed | +| `clusters_platforms_eks` | `usage_activity_by_stage` | `configure` | Unique clusters with AWS installed | +| `clusters_platforms_user` | `usage_activity_by_stage` | `configure` | Unique clusters that are user provided | +| `instance_clusters_disabled` | `usage_activity_by_stage` | `configure` | Unique clusters disabled on instance | +| `instance_clusters_enabled` | `usage_activity_by_stage` | `configure` | Unique clusters enabled on instance | +| `group_clusters_disabled` | `usage_activity_by_stage` | `configure` | Unique clusters disabled on group | +| `group_clusters_enabled` | `usage_activity_by_stage` | `configure` | Unique clusters enabled on group | +| `project_clusters_disabled` | `usage_activity_by_stage` | `configure` | Unique clusters disabled on project | +| `project_clusters_enabled` | `usage_activity_by_stage` | `configure` | Unique clusters enabled on project | +| `projects_slack_notifications_active` | `usage_activity_by_stage` | `configure` | Unique projects with Slack service enabled | +| `projects_slack_slash_active` | `usage_activity_by_stage` | `configure` | Unique projects with Slack '/' commands enabled | +| `projects_with_prometheus_alerts: 0` | `usage_activity_by_stage` | `monitor` | Projects with Promethus enabled and no alerts | | `deploy_keys` | `usage_activity_by_stage` | `create` | | | `keys` | `usage_activity_by_stage` | `create` | | | `projects_jira_dvcs_server_active` | `usage_activity_by_stage` | `plan` | | | `service_desk_enabled_projects` | `usage_activity_by_stage` | `plan` | | | `service_desk_issues` | `usage_activity_by_stage` | `plan` | | | `todos: 0` | `usage_activity_by_stage` | `plan` | | -| `deployments` | `usage_activity_by_stage` | `release` | | -| `failed_deployments` | `usage_activity_by_stage` | `release` | | -| `projects_mirrored_with_pipelines_enabled` | `usage_activity_by_stage` | `release` | | -| `releases` | `usage_activity_by_stage` | `release` | | -| `successful_deployments: 0` | `usage_activity_by_stage` | `release` | | +| `deployments` | `usage_activity_by_stage` | `release` | Total deployments | +| `failed_deployments` | `usage_activity_by_stage` | `release` | Total failed deployments | +| `projects_mirrored_with_pipelines_enabled` | `usage_activity_by_stage` | `release` | Projects with repository mirroring enabled | +| `releases` | `usage_activity_by_stage` | `release` | Unique release tags in project | +| `successful_deployments: 0` | `usage_activity_by_stage` | `release` | Total successful deployments | | `user_preferences_group_overview_security_dashboard: 0` | `usage_activity_by_stage` | `secure` | | -| `ci_builds` | `usage_activity_by_stage` | `verify` | | -| `ci_external_pipelines` | `usage_activity_by_stage` | `verify` | | -| `ci_internal_pipelines` | `usage_activity_by_stage` | `verify` | | -| `ci_pipeline_config_auto_devops` | `usage_activity_by_stage` | `verify` | | -| `ci_pipeline_config_repository` | `usage_activity_by_stage` | `verify` | | -| `ci_pipeline_schedules` | `usage_activity_by_stage` | `verify` | | -| `ci_pipelines` | `usage_activity_by_stage` | `verify` | | -| `ci_triggers` | `usage_activity_by_stage` | `verify` | | -| `clusters_applications_runner` | `usage_activity_by_stage` | `verify` | | -| `projects_reporting_ci_cd_back_to_github: 0` | `usage_activity_by_stage` | `verify` | | +| `ci_builds` | `usage_activity_by_stage` | `verify` | Unique builds in project | +| `ci_external_pipelines` | `usage_activity_by_stage` | `verify` | Total pipelines in external repositories | +| `ci_internal_pipelines` | `usage_activity_by_stage` | `verify` | Total pipelines in GitLab repositories | +| `ci_pipeline_config_auto_devops` | `usage_activity_by_stage` | `verify` | Total pipelines from an Auto DevOps template | +| `ci_pipeline_config_repository` | `usage_activity_by_stage` | `verify` | Pipelines from templates in repository | +| `ci_pipeline_schedules` | `usage_activity_by_stage` | `verify` | Pipeline schedules in GitLab | +| `ci_pipelines` | `usage_activity_by_stage` | `verify` | Total pipelines | +| `ci_triggers` | `usage_activity_by_stage` | `verify` | Triggers enabled | +| `clusters_applications_runner` | `usage_activity_by_stage` | `verify` | Unique clusters with Runner enabled | +| `projects_reporting_ci_cd_back_to_github: 0` | `usage_activity_by_stage` | `verify` | Unique projects with a GitHub pipeline enabled | ## Example Usage Ping payload diff --git a/doc/install/installation.md b/doc/install/installation.md index 8f34ea8311b..b65aec31407 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -134,7 +134,7 @@ Make sure you have the right version of Git installed: # Install Git sudo apt-get install -y git-core -# Make sure Git is version 2.26.2 or higher (minimal supported version is 2.22.0) +# Make sure Git is version 2.26.2 or higher (minimal supported version is 2.24.0) git --version ``` diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 1e2335b808b..c7add46bd43 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -50,11 +50,11 @@ needs several Gems that have native extensions. ### Go versions -The minimum required Go version is 1.12. +The minimum required Go version is 1.13. ### Git versions -GitLab 11.11 and higher only supports Git 2.21.x and newer, and +GitLab 11.11 and higher only supports Git 2.24.x and newer, and [dropped support for older versions](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/54255). ### Node.js versions diff --git a/doc/update/README.md b/doc/update/README.md index 4f529336344..f36a304495c 100644 --- a/doc/update/README.md +++ b/doc/update/README.md @@ -192,6 +192,16 @@ possible. ## Version specific upgrading instructions +### 13.1.0 + +In 13.1.0, you must upgrade to either: + +- At least Git v2.24 (previously, the minimum required version was Git v2.22). +- The recommended Git v2.26. + +Failure to do so will result in internal errors in the Gitaly service in some RPCs due +to the use of the new `--end-of-options` Git flag. + ### 12.2.0 In 12.2.0, we enabled Rails' authenticated cookie encryption. Old sessions are diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md index 3e416b39be2..9f8d4901cde 100644 --- a/doc/update/upgrading_from_source.md +++ b/doc/update/upgrading_from_source.md @@ -122,12 +122,17 @@ rm go1.13.5.linux-amd64.tar.gz ### 6. Update Git -NOTE: To check the minimum required Git version, see [Git versions](../install/requirements.md#git-versions). +CAUTION: **Caution:** +From GitLab 13.1, you must use at least Git v2.24 (previous minimum version was v2.22). +Git v2.26 is recommended. + +To check you are running the minimum required Git version, see +[Git versions](../install/requirements.md#git-versions). In Debian or Ubuntu: ```shell -# Make sure Git is version 2.21.0 or higher +# Make sure Git is version 2.24.0 or higher git --version # Remove packaged Git @@ -147,9 +152,9 @@ make install # Download and compile from source cd /tmp -curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.21.0.tar.gz -echo '85eca51c7404da75e353eba587f87fea9481ba41e162206a6f70ad8118147bee git-2.21.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.21.0.tar.gz -cd git-2.21.0/ +curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.26.0.tar.gz +echo 'aa168c2318e7187cd295a645f7370cc6d71a324aafc932f80f00c780b6a26bed git-2.26.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.26.0.tar.gz +cd git-2.26.0/ ./configure --with-libpcre make prefix=/usr/local all diff --git a/lib/gitlab/search_context.rb b/lib/gitlab/search_context.rb new file mode 100644 index 00000000000..c3bb0ff26f2 --- /dev/null +++ b/lib/gitlab/search_context.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: true + +module Gitlab + # Holds the contextual data used by navbar search component to + # determine the search scope, whether to search for code, or if + # a search should target snippets. + # + # Use the SearchContext::Builder to create an instance of this class + class SearchContext + attr_accessor :project, :project_metadata, :ref, + :group, :group_metadata, + :snippets, + :scope, :search_url + + def initialize + @ref = nil + @project = nil + @project_metadata = {} + @group = nil + @group_metadata = {} + @snippets = [] + @scope = nil + @search_url = nil + end + + def for_project? + project.present? && project.persisted? + end + + def for_group? + group.present? && group.persisted? + end + + def for_snippets? + snippets.any? + end + + def code_search? + project.present? && scope.nil? + end + + class Builder + def initialize(view_context) + @view_context = view_context + @snippets = [] + end + + def with_snippet(snippet) + @snippets << snippet + + self + end + + def with_project(project) + @project = project + with_group(project&.group) + + self + end + + def with_group(group) + @group = group + + self + end + + def with_ref(ref) + @ref = ref + + self + end + + def build! + SearchContext.new.tap do |context| + context.project = @project + context.group = @group + context.ref = @ref + context.snippets = @snippets.dup + context.scope = search_scope + context.search_url = search_url + context.group_metadata = group_search_metadata(@group) + context.project_metadata = project_search_metadata(@project) + end + end + + private + + attr_accessor :view_context + + def project_search_metadata(project) + return {} unless project + + { + project_path: project.path, + name: project.name, + issues_path: view_context.project_issues_path(project), + mr_path: view_context.project_merge_requests_path(project), + issues_disabled: !project.issues_enabled? + } + end + + def group_search_metadata(group) + return {} unless group + + { + group_path: group.path, + name: group.name, + issues_path: view_context.issues_group_path(group), + mr_path: view_context.merge_requests_group_path(group) + } + end + + def search_url + if @project.present? + view_context.search_path(project_id: @project.id) + elsif @group.present? + view_context.search_path(group_id: @group.id) + else + view_context.search_path + end + end + + def search_scope + if view_context.current_controller?(:issues) + 'issues' + elsif view_context.current_controller?(:merge_requests) + 'merge_requests' + elsif view_context.current_controller?(:wikis) + 'wiki_blobs' + elsif view_context.current_controller?(:commits) + 'commits' + else nil + end + end + end + + module ControllerConcern + extend ActiveSupport::Concern + + included do + helper_method :search_context + end + + # rubocop:disable Gitlab/ModuleWithInstanceVariables + # + # Introspect the current controller's assignments and + # and builds the proper SearchContext object for it. + def search_context + builder = Builder.new(view_context) + + builder.with_snippet(@snippet) if @snippet.present? + @snippets.each(&builder.method(:with_snippet)) if @snippets.present? + builder.with_project(@project) if @project.present? && @project.persisted? + builder.with_group(@group) if @group.present? && @group.persisted? + builder.with_ref(@ref) if @ref.present? + + builder.build! + end + # rubocop:enable Gitlab/ModuleWithInstanceVariables + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 57d12cc952b..e800d116408 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -14300,6 +14300,9 @@ msgstr "" msgid "NetworkPolicies|Environment does not have deployment platform" msgstr "" +msgid "NetworkPolicies|If you are using Auto DevOps, your %{monospacedStart}auto-deploy-values.yaml%{monospacedEnd} file will not be updated if you change a policy in this section. Auto DevOps users should make changes by following the %{linkStart}Container Network Policy documentation%{linkEnd}." +msgstr "" + msgid "NetworkPolicies|Invalid or empty policy" msgstr "" @@ -14315,7 +14318,7 @@ msgstr "" msgid "NetworkPolicies|No policies detected" msgstr "" -msgid "NetworkPolicies|Policies are a specification of how groups of pods are allowed to communicate with each other network endpoints." +msgid "NetworkPolicies|Policies are a specification of how groups of pods are allowed to communicate with each other's network endpoints." msgstr "" msgid "NetworkPolicies|Policy %{policyName} was successfully changed" @@ -19086,10 +19089,10 @@ msgstr "" msgid "SearchAutocomplete|in all GitLab" msgstr "" -msgid "SearchAutocomplete|in this group" +msgid "SearchAutocomplete|in group %{groupName}" msgstr "" -msgid "SearchAutocomplete|in this project" +msgid "SearchAutocomplete|in project %{projectName}" msgstr "" msgid "SearchCodeResults|in" @@ -24477,6 +24480,9 @@ msgstr "" msgid "View the performance dashboard at" msgstr "" +msgid "View users statistics" +msgstr "" + msgid "Viewing commit" msgstr "" @@ -25688,6 +25694,12 @@ msgstr "" msgid "Your groups" msgstr "" +msgid "Your instance has %{remaining_user_count} users remaining of the %{total_user_count} included in your subscription. You can add more users than the number included in your license, and we will include the overage in your next bill." +msgstr "" + +msgid "Your instance is approaching its licensed user count" +msgstr "" + msgid "Your issues are being imported. Once finished, you'll get a confirmation email." msgstr "" diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 74b858f118a..e62c4ef9d94 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -332,8 +332,7 @@ describe Projects::IssuesController do end before do - allow(controller).to receive(:find_routable!) - .with(Project, project.full_path, any_args).and_return(project) + allow(controller).to receive(:find_routable!).and_return(project) allow(project).to receive(:default_branch).and_return(master_branch) allow_next_instance_of(Issues::RelatedBranchesService) do |service| allow(service).to receive(:execute).and_return(related_branches) 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 7b969aea547..7ee70077e94 100644 --- a/spec/features/search/user_uses_header_search_field_spec.rb +++ b/spec/features/search/user_uses_header_search_field_spec.rb @@ -95,14 +95,6 @@ describe 'User uses header search field', :js do expect(page).not_to have_selector('.dropdown-header', text: /#{scope_name}/i) end - - it 'hides the dropdown when there are no results' do - page.within('.search-input-wrap') do - fill_in('search', with: 'a_search_term_with_no_results') - end - - expect(page).not_to have_selector('.dropdown-menu') - end end end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 05231cc6d09..75377356445 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -278,7 +278,7 @@ describe ApplicationHelper do page: 'application', page_type_id: nil, find_file: nil, - group: '' + group: nil } ) end @@ -317,7 +317,7 @@ describe ApplicationHelper do page: 'application', page_type_id: nil, find_file: nil, - group: '', + group: nil, project_id: project.id, project: project.name, namespace_id: project.namespace.id @@ -325,6 +325,25 @@ describe ApplicationHelper do ) end + context 'when @project is owned by a group' do + let_it_be(:project) { create(:project, :repository, group: create(:group)) } + + it 'includes all possible body data elements and associates the project elements with project' do + expect(helper).to receive(:can?).with(nil, :download_code, project) + expect(helper.body_data).to eq( + { + page: 'application', + page_type_id: nil, + find_file: nil, + group: project.group.name, + project_id: project.id, + project: project.name, + namespace_id: project.namespace.id + } + ) + end + end + context 'when controller is issues' do before do stub_controller_method(:controller_path, 'projects:issues') @@ -342,7 +361,7 @@ describe ApplicationHelper do page: 'projects:issues:show', page_type_id: issue.id, find_file: nil, - group: '', + group: nil, project_id: issue.project.id, project: issue.project.name, namespace_id: issue.project.namespace.id diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb index 7e851a1af01..55f743ac683 100644 --- a/spec/helpers/page_layout_helper_spec.rb +++ b/spec/helpers/page_layout_helper_spec.rb @@ -117,4 +117,19 @@ describe PageLayoutHelper do expect(tags).to include(%q{content="foo" http-equiv="refresh"}) end end + + describe '#search_context' do + subject(:search_context) { helper.search_context } + + describe 'a bare controller' do + it 'returns an empty context' do + expect(search_context).to have_attributes(project: nil, + group: nil, + snippets: [], + project_metadata: {}, + group_metadata: {}, + search_url: '/search') + end + end + end end diff --git a/spec/lib/gitlab/search_context/builder_spec.rb b/spec/lib/gitlab/search_context/builder_spec.rb new file mode 100644 index 00000000000..1707b54b273 --- /dev/null +++ b/spec/lib/gitlab/search_context/builder_spec.rb @@ -0,0 +1,152 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SearchContext::Builder, type: :controller do + controller(ApplicationController) { } + + subject(:builder) { described_class.new(controller.view_context) } + + shared_examples "has a fluid interface" do + it { is_expected.to be_instance_of(described_class) } + end + + def expected_project_metadata(project) + return {} if project.nil? + + a_hash_including(project_path: project.path, + name: project.name, + issues_path: a_string_including("/issues"), + mr_path: a_string_including("/merge_requests"), + issues_disabled: !project.issues_enabled?) + end + + def expected_group_metadata(group) + return {} if group.nil? + + a_hash_including(group_path: group.path, + name: group.name, + issues_path: a_string_including("/issues"), + mr_path: a_string_including("/merge_requests")) + end + + def expected_search_url(project, group) + if project + search_path(project_id: project.id) + elsif group + search_path(group_id: group.id) + else + search_path + end + end + + def be_search_context(project: nil, group: nil, snippets: [], ref: nil) + group = project ? project.group : group + snippets.compact! + ref = ref + + have_attributes( + project: project, + group: group, + ref: ref, + snippets: snippets, + project_metadata: expected_project_metadata(project), + group_metadata: expected_group_metadata(group), + search_url: expected_search_url(project, group) + ) + end + + describe '#with_project' do + let(:project) { create(:project) } + + subject { builder.with_project(project) } + + it_behaves_like "has a fluid interface" + + describe '#build!' do + subject(:context) { builder.with_project(project).build! } + + context 'when a project is not owned by a group' do + it { is_expected.to be_for_project } + it { is_expected.to be_search_context(project: project) } + end + + context 'when a project is owned by a group' do + let(:project) { create(:project, group: create(:group)) } + + it 'delegates to `#with_group`' do + expect(builder).to receive(:with_group).with(project.group) + expect(context).to be + end + + it { is_expected.to be_search_context(project: project, group: project.group) } + end + end + end + + describe '#with_snippet' do + context 'when there is a single snippet' do + let(:snippet) { create(:snippet) } + + subject { builder.with_snippet(snippet) } + + it_behaves_like "has a fluid interface" + + describe '#build!' do + subject(:context) { builder.with_snippet(snippet).build! } + + it { is_expected.to be_for_snippet } + it { is_expected.to be_search_context(snippets: [snippet]) } + end + end + + context 'when there are multiple snippets' do + let(:snippets) { create_list(:snippet, 3) } + + describe '#build!' do + subject(:context) do + snippets.each(&builder.method(:with_snippet)) + builder.build! + end + + it { is_expected.to be_for_snippet } + it { is_expected.to be_search_context(snippets: snippets) } + end + end + end + + describe '#with_group' do + let(:group) { create(:group) } + + subject { builder.with_group(group) } + + it_behaves_like "has a fluid interface" + + describe '#build!' do + subject(:context) { builder.with_group(group).build! } + + it { is_expected.to be_for_group } + it { is_expected.to be_search_context(group: group) } + end + end + + describe '#with_ref' do + let(:ref) { Gitlab::Git::EMPTY_TREE_ID } + + subject { builder.with_ref(ref) } + + it_behaves_like "has a fluid interface" + + describe '#build!' do + subject(:context) { builder.with_ref(ref).build! } + + it { is_expected.to be_search_context(ref: ref) } + end + end + + describe '#build!' do + subject(:context) { builder.build! } + + it { is_expected.to be_a(Gitlab::SearchContext) } + end +end diff --git a/spec/lib/gitlab/search_context/controller_concern_spec.rb b/spec/lib/gitlab/search_context/controller_concern_spec.rb new file mode 100644 index 00000000000..16784cafb76 --- /dev/null +++ b/spec/lib/gitlab/search_context/controller_concern_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SearchContext::ControllerConcern, type: :controller do + controller(ApplicationController) do + include Gitlab::SearchContext::ControllerConcern + end + + let(:project) { nil } + let(:group) { nil } + let(:snippet) { nil } + let(:snippets) { [] } + let(:ref) { nil } + + let(:builder) { Gitlab::SearchContext::Builder.new(controller.view_context) } + + subject(:search_context) { controller.search_context } + + def weak_assign(ivar, value) + return if value.nil? + + controller.instance_variable_set(ivar.to_sym, value) + end + + before do + weak_assign(:@project, project) + weak_assign(:@group, group) + weak_assign(:@ref, ref) + weak_assign(:@snippet, snippet) + weak_assign(:@snippets, snippets) + + allow(Gitlab::SearchContext::Builder).to receive(:new).and_return(builder) + end + + shared_examples 'has the proper context' do + it :aggregate_failures do + expected_group = project ? project.group : group + expected_snippets = [snippet, *snippets].compact + + expect(builder).to receive(:with_project).with(project).and_call_original if project + expect(builder).to receive(:with_group).with(expected_group).and_call_original if expected_group + expect(builder).to receive(:with_ref).with(ref).and_call_original if ref + expected_snippets.each do |snippet| + expect(builder).to receive(:with_snippet).with(snippet).and_call_original + end + + is_expected.to be_a(Gitlab::SearchContext) + end + end + + context 'exposing @project' do + let(:project) { create(:project) } + + it_behaves_like 'has the proper context' + + context 'when the project is owned by a group' do + let(:project) { create(:project, group: create(:group)) } + + it_behaves_like 'has the proper context' + end + end + + context 'exposing @group' do + let(:group) { create(:group) } + + it_behaves_like 'has the proper context' + end + + context 'exposing @snippet, @snippets' do + let(:snippet) { create(:snippet) } + let(:snippets) { create_list(:snippet, 3) } + + it_behaves_like 'has the proper context' + end + + context 'exposing @ref' do + let(:ref) { Gitlab::Git::EMPTY_TREE_ID } + + it_behaves_like 'has the proper context' + end +end diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb index 150ee6f7472..6063ef4ecb3 100644 --- a/spec/models/project_services/chat_message/merge_message_spec.rb +++ b/spec/models/project_services/chat_message/merge_message_spec.rb @@ -52,7 +52,7 @@ describe ChatMessage::MergeMessage do context 'open' do it 'returns a message regarding opening of merge requests' do expect(subject.pretext).to eq( - 'Test User (test.user) opened in ') + 'Test User (test.user) opened merge request in ') expect(subject.attachments).to be_empty end end @@ -63,7 +63,7 @@ describe ChatMessage::MergeMessage do end it 'returns a message regarding closing of merge requests' do expect(subject.pretext).to eq( - 'Test User (test.user) closed in ') + 'Test User (test.user) closed merge request in ') expect(subject.attachments).to be_empty end end @@ -77,7 +77,7 @@ describe ChatMessage::MergeMessage do context 'open' do it 'returns a message regarding opening of merge requests' do expect(subject.pretext).to eq( - 'Test User (test.user) opened [!100 *Merge Request title*](http://somewhere.com/-/merge_requests/100) in [project_name](http://somewhere.com)') + 'Test User (test.user) opened merge request [!100 *Merge Request title*](http://somewhere.com/-/merge_requests/100) in [project_name](http://somewhere.com)') expect(subject.attachments).to be_empty expect(subject.activity).to eq({ title: 'Merge Request opened by Test User (test.user)', @@ -95,7 +95,7 @@ describe ChatMessage::MergeMessage do it 'returns a message regarding closing of merge requests' do expect(subject.pretext).to eq( - 'Test User (test.user) closed [!100 *Merge Request title*](http://somewhere.com/-/merge_requests/100) in [project_name](http://somewhere.com)') + 'Test User (test.user) closed merge request [!100 *Merge Request title*](http://somewhere.com/-/merge_requests/100) in [project_name](http://somewhere.com)') expect(subject.attachments).to be_empty expect(subject.activity).to eq({ title: 'Merge Request closed by Test User (test.user)',