+
+
+
-
-
- {{ title }}
-
+
+
+ {{ title }}
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+ {{ content }}
+
+
+
+
diff --git a/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js b/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
index a740a3fa6b9..cdbde55901d 100644
--- a/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
+++ b/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
@@ -10,6 +10,10 @@ import { validateParams } from '~/pipelines/utils';
export default {
methods: {
onChangeTab(scope) {
+ if (this.scope === scope) {
+ return;
+ }
+
let params = {
scope,
page: '1',
diff --git a/app/assets/stylesheets/fontawesome_custom.scss b/app/assets/stylesheets/fontawesome_custom.scss
index 30d56d99e1c..4cab61a8e39 100644
--- a/app/assets/stylesheets/fontawesome_custom.scss
+++ b/app/assets/stylesheets/fontawesome_custom.scss
@@ -88,11 +88,6 @@
content: '\f078';
}
-.fa-remove::before,
-.fa-times::before {
- content: '\f00d';
-}
-
.fa-caret-down::before {
content: '\f0d7';
}
diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb
index 29138e7b014..6470c75dfbd 100644
--- a/app/controllers/concerns/milestone_actions.rb
+++ b/app/controllers/concerns/milestone_actions.rb
@@ -3,13 +3,25 @@
module MilestoneActions
extend ActiveSupport::Concern
+ def issues
+ respond_to do |format|
+ format.html { redirect_to milestone_redirect_path }
+ format.json do
+ render json: tabs_json("shared/milestones/_issues_tab", {
+ issues: @milestone.sorted_issues(current_user), # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ show_project_name: Gitlab::Utils.to_boolean(params[:show_project_name])
+ })
+ end
+ end
+ end
+
def merge_requests
respond_to do |format|
format.html { redirect_to milestone_redirect_path }
format.json do
render json: tabs_json("shared/milestones/_merge_requests_tab", {
merge_requests: @milestone.sorted_merge_requests(current_user), # rubocop:disable Gitlab/ModuleWithInstanceVariables
- show_project_name: true
+ show_project_name: Gitlab::Utils.to_boolean(params[:show_project_name])
})
end
end
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index df3fb6b67c2..3f2894d378b 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -3,7 +3,7 @@
class Groups::MilestonesController < Groups::ApplicationController
include MilestoneActions
- before_action :milestone, only: [:edit, :show, :update, :merge_requests, :participants, :labels, :destroy]
+ before_action :milestone, only: [:edit, :show, :update, :issues, :merge_requests, :participants, :labels, :destroy]
before_action :authorize_admin_milestones!, only: [:edit, :new, :create, :update, :destroy]
before_action do
push_frontend_feature_flag(:burnup_charts, @group)
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 16d63cc184f..8049a17068b 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -5,7 +5,7 @@ class Projects::MilestonesController < Projects::ApplicationController
include MilestoneActions
before_action :check_issuables_available!
- before_action :milestone, only: [:edit, :update, :destroy, :show, :merge_requests, :participants, :labels, :promote]
+ before_action :milestone, only: [:edit, :update, :destroy, :show, :issues, :merge_requests, :participants, :labels, :promote]
before_action do
push_frontend_feature_flag(:burnup_charts, @project)
end
@@ -14,7 +14,7 @@ class Projects::MilestonesController < Projects::ApplicationController
before_action :authorize_read_milestone!
# Allow admin milestone
- before_action :authorize_admin_milestone!, except: [:index, :show, :merge_requests, :participants, :labels]
+ before_action :authorize_admin_milestone!, except: [:index, :show, :issues, :merge_requests, :participants, :labels]
# Allow to promote milestone
before_action :authorize_promote_milestone!, only: :promote
diff --git a/app/helpers/timeboxes_helper.rb b/app/helpers/timeboxes_helper.rb
index 34919f994ee..bbf8cf7dac3 100644
--- a/app/helpers/timeboxes_helper.rb
+++ b/app/helpers/timeboxes_helper.rb
@@ -228,8 +228,8 @@ module TimeboxesHelper
end
alias_method :milestone_date_range, :timebox_date_range
- def milestone_tab_path(milestone, tab)
- url_for(action: tab, format: :json)
+ def milestone_tab_path(milestone, tab, params = {})
+ url_for(params.merge(action: tab, format: :json))
end
def update_milestone_path(milestone, params = {})
diff --git a/app/views/admin/hook_logs/show.html.haml b/app/views/admin/hook_logs/show.html.haml
index a8ef19dcf46..ca2737ca56f 100644
--- a/app/views/admin/hook_logs/show.html.haml
+++ b/app/views/admin/hook_logs/show.html.haml
@@ -4,6 +4,6 @@
%hr
-= link_to _("Resend Request"), retry_admin_hook_hook_log_path(@hook, @hook_log), method: :post, class: "btn btn-default float-right gl-ml-3"
+= link_to _("Resend Request"), retry_admin_hook_hook_log_path(@hook, @hook_log), method: :post, class: "btn gl-button btn-default float-right gl-ml-3"
= render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log }
diff --git a/app/views/projects/merge_requests/diffs/_commit_widget.html.haml b/app/views/projects/merge_requests/diffs/_commit_widget.html.haml
deleted file mode 100644
index c022d2c70d8..00000000000
--- a/app/views/projects/merge_requests/diffs/_commit_widget.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
--#-----------------------------------------------------------------
- WARNING: Please keep changes up-to-date with the following files:
- - `assets/javascripts/diffs/components/commit_widget.vue`
--#-----------------------------------------------------------------
-- collapsible = local_assigns.fetch(:collapsible, true)
-
-- if @commit
- .info-well.mw-100.mx-0
- .well-segment
- %ul.blob-commit-info
- = render 'projects/commits/commit', commit: @commit, merge_request: @merge_request, view_details: true, collapsible: collapsible
diff --git a/app/views/projects/merge_requests/diffs/_different_base.html.haml b/app/views/projects/merge_requests/diffs/_different_base.html.haml
deleted file mode 100644
index 06a15b96653..00000000000
--- a/app/views/projects/merge_requests/diffs/_different_base.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-- if @merge_request_diff && different_base?(@start_version, @merge_request_diff)
- .mr-version-controls
- .content-block
- = sprite_icon('information-o')
- Selected versions have different base commits.
- Changes will include
- = link_to project_compare_path(@project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
- new commits
- from
- = succeed '.' do
- %code.ref-name= @merge_request.target_branch
diff --git a/app/views/projects/merge_requests/diffs/_diffs.html.haml b/app/views/projects/merge_requests/diffs/_diffs.html.haml
deleted file mode 100644
index 9ebd91dea0b..00000000000
--- a/app/views/projects/merge_requests/diffs/_diffs.html.haml
+++ /dev/null
@@ -1,21 +0,0 @@
-= render 'projects/merge_requests/diffs/version_controls'
-= render 'projects/merge_requests/diffs/different_base'
-= render 'projects/merge_requests/diffs/not_all_comments_displayed'
-= render 'projects/merge_requests/diffs/commit_widget'
-
-- if @merge_request_diff&.empty?
- .row.empty-state.nothing-here-block
- .col-12
- .svg-content= image_tag 'illustrations/merge_request_changes_empty.svg'
- .col-12
- .text-content.text-center
- %p
- No changes between
- %span.ref-name= @merge_request.source_branch
- and
- %span.ref-name= @merge_request.target_branch
- .text-center= link_to 'Create commit', project_new_blob_path(@project, @merge_request.source_branch), class: 'btn btn-success'
-- else
- - diff_viewable = @merge_request_diff ? @merge_request_diff.viewable? : true
- - if diff_viewable
- = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, merge_request: true
diff --git a/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml b/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml
deleted file mode 100644
index b9dc37c9b54..00000000000
--- a/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-- if @commit || @start_version || (@merge_request_diff && !@merge_request_diff.latest?)
- .mr-version-controls
- .content-block.comments-disabled-notif.clearfix
- = sprite_icon('information-o')
- = succeed '.' do
- - if @commit
- Only comments from the following commit are shown below
- - else
- Not all comments are displayed because you're
- - if @start_version
- comparing two versions of the diff
- - else
- viewing an old version of the diff
- .float-right
- = link_to diffs_project_merge_request_path(@merge_request.project, @merge_request), class: 'btn btn-sm' do
- Show latest version
- = "of the diff" if @commit
diff --git a/app/views/projects/merge_requests/diffs/_version_controls.html.haml b/app/views/projects/merge_requests/diffs/_version_controls.html.haml
deleted file mode 100644
index 52bf584d550..00000000000
--- a/app/views/projects/merge_requests/diffs/_version_controls.html.haml
+++ /dev/null
@@ -1,73 +0,0 @@
-- if @merge_request_diff && @merge_request_diffs.size > 1
- .mr-version-controls
- .mr-version-menus-container.content-block
- Changes between
- %span.dropdown.inline.mr-version-dropdown
- %a.dropdown-toggle.btn.btn-default{ data: { toggle: :dropdown, display: 'static' } }
- %span
- - if @merge_request_diff.latest?
- latest version
- - else
- version #{version_index(@merge_request_diff)}
- = icon('caret-down')
- .dropdown-menu.dropdown-select.dropdown-menu-selectable
- .dropdown-title
- %span Version:
- %button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } }
- = icon('times', class: 'dropdown-menu-close-icon')
- .dropdown-content
- %ul
- - @merge_request_diffs.each do |merge_request_diff|
- %li
- = link_to merge_request_version_path(@project, @merge_request, merge_request_diff, @start_sha), class: ('is-active' if merge_request_diff == @merge_request_diff) do
- %div
- %strong
- - if merge_request_diff.latest?
- latest version
- - else
- version #{version_index(merge_request_diff)}
- %div
- %small.commit-sha= short_sha(merge_request_diff.head_commit_sha)
- %div
- %small
- #{number_with_delimiter(merge_request_diff.commits_count)} #{'commit'.pluralize(merge_request_diff.commits_count)},
- = time_ago_with_tooltip(merge_request_diff.created_at)
-
- - if @merge_request_diff.base_commit_sha
- and
- %span.dropdown.inline.mr-version-compare-dropdown
- %a.btn.btn-default.dropdown-toggle{ data: { toggle: :dropdown, display: 'static' } }
- - if @start_version
- version #{version_index(@start_version)}
- - else
- %span.ref-name= @merge_request.target_branch
- = icon('caret-down')
- .dropdown-menu.dropdown-select.dropdown-menu-selectable
- .dropdown-title
- %span Compared with:
- %button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } }
- = icon('times', class: 'dropdown-menu-close-icon')
- .dropdown-content
- %ul
- - @comparable_diffs.each do |merge_request_diff|
- %li
- = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff == @start_version) do
- %div
- %strong
- - if merge_request_diff.latest?
- latest version
- - else
- version #{version_index(merge_request_diff)}
- %div
- %small.commit-sha= short_sha(merge_request_diff.head_commit_sha)
- %div
- %small
- = time_ago_with_tooltip(merge_request_diff.created_at)
- %li
- = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_version) do
- %div
- %strong
- %span.ref-name= @merge_request.target_branch
- (base)
- %div
- %strong.commit-sha= short_sha(@merge_request_diff.base_commit_sha)
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
index f8bf3e7ad6a..a62ed009552 100644
--- a/app/views/shared/milestones/_issuable.html.haml
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -10,8 +10,6 @@
%span
- if show_project_name
%strong #{project.name} ·
- - elsif show_full_project_name
- %strong #{project.full_name} ·
- if issuable.is_a?(Issue)
= confidential_icon(issuable)
= link_to issuable.title, issuable_url_args, title: issuable.title
diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml
index ee97f0172da..9147e1c50e3 100644
--- a/app/views/shared/milestones/_issuables.html.haml
+++ b/app/views/shared/milestones/_issuables.html.haml
@@ -15,4 +15,4 @@
= render partial: 'shared/milestones/issuable',
collection: issuables,
as: :issuable,
- locals: { show_project_name: show_project_name, show_full_project_name: show_full_project_name }
+ locals: { show_project_name: show_project_name }
diff --git a/app/views/shared/milestones/_issues_tab.html.haml b/app/views/shared/milestones/_issues_tab.html.haml
index dc54eefbaa9..76ef636ec96 100644
--- a/app/views/shared/milestones/_issues_tab.html.haml
+++ b/app/views/shared/milestones/_issues_tab.html.haml
@@ -1,5 +1,4 @@
-- args = { show_project_name: local_assigns.fetch(:show_project_name, false),
- show_full_project_name: local_assigns.fetch(:show_full_project_name, false) }
+- args = { show_project_name: local_assigns.fetch(:show_project_name, false) }
- if display_issues_count_warning?(@milestone)
.flash-container
diff --git a/app/views/shared/milestones/_merge_requests_tab.haml b/app/views/shared/milestones/_merge_requests_tab.haml
index 0dbf2b27c8d..a78440600ad 100644
--- a/app/views/shared/milestones/_merge_requests_tab.haml
+++ b/app/views/shared/milestones/_merge_requests_tab.haml
@@ -1,5 +1,4 @@
-- args = { show_project_name: local_assigns.fetch(:show_project_name, false),
- show_full_project_name: local_assigns.fetch(:show_full_project_name, false) }
+- args = { show_project_name: local_assigns.fetch(:show_project_name, false) }
.row.gl-mt-3
.col-md-3
diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml
index 34f476241c6..33e634c3e7b 100644
--- a/app/views/shared/milestones/_tabs.html.haml
+++ b/app/views/shared/milestones/_tabs.html.haml
@@ -1,14 +1,16 @@
+- show_project_name = local_assigns.fetch(:show_project_name, false)
+
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= sprite_icon('chevron-lg-left', size: 12)
.fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.scrolling-tabs.js-milestone-tabs.nav.nav-tabs
%li.nav-item
- = link_to '#tab-issues', class: 'nav-link active', data: { toggle: 'tab', show: '.tab-issues-buttons' } do
+ = link_to '#tab-issues', class: 'nav-link active', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'issues', show_project_name: show_project_name) } do
= _('Issues')
%span.badge.badge-pill= milestone.issues_visible_to_user(current_user).size
- if milestone.merge_requests_enabled?
%li.nav-item
- = link_to '#tab-merge-requests', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'merge_requests') } do
+ = link_to '#tab-merge-requests', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'merge_requests', show_project_name: show_project_name) } do
= _('Merge Requests')
%span.badge.badge-pill= milestone.merge_requests_visible_to_user(current_user).size
%li.nav-item
@@ -20,20 +22,13 @@
= _('Labels')
%span.badge.badge-pill= milestone.issue_labels_visible_by_user(current_user).count
-- issues = milestone.sorted_issues(current_user)
-- show_project_name = local_assigns.fetch(:show_project_name, false)
-- show_full_project_name = local_assigns.fetch(:show_full_project_name, false)
-
.tab-content.milestone-content
- .tab-pane.active#tab-issues{ data: { sort_endpoint: (sort_issues_project_milestone_path(@project, @milestone) if @project && current_user) } }
- = render 'shared/milestones/issues_tab', issues: issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name
+ .tab-pane.active#tab-issues
+ = render "shared/milestones/tab_loading"
- if milestone.merge_requests_enabled?
.tab-pane#tab-merge-requests
- -# loaded async
= render "shared/milestones/tab_loading"
.tab-pane#tab-participants
- -# loaded async
= render "shared/milestones/tab_loading"
.tab-pane#tab-labels
- -# loaded async
= render "shared/milestones/tab_loading"
diff --git a/changelogs/unreleased/197227-load-milestone-issues-tab-async.yml b/changelogs/unreleased/197227-load-milestone-issues-tab-async.yml
new file mode 100644
index 00000000000..b3f427ddbdd
--- /dev/null
+++ b/changelogs/unreleased/197227-load-milestone-issues-tab-async.yml
@@ -0,0 +1,5 @@
+---
+title: Load issues tab in the milestone page asynchronously
+merge_request: 42473
+author:
+type: performance
diff --git a/changelogs/unreleased/230719-tabs-vue-migrate-app-assets-javascripts-environments-components-en.yml b/changelogs/unreleased/230719-tabs-vue-migrate-app-assets-javascripts-environments-components-en.yml
new file mode 100644
index 00000000000..8cdd77718f8
--- /dev/null
+++ b/changelogs/unreleased/230719-tabs-vue-migrate-app-assets-javascripts-environments-components-en.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate custom Tabs to GlTabs
+merge_request: 42236
+author:
+type: changed
diff --git a/config/routes/group.rb b/config/routes/group.rb
index e5bbfdf7548..bf14afff318 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -61,6 +61,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resources :milestones, constraints: { id: %r{[^/]+} } do
member do
+ get :issues
get :merge_requests
get :participants
get :labels
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 24b44646d95..e0208e36a34 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -161,8 +161,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :milestones, constraints: { id: /\d+/ } do
member do
post :promote
- put :sort_issues
- put :sort_merge_requests
+ get :issues
get :merge_requests
get :participants
get :labels
diff --git a/doc/.vale/gitlab/spelling-exceptions.txt b/doc/.vale/gitlab/spelling-exceptions.txt
index bf816afdfab..eec1d03d1a5 100644
--- a/doc/.vale/gitlab/spelling-exceptions.txt
+++ b/doc/.vale/gitlab/spelling-exceptions.txt
@@ -298,6 +298,7 @@ OmniAuth
onboarding
OpenID
OpenShift
+Opsgenie
Packagist
parallelization
parallelizations
@@ -382,6 +383,7 @@ reusability
reverified
reverifies
reverify
+RHEL
rollout
rollouts
rsync
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 0fcd95c41ed..8468d696838 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -547,6 +547,7 @@ runtime.
at least version **1.8** if you want to use private registries.
- Available for [Kubernetes executor](https://docs.gitlab.com/runner/executors/kubernetes.html)
in GitLab Runner 13.1 and later.
+- [Credentials Store](#using-credentials-store) and [Credential Helpers](#using-credential-helpers) require binaries to be added to the GitLab Runner's `$PATH`, and require access to do so. Therefore, these features are not available on shared runners or any other runner where the user does not have access to the environment where the runner is installed.
### Using statically-defined credentials
diff --git a/doc/development/README.md b/doc/development/README.md
index abdd5c662f3..ee4643ed5a5 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -43,6 +43,7 @@ For information on how to install, configure, update, and upgrade your own GitLa
**Must-reads:**
+- [Guide on adapting existing and introducing new components](architecture.md#adapting-existing-and-introducing-new-components)
- [Code review guidelines](code_review.md) for reviewing code and having code reviewed
- [Database review guidelines](database_review.md) for reviewing database-related changes and complex SQL queries, and having them reviewed
- [Secure coding guidelines](secure_coding_guidelines.md)
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index f6b1c8cd914..b94ee045a60 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -1,32 +1,91 @@
-# GitLab Architecture Overview
+# GitLab architecture overview
## Software delivery
-There are two software distributions of GitLab: the open source [Community Edition](https://gitlab.com/gitlab-org/gitlab-foss/) (CE), and the open core [Enterprise Edition](https://gitlab.com/gitlab-org/gitlab/) (EE). GitLab is available under [different subscriptions](https://about.gitlab.com/pricing/).
+There are two software distributions of GitLab:
-New versions of GitLab are released in stable branches and the master branch is for bleeding edge development.
+- The open source [Community Edition](https://gitlab.com/gitlab-org/gitlab-foss/) (CE).
+- The open core [Enterprise Edition](https://gitlab.com/gitlab-org/gitlab/) (EE).
-For information, see the [GitLab Release Process](https://gitlab.com/gitlab-org/release/docs/-/tree/master#gitlab-release-process).
+GitLab is available under [different subscriptions](https://about.gitlab.com/pricing/).
-Both EE and CE require some add-on components called GitLab Shell and Gitaly. These components are available from the [GitLab Shell](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/master) and [Gitaly](https://gitlab.com/gitlab-org/gitaly/-/tree/master) repositories respectively. New versions are usually tags but staying on the master branch will give you the latest stable version. New releases are generally around the same time as GitLab CE releases with the exception of informal security updates deemed critical.
+New versions of GitLab are released from stable branches, and the `master` branch is used for
+bleeding-edge development.
+
+For more information, visit the [GitLab Release Process](https://about.gitlab.com/handbook/engineering/releases/).
+
+Both distributions require additional components. These components are described in the
+[Component details](#components) section, and all have their own repositories.
+New versions of each dependent component are usually tags, but staying on the `master` branch of the
+GitLab codebase gives you the latest stable version of those components. New versions are
+generally released around the same time as GitLab releases, with the exception of informal security
+updates deemed critical.
## Components
-A typical install of GitLab will be on GNU/Linux. It uses NGINX or Apache as a web front end to proxypass the Unicorn web server. By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and pre-compiled assets. GitLab serves web pages and the [GitLab API](../api/README.md) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses Redis as a non-persistent database backend for job information, meta data, and incoming jobs.
+A typical install of GitLab is on GNU/Linux, but growing number of deployments also use the
+Kubernetes platform. The largest known GitLab instance is on GitLab.com, which is deployed using our
+[official GitLab Helm chart](https://docs.gitlab.com/charts/) and the [official Linux package](https://about.gitlab.com/install/).
-We also support deploying GitLab on Kubernetes using our [GitLab Helm chart](https://docs.gitlab.com/charts/).
+A typical installation uses NGINX or Apache as a web server to proxy through
+[GitLab Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse) and into the [Puma](https://puma.io)
+application server. GitLab serves web pages and the [GitLab API](../api/README.md) using the Puma
+application server. It uses Sidekiq as a job queue which, in turn, uses Redis as a non-persistent
+database backend for job information, metadata, and incoming jobs.
-The GitLab web app uses PostgreSQL for persistent database information (e.g. users, permissions, issues, other meta data). GitLab stores the bare Git repositories it serves in `/home/git/repositories` by default. It also keeps default branch and hook information with the bare repository.
+By default, communication between Puma and Workhorse is via a Unix domain socket, but forwarding
+requests via TCP is also supported. Workhorse accesses the `gitlab/public` directory, bypassing the
+Puma application server to serve static pages, uploads (for example, avatar images or attachments),
+and pre-compiled assets.
-When serving repositories over HTTP/HTTPS GitLab utilizes the GitLab API to resolve authorization and access as well as serving Git objects.
+The GitLab application uses PostgreSQL for persistent database information (for example, users,
+permissions, issues, or other metadata). GitLab stores the bare Git repositories in the location
+defined in [the configuration file, `repositories:` section](https://gitlab.com/gitlab-org/gitlab/blob/master/config/gitlab.yml.example).
+It also keeps default branch and hook information with the bare repository.
-The add-on component GitLab Shell serves repositories over SSH. It manages the SSH keys within `/home/git/.ssh/authorized_keys` which should not be manually edited. GitLab Shell accesses the bare repositories through Gitaly to serve Git objects and communicates with Redis to submit jobs to Sidekiq for GitLab to process. GitLab Shell queries the GitLab API to determine authorization and access.
+When serving repositories over HTTP/HTTPS GitLab uses the GitLab API to resolve authorization and
+access and to serve Git objects.
-Gitaly executes Git operations from GitLab Shell and the GitLab web app, and provides an API to the GitLab web app to get attributes from Git (e.g. title, branches, tags, other meta data), and to get blobs (e.g. diffs, commits, files).
+The add-on component GitLab Shell serves repositories over SSH. It manages the SSH keys within the
+location defined in [the configuration file, `GitLab Shell` section](https://gitlab.com/gitlab-org/gitlab/blob/master/config/gitlab.yml.example).
+The file in that location should never be manually edited. GitLab Shell accesses the bare
+repositories through Gitaly to serve Git objects, and communicates with Redis to submit jobs to
+Sidekiq for GitLab to process. GitLab Shell queries the GitLab API to determine authorization and access.
+
+Gitaly executes Git operations from GitLab Shell and the GitLab web app, and provides an API to the
+GitLab web app to get attributes from Git (for example, title, branches, tags, or other metadata),
+and to get blobs (for example, diffs, commits, or files).
You may also be interested in the [production architecture of GitLab.com](https://about.gitlab.com/handbook/engineering/infrastructure/production/architecture/).
-### Simplified Component Overview
+## Adapting existing and introducing new components
+
+There are fundamental differences in how the application behaves when it is installed on a
+traditional Linux machine compared to a containerized platform, such as Kubernetes.
+
+Compared to [our official installation methods](https://about.gitlab.com/install/), some of the
+notable differences are:
+
+- Official Linux packages can access files on the same file system with different services.
+ [Shared files](shared_files.md) are not an option for the application running on the Kubernetes
+ platform.
+- Official Linux packages by default have services that have access to the shared configuration and
+ network. This is not the case for services running in Kubernetes, where services might be running
+ in complete isolation, or only accessible through specific ports.
+
+In other words, the shared state between services needs to be carefully considered when
+architecting new features and adding new components. Services that need to have access to the same
+files, need to be able to exchange information through the appropriate APIs. Whenever possible,
+this should not be done with files.
+
+Since components written with the API-first philosophy in mind are compatible with both methods, all
+new features and services must be written to consider Kubernetes compatibility **first**.
+
+The simplest way to ensure this, is to add support for your feature or service to
+[the official GitLab Helm chart](https://docs.gitlab.com/charts/) or reach out to
+[the Distribution team](https://about.gitlab.com/handbook/engineering/development/enablement/distribution/#how-to-work-with-distribution).
+
+### Simplified component overview
This is a simplified architecture diagram that can be used to
understand GitLab's architecture.
@@ -411,7 +470,8 @@ For monitoring deployed apps, see [Jaeger tracing documentation](../operations/t
- Layer: Core Service
- Process: `logrotate`
-GitLab is comprised of a large number of services that all log. We started bundling our own logrotate as of 7.4 to make sure we were logging responsibly. This is just a packaged version of the common open source offering.
+GitLab is comprised of a large number of services that all log. We started bundling our own Logrotate
+as of GitLab 7.4 to make sure we were logging responsibly. This is just a packaged version of the common open source offering.
#### Mattermost
@@ -669,7 +729,7 @@ You can install them after you create a cluster. This includes:
- [JupyterHub](https://jupyter.org)
- [Knative](https://cloud.google.com/knative/)
-## GitLab by Request Type
+## GitLab by request type
GitLab provides two "interfaces" for end users to access the service:
@@ -678,7 +738,7 @@ GitLab provides two "interfaces" for end users to access the service:
It's important to understand the distinction as some processes are used in both and others are exclusive to a specific request type.
-### GitLab Web HTTP Request Cycle
+### GitLab Web HTTP request cycle
When making a request to an HTTP Endpoint (think `/users/sign_in`) the request will take the following path through the GitLab Service:
@@ -687,11 +747,11 @@ When making a request to an HTTP Endpoint (think `/users/sign_in`) the request w
- Unicorn - Since this is a web request, and it needs to access the application it will go to Unicorn.
- PostgreSQL/Gitaly/Redis - Depending on the type of request, it may hit these services to store or retrieve data.
-### GitLab Git Request Cycle
+### GitLab Git request cycle
Below we describe the different paths that HTTP vs. SSH Git requests will take. There is some overlap with the Web Request Cycle but also some differences.
-### Web Request (80/443)
+### Web request (80/443)
Git operations over HTTP use the stateless "smart" protocol described in the
[Git documentation](https://git-scm.com/docs/http-protocol), but responsibility
@@ -736,7 +796,7 @@ sequenceDiagram
The sequence is similar for `git push`, except `git-receive-pack` is used
instead of `git-upload-pack`.
-### SSH Request (22)
+### SSH request (22)
Git operations over SSH can use the stateful protocol described in the
[Git documentation](https://git-scm.com/docs/pack-protocol#_ssh_transport), but
@@ -801,7 +861,7 @@ except there is no round-trip into Gitaly - Rails performs the action as part
of the [internal API](internal_api.md) call, and GitLab Shell streams the
response back to the user directly.
-## System Layout
+## System layout
When referring to `~git` in the pictures it means the home directory of the Git user which is typically `/home/git`.
@@ -811,7 +871,7 @@ The bare repositories are located in `/home/git/repositories`. GitLab is a Ruby
To serve repositories over SSH there's an add-on application called GitLab Shell which is installed in `/home/git/gitlab-shell`.
-### Installation Folder Summary
+### Installation folder summary
To summarize here's the [directory structure of the `git` user home directory](../install/structure.md).
@@ -824,7 +884,7 @@ ps aux | grep '^git'
GitLab has several components to operate. It requires a persistent database
(PostgreSQL) and Redis database, and uses Apache `httpd` or NGINX to proxypass
Unicorn. All these components should run as different system users to GitLab
-(e.g., `postgres`, `redis` and `www-data`, instead of `git`).
+(for example, `postgres`, `redis`, and `www-data`, instead of `git`).
As the `git` user it starts Sidekiq and Unicorn (a simple Ruby HTTP server
running on port `8080` by default). Under the GitLab user there are normally 4
@@ -914,15 +974,16 @@ PostgreSQL:
### GitLab specific configuration files
-GitLab has configuration files located in `/home/git/gitlab/config/*`. Commonly referenced config files include:
+GitLab has configuration files located in `/home/git/gitlab/config/*`. Commonly referenced
+configuration files include:
-- `gitlab.yml` - GitLab configuration.
-- `unicorn.rb` - Unicorn web server settings.
-- `database.yml` - Database connection settings.
+- `gitlab.yml` - GitLab configuration
+- `unicorn.rb` - Unicorn web server settings
+- `database.yml` - Database connection settings
GitLab Shell has a configuration file at `/home/git/gitlab-shell/config.yml`.
-### Maintenance Tasks
+### Maintenance tasks
[GitLab](https://gitlab.com/gitlab-org/gitlab/tree/master) provides Rake tasks with which you see version information and run a quick check on your configuration to ensure it is configured properly within the application. See [maintenance Rake tasks](../raketasks/maintenance.md).
In a nutshell, do the following:
@@ -934,7 +995,8 @@ bundle exec rake gitlab:env:info RAILS_ENV=production
bundle exec rake gitlab:check RAILS_ENV=production
```
-Note: It is recommended to log into the `git` user using `sudo -i -u git` or `sudo su - git`. While the sudo commands provided by GitLab work in Ubuntu they do not always work in RHEL.
+Note: It is recommended to log into the `git` user using `sudo -i -u git` or `sudo su - git`. While
+the `sudo` commands provided by GitLab work in Ubuntu they do not always work in RHEL.
## GitLab.com
diff --git a/doc/user/application_security/security_dashboard/index.md b/doc/user/application_security/security_dashboard/index.md
index 51d9b4f45cd..8c461e27e70 100644
--- a/doc/user/application_security/security_dashboard/index.md
+++ b/doc/user/application_security/security_dashboard/index.md
@@ -86,8 +86,9 @@ You can also dismiss vulnerabilities in the table:
The group Security Dashboard gives an overview of the vulnerabilities in the default branches of the
projects in a group and its subgroups. Access it by navigating to **Security > Security Dashboard**
-for your group. By default, the Security Dashboard displays all detected and confirmed
-vulnerabilities.
+after selecting your group. By default, the Security Dashboard displays all detected and confirmed
+vulnerabilities. If you don't see the vulnerabilities over time graph, the likely cause is that you
+have not selected a group.
NOTE: **Note:**
The Security Dashboard only shows projects with [security reports](#supported-reports) enabled in a
@@ -96,20 +97,20 @@ group.
![Dashboard with action buttons and metrics](img/group_security_dashboard_v13_3.png)
There is a timeline chart that shows how many open
-vulnerabilities your projects had at various points in time. You can filter among 30, 60, and
-90 days, with the default being 90. Hover over the chart to get more details about
-the open vulnerabilities at a specific time.
+vulnerabilities your projects had at various points in time. You can display the vulnerability
+trends over a 30, 60, or 90-day time frame (the default is 90 days). Hover over the chart to get
+more details about the open vulnerabilities at a specific time.
Next to the timeline chart is a list of projects, grouped and sorted by the severity of the vulnerability found:
-- F: 1 or more "critical"
-- D: 1 or more "high" or "unknown"
-- C: 1 or more "medium"
-- B: 1 or more "low"
-- A: 0 vulnerabilities
+- F: One or more "critical"
+- D: One or more "high" or "unknown"
+- C: One or more "medium"
+- B: One or more "low"
+- A: Zero vulnerabilities
Projects with no vulnerability tests configured will not appear in the list. Additionally, dismissed
-vulnerabilities are not included either.
+vulnerabilities are excluded.
Navigate to the group's [Vulnerability Report](#vulnerability-list) to view the vulnerabilities found.
diff --git a/doc/user/packages/container_registry/index.md b/doc/user/packages/container_registry/index.md
index 077666bc036..c67f2ab5738 100644
--- a/doc/user/packages/container_registry/index.md
+++ b/doc/user/packages/container_registry/index.md
@@ -512,6 +512,11 @@ Cleanup policies can be run on all projects, with these exceptions:
for all projects (even those created before 12.8) in
[GitLab application settings](../../../api/settings.md#change-application-settings)
by setting `container_expiration_policies_enable_historic_entries` to true.
+ Alternatively, you can execute the following command in the [Rails console](../../../administration/troubleshooting/navigating_gitlab_via_rails_console.md#starting-a-rails-console-session):
+
+ ```ruby
+ ApplicationSetting.last.update(container_expiration_policies_enable_historic_entries: true)
+ ```
There are performance risks with enabling it for all projects, especially if you
are using an [external registry](./index.md#use-with-external-container-registries).
diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md
index 821b42af049..2c4c5d59ae0 100644
--- a/doc/user/project/web_ide/index.md
+++ b/doc/user/project/web_ide/index.md
@@ -32,7 +32,7 @@ file path fragments to start seeing results.
## Syntax highlighting
As expected from an IDE, syntax highlighting for many languages within
-the Web IDE will make your direct editing even easier.
+the Web IDE makes your direct editing even easier.
The Web IDE currently provides:
@@ -143,7 +143,7 @@ The Web IDE supports configuration of certain editor settings by using
[`.editorconfig` files](https://editorconfig.org/). When opening a file, the
Web IDE looks for a file named `.editorconfig` in the current directory
and all parent directories. If a configuration file is found and has settings
-that match the file's path, these settings will be enforced on the opened file.
+that match the file's path, these settings are enforced on the opened file.
The Web IDE currently supports the following `.editorconfig` settings:
@@ -166,7 +166,7 @@ review the list of changed files.
Once you have finalized your changes, you can add a commit message, commit the
changes and directly create a merge request. In case you don't have write
-access to the selected branch, you will see a warning, but still be able to create
+access to the selected branch, you see a warning, but can still create
a new branch and start a merge request.
To discard a change in a particular file, click the **Discard changes** button on that
@@ -201,8 +201,7 @@ left.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/19318) in [GitLab Core](https://about.gitlab.com/pricing/) 11.0.
To switch between your authored and assigned merge requests, click the
-dropdown in the top of the sidebar to open a list of merge requests. You will
-need to commit or discard all your changes before switching to a different merge
+dropdown in the top of the sidebar to open a list of merge requests. You need to commit or discard all your changes before switching to a different merge
request.
## Switching branches
@@ -211,7 +210,7 @@ request.
To switch between branches of the current project repository, click the dropdown
in the top of the sidebar to open a list of branches.
-You will need to commit or discard all your changes before switching to a
+You need to commit or discard all your changes before switching to a
different branch.
## Markdown editing
@@ -226,7 +225,7 @@ supports [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-g
You can also upload any local images by pasting them directly in the Markdown file.
The image is uploaded to the same directory and is named `image.png` by default.
If another file already exists with the same name, a numeric suffix is automatically
-added to the file name.
+added to the filename.
## Live Preview
@@ -249,7 +248,7 @@ The Live Preview feature needs to be enabled in the GitLab instances
admin settings. Live Preview is enabled for all projects on
GitLab.com
-![Admin Live Preview setting](img/admin_live_preview_v13_0.png)
+![Administrator Live Preview setting](img/admin_live_preview_v13_0.png)
Once you have done that, you can preview projects with a `package.json` file and
a `main` entry point inside the Web IDE. An example `package.json` is shown
@@ -292,7 +291,7 @@ to work:
[enabled](../../../administration/integration/terminal.md#enabling-and-disabling-terminal-support). **(ULTIMATE ONLY)**
If you have the terminal open and the job has finished with its tasks, the
-terminal will block the job from finishing for the duration configured in
+terminal blocks the job from finishing for the duration configured in
[`[session_server].session_timeout`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section)
until you close the terminal window.
@@ -308,15 +307,15 @@ In order to enable the Web IDE terminals you need to create the file
file is fairly similar to the [CI configuration file](../../../ci/yaml/README.md)
syntax but with some restrictions:
-- No global blocks can be defined (ie: `before_script` or `after_script`)
+- No global blocks can be defined (i.e., `before_script` or `after_script`)
- Only one job named `terminal` can be added to this file.
- Only the keywords `image`, `services`, `tags`, `before_script`, `script`, and
`variables` are allowed to be used to configure the job.
- To connect to the interactive terminal, the `terminal` job must be still alive
- and running, otherwise the terminal won't be able to connect to the job's session.
+ and running, otherwise the terminal cannot connect to the job's session.
By default the `script` keyword has the value `sleep 60` to prevent
the job from ending and giving the Web IDE enough time to connect. This means
- that, if you override the default `script` value, you'll have to add a command
+ that, if you override the default `script` value, you have to add a command
which would keep the job running, like `sleep`.
In the code below there is an example of this configuration file:
@@ -333,40 +332,39 @@ terminal:
NODE_ENV: "test"
```
-Once the terminal has started, the console will be displayed and we could access
+Once the terminal has started, the console is displayed and we could access
the project repository files.
**Important**. The terminal job is branch dependent. This means that the
-configuration file used to trigger and configure the terminal will be the one in
+configuration file used to trigger and configure the terminal is the one in
the selected branch of the Web IDE.
-If there is no configuration file in a branch, an error message will be shown.
+If there is no configuration file in a branch, an error message is shown.
### Running interactive terminals in the Web IDE
-If Interactive Terminals are available for the current user, the **Terminal** button
-will be visible in the right sidebar of the Web IDE. Click this button to open
+If Interactive Terminals are available for the current user, the **Terminal** button is visible in the right sidebar of the Web IDE. Click this button to open
or close the terminal tab.
-Once open, the tab will show the **Start Web Terminal** button. This button may
+Once open, the tab shows the **Start Web Terminal** button. This button may
be disabled if the environment is not configured correctly. If so, a status
-message will describe the issue. Here are some reasons why **Start Web Terminal**
+message describes the issue. Here are some reasons why **Start Web Terminal**
may be disabled:
- `.gitlab/.gitlab-webide.yml` does not exist or is set up incorrectly.
- No active private runners are available for the project.
-If active, clicking the **Start Web Terminal** button will load the terminal view
+If active, clicking the **Start Web Terminal** button loads the terminal view
and start connecting to the runner's terminal. At any time, the **Terminal** tab
-can be closed and reopened and the state of the terminal will not be affected.
+can be closed and reopened and the state of the terminal is not affected.
When the terminal is started and is successfully connected to the runner, then the
-runner's shell prompt will appear in the terminal. From here, you can enter
-commands that will be executed within the runner's environment. This is similar
+runner's shell prompt appears in the terminal. From here, you can enter
+commands executed within the runner's environment. This is similar
to running commands in a local terminal or through SSH.
While the terminal is running, it can be stopped by clicking **Stop Terminal**.
-This will disconnect the terminal and stop the runner's terminal job. From here,
+This disconnects the terminal and stops the runner's terminal job. From here,
click **Restart Terminal** to start a new terminal session.
### File syncing to web terminal
@@ -408,14 +406,14 @@ terminal:
more information.
- `$CI_PROJECT_DIR` is a
[predefined environment variable](../../../ci/variables/predefined_variables.md)
- for GitLab Runner. This is where your project's repository will be.
+ for GitLab Runners. This is where your project's repository resides.
Once you have configured the web terminal for file syncing, then when the web
-terminal is started, a **Terminal** status will be visible in the status bar.
+terminal is started, a **Terminal** status is visible in the status bar.
![Web IDE Client Side Evaluation](img/terminal_status.png)
-Changes made to your files via the Web IDE will sync to the running terminal
+Changes made to your files via the Web IDE sync to the running terminal
when:
-
Ctrl +
S (or
Cmd +
S on Mac)
@@ -425,7 +423,7 @@ when:
### Limitations
-Interactive Terminals is in a beta phase and will continue to be improved upon in upcoming
+Interactive Terminals is in a beta phase and continues to be improved in upcoming
releases. In the meantime, please note that the user is limited to having only one
active terminal at a time.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 409f045cd82..d0d0606a34e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8,8 +8,6 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-09-17 11:26-0400\n"
-"PO-Revision-Date: 2020-09-17 11:26-0400\n"
"Last-Translator: FULL NAME
\n"
"Language-Team: LANGUAGE \n"
"Language: \n"
@@ -7184,6 +7182,9 @@ msgstr ""
msgid "Could not find iteration"
msgstr ""
+msgid "Could not load instance counts. Please refresh the page to try again."
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -13586,6 +13587,24 @@ msgstr ""
msgid "Instance administrators group already exists"
msgstr ""
+msgid "InstanceStatistics|Groups"
+msgstr ""
+
+msgid "InstanceStatistics|Issues"
+msgstr ""
+
+msgid "InstanceStatistics|Merge Requests"
+msgstr ""
+
+msgid "InstanceStatistics|Pipelines"
+msgstr ""
+
+msgid "InstanceStatistics|Projects"
+msgstr ""
+
+msgid "InstanceStatistics|Users"
+msgstr ""
+
msgid "Integration"
msgstr ""
@@ -26166,6 +26185,9 @@ msgstr ""
msgid "Threat Monitoring"
msgstr ""
+msgid "ThreatMonitoring|All Environments"
+msgstr ""
+
msgid "ThreatMonitoring|Anomalous Requests"
msgstr ""
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
index 5c7b88a218a..2c85fe482e2 100644
--- a/spec/controllers/groups/milestones_controller_spec.rb
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -9,7 +9,6 @@ RSpec.describe Groups::MilestonesController do
let(:user) { create(:user) }
let(:title) { '肯定不是中文的问题' }
let(:milestone) { create(:milestone, project: project) }
- let(:milestone_path) { group_milestone_path(group, milestone.safe_title, title: milestone.title) }
let(:milestone_params) do
{
@@ -25,6 +24,12 @@ RSpec.describe Groups::MilestonesController do
project.add_maintainer(user)
end
+ it_behaves_like 'milestone tabs' do
+ let(:milestone) { create(:milestone, group: group) }
+ let(:milestone_path) { group_milestone_path(group, milestone.iid) }
+ let(:request_params) { { group_id: group, id: milestone.iid } }
+ end
+
describe '#index' do
describe 'as HTML' do
render_views
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index fa32d32f552..9e5d41b1075 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -17,7 +17,9 @@ RSpec.describe Projects::MilestonesController do
controller.instance_variable_set(:@project, project)
end
- it_behaves_like 'milestone tabs'
+ it_behaves_like 'milestone tabs' do
+ let(:request_params) { { namespace_id: project.namespace, project_id: project, id: milestone.iid } }
+ end
describe "#show" do
render_views
diff --git a/spec/features/milestones/user_views_milestone_spec.rb b/spec/features/milestones/user_views_milestone_spec.rb
index 420f8d49483..11c6fa521d5 100644
--- a/spec/features/milestones/user_views_milestone_spec.rb
+++ b/spec/features/milestones/user_views_milestone_spec.rb
@@ -4,12 +4,16 @@ require 'spec_helper'
RSpec.describe "User views milestone" do
let_it_be(:user) { create(:user) }
- let_it_be(:project) { create(:project) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:labels) { create_list(:label, 2, project: project) }
- before do
+ before_all do
project.add_developer(user)
+ end
+
+ before do
sign_in(user)
end
@@ -25,7 +29,7 @@ RSpec.describe "User views milestone" do
expect { visit_milestone }.not_to exceed_query_limit(control)
end
- context 'limiting milestone issues' do
+ context 'issues list', :js do
before_all do
2.times do
create(:issue, milestone: milestone, project: project)
@@ -34,6 +38,28 @@ RSpec.describe "User views milestone" do
end
end
+ context 'for a project milestone' do
+ it 'does not show the project name' do
+ visit(project_milestone_path(project, milestone))
+
+ wait_for_requests
+
+ expect(page.find('#tab-issues')).not_to have_text(project.name)
+ end
+ end
+
+ context 'for a group milestone' do
+ let(:group_milestone) { create(:milestone, group: group) }
+
+ it 'shows the project name' do
+ create(:issue, project: project, milestone: group_milestone)
+
+ visit(group_milestone_path(group, group_milestone))
+
+ expect(page.find('#tab-issues')).to have_text(project.name)
+ end
+ end
+
context 'when issues on milestone are over DISPLAY_ISSUES_LIMIT' do
it "limits issues to display and shows warning" do
stub_const('Milestoneish::DISPLAY_ISSUES_LIMIT', 3)
@@ -56,6 +82,40 @@ RSpec.describe "User views milestone" do
end
end
+ context 'merge requests list', :js do
+ context 'for a project milestone' do
+ it 'does not show the project name' do
+ create(:merge_request, source_project: project, milestone: milestone)
+
+ visit(project_milestone_path(project, milestone))
+
+ within('.js-milestone-tabs') do
+ click_link('Merge Requests')
+ end
+
+ wait_for_requests
+
+ expect(page.find('#tab-merge-requests')).not_to have_text(project.name)
+ end
+ end
+
+ context 'for a group milestone' do
+ let(:group_milestone) { create(:milestone, group: group) }
+
+ it 'shows the project name' do
+ create(:merge_request, source_project: project, milestone: group_milestone)
+
+ visit(group_milestone_path(group, group_milestone))
+
+ within('.js-milestone-tabs') do
+ click_link('Merge Requests')
+ end
+
+ expect(page.find('#tab-merge-requests')).to have_text(project.name)
+ end
+ end
+ end
+
private
def visit_milestone
diff --git a/spec/features/milestones/user_views_milestones_spec.rb b/spec/features/milestones/user_views_milestones_spec.rb
index 3f606577121..f8b4b802a60 100644
--- a/spec/features/milestones/user_views_milestones_spec.rb
+++ b/spec/features/milestones/user_views_milestones_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe "User views milestones" do
.and have_content("Merge Requests")
end
- context "with issues" do
+ context "with issues", :js do
let_it_be(:issue) { create(:issue, project: project, milestone: milestone) }
let_it_be(:closed_issue) { create(:closed_issue, project: project, milestone: milestone) }
@@ -33,7 +33,6 @@ RSpec.describe "User views milestones" do
.and have_selector("#tab-issues li.issuable-row", count: 2)
.and have_content(issue.title)
.and have_content(closed_issue.title)
- .and have_selector("#tab-merge-requests")
end
end
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index 7f2ef61bcbe..8c032660726 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -372,7 +372,7 @@ RSpec.describe 'Environments page', :js do
let(:role) { :developer }
it 'developer creates a new environment with a valid name' do
- within(".top-area") { click_link 'New environment' }
+ within(".environments-section") { click_link 'New environment' }
fill_in('Name', with: 'production')
click_on 'Save'
@@ -380,7 +380,7 @@ RSpec.describe 'Environments page', :js do
end
it 'developer creates a new environmetn with invalid name' do
- within(".top-area") { click_link 'New environment' }
+ within(".environments-section") { click_link 'New environment' }
fill_in('Name', with: 'name,with,commas')
click_on 'Save'
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 cfda25b9ab4..5cbfacf4e48 100644
--- a/spec/features/search/user_uses_header_search_field_spec.rb
+++ b/spec/features/search/user_uses_header_search_field_spec.rb
@@ -30,6 +30,8 @@ RSpec.describe 'User uses header search field', :js do
before do
find('#search')
find('body').native.send_keys('s')
+
+ wait_for_all_requests
end
it 'shows the category search dropdown' do
@@ -89,9 +91,7 @@ RSpec.describe 'User uses header search field', :js do
context 'when entering text into the search field' do
it 'does not display the category search dropdown' do
- page.within('.search-input-wrap') do
- fill_in('search', with: scope_name.first(4))
- end
+ fill_in_search(scope_name.first(4))
expect(page).not_to have_selector('.dropdown-header', text: /#{scope_name}/i)
end
@@ -105,9 +105,7 @@ RSpec.describe 'User uses header search field', :js do
end
it 'displays search options' do
- page.within('.search-input-wrap') do
- fill_in('search', with: 'test')
- end
+ fill_in_search('test')
expect(page).to have_selector(scoped_search_link('test'))
end
@@ -140,9 +138,7 @@ RSpec.describe 'User uses header search field', :js do
end
it 'displays search options' do
- page.within('.search-input-wrap') do
- fill_in('search', with: 'test')
- end
+ fill_in_search('test')
expect(page).to have_selector(scoped_search_link('test'))
expect(page).to have_selector(scoped_search_link('test', group_id: group.id))
@@ -157,9 +153,7 @@ RSpec.describe 'User uses header search field', :js do
end
it 'displays search options' do
- page.within('.search-input-wrap') do
- fill_in('search', with: 'test')
- end
+ fill_in_search('test')
expect(page).to have_selector(scoped_search_link('test'))
expect(page).not_to have_selector(scoped_search_link('test', group_id: project.namespace_id))
@@ -182,9 +176,7 @@ RSpec.describe 'User uses header search field', :js do
end
it 'displays search options' do
- page.within('.search-input-wrap') do
- fill_in('search', with: 'test')
- end
+ fill_in_search('test')
expect(page).to have_selector(scoped_search_link('test'))
expect(page).to have_selector(scoped_search_link('test', group_id: group.id))
@@ -208,9 +200,7 @@ RSpec.describe 'User uses header search field', :js do
end
it 'displays search options' do
- page.within('.search-input-wrap') do
- fill_in('search', with: 'test')
- end
+ fill_in_search('test')
expect(page).to have_selector(scoped_search_link('test'))
expect(page).to have_selector(scoped_search_link('test', group_id: subgroup.id))
diff --git a/spec/frontend/analytics/instance_statistics/components/app_spec.js b/spec/frontend/analytics/instance_statistics/components/app_spec.js
new file mode 100644
index 00000000000..242621dc40c
--- /dev/null
+++ b/spec/frontend/analytics/instance_statistics/components/app_spec.js
@@ -0,0 +1,24 @@
+import { shallowMount } from '@vue/test-utils';
+import InstanceStatisticsApp from '~/analytics/instance_statistics/components/app.vue';
+import InstanceCounts from '~/analytics/instance_statistics/components//instance_counts.vue';
+
+describe('InstanceStatisticsApp', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(InstanceStatisticsApp);
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('displays the instance counts component', () => {
+ expect(wrapper.find(InstanceCounts).exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/analytics/instance_statistics/components/instance_counts_spec.js b/spec/frontend/analytics/instance_statistics/components/instance_counts_spec.js
new file mode 100644
index 00000000000..2274f4c3fde
--- /dev/null
+++ b/spec/frontend/analytics/instance_statistics/components/instance_counts_spec.js
@@ -0,0 +1,54 @@
+import { shallowMount } from '@vue/test-utils';
+import InstanceCounts from '~/analytics/instance_statistics/components/instance_counts.vue';
+import MetricCard from '~/analytics/shared/components/metric_card.vue';
+import countsMockData from '../mock_data';
+
+describe('InstanceCounts', () => {
+ let wrapper;
+
+ const createComponent = ({ loading = false, data = {} } = {}) => {
+ const $apollo = {
+ queries: {
+ counts: {
+ loading,
+ },
+ },
+ };
+
+ wrapper = shallowMount(InstanceCounts, {
+ mocks: { $apollo },
+ data() {
+ return {
+ ...data,
+ };
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findMetricCard = () => wrapper.find(MetricCard);
+
+ describe('while loading', () => {
+ beforeEach(() => {
+ createComponent({ loading: true });
+ });
+
+ it('displays the metric card with isLoading=true', () => {
+ expect(findMetricCard().props('isLoading')).toBe(true);
+ });
+ });
+
+ describe('with data', () => {
+ beforeEach(() => {
+ createComponent({ data: { counts: countsMockData } });
+ });
+
+ it('passes the counts data to the metric card', () => {
+ expect(findMetricCard().props('metrics')).toEqual(countsMockData);
+ });
+ });
+});
diff --git a/spec/frontend/analytics/instance_statistics/mock_data.js b/spec/frontend/analytics/instance_statistics/mock_data.js
new file mode 100644
index 00000000000..9fabf3a4c65
--- /dev/null
+++ b/spec/frontend/analytics/instance_statistics/mock_data.js
@@ -0,0 +1,4 @@
+export default [
+ { key: 'projects', value: 10, label: 'Projects' },
+ { key: 'groups', value: 20, label: 'Group' },
+];
diff --git a/spec/frontend/analytics/shared/components/metric_card_spec.js b/spec/frontend/analytics/shared/components/metric_card_spec.js
new file mode 100644
index 00000000000..e89d499ed9b
--- /dev/null
+++ b/spec/frontend/analytics/shared/components/metric_card_spec.js
@@ -0,0 +1,129 @@
+import { mount } from '@vue/test-utils';
+import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import MetricCard from '~/analytics/shared/components/metric_card.vue';
+
+const metrics = [
+ { key: 'first_metric', value: 10, label: 'First metric', unit: 'days', link: 'some_link' },
+ { key: 'second_metric', value: 20, label: 'Yet another metric' },
+ { key: 'third_metric', value: null, label: 'Null metric without value', unit: 'parsecs' },
+ { key: 'fourth_metric', value: '-', label: 'Metric without value', unit: 'parsecs' },
+];
+
+const defaultProps = {
+ title: 'My fancy title',
+ isLoading: false,
+ metrics,
+};
+
+describe('MetricCard', () => {
+ let wrapper;
+
+ const factory = (props = defaultProps) => {
+ wrapper = mount(MetricCard, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findTitle = () => wrapper.find({ ref: 'title' });
+ const findLoadingIndicator = () => wrapper.find(GlSkeletonLoading);
+ const findMetricsWrapper = () => wrapper.find({ ref: 'metricsWrapper' });
+ const findMetricItem = () => wrapper.findAll({ ref: 'metricItem' });
+ const findTooltip = () => wrapper.find('[data-testid="tooltip"]');
+
+ describe('template', () => {
+ it('renders the title', () => {
+ factory();
+
+ expect(findTitle().text()).toContain('My fancy title');
+ });
+
+ describe('when isLoading is true', () => {
+ beforeEach(() => {
+ factory({ isLoading: true });
+ });
+
+ it('displays a loading indicator', () => {
+ expect(findLoadingIndicator().exists()).toBe(true);
+ });
+
+ it('does not display the metrics container', () => {
+ expect(findMetricsWrapper().exists()).toBe(false);
+ });
+ });
+
+ describe('when isLoading is false', () => {
+ beforeEach(() => {
+ factory({ isLoading: false });
+ });
+
+ it('does not display a loading indicator', () => {
+ expect(findLoadingIndicator().exists()).toBe(false);
+ });
+
+ it('displays the metrics container', () => {
+ expect(findMetricsWrapper().exists()).toBe(true);
+ });
+
+ it('renders two metrics', () => {
+ expect(findMetricItem()).toHaveLength(metrics.length);
+ });
+
+ describe('with tooltip text', () => {
+ const tooltipText = 'This is a tooltip';
+ const tooltipMetric = {
+ key: 'fifth_metric',
+ value: '-',
+ label: 'Metric with tooltip',
+ unit: 'parsecs',
+ tooltipText,
+ };
+
+ beforeEach(() => {
+ factory({
+ isLoading: false,
+ metrics: [tooltipMetric],
+ });
+ });
+
+ it('will render a tooltip', () => {
+ const tt = getBinding(findTooltip().element, 'gl-tooltip');
+ expect(tt.value.title).toEqual(tooltipText);
+ });
+ });
+
+ describe.each`
+ columnIndex | label | value | unit | link
+ ${0} | ${'First metric'} | ${10} | ${' days'} | ${'some_link'}
+ ${1} | ${'Yet another metric'} | ${20} | ${''} | ${null}
+ ${2} | ${'Null metric without value'} | ${'-'} | ${''} | ${null}
+ ${3} | ${'Metric without value'} | ${'-'} | ${''} | ${null}
+ `('metric columns', ({ columnIndex, label, value, unit, link }) => {
+ it(`renders ${value}${unit} ${label} with URL ${link}`, () => {
+ const allMetricItems = findMetricItem();
+ const metricItem = allMetricItems.at(columnIndex);
+ const text = metricItem.text();
+
+ expect(text).toContain(`${value}${unit}`);
+ expect(text).toContain(label);
+
+ if (link) {
+ expect(metricItem.find('a').attributes('href')).toBe(link);
+ } else {
+ expect(metricItem.find('a').exists()).toBe(false);
+ }
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/environments/environments_app_spec.js b/spec/frontend/environments/environments_app_spec.js
index fe32bf918dd..22b066fae41 100644
--- a/spec/frontend/environments/environments_app_spec.js
+++ b/spec/frontend/environments/environments_app_spec.js
@@ -40,6 +40,9 @@ describe('Environment', () => {
return axios.waitForAll();
};
+ const findEnvironmentsTabAvailable = () => wrapper.find('.js-environments-tab-available > a');
+ const findEnvironmentsTabStopped = () => wrapper.find('.js-environments-tab-stopped > a');
+
beforeEach(() => {
mock = new MockAdapter(axios);
});
@@ -108,9 +111,16 @@ describe('Environment', () => {
it('should make an API request when using tabs', () => {
jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {});
- wrapper.find('.js-environments-tab-stopped').trigger('click');
+ findEnvironmentsTabStopped().trigger('click');
expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' });
});
+
+ it('should not make the same API request when clicking on the current scope tab', () => {
+ // component starts at available
+ jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {});
+ findEnvironmentsTabAvailable().trigger('click');
+ expect(wrapper.vm.updateContent).toHaveBeenCalledTimes(0);
+ });
});
});
});
diff --git a/spec/frontend/packages/details/components/__snapshots__/package_title_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/package_title_spec.js.snap
index 4d9e0af1545..d317264bdae 100644
--- a/spec/frontend/packages/details/components/__snapshots__/package_title_spec.js.snap
+++ b/spec/frontend/packages/details/components/__snapshots__/package_title_spec.js.snap
@@ -2,151 +2,163 @@
exports[`PackageTitle renders with tags 1`] = `
-
-
-
- Test package
-
+
-
+
+ Test package
+
-
+
+
+
+
+
+
+
+
-
+
-
+